From eaecf673761fcaf14f8e05796f52c9e2a2ef806b Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 28 Nov 2024 09:58:30 +0530 Subject: [PATCH 0001/1298] Remove error from ops_error_count metric labels. (#2719) ops_error_count cannot be grouped by actual error anymore. It was earlier removed to reduce cardinality. --- docs/metrics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/metrics.md b/docs/metrics.md index c15a596e25..45975ef288 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -9,7 +9,7 @@ gcs calls. * **fs/ops_count:** Cumulative number of operations processed by file system. It allows grouping by op_type to get counts for individual operations. * **fs/ops_error_count:** Cumulative number of errors generated by file system operations. -This metric can be grouped by op_type, error and error_category. +This metric can be grouped by op_type and error_category. Each error is mapped to an error_category in a many-to-one relationship. * **fs/ops_latency:** Cumulative distribution of file system operation latencies. We can group by op_type. @@ -148,4 +148,4 @@ gcs_read_count{read_type="Sequential"} 5 to specify the target Prometheus metric endpoint under the `scrape_configs` section in the Prometheus configuration file. ## References: -* More details around adding custom metrics using OpenCensus can be found [here](https://cloud.google.com/monitoring/custom-metrics/open-census) \ No newline at end of file +* More details around adding custom metrics using OpenCensus can be found [here](https://cloud.google.com/monitoring/custom-metrics/open-census) From 5984b465442bc0d64125dea1752c4b488a873941 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 28 Nov 2024 19:07:50 +0530 Subject: [PATCH 0002/1298] Handle error in uploader go routine (#2695) * handle error in uploader go routine * remove unnecessary failure handling cases * review comments * review comments * remove finalize in case of error in uploader go routine - will be done in file inode --- .../bufferedwrites/buffered_write_handler.go | 18 +++- .../buffered_write_handler_test.go | 27 ++++++ internal/bufferedwrites/upload_handler.go | 43 +++++--- .../bufferedwrites/upload_handler_test.go | 97 ++++++++++++++++--- internal/fs/inode/hns_dir_test.go | 6 +- internal/storage/mock/mock_writer.go | 51 +++------- .../storage/{ => mock}/testify_mock_bucket.go | 2 +- 7 files changed, 178 insertions(+), 66 deletions(-) rename internal/storage/{ => mock}/testify_mock_bucket.go (99%) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 7c6a370a75..94a5ee4b8a 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -56,7 +56,7 @@ func NewBWHandler(objectName string, bucket gcs.Bucket, blockSize int64, maxBloc bwh = &BufferedWriteHandler{ current: nil, blockPool: bp, - uploadHandler: newUploadHandler(objectName, bucket, bp.FreeBlocksChannel(), blockSize), + uploadHandler: newUploadHandler(objectName, bucket, maxBlocks, bp.FreeBlocksChannel(), blockSize), totalSize: 0, mtime: time.Now(), } @@ -71,6 +71,14 @@ func (wh *BufferedWriteHandler) Write(data []byte, offset int64) (err error) { return fmt.Errorf("non sequential writes") } + // Fail early if the uploadHandler has failed. + select { + case <-wh.uploadHandler.SignalUploadFailure(): + return fmt.Errorf("BufferedWriteHandler.Write(): error while uploading object to GCS") + default: + break + } + dataWritten := 0 for dataWritten < len(data) { if wh.current == nil { @@ -111,6 +119,14 @@ func (wh *BufferedWriteHandler) Sync() (err error) { // Flush finalizes the upload. func (wh *BufferedWriteHandler) Flush() (err error) { + // Fail early if the uploadHandler has failed. + select { + case <-wh.uploadHandler.SignalUploadFailure(): + return fmt.Errorf("file cannot be finalized: error while uploading object to GCS") + default: + break + } + if wh.current != nil { err := wh.uploadHandler.Upload(wh.current) if err != nil { diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 3ee5512548..b48d43dd18 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -95,6 +95,21 @@ func (testSuite *BufferedWriteTest) TestWriteDataSizeGreaterThanBlockSize() { assert.Equal(testSuite.T(), int64(size), fileInfo.TotalSize) } +func (testSuite *BufferedWriteTest) TestWrite_SignalUploadFailureInBetween() { + err := testSuite.bwh.Write([]byte("hello"), 0) + require.Nil(testSuite.T(), err) + fileInfo := testSuite.bwh.WriteFileInfo() + assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + assert.Equal(testSuite.T(), int64(5), fileInfo.TotalSize) + + // Close the channel to simulate failure in uploader. + close(testSuite.bwh.uploadHandler.SignalUploadFailure()) + + err = testSuite.bwh.Write([]byte("hello"), 5) + require.Error(testSuite.T(), err) + assert.ErrorContains(testSuite.T(), err, "BufferedWriteHandler.Write(): error while uploading object to GCS") +} + func (testSuite *BufferedWriteTest) TestFlushWithNonNilCurrentBlock() { err := testSuite.bwh.Write([]byte("hi"), 0) currentBlock := testSuite.bwh.current @@ -118,3 +133,15 @@ func (testSuite *BufferedWriteTest) TestFlushWithNilCurrentBlock() { assert.NoError(testSuite.T(), err) } + +func (testSuite *BufferedWriteTest) TestFlush_SignalUploadFailureDuringWrite() { + err := testSuite.bwh.Write([]byte("hi"), 0) + require.Nil(testSuite.T(), err) + + // Close the channel to simulate failure in uploader. + close(testSuite.bwh.uploadHandler.SignalUploadFailure()) + + err = testSuite.bwh.Flush() + require.Error(testSuite.T(), err) + assert.ErrorContains(testSuite.T(), err, "file cannot be finalized: error while uploading object to GCS") +} diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 828be02122..a1d0b2c7cd 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -40,6 +40,10 @@ type UploadHandler struct { // writer to resumable upload the blocks to GCS. writer io.WriteCloser + // signalUploadFailure channel will propagate the upload error to file + // inode. This signals permanent failure in the buffered write job. + signalUploadFailure chan error + // Parameters required for creating a new GCS chunk writer. bucket gcs.Bucket objectName string @@ -47,14 +51,15 @@ type UploadHandler struct { } // newUploadHandler creates the UploadHandler struct. -func newUploadHandler(objectName string, bucket gcs.Bucket, freeBlocksCh chan block.Block, blockSize int64) *UploadHandler { +func newUploadHandler(objectName string, bucket gcs.Bucket, maxBlocks int64, freeBlocksCh chan block.Block, blockSize int64) *UploadHandler { uh := &UploadHandler{ - uploadCh: make(chan block.Block), - wg: sync.WaitGroup{}, - freeBlocksCh: freeBlocksCh, - bucket: bucket, - objectName: objectName, - blockSize: blockSize, + uploadCh: make(chan block.Block, maxBlocks), + wg: sync.WaitGroup{}, + freeBlocksCh: freeBlocksCh, + bucket: bucket, + objectName: objectName, + blockSize: blockSize, + signalUploadFailure: make(chan error, 1), } return uh } @@ -67,7 +72,9 @@ func (uh *UploadHandler) Upload(block block.Block) error { // Lazily create the object writer. err := uh.createObjectWriter() if err != nil { - return fmt.Errorf("createObjectWriter: %w", err) + // createObjectWriter can only fail here due to throttling, so we will not + // handle this error explicitly or fall back to temp file flow. + return fmt.Errorf("createObjectWriter failed for object %s: %w", uh.objectName, err) } // Start the uploader goroutine. go uh.uploader() @@ -96,9 +103,10 @@ func (uh *UploadHandler) uploader() { for currBlock := range uh.uploadCh { _, err := io.Copy(uh.writer, currBlock.Reader()) if err != nil { - logger.Errorf("upload failed: error in io.Copy: %v", err) - uh.wg.Done() - // TODO: handle failure scenario: finalize the upload and trigger edit flow. + logger.Errorf("buffered write upload failed for object %s: error in io.Copy: %v", uh.objectName, err) + // Close the channel to signal upload failure. + close(uh.signalUploadFailure) + return } uh.wg.Done() @@ -113,17 +121,22 @@ func (uh *UploadHandler) Finalize() error { close(uh.uploadCh) if uh.writer == nil { - // Writer may not have been created for empty file creation flow. + // Writer may not have been created for empty file creation flow or for very + // small writes of size less than 1 block. err := uh.createObjectWriter() if err != nil { - return fmt.Errorf("createObjectWriter: %w", err) + return fmt.Errorf("createObjectWriter failed for object %s: %w", uh.objectName, err) } } err := uh.writer.Close() if err != nil { - logger.Errorf("UploadHandler.Finalize(): %v", err) - return fmt.Errorf("writer.Close: %w", err) + logger.Errorf("UploadHandler.Finalize(%s): %v", uh.objectName, err) + return fmt.Errorf("writer.Close failed for object %s: %w", uh.objectName, err) } return nil } + +func (uh *UploadHandler) SignalUploadFailure() chan error { + return uh.signalUploadFailure +} diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 15d101ad0d..3a5231402d 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -17,11 +17,11 @@ package bufferedwrites import ( "errors" "fmt" + "strconv" "testing" "time" "github.com/googlecloudplatform/gcsfuse/v2/internal/block" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" storagemock "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -37,7 +37,7 @@ const ( type UploadHandlerTest struct { uh *UploadHandler blockPool *block.BlockPool - mockBucket *storage.TestifyMockBucket + mockBucket *storagemock.TestifyMockBucket suite.Suite } @@ -46,11 +46,12 @@ func TestUploadHandlerTestSuite(t *testing.T) { } func (t *UploadHandlerTest) SetupTest() { - t.mockBucket = new(storage.TestifyMockBucket) + var maxBlocks int64 = 5 + t.mockBucket = new(storagemock.TestifyMockBucket) var err error - t.blockPool, err = block.NewBlockPool(blockSize, 5, semaphore.NewWeighted(5)) + t.blockPool, err = block.NewBlockPool(blockSize, maxBlocks, semaphore.NewWeighted(5)) require.NoError(t.T(), err) - t.uh = newUploadHandler("testObject", t.mockBucket, t.blockPool.FreeBlocksChannel(), blockSize) + t.uh = newUploadHandler("testObject", t.mockBucket, maxBlocks, t.blockPool.FreeBlocksChannel(), blockSize) } func (t *UploadHandlerTest) TestMultipleBlockUpload() { @@ -62,8 +63,9 @@ func (t *UploadHandlerTest) TestMultipleBlockUpload() { blocks = append(blocks, b) } // CreateObjectChunkWriter -- should be called once. - writer := storagemock.NewMockWriter("mockObject", false, false) + writer := &storagemock.Writer{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) + writer.On("Close").Return(nil) // Upload the blocks. for _, b := range blocks { @@ -102,12 +104,14 @@ func (t *UploadHandlerTest) TestUpload_CreateObjectWriterFails() { // Upload the block. err = t.uh.Upload(b) + require.Error(t.T(), err) assert.ErrorContains(t.T(), err, "createObjectWriter") assert.ErrorContains(t.T(), err, "taco") } func (t *UploadHandlerTest) TestFinalizeWithWriterAlreadyPresent() { - writer := storagemock.NewMockWriter("mockObject", false, false) + writer := &storagemock.Writer{} + writer.On("Close").Return(nil) t.uh.writer = writer err := t.uh.Finalize() @@ -116,9 +120,10 @@ func (t *UploadHandlerTest) TestFinalizeWithWriterAlreadyPresent() { } func (t *UploadHandlerTest) TestFinalizeWithNoWriter() { - writer := storagemock.NewMockWriter("mockObject", false, false) + writer := &storagemock.Writer{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) + writer.On("Close").Return(nil) err := t.uh.Finalize() @@ -131,18 +136,88 @@ func (t *UploadHandlerTest) TestFinalizeWithNoWriter_CreateObjectWriterFails() { err := t.uh.Finalize() - assert.Error(t.T(), err) + require.Error(t.T(), err) assert.ErrorContains(t.T(), err, "taco") assert.ErrorContains(t.T(), err, "createObjectWriter") } func (t *UploadHandlerTest) TestFinalize_WriterCloseFails() { - writer := storagemock.NewMockWriter("mockObject", false, true) + writer := &storagemock.Writer{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) + writer.On("Close").Return(fmt.Errorf("taco")) err := t.uh.Finalize() - assert.Error(t.T(), err) + require.Error(t.T(), err) assert.ErrorContains(t.T(), err, "writer.Close") } + +func (t *UploadHandlerTest) TestUploadHandler_singleBlock_ErrorInCopy() { + // Create a block with test data. + b, err := t.blockPool.Get() + require.NoError(t.T(), err) + err = b.Write([]byte("test data")) + require.NoError(t.T(), err) + // CreateObjectChunkWriter -- should be called once. + writer := &storagemock.Writer{} + t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) + // First write will be an error and Close will be successful. + writer.On("Write", mock.Anything).Return(0, fmt.Errorf("taco")).Once() + + // Upload the block. + err = t.uh.Upload(b) + + require.NoError(t.T(), err) + // Expect an error on the signalUploadFailure channel due to error while copying content to GCS writer. + assertUploadFailureSignal(t.T(), t.uh) +} + +func (t *UploadHandlerTest) TestUploadHandler_multipleBlocks_ErrorInCopy() { + // Create some blocks. + var blocks []block.Block + for i := 0; i < 4; i++ { + b, err := t.blockPool.Get() + require.NoError(t.T(), err) + err = b.Write([]byte("testdata" + strconv.Itoa(i) + " ")) + require.NoError(t.T(), err) + blocks = append(blocks, b) + } + // CreateObjectChunkWriter -- should be called once. + writer := &storagemock.Writer{} + t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) + // Second write will be an error and rest of the operations will be successful. + writer. + On("Write", mock.Anything).Return(10, nil).Once(). + On("Write", mock.Anything).Return(0, fmt.Errorf("taco")) + + // Upload the blocks. + for _, b := range blocks { + err := t.uh.Upload(b) + require.NoError(t.T(), err) + } + + assertUploadFailureSignal(t.T(), t.uh) +} + +func assertUploadFailureSignal(t *testing.T, handler *UploadHandler) { + t.Helper() + + select { + case <-handler.signalUploadFailure: + break + case <-time.After(200 * time.Millisecond): + t.Error("Expected an error on signalUploadFailure channel") + } +} + +func TestBufferedWriteHandler_SignalUploadFailure(t *testing.T) { + mockSignalUploadFailure := make(chan error) + uploadHandler := &UploadHandler{ + signalUploadFailure: mockSignalUploadFailure, + } + + actualChannel := uploadHandler.SignalUploadFailure() + + assert.Equal(t, mockSignalUploadFailure, actualChannel) +} diff --git a/internal/fs/inode/hns_dir_test.go b/internal/fs/inode/hns_dir_test.go index 5d7de3681a..56644c1eaf 100644 --- a/internal/fs/inode/hns_dir_test.go +++ b/internal/fs/inode/hns_dir_test.go @@ -23,8 +23,8 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + storagemock "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/mock" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/timeutil" @@ -42,7 +42,7 @@ type HNSDirTest struct { ctx context.Context bucket gcsx.SyncerBucket in DirInode - mockBucket *storage.TestifyMockBucket + mockBucket *storagemock.TestifyMockBucket typeCache metadata.TypeCache fixedTime timeutil.SimulatedClock } @@ -51,7 +51,7 @@ func TestHNSDirSuite(testSuite *testing.T) { suite.Run(testSuite, new(HNSDirTest func (t *HNSDirTest) SetupTest() { t.ctx = context.Background() - t.mockBucket = new(storage.TestifyMockBucket) + t.mockBucket = new(storagemock.TestifyMockBucket) t.bucket = gcsx.NewSyncerBucket( 1, ".gcsfuse_tmp/", diff --git a/internal/storage/mock/mock_writer.go b/internal/storage/mock/mock_writer.go index 77c891c015..c3f3c73775 100644 --- a/internal/storage/mock/mock_writer.go +++ b/internal/storage/mock/mock_writer.go @@ -15,56 +15,37 @@ package mock import ( - "bytes" - "fmt" "io" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/stretchr/testify/mock" ) -// MockWriter implements io.WriteCloser and is used in unit tests to mock +// Writer implements io.WriteCloser and is used in unit tests to mock // the behavior of a GCS object writer. This is particular used with // storage.TestifyMockBucket implementation and allows for controlled testing of // interactions with the writer without relying on actual GCS operations. -type MockWriter struct { +type Writer struct { io.WriteCloser - buf bytes.Buffer storage.ObjectAttrs - errorOnClose bool - errorOnWrite bool + mock.Mock } -func (w *MockWriter) Write(p []byte) (n int, err error) { - if w.errorOnWrite { - return 0, fmt.Errorf("error while writing") - } - return w.buf.Write(p) +func (mw *Writer) Write(p []byte) (n int, err error) { + args := mw.Called(p) + return args.Int(0), args.Error(1) } - -func (w *MockWriter) Close() error { - if w.errorOnClose { - return fmt.Errorf("error while closing writer") - } - return nil +func (mw *Writer) Attrs() *storage.ObjectAttrs { + args := mw.Called() + return args.Get(0).(*storage.ObjectAttrs) } -func (w *MockWriter) ObjectName() string { - return w.Name -} -func (w *MockWriter) Attrs() *storage.ObjectAttrs { - return &w.ObjectAttrs +func (mw *Writer) Close() error { + args := mw.Called() + return args.Error(0) } -func NewMockWriter(objName string, errorOnWrite, errorOnClose bool) gcs.Writer { - wr := &MockWriter{ - buf: bytes.Buffer{}, - errorOnWrite: errorOnWrite, - errorOnClose: errorOnClose, - ObjectAttrs: storage.ObjectAttrs{ - Name: objName, - }, - } - - return wr +func (mw *Writer) ObjectName() string { + args := mw.Called() + return args.String(0) } diff --git a/internal/storage/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go similarity index 99% rename from internal/storage/testify_mock_bucket.go rename to internal/storage/mock/testify_mock_bucket.go index 8a822921c4..6abeaef9e1 100644 --- a/internal/storage/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package storage +package mock import ( "context" From f9df384be253dc02fdaaaf9d89b4f043a972bafa Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 28 Nov 2024 19:52:22 +0530 Subject: [PATCH 0003/1298] unset the precondition flag until feature is complete (#2721) --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/config_validation_test.go | 6 +++--- cmd/root_test.go | 8 ++++---- cmd/testdata/valid_config.yaml | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index eb2af698b2..37d4351d44 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -461,7 +461,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.StringP("only-dir", "", "", "Mount only a specific directory within the bucket. See docs/mounting for more information") - flagSet.BoolP("precondition-errors", "", true, "Throw Stale NFS file handle error in case the object being synced or read from is modified by some other concurrent process. This helps prevent silent data loss or data corruption.") + flagSet.BoolP("precondition-errors", "", false, "Throw Stale NFS file handle error in case the object being synced or read from is modified by some other concurrent process. This helps prevent silent data loss or data corruption.") if err := flagSet.MarkHidden("precondition-errors"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 2771cb9775..078ef0d448 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -202,7 +202,7 @@ from is modified by some other concurrent process. This helps prevent silent data loss or data corruption. hide-flag: true - default: true + default: false - config-path: "file-system.rename-dir-limit" flag-name: "rename-dir-limit" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 00c7ebab12..c51ce4352c 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -514,7 +514,7 @@ func TestValidateConfigFile_FileSystemConfigSuccessful(t *testing.T) { KernelListCacheTtlSecs: 0, RenameDirLimit: 0, TempDir: "", - PreconditionErrors: true, + PreconditionErrors: false, Uid: -1, HandleSigterm: true, }, @@ -534,7 +534,7 @@ func TestValidateConfigFile_FileSystemConfigSuccessful(t *testing.T) { KernelListCacheTtlSecs: 0, RenameDirLimit: 0, TempDir: "", - PreconditionErrors: true, + PreconditionErrors: false, Uid: -1, HandleSigterm: true, }, @@ -554,7 +554,7 @@ func TestValidateConfigFile_FileSystemConfigSuccessful(t *testing.T) { KernelListCacheTtlSecs: 300, RenameDirLimit: 10, TempDir: cfg.ResolvedPath(path.Join(hd, "temp")), - PreconditionErrors: false, + PreconditionErrors: true, Uid: 8, HandleSigterm: true, }, diff --git a/cmd/root_test.go b/cmd/root_test.go index 0475deedb4..cdcf800b8e 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -641,7 +641,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { }{ { name: "normal", - args: []string{"gcsfuse", "--dir-mode=0777", "--disable-parallel-dirops", "--file-mode=0666", "--o", "ro", "--gid=7", "--ignore-interrupts=false", "--kernel-list-cache-ttl-secs=300", "--rename-dir-limit=10", "--temp-dir=~/temp", "--uid=8", "--precondition-errors=false", "abc", "pqr"}, + args: []string{"gcsfuse", "--dir-mode=0777", "--disable-parallel-dirops", "--file-mode=0666", "--o", "ro", "--gid=7", "--ignore-interrupts=false", "--kernel-list-cache-ttl-secs=300", "--rename-dir-limit=10", "--temp-dir=~/temp", "--uid=8", "--precondition-errors=true", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileSystem: cfg.FileSystemConfig{ DirMode: 0777, @@ -653,7 +653,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { KernelListCacheTtlSecs: 300, RenameDirLimit: 10, TempDir: cfg.ResolvedPath(path.Join(hd, "temp")), - PreconditionErrors: false, + PreconditionErrors: true, Uid: 8, HandleSigterm: true, }, @@ -673,7 +673,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { KernelListCacheTtlSecs: 0, RenameDirLimit: 0, TempDir: "", - PreconditionErrors: true, + PreconditionErrors: false, Uid: -1, HandleSigterm: true, }, @@ -693,7 +693,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { KernelListCacheTtlSecs: 0, RenameDirLimit: 0, TempDir: "", - PreconditionErrors: true, + PreconditionErrors: false, Uid: -1, HandleSigterm: true, }, diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 6cb23064ea..3033023116 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -51,7 +51,7 @@ file-system: kernel-list-cache-ttl-secs: 300 rename-dir-limit: 10 temp-dir: ~/temp - precondition-errors: false + precondition-errors: true list: enable-empty-managed-folders: true enable-hns: false From 7d6e7a39d1f549c1d3fc77e01d2e70fcb25c9e36 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 29 Nov 2024 11:20:43 +0530 Subject: [PATCH 0004/1298] Fix Version test (#2723) The expected version differs depending upon whether the installed package is being used. So, condition the test accordingly. --- tools/integration_tests/mounting/gcsfuse_test.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tools/integration_tests/mounting/gcsfuse_test.go b/tools/integration_tests/mounting/gcsfuse_test.go index 505c99b4ca..1f97ac4afc 100644 --- a/tools/integration_tests/mounting/gcsfuse_test.go +++ b/tools/integration_tests/mounting/gcsfuse_test.go @@ -27,6 +27,8 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/internal/canned" + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/util" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/oglematchers" @@ -230,7 +232,17 @@ func (t *GcsfuseTest) Version() { output, err := cmd.CombinedOutput() AssertEq(nil, err) - AssertTrue(strings.Contains(string(output), "fake_version")) + if setup.TestInstalledPackage() { + assertContains("0.0.0", string(output)) + } else { + assertContains("fake_version", string(output)) + } + } +} + +func assertContains(expected, actual string) { + if !strings.Contains(actual, expected) { + logger.Fatal("Actual: %s does not contain expected: %s", actual, expected) } } From 6ea65a7fecd647a2c7ca77234e9134616f01e57d Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 29 Nov 2024 12:03:57 +0530 Subject: [PATCH 0005/1298] Restrict visibility (#2724) Make NewRootCmd package-private. --- cmd/config_validation_test.go | 2 +- cmd/datatypes_parsing_test.go | 6 +++--- cmd/root.go | 6 +++--- cmd/root_test.go | 38 +++++++++++++++++------------------ 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index c51ce4352c..4d901a37de 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -31,7 +31,7 @@ import ( func getConfigObject(t *testing.T, args []string) (*cfg.Config, error) { t.Helper() var c *cfg.Config - cmd, err := NewRootCmd(func(config *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(config *cfg.Config, _, _ string) error { c = config return nil }) diff --git a/cmd/datatypes_parsing_test.go b/cmd/datatypes_parsing_test.go index 0561011f23..a3290a05f6 100644 --- a/cmd/datatypes_parsing_test.go +++ b/cmd/datatypes_parsing_test.go @@ -574,7 +574,7 @@ func TestCLIFlagPassing(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var c *cfg.Config - command, err := NewRootCmd(func(config *cfg.Config, _, _ string) error { + command, err := newRootCmd(func(config *cfg.Config, _, _ string) error { c = config return nil }) @@ -747,7 +747,7 @@ func TestConfigPassing(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { var c *cfg.Config - command, err := NewRootCmd(func(config *cfg.Config, _, _ string) error { + command, err := newRootCmd(func(config *cfg.Config, _, _ string) error { c = config return nil }) @@ -802,7 +802,7 @@ func TestPredefinedFlagThrowNoError(t *testing.T) { } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - command, err := NewRootCmd(func(config *cfg.Config, _, _ string) error { + command, err := newRootCmd(func(config *cfg.Config, _, _ string) error { return nil }) require.NoError(t, err) diff --git a/cmd/root.go b/cmd/root.go index 105a6cf4a4..297ed2fed4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -31,8 +31,8 @@ import ( type mountFn func(c *cfg.Config, bucketName, mountPoint string) error -// NewRootCmd accepts the mountFn that it executes with the parsed configuration -func NewRootCmd(m mountFn) (*cobra.Command, error) { +// newRootCmd accepts the mountFn that it executes with the parsed configuration +func newRootCmd(m mountFn) (*cobra.Command, error) { var ( configObj cfg.Config cfgFile string @@ -147,7 +147,7 @@ func convertToPosixArgs(args []string, c *cobra.Command) []string { } var ExecuteMountCmd = func() { - rootCmd, err := NewRootCmd(Mount) + rootCmd, err := newRootCmd(Mount) if err != nil { log.Fatalf("Error occurred while creating the root command: %v", err) } diff --git a/cmd/root_test.go b/cmd/root_test.go index cdcf800b8e..5aa75e9a36 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -30,7 +30,7 @@ import ( func TestDefaultMaxParallelDownloads(t *testing.T) { var actual *cfg.Config - cmd, err := NewRootCmd(func(c *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(c *cfg.Config, _, _ string) error { actual = c return nil }) @@ -72,7 +72,7 @@ func TestCobraArgsNumInRange(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - cmd, err := NewRootCmd(func(*cfg.Config, string, string) error { return nil }) + cmd, err := newRootCmd(func(*cfg.Config, string, string) error { return nil }) require.Nil(t, err) cmd.SetArgs(convertToPosixArgs(tc.args, cmd)) @@ -127,7 +127,7 @@ func TestArgsParsing_MountPoint(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var bucketName, mountPoint string - cmd, err := NewRootCmd(func(_ *cfg.Config, b string, m string) error { + cmd, err := newRootCmd(func(_ *cfg.Config, b string, m string) error { bucketName = b mountPoint = m return nil @@ -176,7 +176,7 @@ func TestArgsParsing_MountOptions(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var mountOptions []string - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { mountOptions = cfg.FileSystem.FuseOptions return nil }) @@ -279,7 +279,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var wc cfg.WriteConfig - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { wc = cfg.Write return nil }) @@ -343,7 +343,7 @@ func TestArgsParsing_FileCacheFlags(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var gotConfig *cfg.Config - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { gotConfig = cfg return nil }) @@ -395,7 +395,7 @@ func TestArgParsing_ExperimentalMetadataPrefetchFlag(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var experimentalMetadataPrefetch string - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { experimentalMetadataPrefetch = cfg.MetadataCache.ExperimentalMetadataPrefetchOnMount return nil }) @@ -428,7 +428,7 @@ func TestArgParsing_ExperimentalMetadataPrefetchFlag_Failed(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { return nil }) require.Nil(t, err) @@ -478,7 +478,7 @@ func TestArgsParsing_GCSAuthFlags(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var gotConfig *cfg.Config - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { gotConfig = cfg return nil }) @@ -516,7 +516,7 @@ func TestArgsParsing_GCSAuthFlagsThrowsError(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { return nil }) require.Nil(t, err) @@ -576,7 +576,7 @@ func TestArgsParsing_GCSConnectionFlags(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var gotConfig *cfg.Config - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { gotConfig = cfg return nil }) @@ -620,7 +620,7 @@ func TestArgsParsing_GCSConnectionFlagsThrowsError(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { return nil }) require.Nil(t, err) @@ -704,7 +704,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var gotConfig *cfg.Config - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { gotConfig = cfg return nil }) @@ -749,7 +749,7 @@ func TestArgsParsing_FileSystemFlagsThrowsError(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { return nil }) require.Nil(t, err) @@ -785,7 +785,7 @@ func TestArgsParsing_ListFlags(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var gotConfig *cfg.Config - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { gotConfig = cfg return nil }) @@ -822,7 +822,7 @@ func TestArgsParsing_EnableHNSFlags(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var gotEnableHNS bool - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { gotEnableHNS = cfg.EnableHns return nil }) @@ -886,7 +886,7 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var gotConfig *cfg.Config - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { gotConfig = cfg return nil }) @@ -943,7 +943,7 @@ func TestArgsParsing_MetricsViewConfig(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var gotConfig *cfg.Config - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { gotConfig = cfg return nil }) @@ -1002,7 +1002,7 @@ func TestArgsParsing_MetadataCacheFlags(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { var gotConfig *cfg.Config - cmd, err := NewRootCmd(func(cfg *cfg.Config, _, _ string) error { + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { gotConfig = cfg return nil }) From 644cd9dfdf897fa0203c57e7d65c8e6199647fe6 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 2 Dec 2024 12:27:32 +0530 Subject: [PATCH 0006/1298] =?UTF-8?q?Revert=20"Modify=20fake=20bucket=20im?= =?UTF-8?q?plementation=20to=20throw=20same=20error=20that=20is=20thrown?= =?UTF-8?q?=20=E2=80=A6"=20(#2729)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 833a760c021d3854a6dac2fb2c67190eeec36e62. --- internal/storage/fake/bucket.go | 7 +++++-- internal/storage/fake/testing/bucket_tests.go | 11 +++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index 424f8b9ee0..cc913db477 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -29,7 +29,6 @@ import ( "time" "unicode/utf8" - "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/jacobsa/syncutil" @@ -453,7 +452,11 @@ func (b *bucket) newReaderLocked( // Does the generation match? if req.Generation != 0 && req.Generation != o.metadata.Generation { - err = storage.ErrObjectNotExist + err = &gcs.NotFoundError{ + Err: fmt.Errorf( + "object %s generation %v not found", req.Name, req.Generation), + } + return } diff --git a/internal/storage/fake/testing/bucket_tests.go b/internal/storage/fake/testing/bucket_tests.go index 54bccf970d..d6bf1680be 100644 --- a/internal/storage/fake/testing/bucket_tests.go +++ b/internal/storage/fake/testing/bucket_tests.go @@ -32,7 +32,6 @@ import ( "time" "unicode" - "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" . "github.com/jacobsa/oglematchers" @@ -2231,7 +2230,7 @@ func (t *composeTest) ExplicitGenerations_OneDoesntExist() { }, }) - ExpectThat(err, HasSameTypeAs(storage.ErrObjectNotExist)) + ExpectThat(err, HasSameTypeAs(&gcs.NotFoundError{})) // Make sure the destination object doesn't exist. _, _, err = t.bucket.StatObject( @@ -2754,8 +2753,8 @@ func (t *readTest) ParticularGeneration_NeverExisted() { _, err = rc.Read(make([]byte, 1)) } - AssertThat(err, HasSameTypeAs(storage.ErrObjectNotExist)) - ExpectThat(err, Error(MatchesRegexp("(?i)object doesn't exist"))) + AssertThat(err, HasSameTypeAs(&gcs.NotFoundError{})) + ExpectThat(err, Error(MatchesRegexp("(?i)not found|404"))) } func (t *readTest) ParticularGeneration_HasBeenDeleted() { @@ -2856,8 +2855,8 @@ func (t *readTest) ParticularGeneration_ObjectHasBeenOverwritten() { _, err = rc.Read(make([]byte, 1)) } - AssertThat(err, HasSameTypeAs(storage.ErrObjectNotExist)) - ExpectThat(err, Error(MatchesRegexp("(?i)object doesn't exist"))) + AssertThat(err, HasSameTypeAs(&gcs.NotFoundError{})) + ExpectThat(err, Error(MatchesRegexp("(?i)not found|404"))) // Reading by the new generation should work. req.Generation = o2.Generation From 092d8c843dd7ed55684957af918708e90994609f Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 3 Dec 2024 08:15:36 +0530 Subject: [PATCH 0007/1298] [Emulator tests] Add proxy server - 1 (#2728) * add request mapper * rename function * rename function * linux test fix * small fix * small fix * separating list and stat request * separating list and stat request * nit new line * fix comment * add TODO --- .../proxy_server/request_mapper.go | 80 +++++++++++++++++++ .../proxy_server/request_mappper_test.go | 59 ++++++++++++++ 2 files changed, 139 insertions(+) create mode 100644 tools/integration_tests/emulator_tests/proxy_server/request_mapper.go create mode 100644 tools/integration_tests/emulator_tests/proxy_server/request_mappper_test.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/request_mapper.go b/tools/integration_tests/emulator_tests/proxy_server/request_mapper.go new file mode 100644 index 0000000000..3fc6296bf9 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/request_mapper.go @@ -0,0 +1,80 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proxy_server + +import ( + "net/http" + "strings" +) + +type RequestType string + +const ( + XmlRead RequestType = "XmlRead" + JsonStat RequestType = "JsonStat" + JsonDelete RequestType = "JsonDelete" + JsonUpdate RequestType = "JsonUpdate" + JsonCreate RequestType = "JsonCreate" + JsonCopy RequestType = "JsonCopy" + JsonList RequestType = "JsonList" + JsonCompose RequestType = "JsonCompose" + Unknown RequestType = "Unknown" +) + +type RequestTypeAndInstruction struct { + RequestType RequestType + Instruction string +} + +// deduceRequestTypeAndInstruction determines the type of request and its corresponding instruction +func deduceRequestTypeAndInstruction(r *http.Request) RequestTypeAndInstruction { + path := r.URL.Path + method := r.Method + + if isJsonAPI(path) { + switch method { + // TODO: Implement logic to differentiate JSON read requests from general HTTP GET requests for the purpose of testing JSON read functionality. + case http.MethodGet: + // Check if path ends with `/o` (indicates listing objects) + if strings.HasSuffix(path, "/o") { + return RequestTypeAndInstruction{JsonList, "storage.objects.list"} + } + // Check if path has `/o/` (indicates stat operation) + if strings.Contains(path, "/o/") { + return RequestTypeAndInstruction{JsonStat, "storage.objects.get"} + } + case http.MethodPost: + return RequestTypeAndInstruction{JsonCreate, "storage.objects.insert"} + case http.MethodDelete: + return RequestTypeAndInstruction{JsonDelete, "storage.objects.delete"} + case http.MethodPut: + return RequestTypeAndInstruction{JsonUpdate, "storage.objects.update"} + default: + return RequestTypeAndInstruction{Unknown, ""} + } + } + switch method { + case http.MethodGet: + return RequestTypeAndInstruction{XmlRead, "storage.objects.get"} + default: + return RequestTypeAndInstruction{Unknown, ""} + } +} + +// isJsonAPI checks if the request is targeting the JSON API +func isJsonAPI(path string) bool { + // The JSON API path includes "/storage/v1", while the XML API path does not. + return strings.Contains(path, "/storage/v1") +} diff --git a/tools/integration_tests/emulator_tests/proxy_server/request_mappper_test.go b/tools/integration_tests/emulator_tests/proxy_server/request_mappper_test.go new file mode 100644 index 0000000000..7ba0f85fa8 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/request_mappper_test.go @@ -0,0 +1,59 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proxy_server + +import ( + "net/http" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDeduceRequestTypeAndInstruction(t *testing.T) { + tests := []struct { + name string + method string + path string + expectedReq RequestType + expectedIns string + }{ + // JSON API Tests + {"JsonStat GET", http.MethodGet, "/storage/v1/bucket/o/object", JsonStat, "storage.objects.get"}, + {"JsonList GET", http.MethodGet, "/storage/v1/bucket/o", JsonList, "storage.objects.list"}, + {"JsonCreate POST", http.MethodPost, "/storage/v1/bucket/o", JsonCreate, "storage.objects.insert"}, + {"JsonDelete DELETE", http.MethodDelete, "/storage/v1/bucket/o", JsonDelete, "storage.objects.delete"}, + {"JsonUpdate PUT", http.MethodPut, "/storage/v1/bucket/o", JsonUpdate, "storage.objects.update"}, + {"JsonUnknown PATCH", http.MethodPatch, "/storage/v1/bucket/o", Unknown, ""}, + + // XML API Tests + {"XmlRead GET", http.MethodGet, "/bucket/object", XmlRead, "storage.objects.get"}, + {"XmlUnknown POST", http.MethodPost, "/bucket/object", Unknown, ""}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + req := &http.Request{ + Method: test.method, + URL: &url.URL{Path: test.path}, + } + + result := deduceRequestTypeAndInstruction(req) + + assert.Equal(t, test.expectedReq, result.RequestType) + assert.Equal(t, test.expectedIns, result.Instruction) + }) + } +} From 28b109c375fceb6366671c6d21b805ee007c183b Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 3 Dec 2024 09:29:15 +0530 Subject: [PATCH 0008/1298] Reduce test-parallelism to 1 (#2736) Currently, tests get timed-out probably due to resource constraints on the machine. Reducing the parallelism to 1 so that the resource requirements reduces. --- .github/workflows/ci.yml | 6 ++---- .github/workflows/flake-detector.yml | 7 ++----- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eda21ef14a..7f881d285c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,10 +43,8 @@ jobs: CGO_ENABLED=0 go build ./... go install ./tools/build_gcsfuse build_gcsfuse . /tmp ${GITHUB_SHA} - - name: Test all except caching parallely - run: CGO_ENABLED=0 go test -count 1 -covermode=atomic -coverprofile=coverage.out -coverpkg=./... -v -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` `go list ./... | grep -v internal/cache/...` - - name: Test caching - run: CGO_ENABLED=0 go test -p 1 -count 1 -covermode=atomic -coverprofile=coverage_cache.out -coverpkg=./... -v -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` ./internal/cache/... + - name: Test all + run: CGO_ENABLED=0 go test -p 1 -count 1 -covermode=atomic -coverprofile=coverage.out -coverpkg=./... -v -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` `go list ./...` - name: Cache RaceDetector Test run: go test -p 1 -count 1 -v -race -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` ./internal/cache/... - name: Upload coverage reports to Codecov diff --git a/.github/workflows/flake-detector.yml b/.github/workflows/flake-detector.yml index 9df870fad4..2e5bd514a3 100644 --- a/.github/workflows/flake-detector.yml +++ b/.github/workflows/flake-detector.yml @@ -31,11 +31,8 @@ jobs: - name: Download dependencies run: go mod download - - name: Test all except caching parallely - run: CGO_ENABLED=0 go test -timeout 75m -count 20 -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` `go list ./... | grep -v internal/cache/...` - - - name: Test caching - run: CGO_ENABLED=0 go test -p 1 -timeout 75m -count 20 -v -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` ./internal/cache/... + - name: Test all + run: CGO_ENABLED=0 go test -p 1 -timeout 75m -count 20 -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` `go list ./...` - name: Cache RaceDetector Test run: CGO_ENABLED=0 go test -p 1 -timeout 75m -count 20 ./internal/cache/... From f757cb69cac7cb8dd6ef77ada0e0ca3cf9309181 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 3 Dec 2024 09:43:00 +0530 Subject: [PATCH 0009/1298] add benchmarking test package to e2e testing (#2725) * adding timeout criteria * adding benchmarking test package to louhi pipeline * adding benchmark flag to e2e test script * fix * correct error message * adding comment to explain the need for bench flag --- tools/cd_scripts/e2e_test.sh | 11 ++++++++++- .../benchmarking/benchmark_delete_test.go | 9 +++++++++ .../benchmarking/benchmark_stat_test.go | 9 +++++++++ tools/integration_tests/run_e2e_tests.sh | 10 +++++++++- 4 files changed, 37 insertions(+), 2 deletions(-) diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index ed492db13e..372d83b050 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -157,6 +157,7 @@ TEST_DIR_PARALLEL=( "log_content" "kernel_list_cache" "concurrent_operations" + "benchmarking" ) # These tests never become parallel as they are changing bucket permissions. @@ -198,15 +199,23 @@ function run_parallel_tests() { local -n test_array=$1 local BUCKET_NAME=$2 local pids=() + local benchmark_flags="" + for test_dir_p in "${test_array[@]}" do + # Unlike regular tests,benchmark tests are not executed by default when using go test . + # The -bench flag yells go test to run the benchmark tests and report their results by + # enabling the benchmarking framework. + if [ $test_dir_p == "benchmarking" ]; then + benchmark_flags="-bench=." + fi test_path_parallel="./tools/integration_tests/$test_dir_p" # To make it clear whether tests are running on a flat or HNS bucket, We kept the log file naming # convention to include the bucket name as a suffix (e.g., package_name_bucket_name). local log_file="/tmp/${test_dir_p}_${BUCKET_NAME}.log" echo $log_file >> $TEST_LOGS_FILE # Executing integration tests - GODEBUG=asyncpreemptoff=1 go test $test_path_parallel -p 1 --integrationTest -v --testbucket=$BUCKET_NAME --testInstalledPackage=true -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & + GODEBUG=asyncpreemptoff=1 go test $test_path_parallel $benchmark_flags -p 1 --integrationTest -v --testbucket=$BUCKET_NAME --testInstalledPackage=true -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & pid=$! # Store the PID of the background process pids+=("$pid") # Optionally add the PID to an array for later done diff --git a/tools/integration_tests/benchmarking/benchmark_delete_test.go b/tools/integration_tests/benchmarking/benchmark_delete_test.go index d26016d979..07fdfa03b3 100644 --- a/tools/integration_tests/benchmarking/benchmark_delete_test.go +++ b/tools/integration_tests/benchmarking/benchmark_delete_test.go @@ -19,12 +19,17 @@ import ( "os" "path" "testing" + "time" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/benchmark_setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) +const ( + expectedDeleteLatency time.Duration = 450 * time.Millisecond +) + type benchmarkDeleteTest struct{} func (s *benchmarkDeleteTest) SetupB(b *testing.B) { @@ -53,6 +58,10 @@ func (s *benchmarkDeleteTest) Benchmark_Delete(b *testing.B) { b.Errorf("testing error: %v", err) } } + averageDeleteLatency := time.Duration(int(b.Elapsed()) / b.N) + if averageDeleteLatency > expectedDeleteLatency { + b.Errorf("DeleteFile took more time (%d msec) than expected (%d msec)", averageDeleteLatency.Milliseconds(), expectedDeleteLatency.Milliseconds()) + } } //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/benchmarking/benchmark_stat_test.go b/tools/integration_tests/benchmarking/benchmark_stat_test.go index 4ccc99abd5..ec86033fd1 100644 --- a/tools/integration_tests/benchmarking/benchmark_stat_test.go +++ b/tools/integration_tests/benchmarking/benchmark_stat_test.go @@ -17,6 +17,7 @@ package benchmarking import ( "path" "testing" + "time" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/benchmark_setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" @@ -27,6 +28,10 @@ import ( // Boilerplate //////////////////////////////////////////////////////////////////////// +const ( + expectedStatLatency time.Duration = 260 * time.Millisecond +) + type benchmarkStatTest struct{} func (s *benchmarkStatTest) SetupB(b *testing.B) { @@ -53,6 +58,10 @@ func (s *benchmarkStatTest) Benchmark_Stat(b *testing.B) { b.Errorf("testing error: %v", err) } } + averageStatLatency := time.Duration(int(b.Elapsed()) / b.N) + if averageStatLatency > expectedStatLatency { + b.Errorf("StatFile took more time (%d msec) than expected (%d msec)", averageStatLatency.Milliseconds(), expectedStatLatency.Milliseconds()) + } } //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 48337c304c..9f6792e3be 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -80,6 +80,7 @@ TEST_DIR_PARALLEL=( "log_content" "kernel_list_cache" "concurrent_operations" + "benchmarking" ) # These tests never become parallel as it is changing bucket permissions. @@ -170,17 +171,24 @@ function run_parallel_tests() { local exit_code=0 local -n test_array=$1 local bucket_name_parallel=$2 + local benchmark_flags="" local pids=() for test_dir_p in "${test_array[@]}" do + # Unlike regular tests,benchmark tests are not executed by default when using go test . + # The -bench flag yells go test to run the benchmark tests and report their results by + # enabling the benchmarking framework. + if [ $test_dir_p == "benchmarking" ]; then + benchmark_flags="-bench=." + fi test_path_parallel="./tools/integration_tests/$test_dir_p" # To make it clear whether tests are running on a flat or HNS bucket, We kept the log file naming # convention to include the bucket name as a suffix (e.g., package_name_bucket_name). local log_file="/tmp/${test_dir_p}_${bucket_name_parallel}.log" echo $log_file >> $TEST_LOGS_FILE # Executing integration tests - GODEBUG=asyncpreemptoff=1 go test $test_path_parallel $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG -p 1 --integrationTest -v --testbucket=$bucket_name_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & + GODEBUG=asyncpreemptoff=1 go test $test_path_parallel $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG $benchmark_flags -p 1 --integrationTest -v --testbucket=$bucket_name_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & pid=$! # Store the PID of the background process pids+=("$pid") # Optionally add the PID to an array for later done From 92133566f0f2d882be0dc9b8f276ea20f24a6c27 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 3 Dec 2024 11:10:23 +0530 Subject: [PATCH 0010/1298] map storage.ErrObjectNotExist in NewReader method to gcs.NotFoundError (#2730) * map storage.ErrObjectNotExist in NewReader method to gcs.NotFoundError * fixed formatting --- internal/storage/bucket_handle.go | 8 +++++++- internal/storage/bucket_handle_test.go | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index e75aa468be..f59a7a2026 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -115,7 +115,13 @@ func (bh *bucketHandle) NewReader( } // NewRangeReader creates a "storage.Reader" object which is also io.ReadCloser since it contains both Read() and Close() methods present in io.ReadCloser interface. - return obj.NewRangeReader(ctx, start, length) + r, err := obj.NewRangeReader(ctx, start, length) + + if err == storage.ErrObjectNotExist { + err = &gcs.NotFoundError{Err: storage.ErrObjectNotExist} + } + + return r, err } func (bh *bucketHandle) DeleteObject(ctx context.Context, req *gcs.DeleteObjectRequest) error { obj := bh.bucket.Object(req.Name) diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index d40fc663b1..047ff07d56 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -153,6 +153,8 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithNilRange() { } func (testSuite *BucketHandleTest) TestNewReaderMethodWithInValidObject() { + var notFoundErr *gcs.NotFoundError + rc, err := testSuite.bucketHandle.NewReader(context.Background(), &gcs.ReadObjectRequest{ Name: missingObjectName, @@ -163,6 +165,7 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithInValidObject() { }) assert.NotNil(testSuite.T(), err) + assert.True(testSuite.T(), errors.As(err, ¬FoundErr)) assert.Nil(testSuite.T(), rc) } @@ -186,6 +189,8 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithValidGeneration() { } func (testSuite *BucketHandleTest) TestNewReaderMethodWithInvalidGeneration() { + var notFoundErr *gcs.NotFoundError + rc, err := testSuite.bucketHandle.NewReader(context.Background(), &gcs.ReadObjectRequest{ Name: TestObjectName, @@ -197,6 +202,7 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithInvalidGeneration() { }) assert.NotNil(testSuite.T(), err) + assert.True(testSuite.T(), errors.As(err, ¬FoundErr)) assert.Nil(testSuite.T(), rc) } From 921b94b8f10aef9bd84fa5fb9d4ba5f0724bb99c Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 3 Dec 2024 13:33:55 +0530 Subject: [PATCH 0011/1298] [Emulator tests] Add proxy server - 2 (#2735) * separating list and stat request * separating list and stat request * add operation manager * rebase * add unit tests * add unit tests * update unit tests * update unit tests * small fix * fix comment * fix comment * fix comment * add more unit tetss * add more unit tetss * lint fix * lint fix * small fix * small fix * update comment * add one more test * update comment --- .../emulator_tests/proxy_server/config.go | 66 ++++++++ .../proxy_server/config_test.go | 102 ++++++++++++ .../proxy_server/operation_manager.go | 75 +++++++++ .../proxy_server/operation_manager_test.go | 155 ++++++++++++++++++ 4 files changed, 398 insertions(+) create mode 100644 tools/integration_tests/emulator_tests/proxy_server/config.go create mode 100644 tools/integration_tests/emulator_tests/proxy_server/config_test.go create mode 100644 tools/integration_tests/emulator_tests/proxy_server/operation_manager.go create mode 100644 tools/integration_tests/emulator_tests/proxy_server/operation_manager_test.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/config.go b/tools/integration_tests/emulator_tests/proxy_server/config.go new file mode 100644 index 0000000000..51a4eb56bf --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/config.go @@ -0,0 +1,66 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proxy_server + +import ( + "fmt" + "log" + + "github.com/spf13/viper" +) + +type RetryConfig struct { + // The name of the method to apply retries to (e.g., JsonCreate, JsonStat). + Method string `yaml:"method"` + // Retry instruction (e.g., return-503, stall-33s-after-20K). + RetryInstruction string `yaml:"retryInstruction"` + // Number of times to retry. + RetryCount int `yaml:"retryCount"` + // Number of starting retry attempts to skip. + SkipCount int `yaml:"skipCount"` +} + +type Config struct { + // TargetHost is the address of emulator server to which proxy server interacts. + TargetHost string `yaml:"targetHost"` + RetryConfig []RetryConfig `yaml:"retryConfig"` +} + +func printConfig(config Config) { + log.Println("Target Host:", config.TargetHost) + for _, retry := range config.RetryConfig { + log.Println("Method:", retry.Method) + log.Println("Retry instructions:", retry.RetryInstruction) + log.Println("Retry Count:", retry.RetryCount) + log.Println("Skip Count:", retry.SkipCount) + } +} + +func parseConfigFile(configPath string) (*Config, error) { + var config Config + + viper.SetConfigFile(configPath) + if err := viper.ReadInConfig(); err != nil { + return nil, fmt.Errorf("error reading config file, %s", err) + } + + if err := viper.Unmarshal(&config); err != nil { + return nil, fmt.Errorf("unable to decode into struct, %v", err) + } + + printConfig(config) + + return &config, nil +} diff --git a/tools/integration_tests/emulator_tests/proxy_server/config_test.go b/tools/integration_tests/emulator_tests/proxy_server/config_test.go new file mode 100644 index 0000000000..f9428af3f5 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/config_test.go @@ -0,0 +1,102 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proxy_server + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseConfigFile(t *testing.T) { + t.Run("ValidConfigFile", func(t *testing.T) { + // Create a temporary file with valid YAML content + validContent := ` +targetHost: "http://localhost:8080" +retryConfig: + - method: "JsonCreate" + retryInstruction: "return-503" + retryCount: 5 + skipCount: 1 + - method: "JsonStat" + retryInstruction: "stall-33s-after-20K" + retryCount: 3 + skipCount: 0 +` + tempFile, err := os.CreateTemp("", "valid-config-*.yaml") + assert.NoError(t, err) + defer os.Remove(tempFile.Name()) + + _, err = tempFile.Write([]byte(validContent)) + assert.NoError(t, err) + tempFile.Close() + + // Parse the file + config, err := parseConfigFile(tempFile.Name()) + assert.NoError(t, err) + + // Assertions + assert.Equal(t, "http://localhost:8080", config.TargetHost, "unexpected TargetHost value") + + assert.Len(t, config.RetryConfig, 2, "unexpected number of RetryConfig entries") + assert.Equal(t, "JsonCreate", config.RetryConfig[0].Method, "unexpected method in first RetryConfig entry") + assert.Equal(t, "return-503", config.RetryConfig[0].RetryInstruction, "unexpected retryInstruction in first RetryConfig entry") + assert.Equal(t, 5, config.RetryConfig[0].RetryCount, "unexpected retryCount in first RetryConfig entry") + assert.Equal(t, 1, config.RetryConfig[0].SkipCount, "unexpected skipCount in first RetryConfig entry") + + assert.Equal(t, "JsonStat", config.RetryConfig[1].Method, "unexpected method in second RetryConfig entry") + assert.Equal(t, "stall-33s-after-20K", config.RetryConfig[1].RetryInstruction, "unexpected retryInstruction in second RetryConfig entry") + assert.Equal(t, 3, config.RetryConfig[1].RetryCount, "unexpected retryCount in second RetryConfig entry") + assert.Equal(t, 0, config.RetryConfig[1].SkipCount, "unexpected skipCount in second RetryConfig entry") + }) + + t.Run("EmptyConfigFile", func(t *testing.T) { + // Create an empty temporary file + tempFile, err := os.CreateTemp("", "empty-config-*.yaml") + assert.NoError(t, err) + defer os.Remove(tempFile.Name()) + tempFile.Close() + + // Parse the file + config, err := parseConfigFile(tempFile.Name()) + + // Assertions + assert.NoError(t, err) + assert.Nil(t, config.RetryConfig, "config should be nil") + }) + + t.Run("InvalidConfigFile", func(t *testing.T) { + // Create a file with invalid content + invalidContent := ` +invalid_key: "invalid_value" +another_invalid_key: +` + tempFile, err := os.CreateTemp("", "invalid-config-*.yaml") + assert.NoError(t, err) + defer os.Remove(tempFile.Name()) + + _, err = tempFile.Write([]byte(invalidContent)) + assert.NoError(t, err) + tempFile.Close() + + // Parse the file + config, err := parseConfigFile(tempFile.Name()) + + // Assertions + assert.NoError(t, err) + assert.Nil(t, config.RetryConfig) + }) +} diff --git a/tools/integration_tests/emulator_tests/proxy_server/operation_manager.go b/tools/integration_tests/emulator_tests/proxy_server/operation_manager.go new file mode 100644 index 0000000000..62656ceef3 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/operation_manager.go @@ -0,0 +1,75 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proxy_server + +import ( + "log" + "sync" +) + +type OperationManager struct { + retryConfigs map[RequestType][]RetryConfig + mu sync.Mutex +} + +func NewOperationManager(config Config) *OperationManager { + rc := make(map[RequestType][]RetryConfig) + om := &OperationManager{ + retryConfigs: rc, + } + for _, retryConfig := range config.RetryConfig { + om.addRetryConfig(retryConfig) + } + log.Printf("%+v\n", om) + return om +} + +// Empty string represent there is no plantation required. +func (om *OperationManager) retrieveOperation(requestType RequestType) string { + om.mu.Lock() + defer om.mu.Unlock() + + configs, ok := om.retryConfigs[requestType] + if !ok { + return "" + } + + for len(configs) > 0 { + cc := &configs[0] + if cc.SkipCount > 0 { + cc.SkipCount-- + return "" + } else if cc.RetryCount > 0 { + cc.RetryCount-- + return cc.RetryInstruction + } else { + configs = configs[1:] + om.retryConfigs[requestType] = configs + } + } + return "" +} + +func (om *OperationManager) addRetryConfig(rc RetryConfig) { + rt := RequestType(rc.Method) + println(rt) + if om.retryConfigs[rt] != nil { + // Key exists, append the new retryConfig to the existing list + om.retryConfigs[rt] = append(om.retryConfigs[rt], rc) + } else { + // Key doesn't exist, getRetryID a new list with the retryConfig + om.retryConfigs[rt] = []RetryConfig{rc} + } +} diff --git a/tools/integration_tests/emulator_tests/proxy_server/operation_manager_test.go b/tools/integration_tests/emulator_tests/proxy_server/operation_manager_test.go new file mode 100644 index 0000000000..65ff36ab65 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/operation_manager_test.go @@ -0,0 +1,155 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package proxy_server + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewOperationManager(t *testing.T) { + config := Config{ + RetryConfig: []RetryConfig{ + {Method: "JsonCreate", RetryInstruction: "return-503", RetryCount: 2, SkipCount: 1}, + {Method: "JsonStat", RetryInstruction: "stall-33s-after-20K", RetryCount: 3, SkipCount: 0}, + }, + } + + om := NewOperationManager(config) + + // Assert that retryConfigs are initialized correctly + assert.Len(t, om.retryConfigs, 2) + assert.Len(t, om.retryConfigs["JsonCreate"], 1) + assert.Len(t, om.retryConfigs["JsonStat"], 1) + + assert.Equal(t, "return-503", om.retryConfigs["JsonCreate"][0].RetryInstruction) + assert.Equal(t, "stall-33s-after-20K", om.retryConfigs["JsonStat"][0].RetryInstruction) +} + +func TestRetrieveOperation(t *testing.T) { + t.Run("One config test", func(t *testing.T) { + config := Config{ + RetryConfig: []RetryConfig{ + {Method: "JsonCreate", RetryInstruction: "return-503", RetryCount: 2, SkipCount: 1}, + }, + } + om := NewOperationManager(config) + + // First call: Skip count is decremented, so no retry instruction should be returned + result := om.retrieveOperation("JsonCreate") + assert.Equal(t, "", result, "Expected empty result due to SkipCount") + + // Second call: Retry instruction should be returned + result = om.retrieveOperation("JsonCreate") + assert.Equal(t, "return-503", result, "Expected 'return-503' as RetryInstruction") + + // Third call: Retry instruction should be returned again + result = om.retrieveOperation("JsonCreate") + assert.Equal(t, "return-503", result, "Expected 'return-503' as RetryInstruction") + + // Fourth call: Retry count is exhausted, so no retry instruction should be returned + result = om.retrieveOperation("JsonCreate") + assert.Equal(t, "", result, "Expected empty result as RetryCount is exhausted") + }) + + t.Run("Multiple config tests with same request types", func(t *testing.T) { + // Initialize OperationManager with two retry configs + config := Config{ + RetryConfig: []RetryConfig{ + {Method: "RequestTypeA", RetryInstruction: "retry-503", RetryCount: 2, SkipCount: 1}, + {Method: "RequestTypeA", RetryInstruction: "retry-202", RetryCount: 1, SkipCount: 0}, + }, + } + om := NewOperationManager(config) + + // Test for RequestTypeA + // First call: SkipCount is decremented, so no retry instruction should be returned + result := om.retrieveOperation("RequestTypeA") + assert.Equal(t, "", result, "Expected no result due to SkipCount") + + // Second call: First retry instruction should be returned + result = om.retrieveOperation("RequestTypeA") + assert.Equal(t, "retry-503", result, "Expected 'retry-503' as RetryInstruction") + + // Third call: Second retry instruction should be returned + result = om.retrieveOperation("RequestTypeA") + assert.Equal(t, "retry-503", result, "Expected 'retry-503' as RetryInstruction") + + // Fourth call: Move to the second config for RequestTypeA + result = om.retrieveOperation("RequestTypeA") + assert.Equal(t, "retry-202", result, "Expected 'retry-202' as RetryInstruction") + + // Fifth call: All retry instructions exhausted, so no result + result = om.retrieveOperation("RequestTypeA") + assert.Equal(t, "", result, "Expected no result as all retries are exhausted") + }) + + t.Run("Multiple config tests with different request types", func(t *testing.T) { + // Initialize OperationManager with two retry configs + config := Config{ + RetryConfig: []RetryConfig{ + {Method: "RequestTypeA", RetryInstruction: "retry-503", RetryCount: 2, SkipCount: 1}, + {Method: "RequestTypeB", RetryInstruction: "retry-202", RetryCount: 1, SkipCount: 0}, + }, + } + om := NewOperationManager(config) + + // Test for RequestTypeA + // First call: SkipCount is decremented, so no retry instruction should be returned + result := om.retrieveOperation("RequestTypeA") + assert.Equal(t, "", result, "Expected no result due to SkipCount") + + // Second call: First retry instruction should be returned + result = om.retrieveOperation("RequestTypeA") + assert.Equal(t, "retry-503", result, "Expected 'retry-503' as RetryInstruction") + + // Third call: Second retry instruction should be returned + result = om.retrieveOperation("RequestTypeA") + assert.Equal(t, "retry-503", result, "Expected 'retry-503' as RetryInstruction") + + // Forth call: All retry instructions for A exhausted, so no result + result = om.retrieveOperation("RequestTypeA") + assert.Equal(t, "", result, "Expected no result as all retries are exhausted") + + // Test for RequestTypeB + // Fifth call: Move to the config for RequestTypeB + result = om.retrieveOperation("RequestTypeB") + assert.Equal(t, "retry-202", result, "Expected 'retry-202' as RetryInstruction") + }) +} + +func TestAddRetryConfig(t *testing.T) { + om := &OperationManager{ + retryConfigs: make(map[RequestType][]RetryConfig), + } + + retryConfig := RetryConfig{Method: "JsonUpdate", RetryInstruction: "retry-202", RetryCount: 1, SkipCount: 0} + om.addRetryConfig(retryConfig) + + // Assert the retryConfig is added to the map + assert.Len(t, om.retryConfigs, 1) + assert.Len(t, om.retryConfigs["JsonUpdate"], 1) + assert.Equal(t, "retry-202", om.retryConfigs["JsonUpdate"][0].RetryInstruction) + + // Add another retryConfig for the same method + retryConfig2 := RetryConfig{Method: "JsonUpdate", RetryInstruction: "retry-503", RetryCount: 2, SkipCount: 1} + om.addRetryConfig(retryConfig2) + + // Assert both retryConfigs are stored under the same key + assert.Len(t, om.retryConfigs["JsonUpdate"], 2) + assert.Equal(t, "retry-202", om.retryConfigs["JsonUpdate"][0].RetryInstruction) + assert.Equal(t, "retry-503", om.retryConfigs["JsonUpdate"][1].RetryInstruction) +} From 26fb731245adc274ac9f589de32dbded70c8be22 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 3 Dec 2024 16:57:05 +0530 Subject: [PATCH 0012/1298] adding benchmarking test to mounted directory tests script (#2739) --- .../integration_tests/run_tests_mounted_directory.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index e7843299f5..a69e54c0c7 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -535,6 +535,17 @@ mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs=true,kernel_list_cac GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/concurrent_operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME sudo umount $MOUNT_DIR +# Test package: benchmarking +# Run tests with static mounting. (flags: --implicit-dirs=true) +gcsfuse --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/benchmarking/... --bench=. -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +sudo umount $MOUNT_DIR + +# Run test with persistent mounting. (flags: --implicit-dirs=true) +mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs=true +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/benchmarking/... --bench=. -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +sudo umount $MOUNT_DIR + # Test package: kernel-list-cache # Kernel list cache with infinite ttl. (--kernel-list-cache-ttl-secs=-1) From 84a712971d4d4d57a245752446220de08f53a124 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:06:28 +0530 Subject: [PATCH 0013/1298] updating thresholds (#2740) --- tools/integration_tests/benchmarking/benchmark_delete_test.go | 2 +- tools/integration_tests/benchmarking/benchmark_stat_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/integration_tests/benchmarking/benchmark_delete_test.go b/tools/integration_tests/benchmarking/benchmark_delete_test.go index 07fdfa03b3..874b62b66c 100644 --- a/tools/integration_tests/benchmarking/benchmark_delete_test.go +++ b/tools/integration_tests/benchmarking/benchmark_delete_test.go @@ -27,7 +27,7 @@ import ( ) const ( - expectedDeleteLatency time.Duration = 450 * time.Millisecond + expectedDeleteLatency time.Duration = 675 * time.Millisecond ) type benchmarkDeleteTest struct{} diff --git a/tools/integration_tests/benchmarking/benchmark_stat_test.go b/tools/integration_tests/benchmarking/benchmark_stat_test.go index ec86033fd1..e4eb735a1f 100644 --- a/tools/integration_tests/benchmarking/benchmark_stat_test.go +++ b/tools/integration_tests/benchmarking/benchmark_stat_test.go @@ -29,7 +29,7 @@ import ( //////////////////////////////////////////////////////////////////////// const ( - expectedStatLatency time.Duration = 260 * time.Millisecond + expectedStatLatency time.Duration = 390 * time.Millisecond ) type benchmarkStatTest struct{} From febf169e9a3f49c36dee211229938065c010fbf6 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:40:30 +0530 Subject: [PATCH 0014/1298] E2e test to ensure mounting with timeout (#2714) * adding mount timeout testsuite * making error message verbose * reusing util functions to mount * creating new test package for checking timeout for various scenario * adding mount timeout test package to e2e tests * seperate timeout test suite for relaxed timeuot threshold * making minimum threshold 2 seconds * reducing duplicacy * use t.log --- tools/cd_scripts/e2e_test.sh | 1 + .../gcsfuse_mount_timeout_test.go | 271 ++++++++++++++++++ .../mount_timeout/mount_timeout_test.go | 140 +++++++++ tools/integration_tests/run_e2e_tests.sh | 1 + 4 files changed, 413 insertions(+) create mode 100644 tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go create mode 100644 tools/integration_tests/mount_timeout/mount_timeout_test.go diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 372d83b050..2606cadda1 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -158,6 +158,7 @@ TEST_DIR_PARALLEL=( "kernel_list_cache" "concurrent_operations" "benchmarking" + "mount_timeout" ) # These tests never become parallel as they are changing bucket permissions. diff --git a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go new file mode 100644 index 0000000000..83e5b074f6 --- /dev/null +++ b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go @@ -0,0 +1,271 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mount_timeout + +import ( + "fmt" + "os" + "path" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +func TestMountTimeout(t *testing.T) { + if os.Getenv("TEST_ENV") == testEnvGCEUSCentral { + // Set strict region based timeout values if testing environment is GCE VM in us-central. + timeout := RegionWiseTimeouts{ + multiRegionUSTimeout: multiRegionUSExpectedMountTime, + multiRegionAsiaTimeout: multiRegionAsiaExpectedMountTime, + dualRegionUSTimeout: dualRegionUSExpectedMountTime, + dualRegionAsiaTimeout: dualRegionAsiaExpectedMountTime, + singleRegionUSCentralTimeout: singleRegionUSCentralExpectedMountTime, + singleRegionAsiaEastTimeout: singleRegionAsiaEastExpectedMountTime, + } + t.Log("Running tests with region based timeout values since the GCE VM is located in us-central...\n") + suite.Run(t, &MountTimeoutTest{timeouts: timeout}) + } else if os.Getenv("TEST_ENV") == testEnvGCENonUSCentral { + // Set common relaxed timeout values if testing environment is GCE VM not in us-central. + timeout := RegionWiseTimeouts{ + multiRegionUSTimeout: relaxedExpectedMountTime, + multiRegionAsiaTimeout: relaxedExpectedMountTime, + dualRegionUSTimeout: relaxedExpectedMountTime, + dualRegionAsiaTimeout: relaxedExpectedMountTime, + singleRegionUSCentralTimeout: relaxedExpectedMountTime, + singleRegionAsiaEastTimeout: relaxedExpectedMountTime, + } + t.Logf("Running tests with relaxed timeout of %f sec for all scenarios since the GCE VM is not located in us-central...\n", relaxedExpectedMountTime.Seconds()) + suite.Run(t, &MountTimeoutTest{timeouts: timeout}) + } else { + // Skip the tests if the testing environment is not GCE VM. + t.Log("Skipping tests since the testing environment is not GCE VM...\n") + t.Skip() + } +} + +type RegionWiseTimeouts struct { + multiRegionUSTimeout time.Duration + multiRegionAsiaTimeout time.Duration + dualRegionUSTimeout time.Duration + dualRegionAsiaTimeout time.Duration + singleRegionUSCentralTimeout time.Duration + singleRegionAsiaEastTimeout time.Duration +} + +type MountTimeoutTest struct { + suite.Suite + // Path to the gcsfuse binary. + gcsfusePath string + + // A temporary directory into which a file system may be mounted. Removed in + // TearDown. + dir string + timeouts RegionWiseTimeouts +} + +func (testSuite *MountTimeoutTest) SetupTest() { + var err error + testSuite.gcsfusePath = path.Join(gBuildDir, "bin/gcsfuse") + // Set up the temporary directory. + testSuite.dir, err = os.MkdirTemp("", "mount_timeout_test") + assert.NoError(testSuite.T(), err) +} + +func (testSuite *MountTimeoutTest) TearDownTest() { + err := os.Remove(testSuite.dir) + assert.NoError(testSuite.T(), err) +} + +// mountOrTimeout mounts the bucket with the given client protocol. If the time taken +// exceeds the expected for the particular test case , an error is thrown and test will fail. +func (testSuite *MountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientProtocol string, expectedMountTime time.Duration) error { + args := []string{"--client-protocol", clientProtocol, bucketName, testSuite.dir} + start := time.Now() + if err := mounting.MountGcsfuse(testSuite.gcsfusePath, args); err != nil { + return err + } + defer func() { + if err := util.Unmount(mountDir); err != nil { + fmt.Fprintf(os.Stderr, "Warning: unmount failed: %v\n", err) + } + }() + + if mountTime := time.Since(start); mountTime > expectedMountTime { + return fmt.Errorf("[Client Protocol: %s]Mounting failed due to timeout(exceeding %f seconds).Time taken for the mounting %s: %f sec", clientProtocol, expectedMountTime.Seconds(), bucketName, mountTime.Seconds()) + } + return nil +} + +func (testSuite *MountTimeoutTest) TestMountMultiRegionUSBucketWithTimeout() { + testCases := []struct { + name string + clientProtocol cfg.Protocol + }{ + { + name: "multiRegionUSClientProtocolGRPC", + clientProtocol: cfg.GRPC, + }, + { + name: "multiRegionUSClientProtocolHttp1", + clientProtocol: cfg.HTTP1, + }, + { + name: "multiRegionUSClientProtocolHttp2", + clientProtocol: cfg.HTTP2, + }, + } + for _, tc := range testCases { + setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) + + err := testSuite.mountOrTimeout(multiRegionUSBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.multiRegionUSTimeout) + assert.NoError(testSuite.T(), err) + } +} + +func (testSuite *MountTimeoutTest) TestMountMultiRegionAsiaBucketWithTimeout() { + testCases := []struct { + name string + clientProtocol cfg.Protocol + }{ + { + name: "multiRegionAsiaClientProtocolGRPC", + clientProtocol: cfg.GRPC, + }, + { + name: "multiRegionAsiaClientProtocolHttp1", + clientProtocol: cfg.HTTP1, + }, + { + name: "multiRegionAsiaClientProtocolHttp2", + clientProtocol: cfg.HTTP2, + }, + } + for _, tc := range testCases { + setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) + + err := testSuite.mountOrTimeout(multiRegionAsiaBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.multiRegionAsiaTimeout) + assert.NoError(testSuite.T(), err) + } +} + +func (testSuite *MountTimeoutTest) TestMountDualRegionUSBucketWithTimeout() { + testCases := []struct { + name string + clientProtocol cfg.Protocol + }{ + { + name: "dualRegionUSClientProtocolGRPC", + clientProtocol: cfg.GRPC, + }, + { + name: "dualRegionUSClientProtocolHttp1", + clientProtocol: cfg.HTTP1, + }, + { + name: "dualRegionUSClientProtocolHttp2", + clientProtocol: cfg.HTTP2, + }, + } + for _, tc := range testCases { + setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) + + err := testSuite.mountOrTimeout(dualRegionUSBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.dualRegionUSTimeout) + assert.NoError(testSuite.T(), err) + } +} + +func (testSuite *MountTimeoutTest) TestMountDualRegionAsiaBucketWithTimeout() { + testCases := []struct { + name string + clientProtocol cfg.Protocol + }{ + { + name: "dualRegionAsiaClientProtocolGRPC", + clientProtocol: cfg.GRPC, + }, + { + name: "dualRegionAsiaClientProtocolHttp1", + clientProtocol: cfg.HTTP1, + }, + { + name: "dualRegionAsiaClientProtocolHttp2", + clientProtocol: cfg.HTTP2, + }, + } + for _, tc := range testCases { + setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) + + err := testSuite.mountOrTimeout(dualRegionAsiaBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.dualRegionAsiaTimeout) + assert.NoError(testSuite.T(), err) + } +} + +func (testSuite *MountTimeoutTest) TestMountSingleRegionUSBucketWithTimeout() { + testCases := []struct { + name string + clientProtocol cfg.Protocol + }{ + { + name: "singleRegionUSClientProtocolGRPC", + clientProtocol: cfg.GRPC, + }, + { + name: "singleRegionUSClientProtocolHttp1", + clientProtocol: cfg.HTTP1, + }, + { + name: "singleRegionUSClientProtocolHttp2", + clientProtocol: cfg.HTTP2, + }, + } + for _, tc := range testCases { + setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) + + err := testSuite.mountOrTimeout(singleRegionUSCentralBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.singleRegionUSCentralTimeout) + assert.NoError(testSuite.T(), err) + } +} + +func (testSuite *MountTimeoutTest) TestMountSingleRegionAsiaBucketWithTimeout() { + testCases := []struct { + name string + clientProtocol cfg.Protocol + }{ + { + name: "singleRegionAsiaClientProtocolGRPC", + clientProtocol: cfg.GRPC, + }, + { + name: "singleRegionAsiaClientProtocolHttp1", + clientProtocol: cfg.HTTP1, + }, + { + name: "singleRegionAsiaClientProtocolHttp2", + clientProtocol: cfg.HTTP2, + }, + } + for _, tc := range testCases { + setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) + + err := testSuite.mountOrTimeout(singleRegionAsiaEastBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.singleRegionAsiaEastTimeout) + assert.NoError(testSuite.T(), err) + } +} diff --git a/tools/integration_tests/mount_timeout/mount_timeout_test.go b/tools/integration_tests/mount_timeout/mount_timeout_test.go new file mode 100644 index 0000000000..c322b2c533 --- /dev/null +++ b/tools/integration_tests/mount_timeout/mount_timeout_test.go @@ -0,0 +1,140 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mount_timeout + +import ( + "context" + "fmt" + "log" + "os" + "os/exec" + "runtime" + "strings" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "go.opentelemetry.io/contrib/detectors/gcp" + "go.opentelemetry.io/otel/sdk/resource" +) + +// A directory containing outputs created by build_gcsfuse, set up and deleted +// in TestMain. +var gBuildDir string + +// On Linux, the path to fusermount, whose directory must be in gcsfuse's PATH +// variable in order to successfully mount. Set by TestMain. +var gFusermountPath string + +const ( + testEnvGCEUSCentral string = "gce-us-central" + testEnvGCENonUSCentral string = "gce-non-us-central" + testEnvNonGCE string = "non-gce" + multiRegionUSBucket string = "mount_timeout_test_bucket_us" + multiRegionAsiaBucket string = "mount_timeout_test_bucket_asia" + dualRegionUSBucket string = "mount_timeout_test_bucket_nam4" + dualRegionAsiaBucket string = "mount_timeout_test_bucket_asia1" + singleRegionUSCentralBucket string = "mount_timeout_test_bucket_us-central1" + singleRegionAsiaEastBucket string = "mount_timeout_test_bucket_asia-east1" + singleRegionAsiaEastExpectedMountTime time.Duration = 3200 * time.Millisecond + multiRegionUSExpectedMountTime time.Duration = 2000 * time.Millisecond + multiRegionAsiaExpectedMountTime time.Duration = 4500 * time.Millisecond + dualRegionUSExpectedMountTime time.Duration = 2700 * time.Millisecond + dualRegionAsiaExpectedMountTime time.Duration = 3750 * time.Millisecond + singleRegionUSCentralExpectedMountTime time.Duration = 2000 * time.Millisecond + relaxedExpectedMountTime time.Duration = 8000 * time.Millisecond + logfilePathPrefix string = "/tmp/gcsfuse_mount_timeout_" +) + +// findTestExecutionEnvironment determines the environment in which the tests are running. +// It uses the GCP resource detector to identify the environment. +// +// If the tests are running on a GCE instance with a hostname containing non-gce. +// it returns testEnvNonGCE since it implies that the tests are being run on cloudtop. +// +// If the tests are running on a VM in the "us-central" region, it returns gce-us-central . +// Otherwise, if running in any other region, it returns gce-non-us-central. +// +// For all other cases, it returns non-gce. +func findTestExecutionEnvironment(ctx context.Context) string { + detectedAttrs, err := resource.New(ctx, resource.WithDetectors(gcp.NewDetector())) + if err != nil { + log.Printf("Error fetching the test environment.All tests will be skipped.") + } + attrs := detectedAttrs.Set() + if v, exists := attrs.Value("gcp.gce.instance.hostname"); exists && strings.Contains(strings.ToLower(v.AsString()), "cloudtop-prod") { + return testEnvNonGCE + } + if v, exists := attrs.Value("cloud.region"); exists { + if strings.Contains(strings.ToLower(v.AsString()), "us-central") { + return testEnvGCEUSCentral + } else { + return testEnvGCENonUSCentral + } + } + return testEnvNonGCE +} + +func TestMain(m *testing.M) { + // Parse flags from the setup. + setup.ParseSetUpFlags() + + var err error + + // Find fusermount if we're running on Linux. + if runtime.GOOS == "linux" { + gFusermountPath, err = exec.LookPath("fusermount") + if err != nil { + log.Fatalf("LookPath(fusermount): %p", err) + } + } + testEnv := findTestExecutionEnvironment(context.Background()) + err = os.Setenv("TEST_ENV", testEnv) + if err != nil { + fmt.Println("Error setting environment variable:", err) + return + } + + if setup.TestInstalledPackage() { + // when testInstalledPackage flag is set, gcsfuse is preinstalled on the + // machine. Hence, here we are overwriting gBuildDir to /. + gBuildDir = "/" + code := m.Run() + os.Exit(code) + } + + // To test locally built package + // Set up a directory into which we will build. + gBuildDir, err = os.MkdirTemp("", "gcsfuse_integration_tests") + if err != nil { + log.Fatalf("TempDir: %p", err) + return + } + + // Build into that directory. + err = util.BuildGcsfuse(gBuildDir) + if err != nil { + log.Fatalf("buildGcsfuse: %p", err) + return + } + + // Run tests. + code := m.Run() + + // Clean up and exit. + os.RemoveAll(gBuildDir) + os.Exit(code) +} diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 9f6792e3be..3404f94688 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -81,6 +81,7 @@ TEST_DIR_PARALLEL=( "kernel_list_cache" "concurrent_operations" "benchmarking" + "mount_timeout" ) # These tests never become parallel as it is changing bucket permissions. From 380cff3e7a0b397cf249eb86105db1ea642787ca Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 4 Dec 2024 17:51:12 +0530 Subject: [PATCH 0015/1298] Use an interface to cleanly switch between different implementations of metrics (#2699) * Use an interface to cleanly switch between different implementations of metrics * Instead of using package method calls, call methods on metricHandle which is more object-oriented and enhances testability. * Use the Strategy design pattern to switch between OC, OTel and no-op implementations of metrics. * Currently only OpenCensus implementation is complete. OTel support will be implemented as a follow-up. * Extracting metrics related code in one place helps with a better segregation of concerns. --- cfg/config_util.go | 5 + cfg/config_util_test.go | 22 ++ cmd/legacy_main.go | 21 +- cmd/mount.go | 7 +- common/noop_metrics.go | 39 +++ common/oc_metrics.go | 234 ++++++++++++++++++ common/telemetry.go | 41 +++ common/telemetry_test.go | 43 ++++ internal/cache/file/cache_handle_test.go | 5 + internal/cache/file/cache_handler_test.go | 3 +- internal/cache/file/downloader/downloader.go | 8 +- .../cache/file/downloader/downloader_test.go | 3 +- .../downloader/jm_parallel_downloads_test.go | 5 +- internal/cache/file/downloader/job.go | 8 +- internal/cache/file/downloader/job_test.go | 3 +- .../file/downloader/parallel_downloads_job.go | 4 +- internal/fs/caching_test.go | 2 + internal/fs/fs.go | 15 +- internal/fs/fs_test.go | 4 +- internal/fs/handle/file.go | 7 +- internal/fs/hns_bucket_test.go | 2 + internal/fs/implicit_dirs_test.go | 2 + internal/fs/implicit_dirs_with_cache_test.go | 2 + internal/fs/inode/base_dir.go | 9 +- internal/fs/inode/base_dir_test.go | 6 +- .../kernel_list_cache_inifinite_ttl_test.go | 2 + internal/fs/kernel_list_cache_test.go | 2 + .../fs/kernel_list_cache_zero_ttl_test.go | 2 + internal/fs/local_file_test.go | 2 + internal/fs/parallel_dirops_test.go | 2 + internal/fs/read_cache_test.go | 2 + internal/fs/server.go | 2 +- internal/fs/type_cache_test.go | 2 + internal/fs/wrappers/monitoring.go | 97 ++------ internal/gcsx/bucket_manager.go | 6 +- internal/gcsx/bucket_manager_test.go | 9 +- internal/gcsx/random_reader.go | 22 +- internal/gcsx/random_reader_test.go | 11 +- internal/monitor/bucket.go | 146 +++-------- internal/monitor/reader.go | 158 ------------ internal/monitor/tags/tags.go | 40 --- 41 files changed, 568 insertions(+), 437 deletions(-) create mode 100644 common/noop_metrics.go create mode 100644 common/oc_metrics.go delete mode 100644 internal/monitor/reader.go delete mode 100644 internal/monitor/tags/tags.go diff --git a/cfg/config_util.go b/cfg/config_util.go index 1d4c740faa..b4ffaaaa6a 100644 --- a/cfg/config_util.go +++ b/cfg/config_util.go @@ -50,3 +50,8 @@ func ListCacheTTLSecsToDuration(secs int64) time.Duration { return time.Duration(secs * int64(time.Second)) } + +// IsMetricsEnabled returns true if metrics are enabled. +func IsMetricsEnabled(c *MetricsConfig) bool { + return c.CloudMetricsExportIntervalSecs > 0 || c.PrometheusPort > 0 +} diff --git a/cfg/config_util_test.go b/cfg/config_util_test.go index 13089dd0b6..6a01e08a07 100644 --- a/cfg/config_util_test.go +++ b/cfg/config_util_test.go @@ -181,3 +181,25 @@ func TestIsTracingEnabled(t *testing.T) { }) } } + +func TestIsMetricsEnabled(t *testing.T) { + t.Parallel() + var testCases = []struct { + testName string + m *MetricsConfig + enabled bool + }{ + {"cloud_metrics_export_interval_set", &MetricsConfig{CloudMetricsExportIntervalSecs: 100}, true}, + {"prom_port_set", &MetricsConfig{PrometheusPort: 10000}, true}, + {"none_set", &MetricsConfig{CloudMetricsExportIntervalSecs: 0, PrometheusPort: 0}, false}, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.testName, func(t *testing.T) { + t.Parallel() + + assert.Equal(t, tc.enabled, IsMetricsEnabled(tc.m)) + }) + } +} diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 6c854d5561..896f3472d5 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -145,7 +145,7 @@ func createStorageHandle(newConfig *cfg.Config, userAgent string) (storageHandle //////////////////////////////////////////////////////////////////////// // Mount the file system according to arguments in the supplied context. -func mountWithArgs(bucketName string, mountPoint string, newConfig *cfg.Config) (mfs *fuse.MountedFileSystem, err error) { +func mountWithArgs(bucketName string, mountPoint string, newConfig *cfg.Config, metricHandle common.MetricHandle) (mfs *fuse.MountedFileSystem, err error) { // Enable invariant checking if requested. if newConfig.Debug.ExitOnInvariantViolation { locker.EnableInvariantsCheck() @@ -176,7 +176,8 @@ func mountWithArgs(bucketName string, mountPoint string, newConfig *cfg.Config) bucketName, mountPoint, newConfig, - storageHandle) + storageHandle, + metricHandle) if err != nil { err = fmt.Errorf("mountWithStorageHandle: %w", err) @@ -373,10 +374,16 @@ func Mount(newConfig *cfg.Config, bucketName, mountPoint string) (err error) { ctx := context.Background() var metricExporterShutdownFn common.ShutdownFn - if newConfig.Metrics.EnableOtel { - metricExporterShutdownFn = monitor.SetupOTelMetricExporters(ctx, newConfig) - } else { - metricExporterShutdownFn = monitor.SetupOpenCensusExporters(newConfig) + metricHandle := common.NewNoopMetrics() + if cfg.IsMetricsEnabled(&newConfig.Metrics) { + if newConfig.Metrics.EnableOtel { + metricExporterShutdownFn = monitor.SetupOTelMetricExporters(ctx, newConfig) + } else { + metricExporterShutdownFn = monitor.SetupOpenCensusExporters(newConfig) + if metricHandle, err = common.NewOCMetrics(); err != nil { + metricHandle = common.NewNoopMetrics() + } + } } shutdownTracingFn := monitor.SetupTracing(ctx, newConfig) shutdownFn := common.JoinShutdownFunc(metricExporterShutdownFn, shutdownTracingFn) @@ -385,7 +392,7 @@ func Mount(newConfig *cfg.Config, bucketName, mountPoint string) (err error) { // daemonize gives us and telling it about the outcome. var mfs *fuse.MountedFileSystem { - mfs, err = mountWithArgs(bucketName, mountPoint, newConfig) + mfs, err = mountWithArgs(bucketName, mountPoint, newConfig, metricHandle) // This utility is to absorb the error // returned by daemonize.SignalOutcome calls by simply diff --git a/cmd/mount.go b/cmd/mount.go index 29ead6e8d3..9187752195 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -20,6 +20,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/mount" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" "golang.org/x/net/context" @@ -40,7 +41,8 @@ func mountWithStorageHandle( bucketName string, mountPoint string, newConfig *cfg.Config, - storageHandle storage.StorageHandle) (mfs *fuse.MountedFileSystem, err error) { + storageHandle storage.StorageHandle, + metricHandle common.MetricHandle) (mfs *fuse.MountedFileSystem, err error) { // Sanity check: make sure the temporary directory exists and is writable // currently. This gives a better user experience than harder to debug EIO // errors when reading files in the future. @@ -91,7 +93,7 @@ be interacting with the file system.`) OpRateLimitHz: newConfig.GcsConnection.LimitOpsPerSec, StatCacheMaxSizeMB: uint64(newConfig.MetadataCache.StatCacheMaxSizeMb), StatCacheTTL: time.Duration(newConfig.MetadataCache.TtlSecs) * time.Second, - EnableMonitoring: newConfig.Metrics.StackdriverExportInterval > 0 || newConfig.Metrics.PrometheusPort != 0, + EnableMonitoring: cfg.IsMetricsEnabled(&newConfig.Metrics), AppendThreshold: 1 << 21, // 2 MiB, a total guess. TmpObjectPrefix: ".gcsfuse_tmp/", } @@ -115,6 +117,7 @@ be interacting with the file system.`) SequentialReadSizeMb: int32(newConfig.GcsConnection.SequentialReadSizeMb), EnableNonexistentTypeCache: newConfig.MetadataCache.EnableNonexistentTypeCache, NewConfig: newConfig, + MetricHandle: metricHandle, } logger.Infof("Creating a new server...\n") diff --git a/common/noop_metrics.go b/common/noop_metrics.go new file mode 100644 index 0000000000..fd2e7dc319 --- /dev/null +++ b/common/noop_metrics.go @@ -0,0 +1,39 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import "context" + +func NewNoopMetrics() MetricHandle { + var n noopMetrics + return &n +} + +type noopMetrics struct{} + +func (*noopMetrics) GCSReadBytesCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) GCSReaderCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) GCSRequestCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) GCSRequestLatency(_ context.Context, value float64, _ []MetricAttr) {} +func (*noopMetrics) GCSReadCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) GCSDownloadBytesCount(_ context.Context, _ int64, _ []MetricAttr) {} + +func (*noopMetrics) OpsCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) OpsLatency(_ context.Context, value float64, _ []MetricAttr) {} +func (*noopMetrics) OpsErrorCount(_ context.Context, _ int64, _ []MetricAttr) {} + +func (*noopMetrics) FileCacheReadCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) FileCacheReadBytesCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) FileCacheReadLatency(_ context.Context, value float64, _ []MetricAttr) {} diff --git a/common/oc_metrics.go b/common/oc_metrics.go new file mode 100644 index 0000000000..870a11bf9e --- /dev/null +++ b/common/oc_metrics.go @@ -0,0 +1,234 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "context" + "fmt" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "go.opencensus.io/plugin/ochttp" + "go.opencensus.io/stats" + "go.opencensus.io/stats/view" + "go.opencensus.io/tag" +) + +const ( + // IOMethod annotates the event that opens or closes a connection or file. + IOMethod = "io_method" + + // GCSMethod annotates the method called in the GCS client library. + GCSMethod = "gcs_method" + + // FSOp annotates the file system op processed. + FSOp = "fs_op" + + // FSErrCategory reduces the cardinality of FSError by grouping errors together. + FSErrCategory = "fs_error_category" + + // ReadType annotates the read operation with the type - Sequential/Random + ReadType = "read_type" + + // CacheHit annotates the read operation from file cache with true or false. + CacheHit = "cache_hit" +) + +type ocMetrics struct { + // GCS measures + gcsReadBytesCount *stats.Int64Measure + gcsReaderCount *stats.Int64Measure + gcsRequestCount *stats.Int64Measure + gcsRequestLatency *stats.Float64Measure + gcsReadCount *stats.Int64Measure + gcsDownloadBytesCount *stats.Int64Measure + + // Ops measures + opsCount *stats.Int64Measure + opsErrorCount *stats.Int64Measure + opsLatency *stats.Float64Measure + + // File cache measures + fileCacheReadCount *stats.Int64Measure + fileCacheReadBytesCount *stats.Int64Measure + fileCacheReadLatency *stats.Float64Measure +} + +func attrsToTags(attrs []MetricAttr) []tag.Mutator { + mutators := make([]tag.Mutator, 0, len(attrs)) + for _, attr := range attrs { + mutators = append(mutators, tag.Upsert(tag.MustNewKey(attr.Key), attr.Value)) + } + return mutators +} +func (o *ocMetrics) GCSReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { + recordOCMetric(ctx, o.gcsReadBytesCount, inc, attrs, "GCS read bytes count") +} + +func (o *ocMetrics) GCSReaderCount(ctx context.Context, inc int64, attrs []MetricAttr) { + recordOCMetric(ctx, o.gcsReaderCount, inc, attrs, "GCS reader count") +} + +func (o *ocMetrics) GCSRequestCount(ctx context.Context, inc int64, attrs []MetricAttr) { + recordOCMetric(ctx, o.gcsRequestCount, inc, attrs, "GCS request count") +} + +func (o *ocMetrics) GCSRequestLatency(ctx context.Context, value float64, attrs []MetricAttr) { + recordOCLatencyMetric(ctx, o.gcsRequestLatency, value, attrs, "GCS request latency") +} +func (o *ocMetrics) GCSReadCount(ctx context.Context, inc int64, attrs []MetricAttr) { + recordOCMetric(ctx, o.gcsReadCount, inc, attrs, "GCS read count") +} +func (o *ocMetrics) GCSDownloadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { + recordOCMetric(ctx, o.gcsDownloadBytesCount, inc, attrs, "GCS download bytes count") +} + +func (o *ocMetrics) OpsCount(ctx context.Context, inc int64, attrs []MetricAttr) { + recordOCMetric(ctx, o.opsCount, inc, attrs, "file system op count") +} +func (o *ocMetrics) OpsLatency(ctx context.Context, value float64, attrs []MetricAttr) { + recordOCLatencyMetric(ctx, o.opsLatency, value, attrs, "file system op latency") +} +func (o *ocMetrics) OpsErrorCount(ctx context.Context, inc int64, attrs []MetricAttr) { + recordOCMetric(ctx, o.opsErrorCount, inc, attrs, "file system op error count") +} + +func (o *ocMetrics) FileCacheReadCount(ctx context.Context, inc int64, attrs []MetricAttr) { + recordOCMetric(ctx, o.fileCacheReadCount, inc, attrs, "file cache read count") +} +func (o *ocMetrics) FileCacheReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { + recordOCMetric(ctx, o.fileCacheReadBytesCount, inc, attrs, "file cache read bytes count") +} +func (o *ocMetrics) FileCacheReadLatency(ctx context.Context, value float64, attrs []MetricAttr) { + recordOCLatencyMetric(ctx, o.fileCacheReadLatency, value, attrs, "file cache read latency") +} + +func recordOCMetric(ctx context.Context, m *stats.Int64Measure, inc int64, attrs []MetricAttr, metricStr string) { + if err := stats.RecordWithTags( + ctx, + attrsToTags(attrs), + m.M(inc), + ); err != nil { + logger.Errorf("Cannot record %s: %v: %v", metricStr, attrs, err) + } +} + +func recordOCLatencyMetric(ctx context.Context, m *stats.Float64Measure, inc float64, attrs []MetricAttr, metricStr string) { + if err := stats.RecordWithTags( + ctx, + attrsToTags(attrs), + m.M(inc), + ); err != nil { + logger.Errorf("Cannot record %s: %v: %v", metricStr, attrs, err) + } +} + +func NewOCMetrics() (MetricHandle, error) { + gcsReadBytesCount := stats.Int64("gcs/read_bytes_count", "The number of bytes read from GCS objects.", stats.UnitBytes) + gcsReaderCount := stats.Int64("gcs/reader_count", "The number of GCS object readers opened or closed.", stats.UnitDimensionless) + gcsRequestCount := stats.Int64("gcs/request_count", "The number of GCS requests processed.", stats.UnitDimensionless) + gcsRequestLatency := stats.Float64("gcs/request_latency", "The latency of a GCS request.", stats.UnitMilliseconds) + gcsReadCount := stats.Int64("gcs/read_count", "Specifies the number of gcs reads made along with type - Sequential/Random", stats.UnitDimensionless) + gcsDownloadBytesCount := stats.Int64("gcs/download_bytes_count", "The cumulative number of bytes downloaded from GCS along with type - Sequential/Random", stats.UnitBytes) + + opsCount := stats.Int64("fs/ops_count", "The number of ops processed by the file system.", stats.UnitDimensionless) + opsLatency := stats.Float64("fs/ops_latency", "The latency of a file system operation.", "us") + opsErrorCount := stats.Int64("fs/ops_error_count", "The number of errors generated by file system operation.", stats.UnitDimensionless) + + fileCacheReadCount := stats.Int64("file_cache/read_count", "Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false", stats.UnitDimensionless) + fileCacheReadBytesCount := stats.Int64("file_cache/read_bytes_count", "The cumulative number of bytes read from file cache along with read type - Sequential/Random", stats.UnitBytes) + fileCacheReadLatency := stats.Float64("file_cache/read_latency", "Latency of read from file cache along with cache hit - true/false", "us") + // OpenCensus views (aggregated measures) + if err := view.Register( + &view.View{ + Name: "gcs/read_bytes_count", + Measure: gcsReadBytesCount, + Description: "The cumulative number of bytes read from GCS objects.", + Aggregation: view.Sum(), + }, + &view.View{ + Name: "gcs/reader_count", + Measure: gcsReaderCount, + Description: "The cumulative number of GCS object readers opened or closed.", + Aggregation: view.Sum(), + TagKeys: []tag.Key{tag.MustNewKey(IOMethod)}, + }, + &view.View{ + Name: "gcs/request_count", + Measure: gcsRequestCount, + Description: "The cumulative number of GCS requests processed.", + Aggregation: view.Sum(), + TagKeys: []tag.Key{tag.MustNewKey(GCSMethod)}, + }, + &view.View{ + Name: "gcs/request_latencies", + Measure: gcsRequestLatency, + Description: "The cumulative distribution of the GCS request latencies.", + Aggregation: ochttp.DefaultLatencyDistribution, + TagKeys: []tag.Key{tag.MustNewKey(GCSMethod)}, + }, + &view.View{ + Name: "gcs/read_count", + Measure: gcsReadCount, + Description: "Specifies the number of gcs reads made along with type - Sequential/Random", + Aggregation: view.Sum(), + TagKeys: []tag.Key{tag.MustNewKey(ReadType)}, + }, + &view.View{ + Name: "gcs/download_bytes_count", + Measure: gcsDownloadBytesCount, + Description: "The cumulative number of bytes downloaded from GCS along with type - Sequential/Random", + Aggregation: view.Sum(), + TagKeys: []tag.Key{tag.MustNewKey(ReadType)}, + }, + &view.View{ + Name: "fs/ops_count", + Measure: opsCount, + Description: "The cumulative number of ops processed by the file system.", + Aggregation: view.Sum(), + TagKeys: []tag.Key{tag.MustNewKey(FSOp)}, + }, + &view.View{ + Name: "fs/ops_error_count", + Measure: opsErrorCount, + Description: "The cumulative number of errors generated by file system operations", + Aggregation: view.Sum(), + TagKeys: []tag.Key{tag.MustNewKey(FSOp), tag.MustNewKey(FSErrCategory)}, + }, + &view.View{ + Name: "fs/ops_latency", + Measure: opsLatency, + Description: "The cumulative distribution of file system operation latencies", + Aggregation: ochttp.DefaultLatencyDistribution, + TagKeys: []tag.Key{tag.MustNewKey(FSOp)}, + }); err != nil { + return nil, fmt.Errorf("failed to register OpenCensus metrics for GCS client library: %w", err) + } + return &ocMetrics{ + gcsReadBytesCount: gcsReadBytesCount, + gcsReaderCount: gcsReaderCount, + gcsRequestCount: gcsRequestCount, + gcsRequestLatency: gcsRequestLatency, + gcsReadCount: gcsReadCount, + gcsDownloadBytesCount: gcsDownloadBytesCount, + + opsCount: opsCount, + opsErrorCount: opsErrorCount, + opsLatency: opsLatency, + + fileCacheReadCount: fileCacheReadCount, + fileCacheReadBytesCount: fileCacheReadBytesCount, + fileCacheReadLatency: fileCacheReadLatency, + }, nil +} diff --git a/common/telemetry.go b/common/telemetry.go index 572eca36d6..c23dfa2680 100644 --- a/common/telemetry.go +++ b/common/telemetry.go @@ -17,6 +17,7 @@ package common import ( "context" "errors" + "fmt" ) type ShutdownFn func(ctx context.Context) error @@ -34,3 +35,43 @@ func JoinShutdownFunc(shutdownFns ...ShutdownFn) ShutdownFn { return err } } + +// MetricAttr represents the attributes associated with a metric. +type MetricAttr struct { + Key, Value string +} + +func (a *MetricAttr) String() string { + return fmt.Sprintf("Key: %s, Value: %s", a.Key, a.Value) +} + +type GCSMetricHandle interface { + GCSReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) + GCSReaderCount(ctx context.Context, inc int64, attrs []MetricAttr) + GCSRequestCount(ctx context.Context, inc int64, attrs []MetricAttr) + GCSRequestLatency(ctx context.Context, value float64, attrs []MetricAttr) + GCSReadCount(ctx context.Context, inc int64, attrs []MetricAttr) + GCSDownloadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) +} + +type OpsMetricHandle interface { + OpsCount(ctx context.Context, inc int64, attrs []MetricAttr) + OpsLatency(ctx context.Context, value float64, attrs []MetricAttr) + OpsErrorCount(ctx context.Context, inc int64, attrs []MetricAttr) +} + +type FileCacheMetricHandle interface { + FileCacheReadCount(ctx context.Context, inc int64, attrs []MetricAttr) + FileCacheReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) + FileCacheReadLatency(ctx context.Context, value float64, attrs []MetricAttr) +} +type MetricHandle interface { + GCSMetricHandle + OpsMetricHandle + FileCacheMetricHandle +} + +func CaptureGCSReadMetrics(ctx context.Context, metricHandle MetricHandle, readType string, requestedDataSize int64) { + metricHandle.GCSReadCount(ctx, 1, []MetricAttr{{Key: ReadType, Value: readType}}) + metricHandle.GCSDownloadBytesCount(ctx, requestedDataSize, []MetricAttr{{Key: ReadType, Value: readType}}) +} diff --git a/common/telemetry_test.go b/common/telemetry_test.go index bbe02015e0..09384c4002 100644 --- a/common/telemetry_test.go +++ b/common/telemetry_test.go @@ -86,3 +86,46 @@ func TestJoinShutdownFunc(t *testing.T) { }) } } + +type int64DataPoint struct { + v int64 + attrs []MetricAttr +} + +type fakeMetricHandle struct { + noopMetrics + GCSReadBytesCounter []int64DataPoint + GCSDownloadBytesCounter []int64DataPoint +} + +func (f *fakeMetricHandle) GCSReadCount(ctx context.Context, inc int64, attrs []MetricAttr) { + f.GCSReadBytesCounter = append(f.GCSReadBytesCounter, int64DataPoint{ + v: inc, + attrs: attrs, + }) +} + +func (f *fakeMetricHandle) GCSDownloadBytesCount(ctx context.Context, requestedDataSize int64, attrs []MetricAttr) { + f.GCSDownloadBytesCounter = append(f.GCSDownloadBytesCounter, int64DataPoint{ + v: requestedDataSize, + attrs: attrs, + }) +} + +func TestCaptureGCSReadMetrics(t *testing.T) { + t.Parallel() + metricHandle := fakeMetricHandle{} + + CaptureGCSReadMetrics(context.Background(), &metricHandle, "Sequential", 64) + + require.Len(t, metricHandle.GCSReadBytesCounter, 1) + require.Len(t, metricHandle.GCSDownloadBytesCounter, 1) + assert.Equal(t, metricHandle.GCSReadBytesCounter[0], int64DataPoint{ + v: 1, + attrs: []MetricAttr{{Key: ReadType, Value: "Sequential"}}, + }) + assert.Equal(t, metricHandle.GCSDownloadBytesCounter[0], int64DataPoint{ + v: 64, + attrs: []MetricAttr{{Key: ReadType, Value: "Sequential"}}, + }) +} diff --git a/internal/cache/file/cache_handle_test.go b/internal/cache/file/cache_handle_test.go index 4ea51e0c2e..c44d0bf0ac 100644 --- a/internal/cache/file/cache_handle_test.go +++ b/internal/cache/file/cache_handle_test.go @@ -29,6 +29,7 @@ import ( "testing" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" @@ -148,6 +149,7 @@ func (cht *cacheHandleTest) SetupTest() { func() {}, fileCacheConfig, semaphore.NewWeighted(math.MaxInt64), + common.NewNoopMetrics(), ) cht.cacheHandle = NewCacheHandle(readLocalFileHandle, fileDownloadJob, cht.cache, false, 0) @@ -855,6 +857,7 @@ func (cht *cacheHandleTest) Test_SequentialRead_Parallel_Download_True() { func() {}, fileCacheConfig, semaphore.NewWeighted(math.MaxInt64), + common.NewNoopMetrics(), ) cht.cacheHandle.fileDownloadJob = fileDownloadJob @@ -889,6 +892,7 @@ func (cht *cacheHandleTest) Test_RandomRead_Parallel_Download_True() { func() {}, fileCacheConfig, semaphore.NewWeighted(math.MaxInt64), + common.NewNoopMetrics(), ) cht.cacheHandle.fileDownloadJob = fileDownloadJob @@ -923,6 +927,7 @@ func (cht *cacheHandleTest) Test_RandomRead_CacheForRangeReadFalse_And_ParallelD func() {}, fileCacheConfig, semaphore.NewWeighted(math.MaxInt64), + common.NewNoopMetrics(), ) // Since, it's a random read, download job will not start. diff --git a/internal/cache/file/cache_handler_test.go b/internal/cache/file/cache_handler_test.go index 2daa17883d..c810ab47ab 100644 --- a/internal/cache/file/cache_handler_test.go +++ b/internal/cache/file/cache_handler_test.go @@ -25,6 +25,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" @@ -77,7 +78,7 @@ func initializeCacheHandlerTestArgs(t *testing.T, fileCacheConfig *cfg.FileCache // Job manager jobManager := downloader.NewJobManager(cache, util.DefaultFilePerm, - util.DefaultDirPerm, cacheDir, DefaultSequentialReadSizeMb, fileCacheConfig) + util.DefaultDirPerm, cacheDir, DefaultSequentialReadSizeMb, fileCacheConfig, common.NewNoopMetrics()) // Mocked cached handler object. cacheHandler := NewCacheHandler(cache, jobManager, cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) diff --git a/internal/cache/file/downloader/downloader.go b/internal/cache/file/downloader/downloader.go index 3e4c98aa6f..df1069bde8 100644 --- a/internal/cache/file/downloader/downloader.go +++ b/internal/cache/file/downloader/downloader.go @@ -19,6 +19,7 @@ import ( "os" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" @@ -60,10 +61,12 @@ type JobManager struct { jobs map[string]*Job mu locker.Locker maxParallelismSem *semaphore.Weighted + metricHandle common.MetricHandle } func NewJobManager(fileInfoCache *lru.Cache, filePerm os.FileMode, dirPerm os.FileMode, - cacheDir string, sequentialReadSizeMb int32, c *cfg.FileCacheConfig) (jm *JobManager) { + cacheDir string, sequentialReadSizeMb int32, c *cfg.FileCacheConfig, + metricHandle common.MetricHandle) (jm *JobManager) { maxParallelDownloads := int64(math.MaxInt64) if c.MaxParallelDownloads > 0 { maxParallelDownloads = c.MaxParallelDownloads @@ -77,6 +80,7 @@ func NewJobManager(fileInfoCache *lru.Cache, filePerm os.FileMode, dirPerm os.Fi fileCacheConfig: c, // Shared between jobs - Limits the overall concurrency of downloads. maxParallelismSem: semaphore.NewWeighted(maxParallelDownloads), + metricHandle: metricHandle, } jm.mu = locker.New("JobManager", func() {}) jm.jobs = make(map[string]*Job) @@ -114,7 +118,7 @@ func (jm *JobManager) CreateJobIfNotExists(object *gcs.MinObject, bucket gcs.Buc removeJobCallback := func() { jm.removeJob(object.Name, bucket.Name()) } - job = NewJob(object, bucket, jm.fileInfoCache, jm.sequentialReadSizeMb, fileSpec, removeJobCallback, jm.fileCacheConfig, jm.maxParallelismSem) + job = NewJob(object, bucket, jm.fileInfoCache, jm.sequentialReadSizeMb, fileSpec, removeJobCallback, jm.fileCacheConfig, jm.maxParallelismSem, jm.metricHandle) jm.jobs[objectPath] = job return job } diff --git a/internal/cache/file/downloader/downloader_test.go b/internal/cache/file/downloader/downloader_test.go index 8e16926aac..d080a346c4 100644 --- a/internal/cache/file/downloader/downloader_test.go +++ b/internal/cache/file/downloader/downloader_test.go @@ -23,6 +23,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" @@ -62,7 +63,7 @@ func (dt *downloaderTest) setupHelper() { dt.bucket = storageHandle.BucketHandle(ctx, storage.TestBucketName, "") dt.initJobTest(DefaultObjectName, []byte("taco"), DefaultSequentialReadSizeMb, CacheMaxSize, func() {}) - dt.jm = NewJobManager(dt.cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, DefaultSequentialReadSizeMb, dt.defaultFileCacheConfig) + dt.jm = NewJobManager(dt.cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, DefaultSequentialReadSizeMb, dt.defaultFileCacheConfig, common.NewNoopMetrics()) } func (dt *downloaderTest) SetUp(*TestInfo) { diff --git a/internal/cache/file/downloader/jm_parallel_downloads_test.go b/internal/cache/file/downloader/jm_parallel_downloads_test.go index 1b4d355c8b..900136c23e 100644 --- a/internal/cache/file/downloader/jm_parallel_downloads_test.go +++ b/internal/cache/file/downloader/jm_parallel_downloads_test.go @@ -23,6 +23,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" @@ -149,7 +150,7 @@ func TestParallelDownloads(t *testing.T) { WriteBufferSize: 4 * 1024 * 1024, EnableODirect: tc.enableODirect, } - jm := NewJobManager(cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, 2, fileCacheConfig) + jm := NewJobManager(cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, 2, fileCacheConfig, common.NewNoopMetrics()) job := jm.CreateJobIfNotExists(&minObj, bucket) subscriberC := job.subscribe(tc.subscribedOffset) @@ -191,7 +192,7 @@ func TestMultipleConcurrentDownloads(t *testing.T) { MaxParallelDownloads: 2, WriteBufferSize: 4 * 1024 * 1024, } - jm := NewJobManager(cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, 2, fileCacheConfig) + jm := NewJobManager(cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, 2, fileCacheConfig, common.NewNoopMetrics()) job1 := jm.CreateJobIfNotExists(&minObj1, bucket) job2 := jm.CreateJobIfNotExists(&minObj2, bucket) s1 := job1.subscribe(10 * util.MiB) diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index c0fb2b9aa0..c3d050c1d9 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -26,12 +26,12 @@ import ( "syscall" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" cacheutil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "golang.org/x/net/context" @@ -97,6 +97,8 @@ type Job struct { // Channel which is used by goroutines to know which ranges need to be // downloaded when parallel download is enabled. rangeChan chan data.ObjectRange + + metricsHandle common.MetricHandle } // JobStatus represents the status of job. @@ -122,6 +124,7 @@ func NewJob( removeJobCallback func(), fileCacheConfig *cfg.FileCacheConfig, maxParallelismSem *semaphore.Weighted, + metricHandle common.MetricHandle, ) (job *Job) { job = &Job{ object: object, @@ -132,6 +135,7 @@ func NewJob( removeJobCallback: removeJobCallback, fileCacheConfig: fileCacheConfig, maxParallelismSem: maxParallelismSem, + metricsHandle: metricHandle, } job.mu = locker.New("Job-"+fileSpec.Path, job.checkInvariants) job.init() @@ -319,7 +323,7 @@ func (job *Job) downloadObjectToFile(cacheFile *os.File) (err error) { err = fmt.Errorf("downloadObjectToFile: error in creating NewReader with start %d and limit %d: %w", start, newReaderLimit, err) return err } - monitor.CaptureGCSReadMetrics(job.cancelCtx, util.Sequential, newReaderLimit-start) + common.CaptureGCSReadMetrics(job.cancelCtx, job.metricsHandle, util.Sequential, newReaderLimit-start) } maxRead := min(ReadChunkSize, newReaderLimit-start) diff --git a/internal/cache/file/downloader/job_test.go b/internal/cache/file/downloader/job_test.go index 172a08e693..063078fe38 100644 --- a/internal/cache/file/downloader/job_test.go +++ b/internal/cache/file/downloader/job_test.go @@ -28,6 +28,7 @@ import ( "sync/atomic" "time" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" @@ -59,7 +60,7 @@ func (dt *downloaderTest) initJobTest(objectName string, objectContent []byte, s } dt.cache = lru.NewCache(lruCacheSize) - dt.job = NewJob(&dt.object, dt.bucket, dt.cache, sequentialReadSize, dt.fileSpec, removeCallback, dt.defaultFileCacheConfig, semaphore.NewWeighted(math.MaxInt64)) + dt.job = NewJob(&dt.object, dt.bucket, dt.cache, sequentialReadSize, dt.fileSpec, removeCallback, dt.defaultFileCacheConfig, semaphore.NewWeighted(math.MaxInt64), common.NewNoopMetrics()) fileInfoKey := data.FileInfoKey{ BucketName: storage.TestBucketName, ObjectName: objectName, diff --git a/internal/cache/file/downloader/parallel_downloads_job.go b/internal/cache/file/downloader/parallel_downloads_job.go index 132e91aea4..f8ad042b67 100644 --- a/internal/cache/file/downloader/parallel_downloads_job.go +++ b/internal/cache/file/downloader/parallel_downloads_job.go @@ -21,10 +21,10 @@ import ( "io" "os" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" cacheutil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "golang.org/x/sync/errgroup" @@ -60,7 +60,7 @@ func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, e } }() - monitor.CaptureGCSReadMetrics(ctx, util.Parallel, end-start) + common.CaptureGCSReadMetrics(ctx, job.metricsHandle, util.Parallel, end-start) // Use standard copy function if O_DIRECT is disabled and memory aligned // buffer otherwise. diff --git a/internal/fs/caching_test.go b/internal/fs/caching_test.go index 8370a3d2d2..e66c5a7545 100644 --- a/internal/fs/caching_test.go +++ b/internal/fs/caching_test.go @@ -21,6 +21,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" @@ -473,6 +474,7 @@ func (t *MultiBucketMountCachingTest) SetUpTestSuite() { // Enable directory type caching. t.serverCfg.DirTypeCacheTTL = ttl + t.serverCfg.MetricHandle = common.NewNoopMetrics() // Call through. t.fsTest.SetUpTestSuite() diff --git a/internal/fs/fs.go b/internal/fs/fs.go index bbeb7ab3ef..51e8b810ff 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -29,6 +29,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" @@ -125,6 +126,8 @@ type ServerConfig struct { // NewConfig has all the config specified by the user using config-file or CLI flags. NewConfig *cfg.Config + + MetricHandle common.MetricHandle } // Create a fuse file system server according to the supplied configuration. @@ -190,6 +193,7 @@ func NewFileSystem(ctx context.Context, serverCfg *ServerConfig) (fuseutil.FileS fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: serverCfg.NewConfig.FileCache.CacheFileForRangeRead, globalMaxBlocksSem: semaphore.NewWeighted(serverCfg.NewConfig.Write.GlobalMaxBlocks), + metricHandle: serverCfg.MetricHandle, } // Set up root bucket @@ -199,7 +203,7 @@ func NewFileSystem(ctx context.Context, serverCfg *ServerConfig) (fuseutil.FileS root = makeRootForAllBuckets(fs) } else { logger.Info("Set up root directory for bucket " + serverCfg.BucketName) - syncerBucket, err := fs.bucketManager.SetUpBucket(ctx, serverCfg.BucketName, false) + syncerBucket, err := fs.bucketManager.SetUpBucket(ctx, serverCfg.BucketName, false, fs.metricHandle) if err != nil { return nil, fmt.Errorf("SetUpBucket: %w", err) } @@ -242,7 +246,7 @@ func createFileCacheHandler(serverCfg *ServerConfig) (fileCacheHandler *file.Cac return nil, fmt.Errorf("createFileCacheHandler: while creating file cache directory: %w", cacheDirErr) } - jobManager := downloader.NewJobManager(fileInfoCache, filePerm, dirPerm, cacheDir, serverCfg.SequentialReadSizeMb, &serverCfg.NewConfig.FileCache) + jobManager := downloader.NewJobManager(fileInfoCache, filePerm, dirPerm, cacheDir, serverCfg.SequentialReadSizeMb, &serverCfg.NewConfig.FileCache, serverCfg.MetricHandle) fileCacheHandler = file.NewCacheHandler(fileInfoCache, jobManager, cacheDir, filePerm, dirPerm) return } @@ -291,6 +295,7 @@ func makeRootForAllBuckets(fs *fileSystem) inode.DirInode { Mtime: fs.mtimeClock.Now(), }, fs.bucketManager, + fs.metricHandle, ) } @@ -479,6 +484,8 @@ type fileSystem struct { cacheFileForRangeRead bool globalMaxBlocksSem *semaphore.Weighted + + metricHandle common.MetricHandle } //////////////////////////////////////////////////////////////////////// @@ -1735,7 +1742,7 @@ func (fs *fileSystem) CreateFile( handleID := fs.nextHandleID fs.nextHandleID++ - fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead) + fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle) op.Handle = handleID fs.mu.Unlock() @@ -2379,7 +2386,7 @@ func (fs *fileSystem) OpenFile( handleID := fs.nextHandleID fs.nextHandleID++ - fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead) + fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle) op.Handle = handleID // When we observe object generations that we didn't create, we assign them diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index e9f24d3bb7..616283adf7 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -30,6 +30,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" @@ -166,6 +167,7 @@ func (t *fsTest) SetUpTestSuite() { }, } } + t.serverCfg.MetricHandle = common.NewNoopMetrics() // Set up ownership. t.serverCfg.Uid, t.serverCfg.Gid, err = perms.MyUserAndGroup() @@ -368,7 +370,7 @@ func (bm *fakeBucketManager) ShutDown() {} func (bm *fakeBucketManager) SetUpBucket( ctx context.Context, - name string, isMultibucketMount bool) (sb gcsx.SyncerBucket, err error) { + name string, isMultibucketMount bool, _ common.MetricHandle) (sb gcsx.SyncerBucket, err error) { bucket, ok := bm.buckets[name] if ok { sb = gcsx.NewSyncerBucket( diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index d10abe1c3e..0d5c77962c 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -18,6 +18,7 @@ import ( "fmt" "io" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" @@ -45,13 +46,15 @@ type FileHandle struct { // cacheFileForRangeRead is also valid for cache workflow, if true, object content // will be downloaded for random reads as well too. cacheFileForRangeRead bool + metricHandle common.MetricHandle } -func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool) (fh *FileHandle) { +func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle) (fh *FileHandle) { fh = &FileHandle{ inode: inode, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, + metricHandle: metricHandle, } fh.mu = syncutil.NewInvariantMutex(fh.checkInvariants) @@ -170,7 +173,7 @@ func (fh *FileHandle) tryEnsureReader(ctx context.Context, sequentialReadSizeMb } // Attempt to create an appropriate reader. - rr := gcsx.NewRandomReader(fh.inode.Source(), fh.inode.Bucket(), sequentialReadSizeMb, fh.fileCacheHandler, fh.cacheFileForRangeRead) + rr := gcsx.NewRandomReader(fh.inode.Source(), fh.inode.Bucket(), sequentialReadSizeMb, fh.fileCacheHandler, fh.cacheFileForRangeRead, fh.metricHandle) fh.reader = rr return diff --git a/internal/fs/hns_bucket_test.go b/internal/fs/hns_bucket_test.go index 767e23b32c..9d7b04046c 100644 --- a/internal/fs/hns_bucket_test.go +++ b/internal/fs/hns_bucket_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -54,6 +55,7 @@ func (t *HNSBucketTests) SetupSuite() { t.serverCfg.NewConfig = &cfg.Config{ EnableHns: true, } + t.serverCfg.MetricHandle = common.NewNoopMetrics() bucketType = gcs.Hierarchical t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/implicit_dirs_test.go b/internal/fs/implicit_dirs_test.go index e5000c53be..dc5d20d635 100644 --- a/internal/fs/implicit_dirs_test.go +++ b/internal/fs/implicit_dirs_test.go @@ -24,6 +24,7 @@ import ( "syscall" "time" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/oglematchers" @@ -45,6 +46,7 @@ func init() { func (t *ImplicitDirsTest) SetUpTestSuite() { t.serverCfg.ImplicitDirectories = true + t.serverCfg.MetricHandle = common.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/implicit_dirs_with_cache_test.go b/internal/fs/implicit_dirs_with_cache_test.go index db900392c2..23d4ba6a4f 100644 --- a/internal/fs/implicit_dirs_with_cache_test.go +++ b/internal/fs/implicit_dirs_with_cache_test.go @@ -24,6 +24,7 @@ import ( "path" "time" + "github.com/googlecloudplatform/gcsfuse/v2/common" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" ) @@ -43,6 +44,7 @@ func init() { func (t *ImplicitDirsWithCacheTest) SetUpTestSuite() { t.serverCfg.ImplicitDirectories = true t.serverCfg.DirTypeCacheTTL = time.Minute * 3 + t.serverCfg.MetricHandle = common.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/inode/base_dir.go b/internal/fs/inode/base_dir.go index 8c7b52ab43..00dcf22977 100644 --- a/internal/fs/inode/base_dir.go +++ b/internal/fs/inode/base_dir.go @@ -18,6 +18,7 @@ import ( "syscall" "time" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" @@ -60,6 +61,8 @@ type baseDirInode struct { // GUARDED_BY(mu) buckets map[string]gcsx.SyncerBucket + + metricHandle common.MetricHandle } // NewBaseDirInode returns a baseDirInode that acts as the directory of @@ -68,13 +71,15 @@ func NewBaseDirInode( id fuseops.InodeID, name Name, attrs fuseops.InodeAttributes, - bm gcsx.BucketManager) (d DirInode) { + bm gcsx.BucketManager, + metricHandle common.MetricHandle) (d DirInode) { typed := &baseDirInode{ id: id, name: NewRootName(""), attrs: attrs, bucketManager: bm, buckets: make(map[string]gcsx.SyncerBucket), + metricHandle: metricHandle, } typed.lc.Init(id) typed.mu = locker.NewRW("BaseDirInode"+name.GcsObjectName(), func() {}) @@ -155,7 +160,7 @@ func (d *baseDirInode) LookUpChild(ctx context.Context, name string) (*Core, err var err error bucket, ok := d.buckets[name] if !ok { - bucket, err = d.bucketManager.SetUpBucket(ctx, name, true) + bucket, err = d.bucketManager.SetUpBucket(ctx, name, true, d.metricHandle) if err != nil { return nil, err } diff --git a/internal/fs/inode/base_dir_test.go b/internal/fs/inode/base_dir_test.go index 369dcc816b..e830042db7 100644 --- a/internal/fs/inode/base_dir_test.go +++ b/internal/fs/inode/base_dir_test.go @@ -20,6 +20,7 @@ import ( "testing" "time" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" @@ -87,7 +88,7 @@ type fakeBucketManager struct { func (bm *fakeBucketManager) SetUpBucket( ctx context.Context, - name string, isMultibucketMount bool) (sb gcsx.SyncerBucket, err error) { + name string, isMultibucketMount bool, _ common.MetricHandle) (sb gcsx.SyncerBucket, err error) { bm.setupTimes++ var ok bool @@ -118,7 +119,8 @@ func (t *BaseDirTest) resetInode() { Gid: gid, Mode: dirMode, }, - t.bm) + t.bm, + common.NewNoopMetrics()) t.in.Lock() } diff --git a/internal/fs/kernel_list_cache_inifinite_ttl_test.go b/internal/fs/kernel_list_cache_inifinite_ttl_test.go index d14b1b7af1..45fb291acd 100644 --- a/internal/fs/kernel_list_cache_inifinite_ttl_test.go +++ b/internal/fs/kernel_list_cache_inifinite_ttl_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -46,6 +47,7 @@ func (t *KernelListCacheTestWithInfiniteTtl) SetupSuite() { }, } t.serverCfg.RenameDirLimit = 10 + t.serverCfg.MetricHandle = common.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/kernel_list_cache_test.go b/internal/fs/kernel_list_cache_test.go index 35fb575b2f..e162f4b2fa 100644 --- a/internal/fs/kernel_list_cache_test.go +++ b/internal/fs/kernel_list_cache_test.go @@ -31,6 +31,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -114,6 +115,7 @@ func (t *KernelListCacheTestWithPositiveTtl) SetupSuite() { }, } t.serverCfg.RenameDirLimit = 10 + t.serverCfg.MetricHandle = common.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/kernel_list_cache_zero_ttl_test.go b/internal/fs/kernel_list_cache_zero_ttl_test.go index 26d3207d9e..74da7cd147 100644 --- a/internal/fs/kernel_list_cache_zero_ttl_test.go +++ b/internal/fs/kernel_list_cache_zero_ttl_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -44,6 +45,7 @@ func (t *KernelListCacheTestWithZeroTtl) SetupSuite() { }, } t.serverCfg.RenameDirLimit = 10 + t.serverCfg.MetricHandle = common.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/local_file_test.go b/internal/fs/local_file_test.go index 117aba2712..9bac1b86c1 100644 --- a/internal/fs/local_file_test.go +++ b/internal/fs/local_file_test.go @@ -29,6 +29,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" @@ -868,6 +869,7 @@ func (t *LocalFileTest) TestStatFailsOnNewFileAfterDeletion() { }, Logging: cfg.DefaultLoggingConfig(), } + t.serverCfg.MetricHandle = common.NewNoopMetrics() filePath := path.Join(mntDir, "test.txt") AssertEq(nil, err) f1, err := os.Create(filePath) diff --git a/internal/fs/parallel_dirops_test.go b/internal/fs/parallel_dirops_test.go index bc5c8a9537..d05192ac8d 100644 --- a/internal/fs/parallel_dirops_test.go +++ b/internal/fs/parallel_dirops_test.go @@ -24,6 +24,7 @@ import ( "testing" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -47,6 +48,7 @@ func (t *ParallelDiropsTest) SetupSuite() { DisableParallelDirops: false, }} t.serverCfg.RenameDirLimit = 10 + t.serverCfg.MetricHandle = common.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/read_cache_test.go b/internal/fs/read_cache_test.go index d06d0d0b4b..c745f7c814 100644 --- a/internal/fs/read_cache_test.go +++ b/internal/fs/read_cache_test.go @@ -26,6 +26,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" . "github.com/jacobsa/ogletest" @@ -73,6 +74,7 @@ func (t *FileCacheTest) SetUpTestSuite() { }, CacheDir: cfg.ResolvedPath(CacheDir), } + t.serverCfg.MetricHandle = common.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/server.go b/internal/fs/server.go index ea409182ec..e3bacdeb55 100644 --- a/internal/fs/server.go +++ b/internal/fs/server.go @@ -35,6 +35,6 @@ func NewServer(ctx context.Context, cfg *ServerConfig) (fuse.Server, error) { if newcfg.IsTracingEnabled(cfg.NewConfig) { fs = wrappers.WithTracing(fs) } - fs = wrappers.WithMonitoring(fs) + fs = wrappers.WithMonitoring(fs, cfg.MetricHandle) return fuseutil.NewFileSystemServer(fs), nil } diff --git a/internal/fs/type_cache_test.go b/internal/fs/type_cache_test.go index 1d368a1710..552a02867e 100644 --- a/internal/fs/type_cache_test.go +++ b/internal/fs/type_cache_test.go @@ -28,6 +28,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" gcsfusefs "github.com/googlecloudplatform/gcsfuse/v2/internal/fs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" @@ -82,6 +83,7 @@ func (t *typeCacheTestCommon) SetUpTestSuite() { TtlSecs: ttlInSeconds, }, } + t.serverCfg.MetricHandle = common.NewNoopMetrics() // Fill server-cfg from mount-config. func(newConfig *cfg.Config, serverCfg *gcsfusefs.ServerConfig) { diff --git a/internal/fs/wrappers/monitoring.go b/internal/fs/wrappers/monitoring.go index f84114ad8b..aada6752a0 100644 --- a/internal/fs/wrappers/monitoring.go +++ b/internal/fs/wrappers/monitoring.go @@ -17,29 +17,16 @@ package wrappers import ( "context" "errors" - "fmt" "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor" - "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor/tags" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" - "go.opencensus.io/plugin/ochttp" - "go.opencensus.io/stats" - "go.opencensus.io/stats/view" - "go.opencensus.io/tag" ) const name = "cloud.google.com/gcsfuse" -var ( - opsCountOC = stats.Int64("fs/ops_count", "The number of ops processed by the file system.", monitor.UnitDimensionless) - opsLatencyOC = stats.Int64("fs/ops_latency", "The latency of a file system operation.", monitor.UnitMicroseconds) - opsErrorCountOC = stats.Int64("fs/ops_error_count", "The number of errors generated by file system operation.", monitor.UnitDimensionless) -) - // Error categories const ( errDevice = "DEVICE_ERROR" @@ -60,36 +47,6 @@ const ( errTooManyFiles = "TOO_MANY_OPEN_FILES" ) -// Initialize the metrics. -func init() { - - // Register the view. - if err := view.Register( - &view.View{ - Name: "fs/ops_count", - Measure: opsCountOC, - Description: "The cumulative number of ops processed by the file system.", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tags.FSOp}, - }, - &view.View{ - Name: "fs/ops_error_count", - Measure: opsErrorCountOC, - Description: "The cumulative number of errors generated by file system operations", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tags.FSOp, tags.FSErrCategory}, - }, - &view.View{ - Name: "fs/ops_latency", - Measure: opsLatencyOC, - Description: "The cumulative distribution of file system operation latencies", - Aggregation: ochttp.DefaultLatencyDistribution, - TagKeys: []tag.Key{tags.FSOp}, - }); err != nil { - fmt.Printf("Failed to register metrics for the file system: %v\n", err) - } -} - // categorize maps an error to an error-category. // This helps reduce the cardinality of the labels to less than 30. // This lower number of errors allows the various errors to get piped to Cloud metrics without getting dropped. @@ -268,58 +225,32 @@ func categorize(err error) string { } // Records file system operation count, failed operation count and the operation latency. -func recordOp(ctx context.Context, method string, start time.Time, fsErr error) { - // Recording opCount. - if err := stats.RecordWithTags( - ctx, - []tag.Mutator{ - tag.Upsert(tags.FSOp, method), - }, - opsCountOC.M(1), - ); err != nil { - // Error in recording opCount. - logger.Errorf("Cannot record file system op: %v", err) - } +func recordOp(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time, fsErr error) { + metricHandle.OpsCount(ctx, 1, []common.MetricAttr{{Key: common.FSOp, Value: method}}) // Recording opErrorCount. if fsErr != nil { errCategory := categorize(fsErr) - if err := stats.RecordWithTags( - ctx, - []tag.Mutator{ - tag.Upsert(tags.FSOp, method), - tag.Upsert(tags.FSErrCategory, errCategory), - }, - opsErrorCountOC.M(1), - ); err != nil { - // Error in recording opErrorCount. - logger.Errorf("Cannot record error count of the file system failed operations: %v", err) - } - } - - // Recording opLatency. - if err := stats.RecordWithTags( - ctx, - []tag.Mutator{ - tag.Upsert(tags.FSOp, method), - }, - opsLatencyOC.M(time.Since(start).Microseconds()), - ); err != nil { - // Error in opLatency. - logger.Errorf("Cannot record file system operation latency: %v", err) + metricHandle.OpsErrorCount(ctx, 1, []common.MetricAttr{ + {Key: common.FSOp, Value: method}, + {Key: common.FSErrCategory, Value: errCategory}}, + ) } + metricHandle.OpsLatency(ctx, float64(time.Since(start).Microseconds()), []common.MetricAttr{{Key: common.FSOp, Value: method}}) } // WithMonitoring takes a FileSystem, returns a FileSystem with monitoring // on the counts of requests per API. -func WithMonitoring(fs fuseutil.FileSystem) fuseutil.FileSystem { +func WithMonitoring(fs fuseutil.FileSystem, metricHandle common.MetricHandle) fuseutil.FileSystem { return &monitoring{ - wrapped: fs, + wrapped: fs, + metricHandle: metricHandle, } } type monitoring struct { - wrapped fuseutil.FileSystem + wrapped fuseutil.FileSystem + metricHandle common.MetricHandle } func (fs *monitoring) Destroy() { @@ -331,7 +262,7 @@ type wrappedCall func(ctx context.Context) error func (fs *monitoring) invokeWrapped(ctx context.Context, opName string, w wrappedCall) error { startTime := time.Now() err := w(ctx) - recordOp(ctx, opName, startTime, err) + recordOp(ctx, fs.metricHandle, opName, startTime, err) return err } diff --git a/internal/gcsx/bucket_manager.go b/internal/gcsx/bucket_manager.go index 18435fe0dd..b17a1fd335 100644 --- a/internal/gcsx/bucket_manager.go +++ b/internal/gcsx/bucket_manager.go @@ -21,6 +21,7 @@ import ( "path" "time" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" "github.com/googlecloudplatform/gcsfuse/v2/internal/canned" @@ -66,7 +67,7 @@ type BucketConfig struct { type BucketManager interface { SetUpBucket( ctx context.Context, - name string, isMultibucketMount bool) (b SyncerBucket, err error) + name string, isMultibucketMount bool, metricHandle common.MetricHandle) (b SyncerBucket, err error) // Shuts down the bucket manager and its buckets ShutDown() @@ -155,6 +156,7 @@ func (bm *bucketManager) SetUpBucket( ctx context.Context, name string, isMultibucketMount bool, + metricHandle common.MetricHandle, ) (sb SyncerBucket, err error) { var b gcs.Bucket // Set up the appropriate backing bucket. @@ -166,7 +168,7 @@ func (bm *bucketManager) SetUpBucket( // Enable monitoring. if bm.config.EnableMonitoring { - b = monitor.NewMonitoringBucket(b) + b = monitor.NewMonitoringBucket(b, metricHandle) } // Enable gcs logs. diff --git a/internal/gcsx/bucket_manager_test.go b/internal/gcsx/bucket_manager_test.go index cfdbfa6bfe..066b171d8e 100644 --- a/internal/gcsx/bucket_manager_test.go +++ b/internal/gcsx/bucket_manager_test.go @@ -19,6 +19,7 @@ import ( "testing" "time" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" . "github.com/jacobsa/ogletest" @@ -93,7 +94,7 @@ func (t *BucketManagerTest) TestSetUpBucketMethod() { bm.config = bucketConfig bm.gcCtx = ctx - bucket, err := bm.SetUpBucket(context.Background(), TestBucketName, false) + bucket, err := bm.SetUpBucket(context.Background(), TestBucketName, false, common.NewNoopMetrics()) ExpectNe(nil, bucket.Syncer) ExpectEq(nil, err) @@ -117,7 +118,7 @@ func (t *BucketManagerTest) TestSetUpBucketMethod_IsMultiBucketMountTrue() { bm.config = bucketConfig bm.gcCtx = ctx - bucket, err := bm.SetUpBucket(context.Background(), TestBucketName, true) + bucket, err := bm.SetUpBucket(context.Background(), TestBucketName, true, common.NewNoopMetrics()) ExpectNe(nil, bucket.Syncer) ExpectEq(nil, err) @@ -141,7 +142,7 @@ func (t *BucketManagerTest) TestSetUpBucketMethodWhenBucketDoesNotExist() { bm.config = bucketConfig bm.gcCtx = ctx - bucket, err := bm.SetUpBucket(context.Background(), invalidBucketName, false) + bucket, err := bm.SetUpBucket(context.Background(), invalidBucketName, false, common.NewNoopMetrics()) ExpectEq("error in iterating through objects: storage: bucket doesn't exist", err.Error()) ExpectNe(nil, bucket.Syncer) @@ -165,7 +166,7 @@ func (t *BucketManagerTest) TestSetUpBucketMethodWhenBucketDoesNotExist_IsMultiB bm.config = bucketConfig bm.gcCtx = ctx - bucket, err := bm.SetUpBucket(context.Background(), invalidBucketName, true) + bucket, err := bm.SetUpBucket(context.Background(), invalidBucketName, true, common.NewNoopMetrics()) ExpectEq("error in iterating through objects: storage: bucket doesn't exist", err.Error()) ExpectNe(nil, bucket.Syncer) diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 61f2f0de81..27ac2e03d8 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -17,15 +17,16 @@ package gcsx import ( "fmt" "io" + "strconv" "strings" "time" "github.com/google/uuid" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" cacheutil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/jacobsa/fuse/fuseops" @@ -79,7 +80,7 @@ type RandomReader interface { // NewRandomReader create a random reader for the supplied object record that // reads using the given bucket. -func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb int32, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool) RandomReader { +func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb int32, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle) RandomReader { return &randomReader{ object: o, bucket: bucket, @@ -90,6 +91,7 @@ func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb i sequentialReadSizeMb: sequentialReadSizeMb, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, + metricHandle: metricHandle, } } @@ -129,6 +131,7 @@ type randomReader struct { // fileCacheHandle is used to read from the cached location. It is created on the fly // using fileCacheHandler for the given object and bucket. fileCacheHandle *file.CacheHandle + metricHandle common.MetricHandle } func (rr *randomReader) CheckInvariants() { @@ -201,8 +204,7 @@ func (rr *randomReader) tryReadingFromFileCache(ctx context.Context, if isSeq { readType = util.Sequential } - // Capture file cache metrics to be exported via stackdriver - monitor.CaptureFileCacheMetrics(ctx, readType, n, cacheHit, executionTime) + captureFileCacheMetrics(ctx, rr.metricHandle, readType, n, cacheHit, executionTime) }() // Create fileCacheHandle if not already. @@ -248,6 +250,16 @@ func (rr *randomReader) tryReadingFromFileCache(ctx context.Context, return } +func captureFileCacheMetrics(ctx context.Context, metricHandle common.MetricHandle, readType string, readDataSize int, cacheHit bool, readLatency time.Duration) { + metricHandle.FileCacheReadCount(ctx, 1, []common.MetricAttr{ + {Key: common.ReadType, Value: readType}, + {Key: common.CacheHit, Value: strconv.FormatBool(cacheHit)}, + }) + + metricHandle.FileCacheReadBytesCount(ctx, int64(readDataSize), []common.MetricAttr{{Key: common.ReadType, Value: readType}}) + metricHandle.FileCacheReadLatency(ctx, float64(readLatency.Microseconds()), []common.MetricAttr{{Key: common.CacheHit, Value: strconv.FormatBool(cacheHit)}}) +} + func (rr *randomReader) ReadAt( ctx context.Context, p []byte, @@ -506,7 +518,7 @@ func (rr *randomReader) startRead( rr.limit = end requestedDataSize := end - start - monitor.CaptureGCSReadMetrics(ctx, readType, requestedDataSize) + common.CaptureGCSReadMetrics(ctx, rr.metricHandle, readType, requestedDataSize) return } diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index b43402a9ea..1bb53f2201 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -28,6 +28,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" @@ -177,11 +178,11 @@ func (t *RandomReaderTest) SetUp(ti *TestInfo) { lruCache := lru.NewCache(CacheMaxSize) t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{ EnableCrc: false, - }) + }, common.NewNoopMetrics()) t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) // Set up the reader. - rr := NewRandomReader(t.object, t.bucket, sequentialReadSizeInMb, nil, false) + rr := NewRandomReader(t.object, t.bucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics()) t.rr.wrapped = rr.(*randomReader) } @@ -590,7 +591,7 @@ func (t *RandomReaderTest) UpgradesSequentialReads_NoExistingReader() { t.object.Size = 1 << 40 const readSize = 1 * MB // Set up the custom randomReader. - rr := NewRandomReader(t.object, t.bucket, readSize/MB, nil, false) + rr := NewRandomReader(t.object, t.bucket, readSize/MB, nil, false, common.NewNoopMetrics()) t.rr.wrapped = rr.(*randomReader) // Simulate a previous exhausted reader that ended at the offset from which @@ -623,7 +624,7 @@ func (t *RandomReaderTest) SequentialReads_NoExistingReader_requestedSizeGreater const chunkSize = 1 * MB const readSize = 3 * MB // Set up the custom randomReader. - rr := NewRandomReader(t.object, t.bucket, chunkSize/MB, nil, false) + rr := NewRandomReader(t.object, t.bucket, chunkSize/MB, nil, false, common.NewNoopMetrics()) t.rr.wrapped = rr.(*randomReader) // Create readers for each chunk. chunk1Reader := strings.NewReader(strings.Repeat("x", chunkSize)) @@ -670,7 +671,7 @@ func (t *RandomReaderTest) SequentialReads_existingReader_requestedSizeGreaterTh const chunkSize = 1 * MB const readSize = 3 * MB // Set up the custom randomReader. - rr := NewRandomReader(t.object, t.bucket, chunkSize/MB, nil, false) + rr := NewRandomReader(t.object, t.bucket, chunkSize/MB, nil, false, common.NewNoopMetrics()) t.rr.wrapped = rr.(*randomReader) // Simulate an existing reader at the correct offset, which will be exhausted // by the read below. diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index 8f8568e895..f22b329044 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -20,94 +20,30 @@ import ( "io" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor/tags" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "go.opencensus.io/plugin/ochttp" - "go.opencensus.io/stats" - "go.opencensus.io/stats/view" - "go.opencensus.io/tag" ) -var ( - // OpenCensus measures - readBytesCountOC = stats.Int64("gcs/read_bytes_count", "The number of bytes read from GCS objects.", stats.UnitBytes) - readerCountOC = stats.Int64("gcs/reader_count", "The number of GCS object readers opened or closed.", stats.UnitDimensionless) - requestCountOC = stats.Int64("gcs/request_count", "The number of GCS requests processed.", stats.UnitDimensionless) - requestLatencyOC = stats.Float64("gcs/request_latency", "The latency of a GCS request.", stats.UnitMilliseconds) -) - -// Initialize the metrics. -func init() { - // OpenCensus views (aggregated measures) - if err := view.Register( - &view.View{ - Name: "gcs/read_bytes_count", - Measure: readBytesCountOC, - Description: "The cumulative number of bytes read from GCS objects.", - Aggregation: view.Sum(), - }, - &view.View{ - Name: "gcs/reader_count", - Measure: readerCountOC, - Description: "The cumulative number of GCS object readers opened or closed.", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tags.IOMethod}, - }, - &view.View{ - Name: "gcs/request_count", - Measure: requestCountOC, - Description: "The cumulative number of GCS requests processed.", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tags.GCSMethod}, - }, - &view.View{ - Name: "gcs/request_latencies", - Measure: requestLatencyOC, - Description: "The cumulative distribution of the GCS request latencies.", - Aggregation: ochttp.DefaultLatencyDistribution, - TagKeys: []tag.Key{tags.GCSMethod}, - }); err != nil { - fmt.Printf("Failed to register OpenCensus metrics for GCS client library: %v", err) - } -} - // recordRequest records a request and its latency. -func recordRequest(ctx context.Context, method string, start time.Time) { - if err := stats.RecordWithTags( - ctx, - []tag.Mutator{ - tag.Upsert(tags.GCSMethod, method), - }, - requestCountOC.M(1), - ); err != nil { - // The error should be caused by a bad tag - logger.Errorf("Cannot record request count: %v", err) - } +func recordRequest(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time) { + metricHandle.GCSRequestCount(ctx, 1, []common.MetricAttr{{Key: common.GCSMethod, Value: method}}) latencyUs := time.Since(start).Microseconds() latencyMs := float64(latencyUs) / 1000.0 - if err := stats.RecordWithTags( - ctx, - []tag.Mutator{ - tag.Upsert(tags.GCSMethod, method), - }, - requestLatencyOC.M(latencyMs), - ); err != nil { - // The error should be caused by a bad tag - logger.Errorf("Cannot record request latency: %v", err) - } + metricHandle.GCSRequestLatency(ctx, latencyMs, []common.MetricAttr{{Key: common.GCSMethod, Value: method}}) } // NewMonitoringBucket returns a gcs.Bucket that exports metrics for monitoring -func NewMonitoringBucket(b gcs.Bucket) gcs.Bucket { +func NewMonitoringBucket(b gcs.Bucket, m common.MetricHandle) gcs.Bucket { return &monitoringBucket{ - wrapped: b, + wrapped: b, + metricHandle: m, } } type monitoringBucket struct { - wrapped gcs.Bucket + wrapped gcs.Bucket + metricHandle common.MetricHandle } func (mb *monitoringBucket) Name() string { @@ -125,10 +61,10 @@ func (mb *monitoringBucket) NewReader( rc, err = mb.wrapped.NewReader(ctx, req) if err == nil { - rc = newMonitoringReadCloser(ctx, req.Name, rc) + rc = newMonitoringReadCloser(ctx, req.Name, rc, mb.metricHandle) } - recordRequest(ctx, "NewReader", startTime) + recordRequest(ctx, mb.metricHandle, "NewReader", startTime) return } @@ -137,21 +73,21 @@ func (mb *monitoringBucket) CreateObject( req *gcs.CreateObjectRequest) (*gcs.Object, error) { startTime := time.Now() o, err := mb.wrapped.CreateObject(ctx, req) - recordRequest(ctx, "CreateObject", startTime) + recordRequest(ctx, mb.metricHandle, "CreateObject", startTime) return o, err } func (mb *monitoringBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.CreateObjectRequest, chunkSize int, callBack func(bytesUploadedSoFar int64)) (gcs.Writer, error) { startTime := time.Now() wc, err := mb.wrapped.CreateObjectChunkWriter(ctx, req, chunkSize, callBack) - recordRequest(ctx, "CreateObjectChunkWriter", startTime) + recordRequest(ctx, mb.metricHandle, "CreateObjectChunkWriter", startTime) return wc, err } func (mb *monitoringBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.Object, error) { startTime := time.Now() o, err := mb.wrapped.FinalizeUpload(ctx, w) - recordRequest(ctx, "FinalizeUpload", startTime) + recordRequest(ctx, mb.metricHandle, "FinalizeUpload", startTime) return o, err } @@ -160,7 +96,7 @@ func (mb *monitoringBucket) CopyObject( req *gcs.CopyObjectRequest) (*gcs.Object, error) { startTime := time.Now() o, err := mb.wrapped.CopyObject(ctx, req) - recordRequest(ctx, "CopyObject", startTime) + recordRequest(ctx, mb.metricHandle, "CopyObject", startTime) return o, err } @@ -169,7 +105,7 @@ func (mb *monitoringBucket) ComposeObjects( req *gcs.ComposeObjectsRequest) (*gcs.Object, error) { startTime := time.Now() o, err := mb.wrapped.ComposeObjects(ctx, req) - recordRequest(ctx, "ComposeObjects", startTime) + recordRequest(ctx, mb.metricHandle, "ComposeObjects", startTime) return o, err } @@ -178,7 +114,7 @@ func (mb *monitoringBucket) StatObject( req *gcs.StatObjectRequest) (*gcs.MinObject, *gcs.ExtendedObjectAttributes, error) { startTime := time.Now() m, e, err := mb.wrapped.StatObject(ctx, req) - recordRequest(ctx, "StatObject", startTime) + recordRequest(ctx, mb.metricHandle, "StatObject", startTime) return m, e, err } @@ -187,7 +123,7 @@ func (mb *monitoringBucket) ListObjects( req *gcs.ListObjectsRequest) (*gcs.Listing, error) { startTime := time.Now() listing, err := mb.wrapped.ListObjects(ctx, req) - recordRequest(ctx, "ListObjects", startTime) + recordRequest(ctx, mb.metricHandle, "ListObjects", startTime) return listing, err } @@ -196,7 +132,7 @@ func (mb *monitoringBucket) UpdateObject( req *gcs.UpdateObjectRequest) (*gcs.Object, error) { startTime := time.Now() o, err := mb.wrapped.UpdateObject(ctx, req) - recordRequest(ctx, "UpdateObject", startTime) + recordRequest(ctx, mb.metricHandle, "UpdateObject", startTime) return o, err } @@ -205,71 +141,65 @@ func (mb *monitoringBucket) DeleteObject( req *gcs.DeleteObjectRequest) error { startTime := time.Now() err := mb.wrapped.DeleteObject(ctx, req) - recordRequest(ctx, "DeleteObject", startTime) + recordRequest(ctx, mb.metricHandle, "DeleteObject", startTime) return err } func (mb *monitoringBucket) DeleteFolder(ctx context.Context, folderName string) error { startTime := time.Now() err := mb.wrapped.DeleteFolder(ctx, folderName) - recordRequest(ctx, "DeleteFolder", startTime) + recordRequest(ctx, mb.metricHandle, "DeleteFolder", startTime) return err } func (mb *monitoringBucket) GetFolder(ctx context.Context, folderName string) (*gcs.Folder, error) { startTime := time.Now() folder, err := mb.wrapped.GetFolder(ctx, folderName) - recordRequest(ctx, "GetFolder", startTime) + recordRequest(ctx, mb.metricHandle, "GetFolder", startTime) return folder, err } func (mb *monitoringBucket) CreateFolder(ctx context.Context, folderName string) (*gcs.Folder, error) { startTime := time.Now() folder, err := mb.wrapped.CreateFolder(ctx, folderName) - recordRequest(ctx, "CreateFolder", startTime) + recordRequest(ctx, mb.metricHandle, "CreateFolder", startTime) return folder, err } func (mb *monitoringBucket) RenameFolder(ctx context.Context, folderName string, destinationFolderId string) (o *gcs.Folder, err error) { startTime := time.Now() o, err = mb.wrapped.RenameFolder(ctx, folderName, destinationFolderId) - recordRequest(ctx, "RenameFolder", startTime) + recordRequest(ctx, mb.metricHandle, "RenameFolder", startTime) return } // recordReader increments the reader count when it's opened or closed. -func recordReader(ctx context.Context, ioMethod string) { - if err := stats.RecordWithTags( - ctx, - []tag.Mutator{ - tag.Upsert(tags.IOMethod, ioMethod), - }, - readerCountOC.M(1), - ); err != nil { - logger.Errorf("Cannot record a reader %v: %v", ioMethod, err) - } +func recordReader(ctx context.Context, metricHandle common.MetricHandle, ioMethod string) { + metricHandle.GCSReaderCount(ctx, 1, []common.MetricAttr{{Key: common.IOMethod, Value: ioMethod}}) } // Monitoring on the object reader -func newMonitoringReadCloser(ctx context.Context, object string, rc io.ReadCloser) io.ReadCloser { - recordReader(ctx, "opened") +func newMonitoringReadCloser(ctx context.Context, object string, rc io.ReadCloser, metricHandle common.MetricHandle) io.ReadCloser { + recordReader(ctx, metricHandle, "opened") return &monitoringReadCloser{ - ctx: ctx, - object: object, - wrapped: rc, + ctx: ctx, + object: object, + wrapped: rc, + metricHandle: metricHandle, } } type monitoringReadCloser struct { - ctx context.Context - object string - wrapped io.ReadCloser + ctx context.Context + object string + wrapped io.ReadCloser + metricHandle common.MetricHandle } func (mrc *monitoringReadCloser) Read(p []byte) (n int, err error) { n, err = mrc.wrapped.Read(p) if err == nil || err == io.EOF { - stats.Record(mrc.ctx, readBytesCountOC.M(int64(n))) + mrc.metricHandle.GCSReadBytesCount(mrc.ctx, int64(n), nil) } return } @@ -279,6 +209,6 @@ func (mrc *monitoringReadCloser) Close() (err error) { if err != nil { return fmt.Errorf("close reader: %w", err) } - recordReader(mrc.ctx, "closed") + recordReader(mrc.ctx, mrc.metricHandle, "closed") return } diff --git a/internal/monitor/reader.go b/internal/monitor/reader.go deleted file mode 100644 index 5d3ef2f30a..0000000000 --- a/internal/monitor/reader.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package monitor - -import ( - "log" - "strconv" - "time" - - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor/tags" - "go.opencensus.io/plugin/ochttp" - "go.opencensus.io/stats" - "go.opencensus.io/stats/view" - "go.opencensus.io/tag" - "golang.org/x/net/context" -) - -var ( - // When a first read call is made by the user, we either fetch entire file or x number of bytes from GCS based on the request. - // Now depending on the pagesize multiple read calls will be issued by user to read the entire file. These - // requests will be served from the downloaded data. - // This metric captures only the requests made to GCS, not the subsequent page calls. - gcsReadCountOC = stats.Int64("gcs/read_count", - "Specifies the number of gcs reads made along with type - Sequential/Random", - UnitDimensionless) - downloadBytesCountOC = stats.Int64("gcs/download_bytes_count", - "The cumulative number of bytes downloaded from GCS along with type - Sequential/Random", - UnitBytes) - fileCacheReadCountOC = stats.Int64("file_cache/read_count", - "Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false", - UnitDimensionless) - fileCacheReadBytesCountOC = stats.Int64("file_cache/read_bytes_count", - "The cumulative number of bytes read from file cache along with read type - Sequential/Random", - UnitBytes) - fileCacheReadLatencyOC = stats.Int64("file_cache/read_latency", - "Latency of read from file cache along with cache hit - true/false", - UnitMicroseconds) -) - -const NanosecondsInOneMillisecond = 1000000 - -// Initialize the metrics. -func init() { - // GCS related metrics - if err := view.Register( - &view.View{ - Name: "gcs/read_count", - Measure: gcsReadCountOC, - Description: "Specifies the number of gcs reads made along with type - Sequential/Random", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tags.ReadType}, - }, - &view.View{ - Name: "gcs/download_bytes_count", - Measure: downloadBytesCountOC, - Description: "The cumulative number of bytes downloaded from GCS along with type - Sequential/Random", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tags.ReadType}, - }, - // File cache related metrics - &view.View{ - Name: "file_cache/read_count", - Measure: fileCacheReadCountOC, - Description: "Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tags.ReadType, tags.CacheHit}, - }, - &view.View{ - Name: "file_cache/read_bytes_count", - Measure: fileCacheReadBytesCountOC, - Description: "The cumulative number of bytes read from file cache along with read type - Sequential/Random", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tags.ReadType}, - }, - &view.View{ - Name: "file_cache/read_latencies", - Measure: fileCacheReadLatencyOC, - Description: "The cumulative distribution of the file cache read latencies along with cache hit - true/false", - Aggregation: ochttp.DefaultLatencyDistribution, - TagKeys: []tag.Key{tags.CacheHit}, - }, - ); err != nil { - log.Fatalf("Failed to register the reader view: %v", err) - } -} - -func CaptureGCSReadMetrics(ctx context.Context, readType string, requestedDataSize int64) { - if err := stats.RecordWithTags( - ctx, - []tag.Mutator{ - tag.Upsert(tags.ReadType, readType), - }, - gcsReadCountOC.M(1), - ); err != nil { - // Error in recording gcsReadCountOC. - logger.Errorf("Cannot record gcsReadCountOC %v", err) - } - - if err := stats.RecordWithTags( - ctx, - []tag.Mutator{ - tag.Upsert(tags.ReadType, readType), - }, - downloadBytesCountOC.M(requestedDataSize), - ); err != nil { - // Error in recording downloadBytesCountOC. - logger.Errorf("Cannot record downloadBytesCountOC %v", err) - } -} - -func CaptureFileCacheMetrics(ctx context.Context, readType string, readDataSize int, cacheHit bool, readLatency time.Duration) { - if err := stats.RecordWithTags( - ctx, - []tag.Mutator{ - tag.Upsert(tags.ReadType, readType), - tag.Upsert(tags.CacheHit, strconv.FormatBool(cacheHit)), - }, - fileCacheReadCountOC.M(1), - ); err != nil { - // Error in recording fileCacheReadCountOC. - logger.Errorf("Cannot record fileCacheReadCountOC %v", err) - } - - if err := stats.RecordWithTags( - ctx, - []tag.Mutator{ - tag.Upsert(tags.ReadType, readType), - }, - fileCacheReadBytesCountOC.M(int64(readDataSize)), - ); err != nil { - // Error in recording fileCacheReadBytesCountOC. - logger.Errorf("Cannot record fileCacheReadBytesCountOC %v", err) - } - - if err := stats.RecordWithTags( - ctx, - []tag.Mutator{ - tag.Upsert(tags.CacheHit, strconv.FormatBool(cacheHit)), - }, - fileCacheReadLatencyOC.M(readLatency.Microseconds()), - ); err != nil { - // Error in recording fileCacheReadLatencyOC. - logger.Errorf("Cannot record fileCacheReadLatencyOC %v", err) - } -} diff --git a/internal/monitor/tags/tags.go b/internal/monitor/tags/tags.go deleted file mode 100644 index 77592afa6b..0000000000 --- a/internal/monitor/tags/tags.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package tags provides the tags to annotate the monitoring metrics. -package tags - -import ( - "go.opencensus.io/tag" -) - -var ( - // IOMethod annotates the event that opens or closes a connection or file. - IOMethod = tag.MustNewKey("io_method") - - // GCSMethod annotates the method called in the GCS client library. - GCSMethod = tag.MustNewKey("gcs_method") - - // FSOp annotates the file system op processed. - FSOp = tag.MustNewKey("fs_op") - - // FSErrCategory reduces the cardinality of FSError by grouping errors together. - FSErrCategory = tag.MustNewKey("fs_error_category") - - // ReadType annotates the read operation with the type - Sequential/Random - ReadType = tag.MustNewKey("read_type") - - // CacheHit annotates the read operation from file cache with true or false. - CacheHit = tag.MustNewKey("cache_hit") -) From 38e6272fb8a7320d773fc44fec1007574ff82aca Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 5 Dec 2024 10:48:54 +0530 Subject: [PATCH 0016/1298] [Emulator tests] Add proxy server - 3 (#2738) * separating list and stat request * separating list and stat request * add unit tests * add unit tests * update unit tests * add main.go file * rebase * rebase and add debug flag * update comment * rebase * add comment * lint fix * lint fix * lint fix * review comments --- .../emulator_tests/proxy_server/config.go | 6 +- .../proxy_server/config_test.go | 2 +- .../proxy_server/configs/config.yaml | 1 + .../emulator_tests/proxy_server/main.go | 170 ++++++++++++++++++ .../proxy_server/operation_manager.go | 13 +- .../proxy_server/operation_manager_test.go | 2 +- .../proxy_server/request_mapper.go | 2 +- .../proxy_server/request_mappper_test.go | 2 +- 8 files changed, 188 insertions(+), 10 deletions(-) create mode 100644 tools/integration_tests/emulator_tests/proxy_server/configs/config.yaml create mode 100644 tools/integration_tests/emulator_tests/proxy_server/main.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/config.go b/tools/integration_tests/emulator_tests/proxy_server/config.go index 51a4eb56bf..20130ccdb1 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/config.go +++ b/tools/integration_tests/emulator_tests/proxy_server/config.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package proxy_server +package main import ( "fmt" @@ -60,7 +60,9 @@ func parseConfigFile(configPath string) (*Config, error) { return nil, fmt.Errorf("unable to decode into struct, %v", err) } - printConfig(config) + if *fDebug { + printConfig(config) + } return &config, nil } diff --git a/tools/integration_tests/emulator_tests/proxy_server/config_test.go b/tools/integration_tests/emulator_tests/proxy_server/config_test.go index f9428af3f5..9723e2a12c 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/config_test.go +++ b/tools/integration_tests/emulator_tests/proxy_server/config_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package proxy_server +package main import ( "os" diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/config.yaml b/tools/integration_tests/emulator_tests/proxy_server/configs/config.yaml new file mode 100644 index 0000000000..7fa3ab1262 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/configs/config.yaml @@ -0,0 +1 @@ +targetHost: http://localhost:9000 diff --git a/tools/integration_tests/emulator_tests/proxy_server/main.go b/tools/integration_tests/emulator_tests/proxy_server/main.go new file mode 100644 index 0000000000..6226229003 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/main.go @@ -0,0 +1,170 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "flag" + "fmt" + "io" + "log" + "net/http" + "net/url" + "os" + "os/signal" + "syscall" + "time" +) + +var ( + // Flag to accept config-file path. + fConfigPath = flag.String("config-path", "configs/config.yaml", "Path to the file") + // Flag to turn on fDebug logs. + // TODO: We can add support for specifying a log path for fDebug logs in a future update. + fDebug = flag.Bool("debug", false, "Enable proxy server fDebug logs.") + // Initialized before the server gets started. + gConfig *Config +) + +// Host address of the proxy server. +// TODO: Allow this value to be configured via a command-line flag. +const Port = "8020" + +type ProxyHandler struct { + http.Handler +} + +// ServeHTTP handles incoming HTTP requests. It acts as a proxy, forwarding requests +// to a target server specified in the configuration and then relaying the +// response back to the original client. +func (ph ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + targetURL := fmt.Sprintf("%s%s", gConfig.TargetHost, r.RequestURI) + req, err := http.NewRequest(r.Method, targetURL, r.Body) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + for name, values := range r.Header { + for _, value := range values { + req.Header.Add(name, value) + } + } + + // Send the request to the target server + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + respURL, err := resp.Location() + // Change the response URL host to the proxy server host. + // This is necessary because, from the client's perspective, the proxy server is the endpoint. + // Therefore, the response must appear to originate from the proxy host. + if err == nil { + // Parse the original URL. + u, err := url.Parse(respURL.String()) + if err != nil { + log.Println("Error parsing URL:", err) + return + } + + u.Host = "localhost:" + Port + resp.Header.Set("Location", u.String()) + } + + defer resp.Body.Close() + + // Copy headers from the target server's response + for name, values := range resp.Header { + for _, value := range values { + w.Header().Add(name, value) + } + } + + // Copy the response body + w.WriteHeader(resp.StatusCode) + _, err = io.Copy(w, resp.Body) + if err != nil { + log.Printf("Error in coping response body: %v", err) + } +} + +// ProxyServer represents a simple proxy server over GCS storage based API endpoint. +type ProxyServer struct { + port string + server *http.Server + shutdown chan os.Signal +} + +// NewProxyServer creates a new ProxyServer instance +func NewProxyServer(port string) *ProxyServer { + return &ProxyServer{ + port: port, + shutdown: make(chan os.Signal, 1), + } +} + +// Start starts the proxy server. +func (ps *ProxyServer) Start() { + ps.server = &http.Server{ + Addr: ":" + ps.port, + Handler: ProxyHandler{}, + } + + // Start the server in a new goroutine + go func() { + log.Printf("Proxy server started on port %s\n", ps.port) + if err := ps.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + log.Fatalf("Server error: %v", err) + } + }() + + // Handle graceful shutdown + signal.Notify(ps.shutdown, syscall.SIGINT, syscall.SIGTERM) + <-ps.shutdown + log.Println("Shutting down proxy server...") + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + if err := ps.server.Shutdown(ctx); err != nil { + log.Fatalf("Proxy server forced to shutdown: %v", err) + } else { + log.Println("Proxy server exiting") + } +} + +func main() { + // Parse the command-line flags + flag.Parse() + + var err error + gConfig, err = parseConfigFile(*fConfigPath) + if err != nil { + log.Printf("Parsing error: %v\n", err) + os.Exit(1) + } + + if *fDebug { + log.Printf("%+v\n", gConfig) + } + + // TODO: Create operation manager instance from config. + + ps := NewProxyServer(Port) + ps.Start() +} diff --git a/tools/integration_tests/emulator_tests/proxy_server/operation_manager.go b/tools/integration_tests/emulator_tests/proxy_server/operation_manager.go index 62656ceef3..d002f15b36 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/operation_manager.go +++ b/tools/integration_tests/emulator_tests/proxy_server/operation_manager.go @@ -12,10 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -package proxy_server +package main import ( - "log" "sync" ) @@ -32,7 +31,11 @@ func NewOperationManager(config Config) *OperationManager { for _, retryConfig := range config.RetryConfig { om.addRetryConfig(retryConfig) } - log.Printf("%+v\n", om) + + if *fDebug { + println(om) + } + return om } @@ -64,7 +67,9 @@ func (om *OperationManager) retrieveOperation(requestType RequestType) string { func (om *OperationManager) addRetryConfig(rc RetryConfig) { rt := RequestType(rc.Method) - println(rt) + if *fDebug { + println(rt) + } if om.retryConfigs[rt] != nil { // Key exists, append the new retryConfig to the existing list om.retryConfigs[rt] = append(om.retryConfigs[rt], rc) diff --git a/tools/integration_tests/emulator_tests/proxy_server/operation_manager_test.go b/tools/integration_tests/emulator_tests/proxy_server/operation_manager_test.go index 65ff36ab65..45663c0eb3 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/operation_manager_test.go +++ b/tools/integration_tests/emulator_tests/proxy_server/operation_manager_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package proxy_server +package main import ( "testing" diff --git a/tools/integration_tests/emulator_tests/proxy_server/request_mapper.go b/tools/integration_tests/emulator_tests/proxy_server/request_mapper.go index 3fc6296bf9..3d9a9ef47f 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/request_mapper.go +++ b/tools/integration_tests/emulator_tests/proxy_server/request_mapper.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package proxy_server +package main import ( "net/http" diff --git a/tools/integration_tests/emulator_tests/proxy_server/request_mappper_test.go b/tools/integration_tests/emulator_tests/proxy_server/request_mappper_test.go index 7ba0f85fa8..5b856e68f2 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/request_mappper_test.go +++ b/tools/integration_tests/emulator_tests/proxy_server/request_mappper_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package proxy_server +package main import ( "net/http" From 75d47f269a32439afb85f7335e6020109383a65b Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 5 Dec 2024 15:22:11 +0530 Subject: [PATCH 0017/1298] Implemented code changes to give clobbered error while syncing object to prevent data loss. (#2722) * map storage.ErrObjectNotExist in NewReader method to gcs.NotFoundError * fixed formatting * Implemented code changes to give clobbered error while syncing object to prevent silent data loss * Implemented code changes to give clobbered error while syncing object to prevent silent data loss * fixed formatting * minor fixes * updated startRead and openReader as per update in NewReader error. * Fixed comments * Fixed failing tests --- internal/fs/fs.go | 15 +++-- internal/fs/fs_test.go | 3 + internal/fs/gcsfuse_errors/gcsfuse_errors.go | 33 +++++++++++ .../fs/gcsfuse_errors/gcsfuse_errors_test.go | 55 +++++++++++++++++++ internal/fs/inode/file.go | 29 +++++++--- internal/fs/inode/file_test.go | 27 ++++++++- internal/fs/server.go | 2 +- internal/fs/wrappers/error_mapping.go | 22 ++++++-- internal/fs/wrappers/error_mapping_test.go | 32 +++++++++-- internal/gcsx/random_reader.go | 14 +++++ internal/gcsx/random_reader_test.go | 14 +++++ 11 files changed, 220 insertions(+), 26 deletions(-) create mode 100644 internal/fs/gcsfuse_errors/gcsfuse_errors.go create mode 100644 internal/fs/gcsfuse_errors/gcsfuse_errors_test.go diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 51e8b810ff..762b1436b1 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -28,6 +28,8 @@ import ( "syscall" "time" + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" @@ -1120,9 +1122,12 @@ func (fs *fileSystem) syncFile( ctx context.Context, f *inode.FileInode) (err error) { // SyncFile can be triggered for unlinked files if the fileHandle is open by - // same or another user. Silently ignore the syncFile call. - // This is in sync with non-local file behaviour. + // same or another user. This indicates a potential file clobbering scenario: + // - The file was deleted (unlinked) while a handle to it was still open. if f.IsLocal() && f.IsUnlinked() { + err = &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("file %s was unlinked while it was still open, indicating file clobbering", f.Name().LocalName()), + } return } @@ -1150,12 +1155,12 @@ func (fs *fileSystem) syncFile( // We need not update fileIndex: // // We've held the inode lock the whole time, so there's no way that this - // inode could have been booted from the index. Therefore if it's not in the + // inode could have been booted from the index. Therefore, if it's not in the // index at the moment, it must not have been in there when we started. That - // is, it must have been clobbered remotely, which we treat as unlinking. + // is, it must have been clobbered remotely. // // In other words, either this inode is still in the index or it has been - // unlinked and *should* be anonymous. + // clobbered and *should* be anonymous. return } diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index 616283adf7..63e3ded2aa 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -165,6 +165,9 @@ func (t *fsTest) SetUpTestSuite() { TtlSecs: 60, TypeCacheMaxSizeMb: 4, }, + FileSystem: cfg.FileSystemConfig{ + PreconditionErrors: false, + }, } } t.serverCfg.MetricHandle = common.NewNoopMetrics() diff --git a/internal/fs/gcsfuse_errors/gcsfuse_errors.go b/internal/fs/gcsfuse_errors/gcsfuse_errors.go new file mode 100644 index 0000000000..b5714d954f --- /dev/null +++ b/internal/fs/gcsfuse_errors/gcsfuse_errors.go @@ -0,0 +1,33 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsfuse_errors + +import ( + "fmt" +) + +// FileClobberedError represents a file clobbering scenario where a file was +// modified or deleted while it was being accessed. +type FileClobberedError struct { + Err error +} + +func (fce *FileClobberedError) Error() string { + return fmt.Sprintf("The file was modified or deleted by another process, possibly due to concurrent modification: %v", fce.Err) +} + +func (fce *FileClobberedError) Unwrap() error { + return fce.Err +} diff --git a/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go b/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go new file mode 100644 index 0000000000..15f12555ab --- /dev/null +++ b/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go @@ -0,0 +1,55 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsfuse_errors + +import ( + "errors" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFileClobberedError(t *testing.T) { + testCases := []struct { + name string + err error + wantErrMsg string + }{ + { + name: "with_underlying_error", + err: fmt.Errorf("some error"), + wantErrMsg: "The file was modified or deleted by another process, possibly due to concurrent modification: some error", + }, + { + name: "without_underlying_error", + err: nil, + wantErrMsg: "The file was modified or deleted by another process, possibly due to concurrent modification: ", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clobberedErr := &FileClobberedError{Err: tc.err} + + gotErrMsg := clobberedErr.Error() + + assert.Equal(t, tc.wantErrMsg, gotErrMsg) + if tc.err != nil { + assert.True(t, errors.Is(clobberedErr, tc.err)) + } + }) + } +} diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index d1385cbfd5..91a5dbe742 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -25,6 +25,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/internal/bufferedwrites" "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" @@ -228,6 +229,15 @@ func (f *FileInode) openReader(ctx context.Context) (io.ReadCloser, error) { Generation: f.src.Generation, ReadCompressed: f.src.HasContentEncodingGzip(), }) + // If the object with requested generation doesn't exist in GCS, it indicates + // a file clobbering scenario. This likely occurred because the file was + // modified/deleted leading to different generation number. + var notFoundError *gcs.NotFoundError + if errors.As(err, ¬FoundError) { + err = &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("NewReader: %w", err), + } + } if err != nil { err = fmt.Errorf("NewReader: %w", err) } @@ -576,8 +586,7 @@ func (f *FileInode) SetMtime( } // Sync writes out contents to GCS. If this fails due to the generation having been -// clobbered, treat it as a non-error (simulating the inode having been -// unlinked). +// clobbered, failure is propagated back to the calling function as an error. // // After this method succeeds, SourceGeneration will return the new generation // by which this inode should be known (which may be the same as before). If it @@ -600,9 +609,13 @@ func (f *FileInode) Sync(ctx context.Context) (err error) { // properties. latestGcsObj, isClobbered, err := f.clobbered(ctx, true, true) - // Clobbered is treated as being unlinked. There's no reason to return an - // error in that case. We simply return without syncing the object. - if err != nil || isClobbered { + if isClobbered { + err = &gcsfuse_errors.FileClobberedError{ + Err: err, + } + } + + if err != nil { return } @@ -611,11 +624,11 @@ func (f *FileInode) Sync(ctx context.Context) (err error) { // the latest object fetched from gcs which has all the properties populated. newObj, err := f.bucket.SyncObject(ctx, f.Name().GcsObjectName(), latestGcsObj, f.content) - // Special case: a precondition error means we were clobbered, which we treat - // as being unlinked. There's no reason to return an error in that case. var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { - err = nil + err = &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("SyncObject: %w", err), + } return } diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 31fa292e42..60b528e0a5 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -15,6 +15,7 @@ package inode import ( + "errors" "fmt" "io" "math" @@ -25,6 +26,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" @@ -681,10 +683,12 @@ func (t *FileTest) Sync_Clobbered() { AssertEq(nil, err) - // Sync. The call should succeed, but nothing should change. + // Sync. The call should not succeed, and we expect a FileClobberedError. err = t.in.Sync(t.ctx) - AssertEq(nil, err) + // Check if the error is a FileClobberedError + var fcErr *gcsfuse_errors.FileClobberedError + AssertTrue(errors.As(err, &fcErr), "expected FileClobberedError but got %v", err) ExpectEq(t.backingObj.Generation, t.in.SourceGeneration().Object) ExpectEq(t.backingObj.MetaGeneration, t.in.SourceGeneration().Metadata) @@ -698,6 +702,25 @@ func (t *FileTest) Sync_Clobbered() { ExpectEq(newObj.Size, m.Size) } +func (t *FileTest) TestOpenReader_ThrowsFileClobberedError() { + // Modify the file locally. + err := t.in.Truncate(t.ctx, 2) + AssertEq(nil, err) + // Clobber the backing object. + _, err = storageutil.CreateObject( + t.ctx, + t.bucket, + t.in.Name().GcsObjectName(), + []byte("burrito")) + AssertEq(nil, err) + + _, err = t.in.openReader(t.ctx) + + // assert error is not nil. + var fcErr *gcsfuse_errors.FileClobberedError + AssertTrue(errors.As(err, &fcErr), "expected FileClobberedError but got %v", err) +} + func (t *FileTest) SetMtime_ContentNotFaultedIn() { var err error var attrs fuseops.InodeAttributes diff --git a/internal/fs/server.go b/internal/fs/server.go index e3bacdeb55..1aaafa11be 100644 --- a/internal/fs/server.go +++ b/internal/fs/server.go @@ -31,7 +31,7 @@ func NewServer(ctx context.Context, cfg *ServerConfig) (fuse.Server, error) { return nil, fmt.Errorf("create file system: %w", err) } - fs = wrappers.WithErrorMapping(fs) + fs = wrappers.WithErrorMapping(fs, cfg.NewConfig.FileSystem.PreconditionErrors) if newcfg.IsTracingEnabled(cfg.NewConfig) { fs = wrappers.WithTracing(fs) } diff --git a/internal/fs/wrappers/error_mapping.go b/internal/fs/wrappers/error_mapping.go index 40ea9b51e4..a68724efd7 100644 --- a/internal/fs/wrappers/error_mapping.go +++ b/internal/fs/wrappers/error_mapping.go @@ -23,6 +23,7 @@ import ( "cloud.google.com/go/storage" "github.com/googleapis/gax-go/v2/apierror" + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" @@ -34,7 +35,7 @@ var ( DefaultFSError = syscall.EIO ) -func errno(err error) error { +func errno(err error, preconditionErrCfg bool) error { if err == nil { return nil } @@ -50,6 +51,15 @@ func errno(err error) error { return syscall.EINTR } + // The object is modified or deleted by a concurrent process. + var clobberedErr *gcsfuse_errors.FileClobberedError + if errors.As(err, &clobberedErr) { + if preconditionErrCfg { + return syscall.ESTALE + } + return nil + } + if errors.Is(err, storage.ErrObjectNotExist) { return syscall.ENOENT } @@ -94,14 +104,16 @@ func errno(err error) error { // WithErrorMapping wraps a FileSystem, processing the returned errors, and // mapping them into syscall.Errno that can be understood by FUSE. -func WithErrorMapping(wrapped fuseutil.FileSystem) fuseutil.FileSystem { +func WithErrorMapping(wrapped fuseutil.FileSystem, preconditionErrCfg bool) fuseutil.FileSystem { return &errorMapping{ - wrapped: wrapped, + wrapped: wrapped, + preconditionErrCfg: preconditionErrCfg, } } type errorMapping struct { - wrapped fuseutil.FileSystem + wrapped fuseutil.FileSystem + preconditionErrCfg bool } func (em *errorMapping) handlePanic() { @@ -113,7 +125,7 @@ func (em *errorMapping) handlePanic() { } func (em *errorMapping) mapError(op string, err error) error { - fsErr := errno(err) + fsErr := errno(err, em.preconditionErrCfg) if err != nil && fsErr != nil && err != fsErr { logger.Errorf("%s: %v, %v", op, fsErr, err) } diff --git a/internal/fs/wrappers/error_mapping_test.go b/internal/fs/wrappers/error_mapping_test.go index 9f27be86fb..dc012bb747 100644 --- a/internal/fs/wrappers/error_mapping_test.go +++ b/internal/fs/wrappers/error_mapping_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/googleapis/gax-go/v2/apierror" + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "google.golang.org/api/googleapi" @@ -30,6 +31,7 @@ import ( type ErrorMapping struct { suite.Suite + preconditionErrCfg bool } func TestWithErrorMapping(testSuite *testing.T) { @@ -40,7 +42,7 @@ func (testSuite *ErrorMapping) TestPermissionDeniedGrpcApiError() { statusErr := status.New(codes.PermissionDenied, "Permission denied") apiError, _ := apierror.FromError(statusErr.Err()) - fsErr := errno(apiError) + fsErr := errno(apiError, testSuite.preconditionErrCfg) assert.Equal(testSuite.T(), syscall.EACCES, fsErr) } @@ -49,7 +51,7 @@ func (testSuite *ErrorMapping) TestNotFoundGrpcApiError() { statusErr := status.New(codes.NotFound, "Not found") apiError, _ := apierror.FromError(statusErr.Err()) - fsErr := errno(apiError) + fsErr := errno(apiError, testSuite.preconditionErrCfg) assert.Equal(testSuite.T(), syscall.ENOENT, fsErr) } @@ -58,7 +60,7 @@ func (testSuite *ErrorMapping) TestCanceledGrpcApiError() { statusErr := status.New(codes.Canceled, "Canceled error") apiError, _ := apierror.FromError(statusErr.Err()) - fsErr := errno(apiError) + fsErr := errno(apiError, testSuite.preconditionErrCfg) assert.Equal(testSuite.T(), syscall.EINTR, fsErr) } @@ -67,7 +69,7 @@ func (testSuite *ErrorMapping) TestUnAuthenticatedGrpcApiError() { statusErr := status.New(codes.Unauthenticated, "UnAuthenticated error") apiError, _ := apierror.FromError(statusErr.Err()) - fsErr := errno(apiError) + fsErr := errno(apiError, testSuite.preconditionErrCfg) assert.Equal(testSuite.T(), syscall.EACCES, fsErr) } @@ -76,7 +78,27 @@ func (testSuite *ErrorMapping) TestUnAuthenticatedHttpGoogleApiError() { googleApiError := &googleapi.Error{Code: http.StatusUnauthorized} googleApiError.Wrap(fmt.Errorf("UnAuthenticated error")) - fsErr := errno(googleApiError) + fsErr := errno(googleApiError, testSuite.preconditionErrCfg) assert.Equal(testSuite.T(), syscall.EACCES, fsErr) } + +func (testSuite *ErrorMapping) TestFileClobberedErrorWithPreconditionErrCfg() { + clobberedErr := &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("some error"), + } + + gotErrno := errno(clobberedErr, true) + + assert.Equal(testSuite.T(), syscall.ESTALE, gotErrno) +} + +func (testSuite *ErrorMapping) TestFileClobberedErrorWithoutPreconditionErrCfg() { + clobberedErr := &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("some error"), + } + + gotErrno := errno(clobberedErr, false) + + assert.Equal(testSuite.T(), nil, gotErrno) +} diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 27ac2e03d8..3a58d474d8 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -15,6 +15,7 @@ package gcsx import ( + "errors" "fmt" "io" "strconv" @@ -26,6 +27,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" cacheutil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/util" @@ -507,6 +509,18 @@ func (rr *randomReader) startRead( ReadCompressed: rr.object.HasContentEncodingGzip(), }) + // If a file handle is open locally, but the corresponding object doesn't exist + // in GCS, it indicates a file clobbering scenario. This likely occurred because: + // - The file was deleted in GCS while a local handle was still open. + // - The file content was modified leading to different generation number. + var notFoundError *gcs.NotFoundError + if errors.As(err, ¬FoundError) { + err = &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("NewReader: %w", err), + } + return + } + if err != nil { err = fmt.Errorf("NewReader: %w", err) return diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index 1bb53f2201..3c28be6cb2 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -33,6 +33,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" @@ -1205,6 +1206,19 @@ func (t *RandomReaderTest) Test_Destroy_NonNilCacheHandle() { ExpectEq(nil, t.rr.wrapped.fileCacheHandle) } +func (t *RandomReaderTest) TestNewReader_FileClobbered() { + var notFoundError *gcs.NotFoundError + + ExpectCall(t.bucket, "NewReader")(Any(), Any()). + WillOnce(Return(nil, notFoundError)) + + err := t.rr.wrapped.startRead(t.rr.ctx, 0, 1) + + AssertNe(nil, err) + var clobberedErr *gcsfuse_errors.FileClobberedError + AssertTrue(errors.As(err, &clobberedErr)) +} + // TODO (raj-prince) - to add unit tests for failed scenario while reading via cache. // This requires mocking CacheHandle object, whose read method will return some unexpected // error. From f43f12cf6ff9b7e43861a8f7cc47ea4039fc6879 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 6 Dec 2024 11:17:39 +0530 Subject: [PATCH 0018/1298] Add file-cache metrics to the OC views (#2749) This fixes a bug due to which the file-cache metrics were not getting exported. --- common/oc_metrics.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/common/oc_metrics.go b/common/oc_metrics.go index 870a11bf9e..3e2cff1a2a 100644 --- a/common/oc_metrics.go +++ b/common/oc_metrics.go @@ -212,6 +212,28 @@ func NewOCMetrics() (MetricHandle, error) { Description: "The cumulative distribution of file system operation latencies", Aggregation: ochttp.DefaultLatencyDistribution, TagKeys: []tag.Key{tag.MustNewKey(FSOp)}, + }, + // File cache related metrics + &view.View{ + Name: "file_cache/read_count", + Measure: fileCacheReadCount, + Description: "Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false", + Aggregation: view.Sum(), + TagKeys: []tag.Key{tag.MustNewKey(ReadType), tag.MustNewKey(CacheHit)}, + }, + &view.View{ + Name: "file_cache/read_bytes_count", + Measure: fileCacheReadBytesCount, + Description: "The cumulative number of bytes read from file cache along with read type - Sequential/Random", + Aggregation: view.Sum(), + TagKeys: []tag.Key{tag.MustNewKey(ReadType)}, + }, + &view.View{ + Name: "file_cache/read_latencies", + Measure: fileCacheReadLatency, + Description: "The cumulative distribution of the file cache read latencies along with cache hit - true/false", + Aggregation: ochttp.DefaultLatencyDistribution, + TagKeys: []tag.Key{tag.MustNewKey(CacheHit)}, }); err != nil { return nil, fmt.Errorf("failed to register OpenCensus metrics for GCS client library: %w", err) } From a852a59980e51fd419a37d4bc2cb0f3974c22deb Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:11:25 +0000 Subject: [PATCH 0019/1298] Throwing OutOfOrder error for out of order writes (#2745) * Throwing OutOfOrder error for out of order writes * Throwing OutOfOrder error for out of order writes --- .../bufferedwrites/buffered_write_handler.go | 11 +++-- .../buffered_write_handler_test.go | 40 +++++++++++++++++++ internal/fs/inode/file_test.go | 2 +- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 94a5ee4b8a..2035f805b4 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -15,11 +15,13 @@ package bufferedwrites import ( + "errors" "fmt" "math" "time" "github.com/googlecloudplatform/gcsfuse/v2/internal/block" + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "golang.org/x/sync/semaphore" ) @@ -46,6 +48,8 @@ type WriteFileInfo struct { Mtime time.Time } +var ErrOutOfOrderWrite = errors.New("outOfOrder write detected") + // NewBWHandler creates the bufferedWriteHandler struct. func NewBWHandler(objectName string, bucket gcs.Bucket, blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphore.Weighted) (bwh *BufferedWriteHandler, err error) { bp, err := block.NewBlockPool(blockSize, maxBlocks, globalMaxBlocksSem) @@ -66,9 +70,10 @@ func NewBWHandler(objectName string, bucket gcs.Bucket, blockSize int64, maxBloc // Write writes the given data to the buffer. It writes to an existing buffer if // the capacity is available otherwise writes to a new buffer. func (wh *BufferedWriteHandler) Write(data []byte, offset int64) (err error) { - if offset > wh.totalSize { - // TODO: Will be handled as part of ordered writes. - return fmt.Errorf("non sequential writes") + if offset != wh.totalSize { + logger.Errorf("BufferedWriteHandler.OutOfOrderError for object: %s, expectedOffset: %d, actualOffset: %d", + wh.uploadHandler.objectName, wh.totalSize, offset) + return ErrOutOfOrderWrite } // Fail early if the uploadHandler has failed. diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index b48d43dd18..332d9d516b 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -95,6 +95,46 @@ func (testSuite *BufferedWriteTest) TestWriteDataSizeGreaterThanBlockSize() { assert.Equal(testSuite.T(), int64(size), fileInfo.TotalSize) } +func (testSuite *BufferedWriteTest) TestWriteWhenNextOffsetIsGreaterThanExpected() { + err := testSuite.bwh.Write([]byte("hi"), 0) + require.Nil(testSuite.T(), err) + + // Next offset should be 2, but we are calling with 5. + err = testSuite.bwh.Write([]byte("hello"), 5) + + require.NotNil(testSuite.T(), err) + require.Equal(testSuite.T(), err, ErrOutOfOrderWrite) + fileInfo := testSuite.bwh.WriteFileInfo() + assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + assert.Equal(testSuite.T(), int64(2), fileInfo.TotalSize) +} + +func (testSuite *BufferedWriteTest) TestWriteWhenNextOffsetIsLessThanExpected() { + err := testSuite.bwh.Write([]byte("hello"), 0) + require.Nil(testSuite.T(), err) + + // Next offset should be 5, but we are calling with 2. + err = testSuite.bwh.Write([]byte("abcdefgh"), 2) + + require.NotNil(testSuite.T(), err) + require.Equal(testSuite.T(), err, ErrOutOfOrderWrite) + fileInfo := testSuite.bwh.WriteFileInfo() + assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + assert.Equal(testSuite.T(), int64(5), fileInfo.TotalSize) +} + +func (testSuite *BufferedWriteTest) TestMultipleWrites() { + err := testSuite.bwh.Write([]byte("hello"), 0) + require.Nil(testSuite.T(), err) + + err = testSuite.bwh.Write([]byte("abcdefgh"), 5) + require.Nil(testSuite.T(), err) + + fileInfo := testSuite.bwh.WriteFileInfo() + assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + assert.Equal(testSuite.T(), int64(13), fileInfo.TotalSize) +} + func (testSuite *BufferedWriteTest) TestWrite_SignalUploadFailureInBetween() { err := testSuite.bwh.Write([]byte("hello"), 0) require.Nil(testSuite.T(), err) diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 60b528e0a5..303ec1b94d 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -1024,7 +1024,7 @@ func (t *FileTest) MultipleWritesToLocalFileWhenStreamingWritesAreEnabled() { AssertNe(nil, t.in.bwh) AssertEq(2, t.in.bwh.WriteFileInfo().TotalSize) - err = t.in.Write(t.ctx, []byte("hello"), 0) + err = t.in.Write(t.ctx, []byte("hello"), 2) AssertEq(nil, err) AssertEq(7, t.in.bwh.WriteFileInfo().TotalSize) } From 62340e89b8e21f078107b68ada8bf3034bc352af Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 6 Dec 2024 16:30:00 +0530 Subject: [PATCH 0020/1298] Remove global err variable from fs_test package. (#2751) * Removed global err variable from fs_test package. * Update internal/fs/fs_test.go Co-authored-by: Kislay Kishore --------- Co-authored-by: Kislay Kishore --- internal/fs/fs_test.go | 3 +-- internal/fs/hns_bucket_test.go | 24 ++++++++++++------------ internal/fs/local_file_test.go | 2 -- internal/fs/type_cache_test.go | 10 +++------- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index 63e3ded2aa..8f713f81b9 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -290,8 +290,7 @@ func (t *fsTest) createEmptyObjects(names []string) error { func (t *fsTest) createFolders(folders []string) error { for i := 0; i < len(folders); i++ { - _, err = bucket.CreateFolder(ctx, folders[i]) - if err != nil { + if _, err := bucket.CreateFolder(ctx, folders[i]); err != nil { return err } } diff --git a/internal/fs/hns_bucket_test.go b/internal/fs/hns_bucket_test.go index 9d7b04046c..ced8f38651 100644 --- a/internal/fs/hns_bucket_test.go +++ b/internal/fs/hns_bucket_test.go @@ -65,7 +65,7 @@ func (t *HNSBucketTests) TearDownSuite() { } func (t *HNSBucketTests) SetupTest() { - err = t.createFolders([]string{"foo/", "bar/", "foo/test2/", "foo/test/"}) + err := t.createFolders([]string{"foo/", "bar/", "foo/test2/", "foo/test/"}) require.NoError(t.T(), err) err = t.createObjects( @@ -103,7 +103,7 @@ func (t *HNSBucketTests) TestReadDir() { func (t *HNSBucketTests) TestDeleteFolder() { dirPath := path.Join(mntDir, "foo") - err = os.RemoveAll(dirPath) + err := os.RemoveAll(dirPath) assert.NoError(t.T(), err) _, err = os.Stat(dirPath) @@ -114,7 +114,7 @@ func (t *HNSBucketTests) TestDeleteFolder() { func (t *HNSBucketTests) TestDeleteImplicitDir() { dirPath := path.Join(mntDir, "foo", "implicit_dir") - err = os.RemoveAll(dirPath) + err := os.RemoveAll(dirPath) assert.NoError(t.T(), err) _, err = os.Stat(dirPath) @@ -126,7 +126,7 @@ func (t *HNSBucketTests) TestRenameFolderWithSrcDirectoryDoesNotExist() { oldDirPath := path.Join(mntDir, "foo_not_exist") newDirPath := path.Join(mntDir, "foo_rename") - err = os.Rename(oldDirPath, newDirPath) + err := os.Rename(oldDirPath, newDirPath) assert.Error(t.T(), err) assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) @@ -137,7 +137,7 @@ func (t *HNSBucketTests) TestRenameFolderWithSrcDirectoryDoesNotExist() { func (t *HNSBucketTests) TestRenameFolderWithDstDirectoryNotEmpty() { oldDirPath := path.Join(mntDir, "foo") - _, err = os.Stat(oldDirPath) + _, err := os.Stat(oldDirPath) assert.NoError(t.T(), err) // In the setup phase, we created file1.txt within the bar directory. newDirPath := path.Join(mntDir, "bar") @@ -152,7 +152,7 @@ func (t *HNSBucketTests) TestRenameFolderWithDstDirectoryNotEmpty() { func (t *HNSBucketTests) TestRenameFolderWithEmptySourceDirectory() { oldDirPath := path.Join(mntDir, "foo", "test2") - _, err = os.Stat(oldDirPath) + _, err := os.Stat(oldDirPath) assert.NoError(t.T(), err) newDirPath := path.Join(mntDir, "foo_rename") _, err = os.Stat(newDirPath) @@ -173,7 +173,7 @@ func (t *HNSBucketTests) TestRenameFolderWithEmptySourceDirectory() { func (t *HNSBucketTests) TestRenameFolderWithSourceDirectoryHaveLocalFiles() { oldDirPath := path.Join(mntDir, "foo", "test") - _, err = os.Stat(oldDirPath) + _, err := os.Stat(oldDirPath) assert.NoError(t.T(), err) file, err := os.OpenFile(path.Join(oldDirPath, "file4.txt"), os.O_RDWR|os.O_CREATE, filePerms) assert.NoError(t.T(), err) @@ -191,7 +191,7 @@ func (t *HNSBucketTests) TestRenameFolderWithSourceDirectoryHaveLocalFiles() { func (t *HNSBucketTests) TestRenameFolderWithSameParent() { oldDirPath := path.Join(mntDir, "foo") - _, err = os.Stat(oldDirPath) + _, err := os.Stat(oldDirPath) require.NoError(t.T(), err) newDirPath := path.Join(mntDir, "foo_rename") _, err = os.Stat(newDirPath) @@ -221,7 +221,7 @@ func (t *HNSBucketTests) TestRenameFolderWithSameParent() { func (t *HNSBucketTests) TestRenameFolderWithExistingEmptyDestDirectory() { oldDirPath := path.Join(mntDir, "foo", "test") - _, err = os.Stat(oldDirPath) + _, err := os.Stat(oldDirPath) require.NoError(t.T(), err) newDirPath := path.Join(mntDir, "foo", "test2") _, err = os.Stat(newDirPath) @@ -247,7 +247,7 @@ func (t *HNSBucketTests) TestRenameFolderWithExistingEmptyDestDirectory() { func (t *HNSBucketTests) TestRenameFolderWithDifferentParents() { oldDirPath := path.Join(mntDir, "foo") - _, err = os.Stat(oldDirPath) + _, err := os.Stat(oldDirPath) assert.NoError(t.T(), err) newDirPath := path.Join(mntDir, "bar", "foo_rename") @@ -274,7 +274,7 @@ func (t *HNSBucketTests) TestRenameFolderWithDifferentParents() { func (t *HNSBucketTests) TestRenameFolderWithOpenGCSFile() { oldDirPath := path.Join(mntDir, "bar") - _, err = os.Stat(oldDirPath) + _, err := os.Stat(oldDirPath) assert.NoError(t.T(), err) newDirPath := path.Join(mntDir, "bar_rename") filePath := path.Join(oldDirPath, "file1.txt") @@ -311,7 +311,7 @@ func (t *HNSBucketTests) TestRenameFolderWithOpenGCSFile() { // Read directory again and validate it is empty. func (t *HNSBucketTests) TestCreateDirectoryWithSameNameAfterRename() { oldDirPath := path.Join(mntDir, "foo") - _, err = os.Stat(oldDirPath) + _, err := os.Stat(oldDirPath) require.NoError(t.T(), err) newDirPath := path.Join(mntDir, "foo_rename") // Rename directory foo --> foo_rename diff --git a/internal/fs/local_file_test.go b/internal/fs/local_file_test.go index 9bac1b86c1..fd734502b6 100644 --- a/internal/fs/local_file_test.go +++ b/internal/fs/local_file_test.go @@ -840,7 +840,6 @@ func (t *LocalFileTest) AtimeMtimeAndCtime() { // Stat that local file. func (t *LocalFileTest) TestStatLocalFileAfterRecreatingItWithSameName() { filePath := path.Join(mntDir, "test.txt") - AssertEq(nil, err) f1, err := os.Create(filePath) defer AssertEq(nil, f1.Close()) AssertEq(nil, err) @@ -871,7 +870,6 @@ func (t *LocalFileTest) TestStatFailsOnNewFileAfterDeletion() { } t.serverCfg.MetricHandle = common.NewNoopMetrics() filePath := path.Join(mntDir, "test.txt") - AssertEq(nil, err) f1, err := os.Create(filePath) AssertEq(nil, err) defer AssertEq(nil, f1.Close()) diff --git a/internal/fs/type_cache_test.go b/internal/fs/type_cache_test.go index 552a02867e..0770d99f29 100644 --- a/internal/fs/type_cache_test.go +++ b/internal/fs/type_cache_test.go @@ -20,7 +20,6 @@ package fs_test import ( "fmt" - "io/fs" "math" "os" "path" @@ -71,9 +70,6 @@ var ( typeCacheMaxSizeMb int64 contentInBytes []byte - - fi fs.FileInfo - err error ) func (t *typeCacheTestCommon) SetUpTestSuite() { @@ -176,7 +172,7 @@ func (t *typeCacheTestCommon) createObjectOnGCS(name string) *gcs.Object { } func (t *typeCacheTestCommon) statAndConfirmIsDir(name string, isDir bool) { - fi, err = os.Stat(name) + fi, err := os.Stat(name) ExpectEq(nil, err) AssertNe(nil, fi) @@ -184,7 +180,7 @@ func (t *typeCacheTestCommon) statAndConfirmIsDir(name string, isDir bool) { } func (t *typeCacheTestCommon) statAndExpectNotADirectoryError(name string) { - _, err = os.Stat(name) + _, err := os.Stat(name) ExpectNe(nil, err) ExpectThat(err, oglematchers.Error(oglematchers.HasSubstr("not a directory"))) @@ -212,7 +208,7 @@ func (t *typeCacheTestCommon) testNoInsertionSupported() { func (t *TypeCacheTestWithMaxSize1MB) TestNoEntryInitially() { // Initially, without any existing object, type-cache // should not contain any entry and os.Stat should fail. - _, err = os.Stat(path.Join(mntDir, foo)) + _, err := os.Stat(path.Join(mntDir, foo)) ExpectNe(nil, err) ExpectThat(err, oglematchers.Error(oglematchers.HasSubstr("no such file or directory"))) From 25f16a0e2de5a5fa10fc6feaada652d98354c352 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 6 Dec 2024 16:56:42 +0530 Subject: [PATCH 0021/1298] [Emulator tests] Add proxy server-4 (#2743) * separating list and stat request * separating list and stat request * add unit tests * add unit tests * update unit tests * rebase * add unit tests * linux test fix * add retry id function * rebasing * rebase * add vendor dir * test changes * test changes * update poc according to comments * remove print * remove print * rebase * rebase * rebase * rebase * lint fix * lint fix * undo unnecessary changes * undo unnecessary changes * add unit test * add licence * fix lint * small fix * unnecessary change * review comments * review comments * Move test in main_tes * remove unnecessary change * remove unnecessary change * lint fix * lint fix * small fix --- .../emulator_tests/proxy_server/emulator.go | 105 ++++++++++++++++++ .../proxy_server/emulator_test.go | 65 +++++++++++ .../emulator_tests/proxy_server/main.go | 32 +++++- .../emulator_tests/proxy_server/main_test.go | 51 +++++++++ 4 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 tools/integration_tests/emulator_tests/proxy_server/emulator.go create mode 100644 tools/integration_tests/emulator_tests/proxy_server/emulator_test.go create mode 100644 tools/integration_tests/emulator_tests/proxy_server/main_test.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/emulator.go b/tools/integration_tests/emulator_tests/proxy_server/emulator.go new file mode 100644 index 0000000000..85b1ef4f68 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/emulator.go @@ -0,0 +1,105 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/url" +) + +type emulatorTest struct { + // host is the address of proxy server to which client interacts. + host *url.URL +} + +type RetryTestClient interface { + GetRetryID(instructions map[string][]string, transport string) (string, error) +} + +// GetRetryID creates a retry test resource in the emulator. +func (et *emulatorTest) GetRetryID(instructions map[string][]string, transport string) (string, error) { + c := http.DefaultClient + data := struct { + Instructions map[string][]string `json:"instructions"` + Transport string `json:"transport"` + }{ + Instructions: instructions, + Transport: transport, + } + + buf := new(bytes.Buffer) + if err := json.NewEncoder(buf).Encode(data); err != nil { + return "", fmt.Errorf("encoding request: %v\n", err) + } + + et.host.Path = "retry_test" + resp, err := c.Post(et.host.String(), "application/json", buf) + if err != nil || resp.StatusCode != 200 { + return "", fmt.Errorf("creating retry test: err: %v, resp: %+v\n", err, resp) + } + defer func() { + closeErr := resp.Body.Close() + if err == nil { + err = closeErr + } + }() + + testRes := struct { + TestID string `json:"id"` + }{} + if err := json.NewDecoder(resp.Body).Decode(&testRes); err != nil { + return "", fmt.Errorf("decoding test ID: %v\n", err) + } + + et.host.Path = "" + return testRes.TestID, nil +} + +// CreateRetryTest creates a retry test using the provided instructions. +// +// It takes emulator host (URL string) and a map of instructions. The instructions map +// specifies how the emulator should behave for specific requests. +// +// The keys of the `instructions` map are request paths, and the values are +// lists of strings representing actions to be taken for those requests. +// These actions can include things like returning specific error codes or +// simulating delays. +// +// Example `instructions` map: +// +// { +// "storage.objects.list": []string{"return-503"}, // Return a 503 error for this request +// "storage.objects.get": []string{"stall-100ms", "return-200"}, // Delay for 100ms then return success +// } +// +// The function returns a unique ID for the retry test, which can be used to +// identify and manage the test. It returns an error if there is a problem +// parsing the host URL or setting up the emulator test. +func CreateRetryTest(host string, instructions map[string][]string) (string, error) { + if len(instructions) == 0 { + return "", nil + } + + endpoint, err := url.Parse(host) + if err != nil { + return "", fmt.Errorf("Failed to parse host env: %v\n", err) + } + + et := &emulatorTest{host: endpoint} + return et.GetRetryID(instructions, "http") +} diff --git a/tools/integration_tests/emulator_tests/proxy_server/emulator_test.go b/tools/integration_tests/emulator_tests/proxy_server/emulator_test.go new file mode 100644 index 0000000000..7595b3cd5d --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/emulator_test.go @@ -0,0 +1,65 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "net/url" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestGetRetryID tests the GetRetryID function +func TestGetRetryID(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/retry_test", r.URL.Path, "Unexpected URL path") + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(map[string]string{"id": "test-id-123"}) + assert.NoError(t, err) + })) + defer mockServer.Close() + hostURL, _ := url.Parse(mockServer.URL) + et := &emulatorTest{host: hostURL} + instructions := map[string][]string{"retry": {"retry-instruction"}} + + testID, err := et.GetRetryID(instructions, "http") + + assert.NoError(t, err) + assert.Equal(t, "test-id-123", testID, "Unexpected test ID returned") +} + +// TestCreateRetryTest tests the CreateRetryTest function +func TestCreateRetryTest(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, "/retry_test", r.URL.Path, "Unexpected URL path") + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(map[string]string{"id": "test-id-123"}) + assert.NoError(t, err) + })) + defer mockServer.Close() + + instructions := map[string][]string{"retry": {"retry-instruction"}} + testID, err := CreateRetryTest(mockServer.URL, instructions) + assert.NoError(t, err) + assert.Equal(t, "test-id-123", testID, "Unexpected test ID returned") + + // Test with empty instructions + testID, err = CreateRetryTest(mockServer.URL, map[string][]string{}) + assert.NoError(t, err) + assert.Equal(t, "", testID, "Expected empty test ID for empty instructions") +} diff --git a/tools/integration_tests/emulator_tests/proxy_server/main.go b/tools/integration_tests/emulator_tests/proxy_server/main.go index 6226229003..6ba0e14bdd 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/main.go +++ b/tools/integration_tests/emulator_tests/proxy_server/main.go @@ -35,7 +35,8 @@ var ( // TODO: We can add support for specifying a log path for fDebug logs in a future update. fDebug = flag.Bool("debug", false, "Enable proxy server fDebug logs.") // Initialized before the server gets started. - gConfig *Config + gConfig *Config + gOpManager *OperationManager ) // Host address of the proxy server. @@ -46,6 +47,24 @@ type ProxyHandler struct { http.Handler } +// AddRetryID creates mock error behavior on the target host for specific request types. +// It retrieves the corresponding operation from the operation manager based on the provided RequestTypeAndInstruction. +// If a matching operation is found, it creates a retry test with the target host and instruction, +// and attaches the generated test ID to the HTTP request header "x-retry-test-id". +// +// This function is used to simulate error scenarios for testing retry mechanisms. +func AddRetryID(req *http.Request, r RequestTypeAndInstruction) error { + plantOp := gOpManager.retrieveOperation(r.RequestType) + if plantOp != "" { + testID, err := CreateRetryTest(gConfig.TargetHost, map[string][]string{r.Instruction: {plantOp}}) + if err != nil { + return fmt.Errorf("CreateRetryTest: %v", err) + } + req.Header.Set("x-retry-test-id", testID) + } + return nil +} + // ServeHTTP handles incoming HTTP requests. It acts as a proxy, forwarding requests // to a target server specified in the configuration and then relaying the // response back to the original client. @@ -61,6 +80,15 @@ func (ph ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { req.Header.Add(name, value) } } + // Determine the request type and instruction (e.g., read, write, metadata) based on the incoming request. + reqTypeAndInstruction := deduceRequestTypeAndInstruction(r) + + // Add a unique retry ID to the request headers, associating it with the + // deduced request type and instruction. This is used for adding custom failures on requests. + err = AddRetryID(req, reqTypeAndInstruction) + if err != nil { + log.Printf("AddRetryID: %v", err) + } // Send the request to the target server client := &http.Client{} @@ -163,7 +191,7 @@ func main() { log.Printf("%+v\n", gConfig) } - // TODO: Create operation manager instance from config. + gOpManager = NewOperationManager(*gConfig) ps := NewProxyServer(Port) ps.Start() diff --git a/tools/integration_tests/emulator_tests/proxy_server/main_test.go b/tools/integration_tests/emulator_tests/proxy_server/main_test.go new file mode 100644 index 0000000000..4006cd1014 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/main_test.go @@ -0,0 +1,51 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestAddRetryID tests the AddRetryID function +func TestAddRetryID(t *testing.T) { + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + err := json.NewEncoder(w).Encode(map[string]string{"id": "test-id-123"}) + assert.NoError(t, err) + })) + defer mockServer.Close() + + gConfig = &Config{TargetHost: mockServer.URL} + gOpManager = &OperationManager{ + retryConfigs: map[RequestType][]RetryConfig{ + "TestType": {{Method: "TestType", RetryInstruction: "retry-instruction", RetryCount: 1, SkipCount: 0}}, + }, + } + req, _ := http.NewRequest("GET", "http://example.com", nil) + r := RequestTypeAndInstruction{ + RequestType: "TestType", + Instruction: "retry", + } + + err := AddRetryID(req, r) + + assert.NoError(t, err) + assert.Equal(t, "test-id-123", req.Header.Get("x-retry-test-id"), "Unexpected x-retry-test-id header value") +} From 16b28a7a011982c9b7fc7cea3e5f6d37e67a62ca Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Fri, 6 Dec 2024 11:42:24 +0000 Subject: [PATCH 0022/1298] Made changes to support setting mtime and getting when buffered writes are enabled (#2732) * changes to setattributes and get when streamingWrites are enabled. * changes to setattributes and get when streamingWrites are enabled. * fixes * PR comments * handling empty GCS Files via buffered writes * handling empty GCS Files via buffered writes * checking for mtime * method rename --- internal/fs/fs.go | 11 +-- internal/fs/inode/file.go | 50 +++++++++--- internal/fs/inode/file_test.go | 139 +++++++++++++++++++++++++++++++-- 3 files changed, 175 insertions(+), 25 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 762b1436b1..92828588bf 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1697,13 +1697,10 @@ func (fs *fileSystem) createLocalFile( } child = fs.mintInode(core) fs.localFileInodes[child.Name()] = child - // For buffered writes, we don't create temp files on disk. - if !fs.newConfig.Write.ExperimentalEnableStreamingWrites { - // Empty file is created to be able to set attributes on the file. - fileInode := child.(*inode.FileInode) - if err := fileInode.CreateEmptyTempFile(); err != nil { - return nil, err - } + // Empty file is created to be able to set attributes on the file. + fileInode := child.(*inode.FileInode) + if err := fileInode.CreateBufferedOrTempWriter(); err != nil { + return nil, err } fs.mu.Unlock() diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 91a5dbe742..fca7729b17 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -389,7 +389,7 @@ func (f *FileInode) Attributes( // Obtain default information from the source object. attrs.Mtime = f.src.Updated - attrs.Size = uint64(f.src.Size) + attrs.Size = f.src.Size // If the source object has an mtime metadata key, use that instead of its // update time. @@ -424,6 +424,12 @@ func (f *FileInode) Attributes( } } + if f.bwh != nil { + writeFileInfo := f.bwh.WriteFileInfo() + attrs.Mtime = writeFileInfo.Mtime + attrs.Size = uint64(writeFileInfo.TotalSize) + } + // We require only that atime and ctime be "reasonable". attrs.Atime = attrs.Mtime attrs.Ctime = attrs.Mtime @@ -493,8 +499,16 @@ func (f *FileInode) Write( ctx context.Context, data []byte, offset int64) (err error) { - if f.local && f.writeConfig.ExperimentalEnableStreamingWrites { - return f.writeToBuffer(data, offset) + // For empty GCS files also we will triggered bufferedWrites flow. + if f.src.Size == 0 && f.writeConfig.ExperimentalEnableStreamingWrites { + err = f.ensureBufferedWriteHandler() + if err != nil { + return + } + } + + if f.bwh != nil { + return f.bwh.Write(data, offset) } // Make sure f.content != nil. @@ -517,6 +531,15 @@ func (f *FileInode) Write( func (f *FileInode) SetMtime( ctx context.Context, mtime time.Time) (err error) { + // When bufferedWritesHandler instance is not nil, set time on bwh. + // It will not be nil in 2 cases when bufferedWrites are enabled: + // 1. local files + // 2. After first write on empty GCS files. + if f.bwh != nil { + f.bwh.SetMtime(mtime) + return + } + // If we have a local temp file, stat it. var sr gcsx.StatResult if f.content != nil { @@ -685,7 +708,16 @@ func (f *FileInode) CacheEnsureContent(ctx context.Context) (err error) { return } -func (f *FileInode) CreateEmptyTempFile() (err error) { +func (f *FileInode) CreateBufferedOrTempWriter() (err error) { + // Skip creating empty file when streaming writes are enabled + if f.local && f.writeConfig.ExperimentalEnableStreamingWrites { + err = f.ensureBufferedWriteHandler() + if err != nil { + return + } + return + } + // Creating a file with no contents. The contents will be updated with // writeFile operations. f.content, err = f.contentCache.NewTempFile(io.NopCloser(strings.NewReader(""))) @@ -694,17 +726,15 @@ func (f *FileInode) CreateEmptyTempFile() (err error) { return } -// writeToBuffer writes the given content to the in-memory buffer. -func (f *FileInode) writeToBuffer(data []byte, offset int64) (err error) { - // Initialize bufferedWriteHandler if not done already. +func (f *FileInode) ensureBufferedWriteHandler() error { + var err error if f.bwh == nil { f.bwh, err = bufferedwrites.NewBWHandler(f.name.GcsObjectName(), f.bucket, f.writeConfig.BlockSizeMb, f.writeConfig.MaxBlocksPerFile, f.globalMaxBlocksSem) if err != nil { return fmt.Errorf("failed to create bufferedWriteHandler: %w", err) } + f.bwh.SetMtime(f.mtimeClock.Now()) } - err = f.bwh.Write(data, offset) - - return + return nil } diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 303ec1b94d..2df8af1fa7 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -99,6 +99,20 @@ func (t *FileTest) TearDown() { t.in.Unlock() } +func (t *FileTest) createInodeWithEmptyObject() { + object, err := storageutil.CreateObject( + t.ctx, + t.bucket, + fileName, + []byte{}) + t.backingObj = storageutil.ConvertObjToMinObject(object) + + AssertEq(nil, err) + + // Create the inode. + t.createInode() +} + func (t *FileTest) createInode() { t.createInodeWithLocalParam(fileName, false) } @@ -403,7 +417,7 @@ func (t *FileTest) WriteToLocalFileThenSync() { // Create a local file inode. t.createInodeWithLocalParam("test", true) // Create a temp file for the local inode created above. - err = t.in.CreateEmptyTempFile() + err = t.in.CreateBufferedOrTempWriter() AssertEq(nil, err) // Write some content to temp file. t.clock.AdvanceTime(time.Second) @@ -447,7 +461,7 @@ func (t *FileTest) SyncEmptyLocalFile() { t.createInodeWithLocalParam("test", true) creationTime := t.clock.Now() // Create a temp file for the local inode created above. - err = t.in.CreateEmptyTempFile() + err = t.in.CreateBufferedOrTempWriter() AssertEq(nil, err) // Sync. @@ -617,7 +631,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes var attrs fuseops.InodeAttributes // Create a local file inode. t.createInodeWithLocalParam("test", true) - err = t.in.CreateEmptyTempFile() + err = t.in.CreateBufferedOrTempWriter() AssertEq(nil, err) // Fetch the attributes and check if the file is empty. attrs, err = t.in.Attributes(t.ctx) @@ -643,7 +657,7 @@ func (t *FileTest) TestTruncateDownwardForLocalFileShouldUpdateLocalFileAttribut var attrs fuseops.InodeAttributes // Create a local file inode. t.createInodeWithLocalParam("test", true) - err = t.in.CreateEmptyTempFile() + err = t.in.CreateBufferedOrTempWriter() AssertEq(nil, err) // Write some data to the local file. err = t.in.Write(t.ctx, []byte("burrito"), 0) @@ -876,7 +890,7 @@ func (t *FileTest) TestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() { // Create a local file inode. t.createInodeWithLocalParam("test", true) createTime := t.in.mtimeClock.Now() - err = t.in.CreateEmptyTempFile() + err = t.in.CreateBufferedOrTempWriter() AssertEq(nil, err) // Validate the attributes on an empty file. attrs, err = t.in.Attributes(t.ctx) @@ -901,6 +915,28 @@ func (t *FileTest) TestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() { AssertEq("gcs.NotFoundError: object test not found", err.Error()) } +func (t *FileTest) TestSetMtimeForLocalFileWhenStreamingWritesAreEnabled() { + var err error + var attrs fuseops.InodeAttributes + // Create a local file inode. + t.createInodeWithLocalParam("test", true) + t.in.writeConfig = getWriteConfig() + err = t.in.CreateBufferedOrTempWriter() + AssertEq(nil, err) + + // Set mtime. + mtime := time.Now().UTC().Add(123 * time.Second) + err = t.in.SetMtime(t.ctx, mtime) + + AssertEq(nil, err) + // The inode should agree about the new mtime. + attrs, err = t.in.Attributes(t.ctx) + AssertEq(nil, err) + ExpectThat(attrs.Mtime, timeutil.TimeEq(mtime)) + ExpectThat(attrs.Ctime, timeutil.TimeEq(mtime)) + ExpectThat(attrs.Atime, timeutil.TimeEq(mtime)) +} + func (t *FileTest) ContentEncodingGzip() { // Set up an explicit content-encoding on the backing object and re-create the inode. contentEncoding := "gzip" @@ -940,11 +976,37 @@ func (t *FileTest) TestCheckInvariantsShouldNotThrowExceptionForLocalFiles() { AssertNe(nil, t.in) } -func (t *FileTest) TestCreateEmptyTempFileShouldCreateEmptyFile() { - err := t.in.CreateEmptyTempFile() +func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateEmptyFile() { + err := t.in.CreateBufferedOrTempWriter() + + AssertEq(nil, err) + AssertNe(nil, t.in.content) + // Validate that file size is 0. + sr, err := t.in.content.Stat() + AssertEq(nil, err) + AssertEq(0, sr.Size) +} + +func (t *FileTest) TestCreateBufferedOrTempWriterShouldNotCreateFileWhenStreamingWritesAreEnabled() { + t.createInodeWithLocalParam("test", true) + t.in.writeConfig = getWriteConfig() + + err := t.in.CreateBufferedOrTempWriter() + + AssertEq(nil, err) + AssertEq(nil, t.in.content) + AssertNe(nil, t.in.bwh) +} + +func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateFileForNonLocalFilesForStreamingWrites() { + // Enabling buffered writes. + t.in.writeConfig = getWriteConfig() + + err := t.in.CreateBufferedOrTempWriter() AssertEq(nil, err) AssertNe(nil, t.in.content) + AssertEq(nil, t.in.bwh) // Validate that file size is 0. sr, err := t.in.content.Stat() AssertEq(nil, err) @@ -956,7 +1018,7 @@ func (t *FileTest) UnlinkLocalFile() { // Create a local file inode. t.createInodeWithLocalParam("test", true) // Create a temp file for the local inode created above. - err = t.in.CreateEmptyTempFile() + err = t.in.CreateBufferedOrTempWriter() AssertEq(nil, err) // Unlink. @@ -1016,6 +1078,7 @@ func (t *FileTest) WriteToLocalFileWhenStreamingWritesAreEnabled() { func (t *FileTest) MultipleWritesToLocalFileWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) + createTime := t.in.mtimeClock.Now() t.in.writeConfig = getWriteConfig() AssertEq(nil, t.in.bwh) @@ -1027,6 +1090,66 @@ func (t *FileTest) MultipleWritesToLocalFileWhenStreamingWritesAreEnabled() { err = t.in.Write(t.ctx, []byte("hello"), 2) AssertEq(nil, err) AssertEq(7, t.in.bwh.WriteFileInfo().TotalSize) + // The inode should agree about the new mtime. + attrs, err := t.in.Attributes(t.ctx) + AssertEq(nil, err) + AssertEq(7, attrs.Size) + ExpectThat(attrs.Mtime, timeutil.TimeNear(createTime, Delta)) +} + +func (t *FileTest) WriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { + t.createInodeWithEmptyObject() + t.in.writeConfig = getWriteConfig() + createTime := t.in.mtimeClock.Now() + AssertEq(nil, t.in.bwh) + + err := t.in.Write(t.ctx, []byte("hi"), 0) + + AssertEq(nil, err) + AssertNe(nil, t.in.bwh) + writeFileInfo := t.in.bwh.WriteFileInfo() + AssertEq(2, writeFileInfo.TotalSize) + // The inode should agree about the new mtime. + attrs, err := t.in.Attributes(t.ctx) + AssertEq(nil, err) + AssertEq(2, attrs.Size) + ExpectThat(attrs.Mtime, timeutil.TimeNear(createTime, Delta)) +} + +func (t *FileTest) SetMtimeOnEmptyGCSFileWhenStreamingWritesAreEnabled() { + t.createInodeWithEmptyObject() + t.in.writeConfig = getWriteConfig() + AssertEq(nil, t.in.bwh) + + // This test checks if the mtime is updated to GCS. Since test framework + // doesn't support t.run, calling the test method here directly. + t.SetMtime_ContentNotFaultedIn() + // bufferedWritesHandler shouldn't get initialized. + AssertEq(nil, t.in.bwh) +} + +func (t *FileTest) SetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEnabled() { + t.createInodeWithEmptyObject() + t.in.writeConfig = getWriteConfig() + AssertEq(nil, t.in.bwh) + // Initiate write call. + err := t.in.Write(t.ctx, []byte("hi"), 0) + AssertEq(nil, err) + AssertNe(nil, t.in.bwh) + writeFileInfo := t.in.bwh.WriteFileInfo() + AssertEq(2, writeFileInfo.TotalSize) + + // Set mtime. + mtime := time.Now().UTC().Add(123 * time.Second) + err = t.in.SetMtime(t.ctx, mtime) + + AssertEq(nil, err) + // The inode should agree about the new mtime. + attrs, err := t.in.Attributes(t.ctx) + AssertEq(nil, err) + ExpectThat(attrs.Mtime, timeutil.TimeEq(mtime)) + ExpectThat(attrs.Ctime, timeutil.TimeEq(mtime)) + ExpectThat(attrs.Atime, timeutil.TimeEq(mtime)) } func getWriteConfig() *cfg.WriteConfig { From 53134d9587604debd856ee9c908eb56bd2b031b8 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:48:20 +0530 Subject: [PATCH 0023/1298] [testing-on-gke] fixes and improvements (#2742) * Don't log csi_src_dir if non-existent This is specifically in the helpful only_parse=true command, where csi_src_dir is always passed irrespective of whether it exists or not. * Fail gke test pods if any command fails Also log all the commands run in the gke pods. * generate dlio training data if not already present * [testing-on-gke] gsutil->gcloud in pod Yamls --- .../data-loader/templates/dlio-data-loader.yaml | 9 +++++++-- .../templates/dlio-tester.yaml | 11 +++++++++-- .../data-loader/templates/fio-data-loader.yaml | 13 +++++++++---- .../fio/loading-test/templates/fio-tester.yaml | 15 ++++++++++----- .../testing_on_gke/examples/run-gke-tests.sh | 6 +++++- 5 files changed, 40 insertions(+), 14 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/data-loader/templates/dlio-data-loader.yaml b/perfmetrics/scripts/testing_on_gke/examples/dlio/data-loader/templates/dlio-data-loader.yaml index cc2ab15069..5703ee4c52 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/data-loader/templates/dlio-data-loader.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/data-loader/templates/dlio-data-loader.yaml @@ -40,7 +40,12 @@ spec: - "/bin/sh" - "-c" - | - echo "Installing gsutil..." + # Fail if any of the commands fails. + set -e + # Print out the individual commands run. + set -x + + echo "Installing gcloud ..." apt-get update && apt-get install -y apt-transport-https ca-certificates gnupg curl curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list @@ -57,7 +62,7 @@ spec: ++workload.dataset.record_length_stdev=0 \ ++workload.dataset.record_length_resize=0 - gsutil -m cp -R /data/train gs://{{ .Values.bucketName }} + gcloud storage cp -r /data/train gs://{{ .Values.bucketName }} mkdir -p /bucket/valid volumeMounts: - name: local-dir diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml index 75829fc93b..e68fc84006 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml @@ -44,13 +44,18 @@ spec: - "/bin/sh" - "-c" - | + # Fail if any of the commands fails. + set -e + # Print out the individual commands run. + set -x + # Change the source code of dlio benchmark so that page cache is cleared at every epoch main_file="dlio_benchmark/main.py" x=$(grep -n "for epoch in range(1, self.epochs + 1):" $main_file | cut -f1 -d ':') x=$((x + 1)) sed -i "${x} i \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ os.system(\"sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'\")" $main_file - echo "Installing gsutil..." + echo "Installing gcloud..." apt-get update && apt-get install -y apt-transport-https ca-certificates gnupg curl curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list @@ -74,9 +79,11 @@ spec: {{ end }} outputDir=/logs/{{ .Values.outputDirPrefix }} + mkdir -pv /data/train /data/valid echo "Testing {{ .Values.scenario }}" mpirun -np 8 dlio_benchmark workload=unet3d_a100 \ + ++workload.workflow.generate_data=True \ ++workload.train.epochs=4 \ ++workload.workflow.profiling=True \ ++workload.profiling.profiler=iostat \ @@ -93,7 +100,7 @@ spec: echo "{{ .Values.gcsfuse.mountOptions }}" > ${outputDir}/gcsfuse_mount_options {{ end }} - gsutil -m cp -R /logs/* gs://{{ .Values.bucketName }}/logs/ + gcloud storage cp -r /logs/* gs://{{ .Values.bucketName }}/logs/ volumeMounts: - name: dshm mountPath: /dev/shm diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/data-loader/templates/fio-data-loader.yaml b/perfmetrics/scripts/testing_on_gke/examples/fio/data-loader/templates/fio-data-loader.yaml index a48e84f6c6..b845d0c11b 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/data-loader/templates/fio-data-loader.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/data-loader/templates/fio-data-loader.yaml @@ -35,11 +35,16 @@ spec: - "/bin/sh" - "-c" - | + # Fail if any of the commands fails. + set -e + # Print out the individual commands run. + set -x + echo "Install dependencies..." apt-get update apt-get install -y libaio-dev gcc make git wget - - echo "Installing gsutil..." + + echo "Installing gcloud ..." apt-get update && apt-get install -y apt-transport-https ca-certificates gnupg curl curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list @@ -82,11 +87,11 @@ spec: {{ else }} wget -O $filename https://raw.githubusercontent.com/GoogleCloudPlatform/gcsfuse/master/perfmetrics/scripts/job_files/read_cache_load_test.fio {{ end }} - + NUMJOBS=50 NRFILES={{ .Values.fio.filesPerThread }} FILE_SIZE={{ .Values.fio.fileSize }} BLOCK_SIZE={{ .Values.fio.blockSize }} READ_TYPE=read DIR=/data fio ${filename} --alloc-size=1048576 echo "Uploading data to bucket {{ .Values.bucketName }}..." - gsutil -m cp -R /data/* gs://{{ .Values.bucketName }} + gcloud cp -r /data/* gs://{{ .Values.bucketName }} volumeMounts: - name: local-dir mountPath: /data diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml index 3cbc9e6c78..87597e4528 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml @@ -42,6 +42,11 @@ spec: - "/bin/sh" - "-c" - | + # Fail if any of the commands fails. + set -e + # Print out the individual commands run. + set -x + echo "Install dependencies..." apt-get update apt-get install -y libaio-dev gcc make git time wget @@ -52,13 +57,13 @@ spec: num_of_threads={{ .Values.fio.numThreads }} {{ if eq .Values.scenario "local-ssd" }} - echo "Installing gsutil..." + echo "Installing gcloud..." apt-get update && apt-get install -y apt-transport-https ca-certificates gnupg curl curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | tee -a /etc/apt/sources.list.d/google-cloud-sdk.list apt-get update && apt-get install -y google-cloud-cli - gsutil -m cp -R gs://{{ .Values.bucketName }}/* /data + gcloud storage cp -r gs://{{ .Values.bucketName }}/* /data echo "Sleeping 5 minutes to wait for Local SSD RAID to populate data." sleep 300 @@ -154,11 +159,11 @@ spec: sleep $pause_in_seconds done - + {{ if eq .Values.scenario "local-ssd" }} - gsutil -m cp -R /data/fio-output/* gs://{{ .Values.bucketName }}/fio-output/ + gcloud storage cp -r /data/fio-output/* gs://{{ .Values.bucketName }}/fio-output/ {{ end }} - + echo "fio job completed!" volumeMounts: - name: dshm diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index 2a86065b46..b5471a37fc 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -629,7 +629,11 @@ function waitTillAllPodsComplete() { else printf "\n${num_noncompleted_pods} pod(s) is/are still pending/running (time till timeout=${time_till_timeout} seconds). Will check again in "${pod_wait_time_in_seconds}" seconds. Sleeping for now.\n\n" printf "\nYou can take a break too if you want. Just kill this run and connect back to it later, for fetching and parsing outputs, using the following command: \n" - printf " only_parse=true instance_id=${instance_id} project_id=${project_id} project_number=${project_number} zone=${zone} machine_type=${machine_type} use_custom_csi_driver=${use_custom_csi_driver} gcsfuse_src_dir=\"${gcsfuse_src_dir}\" csi_src_dir=\"${csi_src_dir}\" pod_wait_time_in_seconds=${pod_wait_time_in_seconds} pod_timeout_in_seconds=${pod_timeout_in_seconds} workload_config=\"${workload_config}\" cluster_name=${cluster_name} output_dir=\"${output_dir}\" $0 \n" + printf " only_parse=true instance_id=${instance_id} project_id=${project_id} project_number=${project_number} zone=${zone} machine_type=${machine_type} use_custom_csi_driver=${use_custom_csi_driver} gcsfuse_src_dir=\"${gcsfuse_src_dir}\" " + if test -d "${csi_src_dir}"; then + printf "csi_src_dir=\"${csi_src_dir}\" " + fi + printf "pod_wait_time_in_seconds=${pod_wait_time_in_seconds} pod_timeout_in_seconds=${pod_timeout_in_seconds} workload_config=\"${workload_config}\" cluster_name=${cluster_name} output_dir=\"${output_dir}\" output_gsheet_id=\"${output_gsheet_id}\" output_gsheet_keyfile=\"${output_gsheet_keyfile}\" $0 \n" printf "\nbut remember that this will reset the start-timer for pod timeout.\n\n" printf "\nTo ssh to any specific pod, use the following command: \n" printf " gcloud container clusters get-credentials ${cluster_name} --location=${zone}\n" From e38120f3f269b272284e38e307bccd432dbbb8ca Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:23:48 +0530 Subject: [PATCH 0024/1298] upgrade gosdk version to v1.48.0 (#2747) * upgrade gosdk * trigger perf/integration tests * downgrading grpc version --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index fd6803c1bd..3976f516b1 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cloud.google.com/go/compute/metadata v0.5.2 cloud.google.com/go/iam v1.2.2 cloud.google.com/go/secretmanager v1.14.2 - cloud.google.com/go/storage v1.47.0 + cloud.google.com/go/storage v1.48.0 contrib.go.opencensus.io/exporter/ocagent v0.7.0 contrib.go.opencensus.io/exporter/prometheus v0.4.2 contrib.go.opencensus.io/exporter/stackdriver v0.13.14 @@ -44,8 +44,8 @@ require ( golang.org/x/sys v0.27.0 golang.org/x/text v0.20.0 golang.org/x/time v0.8.0 - google.golang.org/api v0.209.0 - google.golang.org/grpc v1.68.0 + google.golang.org/api v0.210.0 + google.golang.org/grpc v1.67.2 google.golang.org/protobuf v1.35.2 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 @@ -54,8 +54,8 @@ require ( require ( cel.dev/expr v0.16.1 // indirect cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.10.2 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.5 // indirect + cloud.google.com/go/auth v0.11.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect cloud.google.com/go/longrunning v0.6.2 // indirect cloud.google.com/go/monitoring v1.21.2 // indirect cloud.google.com/go/pubsub v1.45.1 // indirect @@ -113,9 +113,9 @@ require ( go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.29.0 // indirect golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 // indirect - google.golang.org/genproto v0.0.0-20241113202542-65e8d215514f // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f // indirect + google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 723579c8b4..8199d91fa4 100644 --- a/go.sum +++ b/go.sum @@ -33,10 +33,10 @@ cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2Z cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.10.2 h1:oKF7rgBfSHdp/kuhXtqU/tNDr0mZqhYbEh+6SiqzkKo= -cloud.google.com/go/auth v0.10.2/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= -cloud.google.com/go/auth/oauth2adapt v0.2.5 h1:2p29+dePqsCHPP1bqDJcKj4qxRyYCcbzKpFyKGt3MTk= -cloud.google.com/go/auth/oauth2adapt v0.2.5/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/auth v0.11.0 h1:Ic5SZz2lsvbYcWT5dfjNWgw6tTlGi2Wc8hyQSC9BstA= +cloud.google.com/go/auth v0.11.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -74,8 +74,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.47.0 h1:ajqgt30fnOMmLfWfu1PWcb+V9Dxz6n+9WKjdNg5R4HM= -cloud.google.com/go/storage v1.47.0/go.mod h1:Ks0vP374w0PW6jOUameJbapbQKXqkjGd/OJRp2fb9IQ= +cloud.google.com/go/storage v1.48.0 h1:FhBDHACbVtdPx7S/AbcKujPWiHvfO6F8OXGgCEbB2+o= +cloud.google.com/go/storage v1.48.0/go.mod h1:aFoDYNMAjv67lp+xcuZqjUKv/ctmplzQ3wJgodA7b+M= cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= contrib.go.opencensus.io/exporter/ocagent v0.7.0 h1:BEfdCTXfMV30tLZD8c9n64V/tIZX5+9sXiuFLnrr1k8= @@ -1744,8 +1744,8 @@ google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQ google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.209.0 h1:Ja2OXNlyRlWCWu8o+GgI4yUn/wz9h/5ZfFbKz+dQX+w= -google.golang.org/api v0.209.0/go.mod h1:I53S168Yr/PNDNMi5yPnDc0/LGRZO6o7PoEbl/HY3CM= +google.golang.org/api v0.210.0 h1:HMNffZ57OoZCRYSbdWVRoqOa8V8NIHLL0CzdBPLztWk= +google.golang.org/api v0.210.0/go.mod h1:B9XDZGnx2NtyjzVkOVTGrFSAVZgPcbedzKg/gTLwqBs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1827,12 +1827,12 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2 google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20241113202542-65e8d215514f h1:zDoHYmMzMacIdjNe+P2XiTmPsLawi/pCbSPfxt6lTfw= -google.golang.org/genproto v0.0.0-20241113202542-65e8d215514f/go.mod h1:Q5m6g8b5KaFFzsQFIGdJkSJDGeJiybVenoYFMMa3ohI= -google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= -google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f h1:C1QccEa9kUwvMgEUORqQD9S17QesQijxjZ84sO82mfo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241113202542-65e8d215514f/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f h1:M65LEviCfuZTfrfzwwEoxVtgvfkFkBUbFnRbxCXuXhU= +google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1867,8 +1867,8 @@ google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= -google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/grpc v1.67.2 h1:Lq11HW1nr5m4OYV+ZVy2BjOK78/zqnTx24vyDBP1JcQ= +google.golang.org/grpc v1.67.2/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a h1:UIpYSuWdWHSzjwcAFRLjKcPXFZVVLXGEM23W+NWqipw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a/go.mod h1:9i1T9n4ZinTUZGgzENMi8MDDgbGC5mqTS75JAv6xN3A= From c7a6f74372107f8bc95b76d1d42cb268e07628bf Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 9 Dec 2024 10:28:25 +0530 Subject: [PATCH 0025/1298] [testing-on-gke] Add ports, activeDeadlineSeconds on fio/dlio workloads (#2750) * Ensures that each pod runs on a unique node. This is so that the performance doesn't get affected due to multiple pods running on the same node. * Specify timeout so that a pod doesn't keep running indefinitely. --- .../dlio/unet3d-loading-test/templates/dlio-tester.yaml | 4 ++++ .../examples/fio/loading-test/templates/fio-tester.yaml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml index e68fc84006..8a8258eded 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml @@ -24,12 +24,16 @@ metadata: {{- end }} spec: restartPolicy: Never + activeDeadlineSeconds: 5000 nodeSelector: cloud.google.com/gke-ephemeral-storage-local-ssd: "true" node.kubernetes.io/instance-type: {{ .Values.nodeType }} containers: - name: dlio-tester image: {{ .Values.image }} + ports: + - containerPort: 11021 + hostPort: 11021 resources: limits: cpu: {{ .Values.resourceLimits.cpu }} diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml index 87597e4528..f536788be4 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml @@ -23,12 +23,16 @@ metadata: {{- end }} spec: restartPolicy: Never + activeDeadlineSeconds: 5000 nodeSelector: cloud.google.com/gke-ephemeral-storage-local-ssd: "true" node.kubernetes.io/instance-type: {{ .Values.nodeType }} containers: - name: fio-tester image: {{ .Values.image }} + ports: + - containerPort: 11021 + hostPort: 11021 securityContext: # for cache dropping in the benchmarking tests. privileged: true resources: From 03dcf7ab07d56cd4a82df2324bb78c3482b84701 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:26:16 +0530 Subject: [PATCH 0026/1298] logs are printed only in case of failure so removed conditional logs (#2754) --- .../integration_tests/list_large_dir/testdata/delete_objects.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/list_large_dir/testdata/delete_objects.sh b/tools/integration_tests/list_large_dir/testdata/delete_objects.sh index 4d79e99cae..6c8d3bd18d 100755 --- a/tools/integration_tests/list_large_dir/testdata/delete_objects.sh +++ b/tools/integration_tests/list_large_dir/testdata/delete_objects.sh @@ -13,7 +13,7 @@ # limitations under the License. set -e # Here $1 refers to the testBucket argument -gcloud storage rm -r gs://$1/** +gcloud storage rm -r --log-http --verbosity=debug gs://$1/** # If bucket is empty it will throw an CommandException. if [ $? -eq 1 ]; then From 78c886285a5f4b01dee82b4acc824a288a6bf7cd Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 10 Dec 2024 09:54:40 +0530 Subject: [PATCH 0027/1298] [Emulator tests] Adding bash script run emulator based tests (#2752) * separating list and stat request * separating list and stat request * add unit tests * add unit tests * update unit tests * rebase * add unit tests * linux test fix * rebasing * rebase * add vendor dir * test changes * test changes * update poc according to comments * remove print * remove print * rebase * rebase * rebase * rebase * lint fix * lint fix * undo unnecessary changes * undo unnecessary changes * small fix * add bash script and readme * add bash script and readme * small fix * remove sponge file * update readme * update readme * update readme * update readme * minor fixes * minor fixes * minor fixes * remove unnecessary changes * remove unnecessary changes * fix review comments * remove unnecessary changes * remove unnecessary changes * remove ignore integration test command * remove ignore integration test command --- .../emulator_tests/README.md | 29 +++++++ .../emulator_tests/emulator_tests.sh | 76 +++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 tools/integration_tests/emulator_tests/README.md create mode 100644 tools/integration_tests/emulator_tests/emulator_tests.sh diff --git a/tools/integration_tests/emulator_tests/README.md b/tools/integration_tests/emulator_tests/README.md new file mode 100644 index 0000000000..9eddd7a775 --- /dev/null +++ b/tools/integration_tests/emulator_tests/README.md @@ -0,0 +1,29 @@ +# Emulator based tests +## go-proxy server +Proxy server, which intercepts [storage-testbench](https://github.com/googleapis/storage-testbench) server and perform pre-defined +retry test. + +### Steps to run the test manually +1. Run storage-testbench server by following [this](https://github.com/googleapis/storage-testbench/tree/main?tab=readme-ov-file#initial-set-up) steps. +2. Create test-bucket on server with below command. +``` +cat << EOF > test.json +{"name":"test-bucket"} +EOF + +# Execute the curl command to create bucket on storagetestbench server. +curl -X POST --data-binary @test.json \ + -H "Content-Type: application/json" \ + "$STORAGE_EMULATOR_HOST/storage/v1/b?project=test-project" +rm test.json +``` +3. Run tests with the current directory as emulator_tests. +``` +go test --integrationTest -v --testbucket=test-bucket -timeout 10m +``` + +### Automated emulator test script +1. Run ./emulator_tests.sh + +### Steps to add new tests in the future: +TODO: Will add in next PR diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh new file mode 100644 index 0000000000..8d80ca3c69 --- /dev/null +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Fail on any error +set -eo pipefail + +# Display commands being run +set -x + +# Only run on Go 1.17+ +min_minor_ver=17 + +v=`go version | { read _ _ v _; echo ${v#go}; }` +comps=(${v//./ }) +minor_ver=${comps[1]} + +if [ "$minor_ver" -lt "$min_minor_ver" ]; then + echo minor version $minor_ver, skipping + exit 0 +fi + +export STORAGE_EMULATOR_HOST="http://localhost:9000" + +DEFAULT_IMAGE_NAME='gcr.io/cloud-devrel-public-resources/storage-testbench' +DEFAULT_IMAGE_TAG='latest' +DOCKER_IMAGE=${DEFAULT_IMAGE_NAME}:${DEFAULT_IMAGE_TAG} +CONTAINER_NAME=storage_testbench + +# Note: --net=host makes the container bind directly to the Docker host’s network, +# with no network isolation. If we were to use port-mapping instead, reset connection errors +# would be captured differently and cause unexpected test behaviour. +# The host networking driver works only on Linux hosts. +# See more about using host networking: https://docs.docker.com/network/host/ +DOCKER_NETWORK="--net=host" + +# Get the docker image for the testbench +docker pull $DOCKER_IMAGE + +# Start the testbench +docker run --name $CONTAINER_NAME --rm -d $DOCKER_NETWORK $DOCKER_IMAGE +echo "Running the Cloud Storage testbench: $STORAGE_EMULATOR_HOST" +sleep 5 + +# Stop the testbench & cleanup environment variables +function cleanup() { + echo "Cleanup testbench" + docker stop $CONTAINER_NAME + unset STORAGE_EMULATOR_HOST; +} +trap cleanup EXIT + +# Create the JSON file to create bucket +cat << EOF > test.json +{"name":"test-bucket"} +EOF + +# Execute the curl command to create bucket on storagetestbench server. +curl -X POST --data-binary @test.json \ + -H "Content-Type: application/json" \ + "$STORAGE_EMULATOR_HOST/storage/v1/b?project=test-project" +rm test.json + +# Run specific test suite +go test --integrationTest -v --testbucket=test-bucket -timeout 10m From 435fd9bb9dfdba87c18120bfd615ea7d9a857114 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Tue, 10 Dec 2024 04:56:41 +0000 Subject: [PATCH 0028/1298] changing test framework of file_test from jacobsa/ogletest to stretchr (#2765) * Changing test framework # Conflicts: # internal/fs/inode/file_test.go * fixing nil assert * not nil fix --- internal/fs/inode/file_test.go | 621 ++++++++++++++++----------------- 1 file changed, 308 insertions(+), 313 deletions(-) diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 2df8af1fa7..f6975372a3 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -31,18 +31,17 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/jacobsa/syncutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "golang.org/x/net/context" "golang.org/x/sync/semaphore" "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" "github.com/jacobsa/fuse/fuseops" - . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" ) -func TestFile(t *testing.T) { RunTests(t) } - //////////////////////////////////////////////////////////////////////// // Boilerplate //////////////////////////////////////////////////////////////////////// @@ -56,6 +55,7 @@ const fileMode os.FileMode = 0641 const Delta = 30 * time.Minute type FileTest struct { + suite.Suite ctx context.Context bucket gcs.Bucket clock timeutil.SimulatedClock @@ -66,15 +66,14 @@ type FileTest struct { in *FileInode } -var _ SetUpInterface = &FileTest{} -var _ TearDownInterface = &FileTest{} - -func init() { RegisterTestSuite(&FileTest{}) } +func TestFileTestSuite(t *testing.T) { + suite.Run(t, new(FileTest)) +} -func (t *FileTest) SetUp(ti *TestInfo) { +func (t *FileTest) SetupTest() { // Enabling invariant check for all tests. syncutil.EnableInvariantChecking() - t.ctx = ti.Ctx + t.ctx = context.Background() t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)) t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical) @@ -89,13 +88,13 @@ func (t *FileTest) SetUp(ti *TestInfo) { []byte(t.initialContents)) t.backingObj = storageutil.ConvertObjToMinObject(object) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Create the inode. t.createInode() } -func (t *FileTest) TearDown() { +func (t *FileTest) TearDownTest() { t.in.Unlock() } @@ -107,7 +106,7 @@ func (t *FileTest) createInodeWithEmptyObject() { []byte{}) t.backingObj = storageutil.ConvertObjToMinObject(object) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Create the inode. t.createInode() @@ -117,10 +116,6 @@ func (t *FileTest) createInode() { t.createInodeWithLocalParam(fileName, false) } func (t *FileTest) createInodeWithLocalParam(fileName string, local bool) { - if t.in != nil { - t.in.Unlock() - } - name := NewFileName( NewRootName(""), fileName, @@ -157,35 +152,35 @@ func (t *FileTest) createInodeWithLocalParam(fileName string, local bool) { // Tests //////////////////////////////////////////////////////////////////////// -func (t *FileTest) ID() { - ExpectEq(fileInodeID, t.in.ID()) +func (t *FileTest) TestID() { + assert.Equal(t.T(), fileInodeID, int(t.in.ID())) } -func (t *FileTest) Name() { - ExpectEq(fileName, t.in.Name().GcsObjectName()) +func (t *FileTest) TestName() { + assert.Equal(t.T(), fileName, t.in.Name().GcsObjectName()) } -func (t *FileTest) InitialSourceGeneration() { +func (t *FileTest) TestInitialSourceGeneration() { sg := t.in.SourceGeneration() - ExpectEq(t.backingObj.Generation, sg.Object) - ExpectEq(t.backingObj.MetaGeneration, sg.Metadata) + assert.Equal(t.T(), t.backingObj.Generation, sg.Object) + assert.Equal(t.T(), t.backingObj.MetaGeneration, sg.Metadata) } -func (t *FileTest) InitialAttributes() { +func (t *FileTest) TestInitialAttributes() { attrs, err := t.in.Attributes(t.ctx) - AssertEq(nil, err) - - ExpectEq(len(t.initialContents), attrs.Size) - ExpectEq(1, attrs.Nlink) - ExpectEq(uid, attrs.Uid) - ExpectEq(gid, attrs.Gid) - ExpectEq(fileMode, attrs.Mode) - ExpectThat(attrs.Atime, timeutil.TimeEq(t.backingObj.Updated)) - ExpectThat(attrs.Ctime, timeutil.TimeEq(t.backingObj.Updated)) - ExpectThat(attrs.Mtime, timeutil.TimeEq(t.backingObj.Updated)) + assert.Nil(t.T(), err) + + assert.Equal(t.T(), uint64(len(t.initialContents)), attrs.Size) + assert.Equal(t.T(), uint32(1), attrs.Nlink) + assert.Equal(t.T(), uint32(uid), attrs.Uid) + assert.Equal(t.T(), uint32(gid), attrs.Gid) + assert.Equal(t.T(), fileMode, attrs.Mode) + assert.Equal(t.T(), attrs.Atime, t.backingObj.Updated) + assert.Equal(t.T(), attrs.Ctime, t.backingObj.Updated) + assert.Equal(t.T(), attrs.Mtime, t.backingObj.Updated) } -func (t *FileTest) InitialAttributes_MtimeFromObjectMetadata_Gcsfuse() { +func (t *FileTest) TestInitialAttributes_MtimeFromObjectMetadata_Gcsfuse() { // Set up an explicit mtime on the backing object and re-create the inode. if t.backingObj.Metadata == nil { t.backingObj.Metadata = make(map[string]string) @@ -198,12 +193,12 @@ func (t *FileTest) InitialAttributes_MtimeFromObjectMetadata_Gcsfuse() { // Ask it for its attributes. attrs, err := t.in.Attributes(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) - ExpectThat(attrs.Mtime, timeutil.TimeEq(mtime)) + assert.Equal(t.T(), attrs.Mtime, mtime) } -func (t *FileTest) InitialAttributes_MtimeFromObjectMetadata_Gsutil() { +func (t *FileTest) TestInitialAttributes_MtimeFromObjectMetadata_Gsutil() { // Set up an explicit mtime on the backing object and re-create the inode. if t.backingObj.Metadata == nil { t.backingObj.Metadata = make(map[string]string) @@ -216,12 +211,12 @@ func (t *FileTest) InitialAttributes_MtimeFromObjectMetadata_Gsutil() { // Ask it for its attributes. attrs, err := t.in.Attributes(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) - ExpectThat(attrs.Mtime.UTC(), timeutil.TimeEq(mtime)) + assert.Equal(t.T(), attrs.Mtime.UTC(), mtime) } -func (t *FileTest) InitialAttributes_MtimeFromObjectMetadata_GcsfuseOutranksGsutil() { +func (t *FileTest) TestInitialAttributes_MtimeFromObjectMetadata_GcsfuseOutranksGsutil() { // Set up an explicit mtime on the backing object and re-create the inode. if t.backingObj.Metadata == nil { t.backingObj.Metadata = make(map[string]string) @@ -237,13 +232,13 @@ func (t *FileTest) InitialAttributes_MtimeFromObjectMetadata_GcsfuseOutranksGsut // Ask it for its attributes. attrs, err := t.in.Attributes(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) - ExpectThat(attrs.Mtime, timeutil.TimeEq(canonicalMtime)) + assert.Equal(t.T(), attrs.Mtime, canonicalMtime) } -func (t *FileTest) Read() { - AssertEq("taco", t.initialContents) +func (t *FileTest) TestRead() { + assert.Equal(t.T(), "taco", t.initialContents) // Make several reads, checking the expected contents. testCases := []struct { @@ -286,26 +281,26 @@ func (t *FileTest) Read() { err = nil } - AssertEq(nil, err, "%s", desc) - ExpectEq(tc.expected, string(data), "%s", desc) + assert.Nil(t.T(), err, "%s", desc) + assert.Equal(t.T(), tc.expected, string(data), "%s", desc) } } -func (t *FileTest) Write() { +func (t *FileTest) TestWrite() { var err error - AssertEq("taco", t.initialContents) + assert.Equal(t.T(), "taco", t.initialContents) // Overwite a byte. err = t.in.Write(t.ctx, []byte("p"), 0) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Add some data at the end. t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() err = t.in.Write(t.ctx, []byte("burrito"), 4) - AssertEq(nil, err) + assert.Nil(t.T(), err) t.clock.AdvanceTime(time.Second) @@ -317,29 +312,29 @@ func (t *FileTest) Write() { err = nil } - AssertEq(nil, err) - ExpectEq("pacoburrito", string(buf[:n])) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "pacoburrito", string(buf[:n])) // Check attributes. attrs, err := t.in.Attributes(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) - ExpectEq(len("pacoburrito"), attrs.Size) - ExpectThat(attrs.Mtime, timeutil.TimeEq(writeTime)) + assert.Equal(t.T(), uint64(len("pacoburrito")), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, writeTime) } -func (t *FileTest) Truncate() { +func (t *FileTest) TestTruncate() { var attrs fuseops.InodeAttributes var err error - AssertEq("taco", t.initialContents) + assert.Equal(t.T(), "taco", t.initialContents) // Truncate downward. t.clock.AdvanceTime(time.Second) truncateTime := t.clock.Now() err = t.in.Truncate(t.ctx, 2) - AssertEq(nil, err) + assert.Nil(t.T(), err) t.clock.AdvanceTime(time.Second) @@ -351,110 +346,110 @@ func (t *FileTest) Truncate() { err = nil } - AssertEq(nil, err) - ExpectEq("ta", string(buf[:n])) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "ta", string(buf[:n])) // Check attributes. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) - ExpectEq(len("ta"), attrs.Size) - ExpectThat(attrs.Mtime, timeutil.TimeEq(truncateTime)) + assert.Equal(t.T(), uint64(len("ta")), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, truncateTime) } -func (t *FileTest) WriteThenSync() { +func (t *FileTest) TestWriteThenSync() { var attrs fuseops.InodeAttributes var err error - AssertEq("taco", t.initialContents) + assert.Equal(t.T(), "taco", t.initialContents) // Overwite a byte. t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() err = t.in.Write(t.ctx, []byte("p"), 0) - AssertEq(nil, err) + assert.Nil(t.T(), err) t.clock.AdvanceTime(time.Second) // Sync. err = t.in.Sync(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The generation should have advanced. - ExpectLt(t.backingObj.Generation, t.in.SourceGeneration().Object) + assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq(t.in.SourceGeneration().Object, m.Generation) - ExpectEq(t.in.SourceGeneration().Metadata, m.MetaGeneration) - ExpectEq(len("paco"), m.Size) - ExpectEq( + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(len("paco")), m.Size) + assert.Equal(t.T(), writeTime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - AssertEq(nil, err) - ExpectEq("paco", string(contents)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "paco", string(contents)) // Check attributes. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) - ExpectEq(len("paco"), attrs.Size) - ExpectThat(attrs.Mtime, timeutil.TimeEq(writeTime.UTC())) + assert.Equal(t.T(), uint64(len("paco")), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) } -func (t *FileTest) WriteToLocalFileThenSync() { +func (t *FileTest) TestWriteToLocalFileThenSync() { var attrs fuseops.InodeAttributes var err error // Create a local file inode. t.createInodeWithLocalParam("test", true) // Create a temp file for the local inode created above. err = t.in.CreateBufferedOrTempWriter() - AssertEq(nil, err) + assert.Nil(t.T(), err) // Write some content to temp file. t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() err = t.in.Write(t.ctx, []byte("tacos"), 0) - AssertEq(nil, err) + assert.Nil(t.T(), err) t.clock.AdvanceTime(time.Second) // Sync. err = t.in.Sync(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Verify that fileInode is no more local - AssertFalse(t.in.IsLocal()) + assert.False(t.T(), t.in.IsLocal()) // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq(t.in.SourceGeneration().Object, m.Generation) - ExpectEq(t.in.SourceGeneration().Metadata, m.MetaGeneration) - ExpectEq(len("tacos"), m.Size) - ExpectEq( + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(len("tacos")), m.Size) + assert.Equal(t.T(), writeTime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - AssertEq(nil, err) - ExpectEq("tacos", string(contents)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "tacos", string(contents)) // Check attributes. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - ExpectEq(len("tacos"), attrs.Size) - ExpectThat(attrs.Mtime, timeutil.TimeEq(writeTime.UTC())) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(len("tacos")), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) } -func (t *FileTest) SyncEmptyLocalFile() { +func (t *FileTest) TestSyncEmptyLocalFile() { var attrs fuseops.InodeAttributes var err error // Create a local file inode. @@ -462,87 +457,87 @@ func (t *FileTest) SyncEmptyLocalFile() { creationTime := t.clock.Now() // Create a temp file for the local inode created above. err = t.in.CreateBufferedOrTempWriter() - AssertEq(nil, err) + assert.Nil(t.T(), err) // Sync. err = t.in.Sync(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Verify that fileInode is no more local - AssertFalse(t.in.IsLocal()) + assert.False(t.T(), t.in.IsLocal()) // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq(t.in.SourceGeneration().Object, m.Generation) - ExpectEq(t.in.SourceGeneration().Metadata, m.MetaGeneration) - ExpectEq(0, m.Size) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(0), m.Size) // Validate the mtime. mtimeInBucket, ok := m.Metadata["gcsfuse_mtime"] - AssertTrue(ok) + assert.True(t.T(), ok) mtime, _ := time.Parse(time.RFC3339Nano, mtimeInBucket) - ExpectThat(mtime, timeutil.TimeNear(creationTime, Delta)) + assert.WithinDuration(t.T(), mtime, creationTime, Delta) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - AssertEq(nil, err) - ExpectEq("", string(contents)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "", string(contents)) // Check attributes. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - ExpectEq(0, attrs.Size) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(0), attrs.Size) } -func (t *FileTest) AppendThenSync() { +func (t *FileTest) TestAppendThenSync() { var attrs fuseops.InodeAttributes var err error - AssertEq("taco", t.initialContents) + assert.Equal(t.T(), "taco", t.initialContents) // Append some data. t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() err = t.in.Write(t.ctx, []byte("burrito"), int64(len("taco"))) - AssertEq(nil, err) + assert.Nil(t.T(), err) t.clock.AdvanceTime(time.Second) // Sync. err = t.in.Sync(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The generation should have advanced. - ExpectLt(t.backingObj.Generation, t.in.SourceGeneration().Object) + assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq(t.in.SourceGeneration().Object, m.Generation) - ExpectEq(t.in.SourceGeneration().Metadata, m.MetaGeneration) - ExpectEq(len("tacoburrito"), m.Size) - ExpectEq( + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(len("tacoburrito")), m.Size) + assert.Equal(t.T(), writeTime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - AssertEq(nil, err) - ExpectEq("tacoburrito", string(contents)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "tacoburrito", string(contents)) // Check attributes. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) - ExpectEq(len("tacoburrito"), attrs.Size) - ExpectThat(attrs.Mtime, timeutil.TimeEq(writeTime.UTC())) + assert.Equal(t.T(), uint64(len("tacoburrito")), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) } -func (t *FileTest) TruncateDownwardThenSync() { +func (t *FileTest) TestTruncateDownwardThenSync() { var attrs fuseops.InodeAttributes var err error @@ -551,142 +546,142 @@ func (t *FileTest) TruncateDownwardThenSync() { truncateTime := t.clock.Now() err = t.in.Truncate(t.ctx, 2) - AssertEq(nil, err) + assert.Nil(t.T(), err) t.clock.AdvanceTime(time.Second) // Sync. err = t.in.Sync(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The generation should have advanced. - ExpectLt(t.backingObj.Generation, t.in.SourceGeneration().Object) + assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq(t.in.SourceGeneration().Object, m.Generation) - ExpectEq(t.in.SourceGeneration().Metadata, m.MetaGeneration) - ExpectEq(2, m.Size) - ExpectEq( + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(2), m.Size) + assert.Equal(t.T(), truncateTime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) // Check attributes. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) - ExpectEq(2, attrs.Size) - ExpectThat(attrs.Mtime, timeutil.TimeEq(truncateTime.UTC())) + assert.Equal(t.T(), uint64(2), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, truncateTime.UTC()) } -func (t *FileTest) TruncateUpwardThenSync() { +func (t *FileTest) TestTruncateUpwardThenSync() { var attrs fuseops.InodeAttributes var err error - AssertEq(4, len(t.initialContents)) + assert.Equal(t.T(), 4, len(t.initialContents)) // Truncate upward. t.clock.AdvanceTime(time.Second) truncateTime := t.clock.Now() err = t.in.Truncate(t.ctx, 6) - AssertEq(nil, err) + assert.Nil(t.T(), err) t.clock.AdvanceTime(time.Second) // Sync. err = t.in.Sync(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The generation should have advanced. - ExpectLt(t.backingObj.Generation, t.in.SourceGeneration().Object) + assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - ExpectEq( + assert.Equal(t.T(), truncateTime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq(t.in.SourceGeneration().Object, m.Generation) - ExpectEq(t.in.SourceGeneration().Metadata, m.MetaGeneration) - ExpectEq(6, m.Size) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(6), m.Size) // Check attributes. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) - ExpectEq(6, attrs.Size) - ExpectThat(attrs.Mtime, timeutil.TimeEq(truncateTime.UTC())) + assert.Equal(t.T(), uint64(6), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, truncateTime.UTC()) } -func (t *FileTest) TestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes() { +func (t *FileTest) TestTestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes() { var err error var attrs fuseops.InodeAttributes // Create a local file inode. t.createInodeWithLocalParam("test", true) err = t.in.CreateBufferedOrTempWriter() - AssertEq(nil, err) + assert.Nil(t.T(), err) // Fetch the attributes and check if the file is empty. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - AssertEq(0, attrs.Size) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(0), attrs.Size) err = t.in.Truncate(t.ctx, 6) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The inode should return the new size. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - AssertEq(6, attrs.Size) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(6), attrs.Size) // Data shouldn't be updated to GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} _, _, err = t.bucket.StatObject(t.ctx, statReq) - AssertNe(nil, err) - AssertEq("gcs.NotFoundError: object test not found", err.Error()) + assert.NotNil(t.T(), err) + assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) } -func (t *FileTest) TestTruncateDownwardForLocalFileShouldUpdateLocalFileAttributes() { +func (t *FileTest) TestTestTruncateDownwardForLocalFileShouldUpdateLocalFileAttributes() { var err error var attrs fuseops.InodeAttributes // Create a local file inode. t.createInodeWithLocalParam("test", true) err = t.in.CreateBufferedOrTempWriter() - AssertEq(nil, err) + assert.Nil(t.T(), err) // Write some data to the local file. err = t.in.Write(t.ctx, []byte("burrito"), 0) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Validate the new data is written correctly. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - AssertEq(7, attrs.Size) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(7), attrs.Size) err = t.in.Truncate(t.ctx, 2) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The inode should return the new size. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - AssertEq(2, attrs.Size) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(2), attrs.Size) // Data shouldn't be updated to GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} _, _, err = t.bucket.StatObject(t.ctx, statReq) - AssertNe(nil, err) - AssertEq("gcs.NotFoundError: object test not found", err.Error()) + assert.NotNil(t.T(), err) + assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) } -func (t *FileTest) Sync_Clobbered() { +func (t *FileTest) TestSync_Clobbered() { var err error // Truncate downward. err = t.in.Truncate(t.ctx, 2) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Clobber the backing object. newObj, err := storageutil.CreateObject( @@ -695,47 +690,47 @@ func (t *FileTest) Sync_Clobbered() { t.in.Name().GcsObjectName(), []byte("burrito")) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Sync. The call should not succeed, and we expect a FileClobberedError. err = t.in.Sync(t.ctx) // Check if the error is a FileClobberedError var fcErr *gcsfuse_errors.FileClobberedError - AssertTrue(errors.As(err, &fcErr), "expected FileClobberedError but got %v", err) - ExpectEq(t.backingObj.Generation, t.in.SourceGeneration().Object) - ExpectEq(t.backingObj.MetaGeneration, t.in.SourceGeneration().Metadata) + assert.True(t.T(), errors.As(err, &fcErr), "expected FileClobberedError but got %v", err) + assert.Equal(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) + assert.Equal(t.T(), t.backingObj.MetaGeneration, t.in.SourceGeneration().Metadata) // The object in the bucket should not have been changed. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq(newObj.Generation, m.Generation) - ExpectEq(newObj.Size, m.Size) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), newObj.Generation, m.Generation) + assert.Equal(t.T(), newObj.Size, m.Size) } func (t *FileTest) TestOpenReader_ThrowsFileClobberedError() { // Modify the file locally. err := t.in.Truncate(t.ctx, 2) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Clobber the backing object. _, err = storageutil.CreateObject( t.ctx, t.bucket, t.in.Name().GcsObjectName(), []byte("burrito")) - AssertEq(nil, err) + assert.Nil(t.T(), err) _, err = t.in.openReader(t.ctx) // assert error is not nil. var fcErr *gcsfuse_errors.FileClobberedError - AssertTrue(errors.As(err, &fcErr), "expected FileClobberedError but got %v", err) + assert.True(t.T(), errors.As(err, &fcErr), "expected FileClobberedError but got %v", err) } -func (t *FileTest) SetMtime_ContentNotFaultedIn() { +func (t *FileTest) TestSetMtime_ContentNotFaultedIn() { var err error var attrs fuseops.InodeAttributes @@ -743,92 +738,92 @@ func (t *FileTest) SetMtime_ContentNotFaultedIn() { mtime := time.Now().UTC().Add(123*time.Second).AddDate(0, 0, 0) err = t.in.SetMtime(t.ctx, mtime) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The inode should agree about the new mtime. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - ExpectThat(attrs.Mtime, timeutil.TimeEq(mtime)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), attrs.Mtime, mtime) // The inode should have added the mtime to the backing object's metadata. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq( + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), mtime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) } -func (t *FileTest) SetMtime_ContentClean() { +func (t *FileTest) TestSetMtime_ContentClean() { var err error var attrs fuseops.InodeAttributes // Cause the content to be faulted in. _, err = t.in.Read(t.ctx, make([]byte, 1), 0) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Set mtime. mtime := time.Now().UTC().Add(123*time.Second).AddDate(0, 0, 0) err = t.in.SetMtime(t.ctx, mtime) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The inode should agree about the new mtime. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - ExpectThat(attrs.Mtime, timeutil.TimeEq(mtime)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), attrs.Mtime, mtime) // The inode should have added the mtime to the backing object's metadata. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq( + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), mtime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) } -func (t *FileTest) SetMtime_ContentDirty() { +func (t *FileTest) TestSetMtime_ContentDirty() { var err error var attrs fuseops.InodeAttributes // Dirty the content. err = t.in.Write(t.ctx, []byte("a"), 0) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Set mtime. mtime := time.Now().UTC().Add(123 * time.Second) err = t.in.SetMtime(t.ctx, mtime) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The inode should agree about the new mtime. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - ExpectThat(attrs.Mtime, timeutil.TimeEq(mtime)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), attrs.Mtime, mtime) // Sync. err = t.in.Sync(t.ctx) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Now the object in the bucket should have the appropriate mtime. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq( + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), mtime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) } -func (t *FileTest) SetMtime_SourceObjectGenerationChanged() { +func (t *FileTest) TestSetMtime_SourceObjectGenerationChanged() { var err error // Clobber the backing object. @@ -838,24 +833,24 @@ func (t *FileTest) SetMtime_SourceObjectGenerationChanged() { t.in.Name().GcsObjectName(), []byte("burrito")) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Set mtime. mtime := time.Now().UTC().Add(123 * time.Second) err = t.in.SetMtime(t.ctx, mtime) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The object in the bucket should not have been changed. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq(newObj.Generation, m.Generation) - ExpectEq(0, len(m.Metadata)) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), newObj.Generation, m.Generation) + assert.Equal(t.T(), 0, len(m.Metadata)) } -func (t *FileTest) SetMtime_SourceObjectMetaGenerationChanged() { +func (t *FileTest) TestSetMtime_SourceObjectMetaGenerationChanged() { var err error // Update the backing object. @@ -867,52 +862,52 @@ func (t *FileTest) SetMtime_SourceObjectMetaGenerationChanged() { ContentLanguage: &lang, }) - AssertEq(nil, err) + assert.Nil(t.T(), err) // Set mtime. mtime := time.Now().UTC().Add(123 * time.Second) err = t.in.SetMtime(t.ctx, mtime) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The object in the bucket should not have been changed. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq(newObj.Generation, m.Generation) - ExpectEq(newObj.MetaGeneration, m.MetaGeneration) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), newObj.Generation, m.Generation) + assert.Equal(t.T(), newObj.MetaGeneration, m.MetaGeneration) } -func (t *FileTest) TestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() { +func (t *FileTest) TestTestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() { var err error var attrs fuseops.InodeAttributes // Create a local file inode. t.createInodeWithLocalParam("test", true) createTime := t.in.mtimeClock.Now() err = t.in.CreateBufferedOrTempWriter() - AssertEq(nil, err) + assert.Nil(t.T(), err) // Validate the attributes on an empty file. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - ExpectThat(attrs.Mtime, timeutil.TimeNear(createTime, Delta)) + assert.Nil(t.T(), err) + assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) // Set mtime. mtime := time.Now().UTC().Add(123 * time.Second) err = t.in.SetMtime(t.ctx, mtime) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The inode should agree about the new mtime. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - ExpectThat(attrs.Mtime, timeutil.TimeEq(mtime)) - ExpectThat(attrs.Ctime, timeutil.TimeEq(mtime)) - ExpectThat(attrs.Atime, timeutil.TimeEq(mtime)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), attrs.Mtime, mtime) + assert.Equal(t.T(), attrs.Ctime, mtime) + assert.Equal(t.T(), attrs.Atime, mtime) // Data shouldn't be updated to GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} _, _, err = t.bucket.StatObject(t.ctx, statReq) - AssertNe(nil, err) - AssertEq("gcs.NotFoundError: object test not found", err.Error()) + assert.NotNil(t.T(), err) + assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) } func (t *FileTest) TestSetMtimeForLocalFileWhenStreamingWritesAreEnabled() { @@ -922,69 +917,69 @@ func (t *FileTest) TestSetMtimeForLocalFileWhenStreamingWritesAreEnabled() { t.createInodeWithLocalParam("test", true) t.in.writeConfig = getWriteConfig() err = t.in.CreateBufferedOrTempWriter() - AssertEq(nil, err) + assert.Nil(t.T(), err) // Set mtime. mtime := time.Now().UTC().Add(123 * time.Second) err = t.in.SetMtime(t.ctx, mtime) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The inode should agree about the new mtime. attrs, err = t.in.Attributes(t.ctx) - AssertEq(nil, err) - ExpectThat(attrs.Mtime, timeutil.TimeEq(mtime)) - ExpectThat(attrs.Ctime, timeutil.TimeEq(mtime)) - ExpectThat(attrs.Atime, timeutil.TimeEq(mtime)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), attrs.Mtime, mtime) + assert.Equal(t.T(), attrs.Ctime, mtime) + assert.Equal(t.T(), attrs.Atime, mtime) } -func (t *FileTest) ContentEncodingGzip() { +func (t *FileTest) TestContentEncodingGzip() { // Set up an explicit content-encoding on the backing object and re-create the inode. contentEncoding := "gzip" t.backingObj.ContentEncoding = contentEncoding t.createInode() - AssertEq(contentEncoding, t.in.Source().ContentEncoding) - AssertTrue(t.in.Source().HasContentEncodingGzip()) + assert.Equal(t.T(), contentEncoding, t.in.Source().ContentEncoding) + assert.True(t.T(), t.in.Source().HasContentEncodingGzip()) } -func (t *FileTest) ContentEncodingNone() { +func (t *FileTest) TestContentEncodingNone() { // Set up an explicit content-encoding on the backing object and re-create the inode. contentEncoding := "" t.backingObj.ContentEncoding = contentEncoding t.createInode() - AssertEq(contentEncoding, t.in.Source().ContentEncoding) - AssertFalse(t.in.Source().HasContentEncodingGzip()) + assert.Equal(t.T(), contentEncoding, t.in.Source().ContentEncoding) + assert.False(t.T(), t.in.Source().HasContentEncodingGzip()) } -func (t *FileTest) ContentEncodingOther() { +func (t *FileTest) TestContentEncodingOther() { // Set up an explicit content-encoding on the backing object and re-create the inode. contentEncoding := "other" t.backingObj.ContentEncoding = contentEncoding t.createInode() - AssertEq(contentEncoding, t.in.Source().ContentEncoding) - AssertFalse(t.in.Source().HasContentEncodingGzip()) + assert.Equal(t.T(), contentEncoding, t.in.Source().ContentEncoding) + assert.False(t.T(), t.in.Source().HasContentEncodingGzip()) } -func (t *FileTest) TestCheckInvariantsShouldNotThrowExceptionForLocalFiles() { +func (t *FileTest) TestTestCheckInvariantsShouldNotThrowExceptionForLocalFiles() { t.createInodeWithLocalParam("test", true) - AssertNe(nil, t.in) + assert.NotNil(t.T(), t.in) } func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateEmptyFile() { err := t.in.CreateBufferedOrTempWriter() - AssertEq(nil, err) - AssertNe(nil, t.in.content) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.content) // Validate that file size is 0. sr, err := t.in.content.Stat() - AssertEq(nil, err) - AssertEq(0, sr.Size) + assert.Nil(t.T(), err) + assert.Equal(t.T(), int64(0), sr.Size) } func (t *FileTest) TestCreateBufferedOrTempWriterShouldNotCreateFileWhenStreamingWritesAreEnabled() { @@ -993,9 +988,9 @@ func (t *FileTest) TestCreateBufferedOrTempWriterShouldNotCreateFileWhenStreamin err := t.in.CreateBufferedOrTempWriter() - AssertEq(nil, err) - AssertEq(nil, t.in.content) - AssertNe(nil, t.in.bwh) + assert.Nil(t.T(), err) + assert.Nil(t.T(), t.in.content) + assert.NotNil(t.T(), t.in.bwh) } func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateFileForNonLocalFilesForStreamingWrites() { @@ -1004,152 +999,152 @@ func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateFileForNonLocalFile err := t.in.CreateBufferedOrTempWriter() - AssertEq(nil, err) - AssertNe(nil, t.in.content) - AssertEq(nil, t.in.bwh) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.content) + assert.Nil(t.T(), t.in.bwh) // Validate that file size is 0. sr, err := t.in.content.Stat() - AssertEq(nil, err) - AssertEq(0, sr.Size) + assert.Nil(t.T(), err) + assert.Equal(t.T(), int64(0), sr.Size) } -func (t *FileTest) UnlinkLocalFile() { +func (t *FileTest) TestUnlinkLocalFile() { var err error // Create a local file inode. t.createInodeWithLocalParam("test", true) // Create a temp file for the local inode created above. err = t.in.CreateBufferedOrTempWriter() - AssertEq(nil, err) + assert.Nil(t.T(), err) // Unlink. t.in.Unlink() // Verify that fileInode is now unlinked - AssertTrue(t.in.IsUnlinked()) + assert.True(t.T(), t.in.IsUnlinked()) // Data shouldn't be updated to GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} _, _, err = t.bucket.StatObject(t.ctx, statReq) - AssertNe(nil, err) - AssertEq("gcs.NotFoundError: object test not found", err.Error()) + assert.NotNil(t.T(), err) + assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) } -func (t *FileTest) ReadLocalFileWhenStreamingWritesAreEnabled() { +func (t *FileTest) TestReadLocalFileWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.writeConfig = getWriteConfig() err := t.in.Write(t.ctx, []byte("hi"), 0) - AssertEq(nil, err) - AssertEq(2, t.in.bwh.WriteFileInfo().TotalSize) + assert.Nil(t.T(), err) + assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) data := make([]byte, 10) n, err := t.in.Read(t.ctx, data, 0) - AssertEq(0, n) - AssertNe(nil, err) - AssertEq("cannot read a local file when upload in progress", err.Error()) + assert.Equal(t.T(), 0, n) + assert.NotNil(t.T(), err) + assert.Equal(t.T(), "cannot read a local file when upload in progress", err.Error()) } -func (t *FileTest) WriteToLocalFileWithInvalidConfigWhenStreamingWritesAreEnabled() { +func (t *FileTest) TestWriteToLocalFileWithInvalidConfigWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.writeConfig.ExperimentalEnableStreamingWrites = true - AssertEq(nil, t.in.bwh) + assert.Nil(t.T(), t.in.bwh) err := t.in.Write(t.ctx, []byte("hi"), 0) - AssertNe(nil, err) - AssertTrue(strings.Contains(err.Error(), "invalid configuration")) + assert.NotNil(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "invalid configuration")) } -func (t *FileTest) WriteToLocalFileWhenStreamingWritesAreEnabled() { +func (t *FileTest) TestWriteToLocalFileWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.writeConfig = getWriteConfig() - AssertEq(nil, t.in.bwh) + assert.Nil(t.T(), t.in.bwh) err := t.in.Write(t.ctx, []byte("hi"), 0) - AssertEq(nil, err) - AssertNe(nil, t.in.bwh) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.bwh) writeFileInfo := t.in.bwh.WriteFileInfo() - AssertEq(2, writeFileInfo.TotalSize) + assert.Equal(t.T(), int64(2), writeFileInfo.TotalSize) } -func (t *FileTest) MultipleWritesToLocalFileWhenStreamingWritesAreEnabled() { +func (t *FileTest) TestMultipleWritesToLocalFileWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) createTime := t.in.mtimeClock.Now() t.in.writeConfig = getWriteConfig() - AssertEq(nil, t.in.bwh) + assert.Nil(t.T(), t.in.bwh) err := t.in.Write(t.ctx, []byte("hi"), 0) - AssertEq(nil, err) - AssertNe(nil, t.in.bwh) - AssertEq(2, t.in.bwh.WriteFileInfo().TotalSize) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.bwh) + assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) err = t.in.Write(t.ctx, []byte("hello"), 2) - AssertEq(nil, err) - AssertEq(7, t.in.bwh.WriteFileInfo().TotalSize) + assert.Nil(t.T(), err) + assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) // The inode should agree about the new mtime. attrs, err := t.in.Attributes(t.ctx) - AssertEq(nil, err) - AssertEq(7, attrs.Size) - ExpectThat(attrs.Mtime, timeutil.TimeNear(createTime, Delta)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(7), attrs.Size) + assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) } func (t *FileTest) WriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() t.in.writeConfig = getWriteConfig() createTime := t.in.mtimeClock.Now() - AssertEq(nil, t.in.bwh) + assert.Nil(t.T(), t.in.bwh) err := t.in.Write(t.ctx, []byte("hi"), 0) - AssertEq(nil, err) - AssertNe(nil, t.in.bwh) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.bwh) writeFileInfo := t.in.bwh.WriteFileInfo() - AssertEq(2, writeFileInfo.TotalSize) + assert.Equal(t.T(), int64(2), writeFileInfo.TotalSize) // The inode should agree about the new mtime. attrs, err := t.in.Attributes(t.ctx) - AssertEq(nil, err) - AssertEq(2, attrs.Size) - ExpectThat(attrs.Mtime, timeutil.TimeNear(createTime, Delta)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), int64(2), attrs.Size) + assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) } func (t *FileTest) SetMtimeOnEmptyGCSFileWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() t.in.writeConfig = getWriteConfig() - AssertEq(nil, t.in.bwh) + assert.Nil(t.T(), t.in.bwh) // This test checks if the mtime is updated to GCS. Since test framework // doesn't support t.run, calling the test method here directly. - t.SetMtime_ContentNotFaultedIn() + t.TestSetMtime_ContentNotFaultedIn() // bufferedWritesHandler shouldn't get initialized. - AssertEq(nil, t.in.bwh) + assert.Nil(t.T(), t.in.bwh) } func (t *FileTest) SetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() t.in.writeConfig = getWriteConfig() - AssertEq(nil, t.in.bwh) + assert.Nil(t.T(), t.in.bwh) // Initiate write call. err := t.in.Write(t.ctx, []byte("hi"), 0) - AssertEq(nil, err) - AssertNe(nil, t.in.bwh) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.bwh) writeFileInfo := t.in.bwh.WriteFileInfo() - AssertEq(2, writeFileInfo.TotalSize) + assert.Equal(t.T(), 2, writeFileInfo.TotalSize) // Set mtime. mtime := time.Now().UTC().Add(123 * time.Second) err = t.in.SetMtime(t.ctx, mtime) - AssertEq(nil, err) + assert.Nil(t.T(), err) // The inode should agree about the new mtime. attrs, err := t.in.Attributes(t.ctx) - AssertEq(nil, err) - ExpectThat(attrs.Mtime, timeutil.TimeEq(mtime)) - ExpectThat(attrs.Ctime, timeutil.TimeEq(mtime)) - ExpectThat(attrs.Atime, timeutil.TimeEq(mtime)) + assert.Nil(t.T(), err) + assert.Equal(t.T(), attrs.Mtime, mtime) + assert.Equal(t.T(), attrs.Ctime, mtime) + assert.Equal(t.T(), attrs.Atime, mtime) } func getWriteConfig() *cfg.WriteConfig { From 90de602012c0c19e0df7f56dadcd0046855abf44 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 10 Dec 2024 12:04:04 +0530 Subject: [PATCH 0029/1298] throw error upload failure (#2768) --- internal/bufferedwrites/buffered_write_handler.go | 5 +++-- internal/bufferedwrites/buffered_write_handler_test.go | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 2035f805b4..c21b7d251c 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -49,6 +49,7 @@ type WriteFileInfo struct { } var ErrOutOfOrderWrite = errors.New("outOfOrder write detected") +var ErrUploadFailure = errors.New("error while uploading object to GCS") // NewBWHandler creates the bufferedWriteHandler struct. func NewBWHandler(objectName string, bucket gcs.Bucket, blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphore.Weighted) (bwh *BufferedWriteHandler, err error) { @@ -79,7 +80,7 @@ func (wh *BufferedWriteHandler) Write(data []byte, offset int64) (err error) { // Fail early if the uploadHandler has failed. select { case <-wh.uploadHandler.SignalUploadFailure(): - return fmt.Errorf("BufferedWriteHandler.Write(): error while uploading object to GCS") + return ErrUploadFailure default: break } @@ -127,7 +128,7 @@ func (wh *BufferedWriteHandler) Flush() (err error) { // Fail early if the uploadHandler has failed. select { case <-wh.uploadHandler.SignalUploadFailure(): - return fmt.Errorf("file cannot be finalized: error while uploading object to GCS") + return ErrUploadFailure default: break } diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 332d9d516b..7f61c665e8 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -147,7 +147,7 @@ func (testSuite *BufferedWriteTest) TestWrite_SignalUploadFailureInBetween() { err = testSuite.bwh.Write([]byte("hello"), 5) require.Error(testSuite.T(), err) - assert.ErrorContains(testSuite.T(), err, "BufferedWriteHandler.Write(): error while uploading object to GCS") + assert.Equal(testSuite.T(), err, ErrUploadFailure) } func (testSuite *BufferedWriteTest) TestFlushWithNonNilCurrentBlock() { @@ -183,5 +183,5 @@ func (testSuite *BufferedWriteTest) TestFlush_SignalUploadFailureDuringWrite() { err = testSuite.bwh.Flush() require.Error(testSuite.T(), err) - assert.ErrorContains(testSuite.T(), err, "file cannot be finalized: error while uploading object to GCS") + assert.Equal(testSuite.T(), err, ErrUploadFailure) } From 3ed911977844080581fb5ec803a162d91f8de572 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 10 Dec 2024 13:57:27 +0530 Subject: [PATCH 0030/1298] Move const MinimumAlignSizeForWriting from cache/util to cfg/constants. (#2771) * Move const MinimumAlignSizeForWriting from cacheutil to cfg constants. * renamed contant name * Used const CacheUtilMinimumAlignSizeForWriting which was previously unused * changed error formatting --- cfg/constants.go | 5 +++++ cfg/validate.go | 17 ++++++++--------- internal/cache/file/downloader/job.go | 2 +- internal/cache/util/util.go | 24 ++++++++++++------------ 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/cfg/constants.go b/cfg/constants.go index 7a1b2bed74..843d76d16b 100644 --- a/cfg/constants.go +++ b/cfg/constants.go @@ -82,4 +82,9 @@ const ( maxSupportedStatCacheMaxSizeMB = util.MaxMiBsInUint64 ) +// CacheUtilMinimumAlignSizeForWriting is the minimum buffer size used for memory-aligned +// writes, ensuring all writes are a multiple of this value to optimize for +// underlying storage. This may result in padding with null data if the content +// size is not a multiple of CacheUtilMinimumAlignSizeForWriting. +const CacheUtilMinimumAlignSizeForWriting = 4096 const ConfigFileFlagName = "config-file" diff --git a/cfg/validate.go b/cfg/validate.go index 360ef264e6..ffad35daac 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -15,10 +15,9 @@ package cfg import ( + "errors" "fmt" "math" - - cacheutil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" ) const ( @@ -46,20 +45,20 @@ func isValidURL(u string) error { func isValidFileCacheConfig(config *FileCacheConfig) error { if config.MaxSizeMb < -1 { - return fmt.Errorf(FileCacheMaxSizeMBInvalidValueError) + return errors.New(FileCacheMaxSizeMBInvalidValueError) } if config.MaxParallelDownloads < -1 { - return fmt.Errorf(MaxParallelDownloadsInvalidValueError) + return errors.New(MaxParallelDownloadsInvalidValueError) } if config.EnableParallelDownloads { if config.MaxParallelDownloads == 0 { - return fmt.Errorf("the value of max-parallel-downloads for file-cache must not be 0 when enable-parallel-downloads is true") + return errors.New(MaxParallelDownloadsCantBeZeroError) } - if config.WriteBufferSize < cacheutil.MinimumAlignSizeForWriting { - return fmt.Errorf("the value of write-buffer-size for file-cache can't be less than 4096") + if config.WriteBufferSize < CacheUtilMinimumAlignSizeForWriting { + return errors.New("the value of write-buffer-size for file-cache can't be less than 4096") } - if (config.WriteBufferSize % cacheutil.MinimumAlignSizeForWriting) != 0 { - return fmt.Errorf("the value of write-buffer-size for file-cache should be in multiple of 4096") + if (config.WriteBufferSize % CacheUtilMinimumAlignSizeForWriting) != 0 { + return errors.New("the value of write-buffer-size for file-cache should be in multiple of 4096") } } if config.ParallelDownloadsPerFile < 1 { diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index c3d050c1d9..be6224fd53 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -445,7 +445,7 @@ func (job *Job) downloadObjectAsync() { // Truncate as the parallel downloads can create file with size little higher // than the actual object size because writing with O_DIRECT happens in size - // multiple of cacheutil.MinimumAlignSizeForWriting. + // multiple of cfg.MinimumAlignSizeForWriting. err = cacheFile.Truncate(int64(job.object.Size)) if err != nil { err = fmt.Errorf("downloadObjectAsync: error while truncating cache file: %w", err) diff --git a/internal/cache/util/util.go b/internal/cache/util/util.go index f1784a8933..0b0cbe9e08 100644 --- a/internal/cache/util/util.go +++ b/internal/cache/util/util.go @@ -26,6 +26,7 @@ import ( "strings" "unsafe" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" "github.com/jacobsa/fuse/fsutil" ) @@ -42,13 +43,12 @@ const ( ) const ( - MiB = 1024 * 1024 - KiB = 1024 - DefaultFilePerm = os.FileMode(0600) - DefaultDirPerm = os.FileMode(0700) - FileCache = "gcsfuse-file-cache" - BufferSizeForCRC = 65536 - MinimumAlignSizeForWriting = 4096 + MiB = 1024 * 1024 + KiB = 1024 + DefaultFilePerm = os.FileMode(0600) + DefaultDirPerm = os.FileMode(0700) + FileCache = "gcsfuse-file-cache" + BufferSizeForCRC = 65536 ) // CreateFile creates file with given file spec i.e. permissions and returns @@ -221,13 +221,13 @@ func GetMemoryAlignedBuffer(bufferSize int64, alignSize int64) (buffer []byte, e // CopyUsingMemoryAlignedBuffer copies content from src reader to dst writer // by staging content into a memory aligned buffer of size bufferSize and -// aligned to multiple of MinimumAlignSizeForWriting. Note: The minimum write -// size is MinimumAlignSizeForWriting which means the total size of content -// written to dst writer is always in multiple of MinimumAlignSizeForWriting. -// If contentSize is lesser than MinimumAlignSizeForWriting then extra null data +// aligned to multiple of cfg.CacheUtilMinimumAlignSizeForWriting. Note: The minimum write +// size is cfg.CacheUtilMinimumAlignSizeForWriting which means the total size of content +// written to dst writer is always in multiple of cfg.CacheUtilMinimumAlignSizeForWriting. +// If contentSize is lesser than cfg.CacheUtilMinimumAlignSizeForWriting then extra null data // is written at the last. func CopyUsingMemoryAlignedBuffer(ctx context.Context, src io.Reader, dst io.Writer, contentSize, bufferSize int64) (n int64, err error) { - var alignSize int64 = MinimumAlignSizeForWriting + var alignSize int64 = cfg.CacheUtilMinimumAlignSizeForWriting if bufferSize < alignSize || ((bufferSize % alignSize) != 0) { return 0, fmt.Errorf("buffer size (%v) should be a multiple of %v", bufferSize, alignSize) } From 163b5ebde6b4f319b3d74043f0d3c8807f52fabf Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 2 Dec 2024 08:15:42 +0000 Subject: [PATCH 0031/1298] map storage.ErrObjectNotExist in NewReader method to gcs.NotFoundError --- internal/storage/bucket_handle.go | 6 +++--- internal/storage/bucket_handle_test.go | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index f59a7a2026..0493d36b48 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -90,8 +90,8 @@ func (bh *bucketHandle) BucketType() gcs.BucketType { } func (bh *bucketHandle) NewReader( - ctx context.Context, - req *gcs.ReadObjectRequest) (io.ReadCloser, error) { + ctx context.Context, + req *gcs.ReadObjectRequest) (io.ReadCloser, error) { // Initialising the starting offset and the length to be read by the reader. start := int64(0) length := int64(-1) @@ -159,7 +159,7 @@ func (bh *bucketHandle) DeleteObject(ctx context.Context, req *gcs.DeleteObjectR } func (bh *bucketHandle) StatObject(ctx context.Context, - req *gcs.StatObjectRequest) (m *gcs.MinObject, e *gcs.ExtendedObjectAttributes, err error) { + req *gcs.StatObjectRequest) (m *gcs.MinObject, e *gcs.ExtendedObjectAttributes, err error) { var attrs *storage.ObjectAttrs // Retrieving object attrs through Go Storage Client. attrs, err = bh.bucket.Object(req.Name).Attrs(ctx) diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 047ff07d56..5ba527ec56 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -1380,9 +1380,9 @@ func (testSuite *BucketHandleTest) TestComposeObjectMethodWithOneSrcObjectIsDstO func (testSuite *BucketHandleTest) TestBucketTypeForHierarchicalNameSpaceTrue() { testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). - Return(&controlpb.StorageLayout{ - HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, - }, nil) + Return(&controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + }, nil) testSuite.bucketHandle.BucketType() @@ -1391,9 +1391,9 @@ func (testSuite *BucketHandleTest) TestBucketTypeForHierarchicalNameSpaceTrue() func (testSuite *BucketHandleTest) TestBucketTypeForHierarchicalNameSpaceFalse() { testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). - Return(&controlpb.StorageLayout{ - HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: false}, - }, nil) + Return(&controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: false}, + }, nil) testSuite.bucketHandle.BucketType() From dac48f8108713f2cdfb4ea744842e727059d11a6 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 2 Dec 2024 08:19:51 +0000 Subject: [PATCH 0032/1298] fixed formatting --- internal/storage/bucket_handle.go | 6 +++--- internal/storage/bucket_handle_test.go | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 0493d36b48..f59a7a2026 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -90,8 +90,8 @@ func (bh *bucketHandle) BucketType() gcs.BucketType { } func (bh *bucketHandle) NewReader( - ctx context.Context, - req *gcs.ReadObjectRequest) (io.ReadCloser, error) { + ctx context.Context, + req *gcs.ReadObjectRequest) (io.ReadCloser, error) { // Initialising the starting offset and the length to be read by the reader. start := int64(0) length := int64(-1) @@ -159,7 +159,7 @@ func (bh *bucketHandle) DeleteObject(ctx context.Context, req *gcs.DeleteObjectR } func (bh *bucketHandle) StatObject(ctx context.Context, - req *gcs.StatObjectRequest) (m *gcs.MinObject, e *gcs.ExtendedObjectAttributes, err error) { + req *gcs.StatObjectRequest) (m *gcs.MinObject, e *gcs.ExtendedObjectAttributes, err error) { var attrs *storage.ObjectAttrs // Retrieving object attrs through Go Storage Client. attrs, err = bh.bucket.Object(req.Name).Attrs(ctx) diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 5ba527ec56..047ff07d56 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -1380,9 +1380,9 @@ func (testSuite *BucketHandleTest) TestComposeObjectMethodWithOneSrcObjectIsDstO func (testSuite *BucketHandleTest) TestBucketTypeForHierarchicalNameSpaceTrue() { testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). - Return(&controlpb.StorageLayout{ - HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, - }, nil) + Return(&controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + }, nil) testSuite.bucketHandle.BucketType() @@ -1391,9 +1391,9 @@ func (testSuite *BucketHandleTest) TestBucketTypeForHierarchicalNameSpaceTrue() func (testSuite *BucketHandleTest) TestBucketTypeForHierarchicalNameSpaceFalse() { testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). - Return(&controlpb.StorageLayout{ - HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: false}, - }, nil) + Return(&controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: false}, + }, nil) testSuite.bucketHandle.BucketType() From 4ddd062a089791a633d9da66edbdb2c5f0cb3e18 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 28 Nov 2024 15:55:00 +0000 Subject: [PATCH 0033/1298] Implemented code changes to give clobbered error while syncing object to prevent silent data loss --- internal/gcsx/random_reader.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 3a58d474d8..e78df24dcb 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -170,8 +170,8 @@ func (rr *randomReader) CheckInvariants() { // fileHandle to file in cache. So, we will get the correct data from fileHandle // because Linux does not delete a file until open fileHandle count for a file is zero. func (rr *randomReader) tryReadingFromFileCache(ctx context.Context, - p []byte, - offset int64) (n int, cacheHit bool, err error) { + p []byte, + offset int64) (n int, cacheHit bool, err error) { if rr.fileCacheHandler == nil { return @@ -263,9 +263,9 @@ func captureFileCacheMetrics(ctx context.Context, metricHandle common.MetricHand } func (rr *randomReader) ReadAt( - ctx context.Context, - p []byte, - offset int64) (n int, cacheHit bool, err error) { + ctx context.Context, + p []byte, + offset int64) (n int, cacheHit bool, err error) { if offset >= int64(rr.object.Size) { err = io.EOF @@ -409,8 +409,8 @@ func (rr *randomReader) Destroy() { // // REQUIRES: rr.reader != nil func (rr *randomReader) readFull( - ctx context.Context, - p []byte) (n int, err error) { + ctx context.Context, + p []byte) (n int, err error) { // Start a goroutine that will cancel the read operation we block on below if // the calling context is cancelled, but only if this method has not already // returned (to avoid souring the reader for the next read if this one is @@ -444,9 +444,9 @@ func (rr *randomReader) readFull( // a prefix. Irrespective of the size requested, we try to fetch more data // from GCS defined by sequentialReadSizeMb flag to serve future read requests. func (rr *randomReader) startRead( - ctx context.Context, - start int64, - size int64) (err error) { + ctx context.Context, + start int64, + size int64) (err error) { // Make sure start and size are legal. if start < 0 || uint64(start) > rr.object.Size || size < 0 { err = fmt.Errorf( From 1a8f09f27d1709f88dd5f0f11eea171d3babc3c1 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 28 Nov 2024 16:24:33 +0000 Subject: [PATCH 0034/1298] fixed formatting --- internal/gcsx/random_reader.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index e78df24dcb..3a58d474d8 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -170,8 +170,8 @@ func (rr *randomReader) CheckInvariants() { // fileHandle to file in cache. So, we will get the correct data from fileHandle // because Linux does not delete a file until open fileHandle count for a file is zero. func (rr *randomReader) tryReadingFromFileCache(ctx context.Context, - p []byte, - offset int64) (n int, cacheHit bool, err error) { + p []byte, + offset int64) (n int, cacheHit bool, err error) { if rr.fileCacheHandler == nil { return @@ -263,9 +263,9 @@ func captureFileCacheMetrics(ctx context.Context, metricHandle common.MetricHand } func (rr *randomReader) ReadAt( - ctx context.Context, - p []byte, - offset int64) (n int, cacheHit bool, err error) { + ctx context.Context, + p []byte, + offset int64) (n int, cacheHit bool, err error) { if offset >= int64(rr.object.Size) { err = io.EOF @@ -409,8 +409,8 @@ func (rr *randomReader) Destroy() { // // REQUIRES: rr.reader != nil func (rr *randomReader) readFull( - ctx context.Context, - p []byte) (n int, err error) { + ctx context.Context, + p []byte) (n int, err error) { // Start a goroutine that will cancel the read operation we block on below if // the calling context is cancelled, but only if this method has not already // returned (to avoid souring the reader for the next read if this one is @@ -444,9 +444,9 @@ func (rr *randomReader) readFull( // a prefix. Irrespective of the size requested, we try to fetch more data // from GCS defined by sequentialReadSizeMb flag to serve future read requests. func (rr *randomReader) startRead( - ctx context.Context, - start int64, - size int64) (err error) { + ctx context.Context, + start int64, + size int64) (err error) { // Make sure start and size are legal. if start < 0 || uint64(start) > rr.object.Size || size < 0 { err = fmt.Errorf( From 5f7d53288faf815c9b6bcb22a474babb739fd7df Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 2 Dec 2024 14:31:29 +0000 Subject: [PATCH 0035/1298] Added composite tests for Improve Write Failure Experience Project --- internal/fs/stale_file_handle_test.go | 649 ++++++++++++++++++++++++++ 1 file changed, 649 insertions(+) create mode 100644 internal/fs/stale_file_handle_test.go diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go new file mode 100644 index 0000000000..988334f871 --- /dev/null +++ b/internal/fs/stale_file_handle_test.go @@ -0,0 +1,649 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs_test + +import ( + "errors" + "os" + "path" + "strings" + "syscall" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + . "github.com/jacobsa/oglematchers" + . "github.com/jacobsa/ogletest" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type StaleHandleTest struct { + // fsTest has f1 *osFile and f2 *osFile which we will reuse here. + f3 *os.File + fsTest +} + +func init() { + RegisterTestSuite(&StaleHandleTest{}) +} + +func (t *StaleHandleTest) SetUpTestSuite() { + t.serverCfg.ImplicitDirectories = true + t.serverCfg.LocalFileCache = false + t.serverCfg.NewConfig = &cfg.Config{ + Write: cfg.WriteConfig{ + CreateEmptyFile: false, + }, + FileSystem: cfg.FileSystemConfig{ + PreconditionErrors: true, + }, + MetadataCache: cfg.MetadataCacheConfig{ + TtlSecs: 0, + }, + } + t.fsTest.SetUpTestSuite() +} + +func (t *StaleHandleTest) TearDown() { + // Close t.f3 in case of test failure. + if t.f3 != nil { + AssertEq(nil, t.f3.Close()) + t.f3 = nil + } + + // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. + t.fsTest.TearDown() +} + +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// + +func (t *StaleHandleTest) createLocalFile(fileName string) (filePath string, f *os.File) { + // Creating a file shouldn't create file on GCS. + filePath = path.Join(mntDir, fileName) + _, err = os.Stat(mntDir) + AssertEq(nil, err) + + f, err = os.Create(filePath) + + AssertEq(nil, err) + t.validateObjectNotFoundErr(fileName) + + return +} + +func (t *StaleHandleTest) validateObjectNotFoundErr(fileName string) { + var notFoundErr *gcs.NotFoundError + _, err := storageutil.ReadObject(ctx, bucket, fileName) + + ExpectTrue(errors.As(err, ¬FoundErr)) +} + +func (t *StaleHandleTest) validateNoFileOrDirError(filename string) { + _, err := os.Stat(path.Join(mntDir, filename)) + AssertNe(nil, err) + AssertTrue(strings.Contains(err.Error(), "no such file or directory")) +} + +func (t *StaleHandleTest) closeLocalFile(f **os.File) error { + err := (*f).Close() + *f = nil + return err +} + +func (t *StaleHandleTest) readDirectory(dirPath string) (entries []os.DirEntry) { + entries, err := os.ReadDir(dirPath) + AssertEq(nil, err) + return +} + +func (t *StaleHandleTest) verifyLocalFileEntry(entry os.DirEntry, fileName string, size int) { + AssertEq(false, entry.IsDir()) + AssertEq(fileName, entry.Name()) + + fileInfo, err := entry.Info() + AssertEq(nil, err) + AssertEq(size, fileInfo.Size()) +} + +func (t *StaleHandleTest) closeFileAndValidateObjectContents(f **os.File, fileName string, contents string) { + err := t.closeLocalFile(f) + AssertEq(nil, err) + t.validateObjectContents(fileName, contents) +} + +func (t *StaleHandleTest) validateObjectContents(fileName string, contents string) { + contentBytes, err := storageutil.ReadObject(ctx, bucket, fileName) + AssertEq(nil, err) + ExpectEq(contents, string(contentBytes)) +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *StaleHandleTest) StatOnUnlinkedLocalFile() { + // Create a local file. + var filePath string + filePath, t.f1 = t.createLocalFile(FileName) + // unlink the local file. + err := os.Remove(filePath) + AssertEq(nil, err) + + // Stat the local file and validate error. + t.validateNoFileOrDirError(FileName) + + // Validate that flushing local unlinked file throws stale NFS file handle + // error and the object is not created on GCS. + err = t.closeLocalFile(&t.f1) + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + t.validateObjectNotFoundErr(FileName) +} + +func (t *StaleHandleTest) TestReadDirContainingUnlinkedLocalFiles() { + // Create local files. + var filepath3 string + _, t.f1 = t.createLocalFile(FileName + "1") + _, t.f2 = t.createLocalFile(FileName + "2") + filepath3, t.f3 = t.createLocalFile(FileName + "3") + // Unlink local file 3 + err := os.Remove(filepath3) + AssertEq(nil, err) + + // Attempt to list mntDir. + entries := t.readDirectory(mntDir) + + // Verify unlinked entries are not listed. + AssertEq(2, len(entries)) + t.verifyLocalFileEntry(entries[0], FileName+"1", 0) + t.verifyLocalFileEntry(entries[1], FileName+"2", 0) + // Close the local files. + t.closeFileAndValidateObjectContents(&t.f1, FileName+"1", "") + t.closeFileAndValidateObjectContents(&t.f2, FileName+"2", "") + // Validate flushing unlinked local file throws stale NFS file handle error. + err = t.closeLocalFile(&t.f3) + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Verify unlinked file is not written to GCS + t.validateObjectNotFoundErr(FileName + "3") +} + +func (t *StaleHandleTest) TestUnlinkOfLocalFile() { + // Create empty local file. + var filepath string + filepath, t.f1 = t.createLocalFile(FileName) + + // Attempt to unlink local file. + err := os.Remove(filepath) + + // Verify unlink operation succeeds. + AssertEq(nil, err) + t.validateNoFileOrDirError(FileName) + // Validate flushing unlinked local file throws stale NFS file handle error. + err = t.closeLocalFile(&t.f1) + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Verify unlinked is not present on GCS. + t.validateObjectNotFoundErr(FileName) +} + +func (t *StaleHandleTest) TestWriteOnUnlinkedLocalFileSucceeds() { + // Create local file and unlink. + var filepath string + filepath, t.f1 = t.createLocalFile(FileName) + err := os.Remove(filepath) + // Verify unlink operation succeeds. + AssertEq(nil, err) + t.validateNoFileOrDirError(FileName) + + // Write to unlinked local file. + _, err = t.f1.WriteString(FileContents) + AssertEq(nil, err) + // Validate flushing unlinked local file throws stale NFS file handle error. + err = t.closeLocalFile(&t.f1) + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Verify unlinked file is not written to GCS + t.validateObjectNotFoundErr(FileName) +} + +func (t *StaleHandleTest) TestSyncOnUnlinkedLocalFile() { + // Create local file. + var filepath string + filepath, t.f1 = t.createLocalFile(FileName) + + // Attempt to unlink local file. + err := os.Remove(filepath) + + // Verify unlink operation succeeds. + AssertEq(nil, err) + t.validateNoFileOrDirError(FileName) + // Validate sync operation throws stale NFS file handle error + // and does not write to GCS after unlink. + err = t.f1.Sync() + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + t.validateObjectNotFoundErr(FileName) + // Validate flushing unlinked local file throws stale NFS file handle error. + err = t.closeLocalFile(&t.f1) + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Verify unlinked file is not present on GCS. + t.validateObjectNotFoundErr(FileName) +} + +func (t *StaleHandleTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { + // Create explicit directory with one synced and one local file. + AssertEq( + nil, + t.createObjects( + map[string]string{ + // File + "explicit/": "", + "explicit/foo": "", + })) + _, t.f1 = t.createLocalFile("explicit/" + explicitLocalFileName) + + // Attempt to remove explicit directory. + err := os.RemoveAll(path.Join(mntDir, "explicit")) + + // Verify rmDir operation succeeds. + AssertEq(nil, err) + t.validateNoFileOrDirError("explicit/" + explicitLocalFileName) + t.validateNoFileOrDirError("explicit/foo") + t.validateNoFileOrDirError("explicit") + // Validate writing content to unlinked local file does not throw error + _, err = t.f1.WriteString(FileContents) + AssertEq(nil, err) + // Validate flush file throws stale NFS file handle error and does not create + // object on GCS. + err = t.closeLocalFile(&t.f1) + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + t.validateObjectNotFoundErr("explicit/" + explicitLocalFileName) + // Validate synced files are also deleted. + t.validateObjectNotFoundErr("explicit/foo") + t.validateObjectNotFoundErr("explicit/") +} + +func (t *StaleHandleTest) TestRmDirOfDirectoryContainingOnlyLocalFiles() { + // Create a directory with two local files. + err := os.Mkdir(path.Join(mntDir, "explicit"), dirPerms) + AssertEq(nil, err) + _, t.f1 = t.createLocalFile("explicit/" + explicitLocalFileName) + _, t.f2 = t.createLocalFile("explicit/" + FileName) + + // Attempt to remove explicit directory. + err = os.RemoveAll(path.Join(mntDir, "explicit")) + + // Verify rmDir operation succeeds. + AssertEq(nil, err) + t.validateNoFileOrDirError("explicit/" + explicitLocalFileName) + t.validateNoFileOrDirError("explicit/" + FileName) + t.validateNoFileOrDirError("explicit") + // Validate flushing local unlinked files throw stale NFS file handle errors + // and do not create objects on GCS. + err = t.closeLocalFile(&t.f1) + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + t.validateObjectNotFoundErr("explicit/" + explicitLocalFileName) + err = t.closeLocalFile(&t.f2) + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + t.validateObjectNotFoundErr("explicit/" + FileName) + // Validate directory is also deleted. + t.validateObjectNotFoundErr("explicit/") +} + +func (t *StaleHandleTest) TestReadSymlinkForDeletedLocalFile() { + var filePath string + // Create a local file. + filePath, t.f1 = t.createLocalFile(FileName) + // Writing contents to local file shouldn't create file on GCS. + _, err := t.f1.Write([]byte(FileContents)) + AssertEq(nil, err) + t.validateObjectNotFoundErr(FileName) + // Create the symlink. + symlinkName := path.Join(mntDir, "bar") + err = os.Symlink(filePath, symlinkName) + AssertEq(nil, err) + // Read the link. + target, err := os.Readlink(symlinkName) + AssertEq(nil, err) + ExpectEq(filePath, target) + + // Attempt to unlink local file. + err = os.Remove(filePath) + // Verify unlink operation succeeds. + AssertEq(nil, err) + + // Validate flushing local unlinked file throws stale NFS file handle error + // and does not create object on GCS. + err = t.closeLocalFile(&t.f1) + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + t.validateObjectNotFoundErr(FileName) + + // Reading symlink should fail. + _, err = os.Stat(symlinkName) + AssertTrue(strings.Contains(err.Error(), "no such file or directory")) +} + +func (t *StaleHandleTest) SyncClobberedLocalInode() { + var err error + var n int + + // Create a local file. + _, t.f1 = t.createLocalFile("foo") + + // Dirty the file by giving it some contents. + n, err = t.f1.Write([]byte("taco")) + AssertEq(nil, err) + AssertEq(4, n) + + // Replace the underlying object with a new generation. + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("foobar")) + + AssertEq(nil, err) + + // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Validate closing the file also throws stale NFS file handle error + err = t.closeLocalFile(&t.f1) + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + contents, err := storageutil.ReadObject(ctx, bucket, "foo") + AssertEq(nil, err) + ExpectEq("foobar", string(contents)) +} + +func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleHandle() { + var err error + var contents []byte + + // Create an object on bucket + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("bar")) + AssertEq(nil, err) + + // Open the read handle + t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDONLY|syscall.O_DIRECT, filePerms) + // Replace the underlying object with a new generation. + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("foobar")) + AssertEq(nil, err) + // Attempt to read the file should result in stale NFS file handle error. + buffer := make([]byte, 6) + _, err = t.f1.Read(buffer) + + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + contents, err = storageutil.ReadObject(ctx, bucket, "foo") + AssertEq(nil, err) + ExpectEq("foobar", string(contents)) +} + +func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStaleHandle() { + var err error + var contents []byte + + // Create an object on bucket + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("bar")) + AssertEq(nil, err) + + // Open file handle to write + t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + // Replace the underlying object with a new generation. + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("foobar")) + AssertEq(nil, err) + // Attempt to write to file should result in stale NFS file handle error. + _, err = t.f1.Write([]byte("taco")) + + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Attempt to sync to file should not result in error as content written is + // nil. + err = t.f1.Sync() + AssertEq(nil, err) + contents, err = storageutil.ReadObject(ctx, bucket, "foo") + AssertEq(nil, err) + ExpectEq("foobar", string(contents)) +} + +func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleHandle() { + var err error + var n int + var contents []byte + + // Create an object on bucket + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("bar")) + AssertEq(nil, err) + + // Open file handle to write + t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + // Dirty the file by giving it some contents. + n, err = t.f1.Write([]byte("taco")) + AssertEq(nil, err) + AssertEq(4, n) + // Replace the underlying object with a new generation. + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("foobar")) + AssertEq(nil, err) + // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() + + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Validate closing the file also throws stale NFS file handle error + err = t.f1.Close() + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file + t.f1 = nil + contents, err = storageutil.ReadObject(ctx, bucket, "foo") + AssertEq(nil, err) + ExpectEq("foobar", string(contents)) +} + +func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { + var err error + + // Create an object on bucket + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("bar")) + AssertEq(nil, err) + + // Open file handle to write + t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + // Dirty the file by giving it some contents. + n, err := t.f1.Write([]byte("foobar")) + AssertEq(nil, err) + AssertEq(6, n) + // Delete the object. + err = os.Remove(t.f1.Name()) + AssertEq(nil, err) + // Attempt to write to file should not give any error. + n, err = t.f1.Write([]byte("taco")) + AssertEq(4, n) + AssertEq(nil, err) + // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() + + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Closing file should also give error + err = t.f1.Close() + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file + t.f1 = nil +} + +func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() { + var err error + + // Create an object on bucket + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("bar")) + AssertEq(nil, err) + + // Open file handle to write + t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + // Delete the object. + err = os.Remove(t.f1.Name()) + AssertEq(nil, err) + // Attempt to write to file should result in stale NFS file handle error. + _, err = t.f1.Write([]byte("taco")) + + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Attempt to sync to file should not result in error as content written is + // nil. + err = t.f1.Sync() + AssertEq(nil, err) +} + +func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandle() { + var err error + + // Create a local file. + _, t.f1 = t.createLocalFile("foo") + + // Delete the object. + err = os.Remove(t.f1.Name()) + AssertEq(nil, err) + // Attempt to write to file should not give any error as for local inode data + // is written to buffer, and we don't check for object on GCS. + n, err := t.f1.Write([]byte("taco")) + AssertEq(nil, err) + AssertEq(4, n) + // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() + + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Closing file should also give error + err = t.f1.Close() + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file + t.f1 = nil +} + +func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { + var err error + + // Create an object on bucket + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("bar")) + AssertEq(nil, err) + + // Open file handle to write + t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + // Dirty the file by giving it some contents. + n, err := t.f1.Write([]byte("foobar")) + AssertEq(nil, err) + AssertEq(6, n) + // Rename the object. + err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) + AssertEq(nil, err) + // Attempt to write to file should not give any error. + n, err = t.f1.Write([]byte("taco")) + AssertEq(4, n) + AssertEq(nil, err) + // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() + + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Closing file should also give error + err = t.f1.Close() + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file + t.f1 = nil +} + +func (t *StaleHandleTest) WritingToFileAfterObjectRenamedFailsWithStaleHandle() { + var err error + + // Create an object on bucket + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("bar")) + AssertEq(nil, err) + + // Open file handle to write + t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + // Rename the object. + err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) + AssertEq(nil, err) + // Attempt to write to file should result in stale NFS file handle error. + _, err = t.f1.Write([]byte("taco")) + + AssertNe(nil, err) + ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + // Attempt to sync to file should not result in error as content written is + // nil. + err = t.f1.Sync() + AssertEq(nil, err) +} From 6679bdc81835ea86da89d017a3116c0769f5c5aa Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 2 Dec 2024 15:29:08 +0000 Subject: [PATCH 0036/1298] Fixed lint --- internal/fs/stale_file_handle_test.go | 72 +++++++++++++++++---------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go index 988334f871..332f622fa5 100644 --- a/internal/fs/stale_file_handle_test.go +++ b/internal/fs/stale_file_handle_test.go @@ -90,25 +90,25 @@ func (t *StaleHandleTest) createLocalFile(fileName string) (filePath string, f * func (t *StaleHandleTest) validateObjectNotFoundErr(fileName string) { var notFoundErr *gcs.NotFoundError - _, err := storageutil.ReadObject(ctx, bucket, fileName) + _, err = storageutil.ReadObject(ctx, bucket, fileName) ExpectTrue(errors.As(err, ¬FoundErr)) } func (t *StaleHandleTest) validateNoFileOrDirError(filename string) { - _, err := os.Stat(path.Join(mntDir, filename)) + _, err = os.Stat(path.Join(mntDir, filename)) AssertNe(nil, err) AssertTrue(strings.Contains(err.Error(), "no such file or directory")) } func (t *StaleHandleTest) closeLocalFile(f **os.File) error { - err := (*f).Close() + err = (*f).Close() *f = nil return err } func (t *StaleHandleTest) readDirectory(dirPath string) (entries []os.DirEntry) { - entries, err := os.ReadDir(dirPath) + entries, err = os.ReadDir(dirPath) AssertEq(nil, err) return } @@ -123,7 +123,7 @@ func (t *StaleHandleTest) verifyLocalFileEntry(entry os.DirEntry, fileName strin } func (t *StaleHandleTest) closeFileAndValidateObjectContents(f **os.File, fileName string, contents string) { - err := t.closeLocalFile(f) + err = t.closeLocalFile(f) AssertEq(nil, err) t.validateObjectContents(fileName, contents) } @@ -139,11 +139,13 @@ func (t *StaleHandleTest) validateObjectContents(fileName string, contents strin //////////////////////////////////////////////////////////////////////// func (t *StaleHandleTest) StatOnUnlinkedLocalFile() { - // Create a local file. var filePath string + var err error + + // Create a local file. filePath, t.f1 = t.createLocalFile(FileName) // unlink the local file. - err := os.Remove(filePath) + err = os.Remove(filePath) AssertEq(nil, err) // Stat the local file and validate error. @@ -158,13 +160,15 @@ func (t *StaleHandleTest) StatOnUnlinkedLocalFile() { } func (t *StaleHandleTest) TestReadDirContainingUnlinkedLocalFiles() { - // Create local files. var filepath3 string + var err error + + // Create local files. _, t.f1 = t.createLocalFile(FileName + "1") _, t.f2 = t.createLocalFile(FileName + "2") filepath3, t.f3 = t.createLocalFile(FileName + "3") // Unlink local file 3 - err := os.Remove(filepath3) + err = os.Remove(filepath3) AssertEq(nil, err) // Attempt to list mntDir. @@ -186,12 +190,14 @@ func (t *StaleHandleTest) TestReadDirContainingUnlinkedLocalFiles() { } func (t *StaleHandleTest) TestUnlinkOfLocalFile() { - // Create empty local file. var filepath string + var err error + + // Create empty local file. filepath, t.f1 = t.createLocalFile(FileName) // Attempt to unlink local file. - err := os.Remove(filepath) + err = os.Remove(filepath) // Verify unlink operation succeeds. AssertEq(nil, err) @@ -205,10 +211,12 @@ func (t *StaleHandleTest) TestUnlinkOfLocalFile() { } func (t *StaleHandleTest) TestWriteOnUnlinkedLocalFileSucceeds() { - // Create local file and unlink. var filepath string + var err error + + // Create local file and unlink. filepath, t.f1 = t.createLocalFile(FileName) - err := os.Remove(filepath) + err = os.Remove(filepath) // Verify unlink operation succeeds. AssertEq(nil, err) t.validateNoFileOrDirError(FileName) @@ -225,12 +233,14 @@ func (t *StaleHandleTest) TestWriteOnUnlinkedLocalFileSucceeds() { } func (t *StaleHandleTest) TestSyncOnUnlinkedLocalFile() { - // Create local file. var filepath string + var err error + + // Create local file. filepath, t.f1 = t.createLocalFile(FileName) // Attempt to unlink local file. - err := os.Remove(filepath) + err = os.Remove(filepath) // Verify unlink operation succeeds. AssertEq(nil, err) @@ -250,6 +260,8 @@ func (t *StaleHandleTest) TestSyncOnUnlinkedLocalFile() { } func (t *StaleHandleTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { + var err error + // Create explicit directory with one synced and one local file. AssertEq( nil, @@ -262,7 +274,7 @@ func (t *StaleHandleTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { _, t.f1 = t.createLocalFile("explicit/" + explicitLocalFileName) // Attempt to remove explicit directory. - err := os.RemoveAll(path.Join(mntDir, "explicit")) + err = os.RemoveAll(path.Join(mntDir, "explicit")) // Verify rmDir operation succeeds. AssertEq(nil, err) @@ -284,8 +296,10 @@ func (t *StaleHandleTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { } func (t *StaleHandleTest) TestRmDirOfDirectoryContainingOnlyLocalFiles() { + var err error + // Create a directory with two local files. - err := os.Mkdir(path.Join(mntDir, "explicit"), dirPerms) + err = os.Mkdir(path.Join(mntDir, "explicit"), dirPerms) AssertEq(nil, err) _, t.f1 = t.createLocalFile("explicit/" + explicitLocalFileName) _, t.f2 = t.createLocalFile("explicit/" + FileName) @@ -313,19 +327,21 @@ func (t *StaleHandleTest) TestRmDirOfDirectoryContainingOnlyLocalFiles() { } func (t *StaleHandleTest) TestReadSymlinkForDeletedLocalFile() { - var filePath string + var err error + var filePath, symlinkName, target string + // Create a local file. filePath, t.f1 = t.createLocalFile(FileName) // Writing contents to local file shouldn't create file on GCS. - _, err := t.f1.Write([]byte(FileContents)) + _, err = t.f1.Write([]byte(FileContents)) AssertEq(nil, err) t.validateObjectNotFoundErr(FileName) // Create the symlink. - symlinkName := path.Join(mntDir, "bar") + symlinkName = path.Join(mntDir, "bar") err = os.Symlink(filePath, symlinkName) AssertEq(nil, err) // Read the link. - target, err := os.Readlink(symlinkName) + target, err = os.Readlink(symlinkName) AssertEq(nil, err) ExpectEq(filePath, target) @@ -349,7 +365,7 @@ func (t *StaleHandleTest) TestReadSymlinkForDeletedLocalFile() { func (t *StaleHandleTest) SyncClobberedLocalInode() { var err error var n int - + var contents []byte // Create a local file. _, t.f1 = t.createLocalFile("foo") @@ -375,7 +391,7 @@ func (t *StaleHandleTest) SyncClobberedLocalInode() { err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - contents, err := storageutil.ReadObject(ctx, bucket, "foo") + contents, err = storageutil.ReadObject(ctx, bucket, "foo") AssertEq(nil, err) ExpectEq("foobar", string(contents)) } @@ -492,7 +508,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleH func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { var err error - + var n int // Create an object on bucket _, err = storageutil.CreateObject( ctx, @@ -504,7 +520,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) // Dirty the file by giving it some contents. - n, err := t.f1.Write([]byte("foobar")) + n, err = t.f1.Write([]byte("foobar")) AssertEq(nil, err) AssertEq(6, n) // Delete the object. @@ -557,6 +573,7 @@ func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandle() { var err error + var n int // Create a local file. _, t.f1 = t.createLocalFile("foo") @@ -566,7 +583,7 @@ func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandl AssertEq(nil, err) // Attempt to write to file should not give any error as for local inode data // is written to buffer, and we don't check for object on GCS. - n, err := t.f1.Write([]byte("taco")) + n, err = t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) // Attempt to sync the file should result in clobbered error. @@ -585,6 +602,7 @@ func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandl func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { var err error + var n int // Create an object on bucket _, err = storageutil.CreateObject( @@ -597,7 +615,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) // Dirty the file by giving it some contents. - n, err := t.f1.Write([]byte("foobar")) + n, err = t.f1.Write([]byte("foobar")) AssertEq(nil, err) AssertEq(6, n) // Rename the object. From d064fe4a2c7807c26a245fd5191ecb02f4ed09de Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 6 Dec 2024 11:19:30 +0000 Subject: [PATCH 0037/1298] Removed global err variable --- internal/fs/stale_file_handle_test.go | 127 +++++++++----------------- 1 file changed, 43 insertions(+), 84 deletions(-) diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go index 332f622fa5..e07433d3c7 100644 --- a/internal/fs/stale_file_handle_test.go +++ b/internal/fs/stale_file_handle_test.go @@ -77,7 +77,7 @@ func (t *StaleHandleTest) TearDown() { func (t *StaleHandleTest) createLocalFile(fileName string) (filePath string, f *os.File) { // Creating a file shouldn't create file on GCS. filePath = path.Join(mntDir, fileName) - _, err = os.Stat(mntDir) + _, err := os.Stat(mntDir) AssertEq(nil, err) f, err = os.Create(filePath) @@ -90,25 +90,25 @@ func (t *StaleHandleTest) createLocalFile(fileName string) (filePath string, f * func (t *StaleHandleTest) validateObjectNotFoundErr(fileName string) { var notFoundErr *gcs.NotFoundError - _, err = storageutil.ReadObject(ctx, bucket, fileName) + _, err := storageutil.ReadObject(ctx, bucket, fileName) ExpectTrue(errors.As(err, ¬FoundErr)) } func (t *StaleHandleTest) validateNoFileOrDirError(filename string) { - _, err = os.Stat(path.Join(mntDir, filename)) + _, err := os.Stat(path.Join(mntDir, filename)) AssertNe(nil, err) AssertTrue(strings.Contains(err.Error(), "no such file or directory")) } func (t *StaleHandleTest) closeLocalFile(f **os.File) error { - err = (*f).Close() + err := (*f).Close() *f = nil return err } func (t *StaleHandleTest) readDirectory(dirPath string) (entries []os.DirEntry) { - entries, err = os.ReadDir(dirPath) + entries, err := os.ReadDir(dirPath) AssertEq(nil, err) return } @@ -123,7 +123,7 @@ func (t *StaleHandleTest) verifyLocalFileEntry(entry os.DirEntry, fileName strin } func (t *StaleHandleTest) closeFileAndValidateObjectContents(f **os.File, fileName string, contents string) { - err = t.closeLocalFile(f) + err := t.closeLocalFile(f) AssertEq(nil, err) t.validateObjectContents(fileName, contents) } @@ -139,13 +139,11 @@ func (t *StaleHandleTest) validateObjectContents(fileName string, contents strin //////////////////////////////////////////////////////////////////////// func (t *StaleHandleTest) StatOnUnlinkedLocalFile() { - var filePath string - var err error - // Create a local file. + var filePath string filePath, t.f1 = t.createLocalFile(FileName) // unlink the local file. - err = os.Remove(filePath) + err := os.Remove(filePath) AssertEq(nil, err) // Stat the local file and validate error. @@ -160,15 +158,13 @@ func (t *StaleHandleTest) StatOnUnlinkedLocalFile() { } func (t *StaleHandleTest) TestReadDirContainingUnlinkedLocalFiles() { - var filepath3 string - var err error - // Create local files. _, t.f1 = t.createLocalFile(FileName + "1") _, t.f2 = t.createLocalFile(FileName + "2") - filepath3, t.f3 = t.createLocalFile(FileName + "3") + var filePath3 string + filePath3, t.f3 = t.createLocalFile(FileName + "3") // Unlink local file 3 - err = os.Remove(filepath3) + err := os.Remove(filePath3) AssertEq(nil, err) // Attempt to list mntDir. @@ -190,14 +186,12 @@ func (t *StaleHandleTest) TestReadDirContainingUnlinkedLocalFiles() { } func (t *StaleHandleTest) TestUnlinkOfLocalFile() { - var filepath string - var err error - // Create empty local file. - filepath, t.f1 = t.createLocalFile(FileName) + var filePath string + filePath, t.f1 = t.createLocalFile(FileName) // Attempt to unlink local file. - err = os.Remove(filepath) + err := os.Remove(filePath) // Verify unlink operation succeeds. AssertEq(nil, err) @@ -211,12 +205,10 @@ func (t *StaleHandleTest) TestUnlinkOfLocalFile() { } func (t *StaleHandleTest) TestWriteOnUnlinkedLocalFileSucceeds() { - var filepath string - var err error - // Create local file and unlink. - filepath, t.f1 = t.createLocalFile(FileName) - err = os.Remove(filepath) + var filePath string + filePath, t.f1 = t.createLocalFile(FileName) + err := os.Remove(filePath) // Verify unlink operation succeeds. AssertEq(nil, err) t.validateNoFileOrDirError(FileName) @@ -233,14 +225,12 @@ func (t *StaleHandleTest) TestWriteOnUnlinkedLocalFileSucceeds() { } func (t *StaleHandleTest) TestSyncOnUnlinkedLocalFile() { - var filepath string - var err error - // Create local file. - filepath, t.f1 = t.createLocalFile(FileName) + var filePath string + filePath, t.f1 = t.createLocalFile(FileName) // Attempt to unlink local file. - err = os.Remove(filepath) + err := os.Remove(filePath) // Verify unlink operation succeeds. AssertEq(nil, err) @@ -260,8 +250,6 @@ func (t *StaleHandleTest) TestSyncOnUnlinkedLocalFile() { } func (t *StaleHandleTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { - var err error - // Create explicit directory with one synced and one local file. AssertEq( nil, @@ -274,7 +262,7 @@ func (t *StaleHandleTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { _, t.f1 = t.createLocalFile("explicit/" + explicitLocalFileName) // Attempt to remove explicit directory. - err = os.RemoveAll(path.Join(mntDir, "explicit")) + err := os.RemoveAll(path.Join(mntDir, "explicit")) // Verify rmDir operation succeeds. AssertEq(nil, err) @@ -296,10 +284,8 @@ func (t *StaleHandleTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { } func (t *StaleHandleTest) TestRmDirOfDirectoryContainingOnlyLocalFiles() { - var err error - // Create a directory with two local files. - err = os.Mkdir(path.Join(mntDir, "explicit"), dirPerms) + err := os.Mkdir(path.Join(mntDir, "explicit"), dirPerms) AssertEq(nil, err) _, t.f1 = t.createLocalFile("explicit/" + explicitLocalFileName) _, t.f2 = t.createLocalFile("explicit/" + FileName) @@ -327,21 +313,19 @@ func (t *StaleHandleTest) TestRmDirOfDirectoryContainingOnlyLocalFiles() { } func (t *StaleHandleTest) TestReadSymlinkForDeletedLocalFile() { - var err error - var filePath, symlinkName, target string - // Create a local file. + var filePath string filePath, t.f1 = t.createLocalFile(FileName) // Writing contents to local file shouldn't create file on GCS. - _, err = t.f1.Write([]byte(FileContents)) + _, err := t.f1.Write([]byte(FileContents)) AssertEq(nil, err) t.validateObjectNotFoundErr(FileName) // Create the symlink. - symlinkName = path.Join(mntDir, "bar") + symlinkName := path.Join(mntDir, "bar") err = os.Symlink(filePath, symlinkName) AssertEq(nil, err) // Read the link. - target, err = os.Readlink(symlinkName) + target, err := os.Readlink(symlinkName) AssertEq(nil, err) ExpectEq(filePath, target) @@ -363,14 +347,11 @@ func (t *StaleHandleTest) TestReadSymlinkForDeletedLocalFile() { } func (t *StaleHandleTest) SyncClobberedLocalInode() { - var err error - var n int - var contents []byte // Create a local file. _, t.f1 = t.createLocalFile("foo") // Dirty the file by giving it some contents. - n, err = t.f1.Write([]byte("taco")) + n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) @@ -391,17 +372,14 @@ func (t *StaleHandleTest) SyncClobberedLocalInode() { err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - contents, err = storageutil.ReadObject(ctx, bucket, "foo") + contents, err := storageutil.ReadObject(ctx, bucket, "foo") AssertEq(nil, err) ExpectEq("foobar", string(contents)) } func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleHandle() { - var err error - var contents []byte - // Create an object on bucket - _, err = storageutil.CreateObject( + _, err := storageutil.CreateObject( ctx, bucket, "foo", @@ -423,17 +401,14 @@ func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleH AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - contents, err = storageutil.ReadObject(ctx, bucket, "foo") + contents, err := storageutil.ReadObject(ctx, bucket, "foo") AssertEq(nil, err) ExpectEq("foobar", string(contents)) } func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStaleHandle() { - var err error - var contents []byte - // Create an object on bucket - _, err = storageutil.CreateObject( + _, err := storageutil.CreateObject( ctx, bucket, "foo", @@ -458,18 +433,14 @@ func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStal // nil. err = t.f1.Sync() AssertEq(nil, err) - contents, err = storageutil.ReadObject(ctx, bucket, "foo") + contents, err := storageutil.ReadObject(ctx, bucket, "foo") AssertEq(nil, err) ExpectEq("foobar", string(contents)) } func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleHandle() { - var err error - var n int - var contents []byte - // Create an object on bucket - _, err = storageutil.CreateObject( + _, err := storageutil.CreateObject( ctx, bucket, "foo", @@ -479,7 +450,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleH // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) // Dirty the file by giving it some contents. - n, err = t.f1.Write([]byte("taco")) + n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) // Replace the underlying object with a new generation. @@ -501,16 +472,14 @@ func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleH // Make f1 nil, so that another attempt is not taken in TearDown to close the // file t.f1 = nil - contents, err = storageutil.ReadObject(ctx, bucket, "foo") + contents, err := storageutil.ReadObject(ctx, bucket, "foo") AssertEq(nil, err) ExpectEq("foobar", string(contents)) } func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { - var err error - var n int // Create an object on bucket - _, err = storageutil.CreateObject( + _, err := storageutil.CreateObject( ctx, bucket, "foo", @@ -520,7 +489,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) // Dirty the file by giving it some contents. - n, err = t.f1.Write([]byte("foobar")) + n, err := t.f1.Write([]byte("foobar")) AssertEq(nil, err) AssertEq(6, n) // Delete the object. @@ -545,10 +514,8 @@ func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { } func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() { - var err error - // Create an object on bucket - _, err = storageutil.CreateObject( + _, err := storageutil.CreateObject( ctx, bucket, "foo", @@ -572,18 +539,15 @@ func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() } func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandle() { - var err error - var n int - // Create a local file. _, t.f1 = t.createLocalFile("foo") // Delete the object. - err = os.Remove(t.f1.Name()) + err := os.Remove(t.f1.Name()) AssertEq(nil, err) // Attempt to write to file should not give any error as for local inode data // is written to buffer, and we don't check for object on GCS. - n, err = t.f1.Write([]byte("taco")) + n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) // Attempt to sync the file should result in clobbered error. @@ -601,11 +565,8 @@ func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandl } func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { - var err error - var n int - // Create an object on bucket - _, err = storageutil.CreateObject( + _, err := storageutil.CreateObject( ctx, bucket, "foo", @@ -615,7 +576,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) // Dirty the file by giving it some contents. - n, err = t.f1.Write([]byte("foobar")) + n, err := t.f1.Write([]byte("foobar")) AssertEq(nil, err) AssertEq(6, n) // Rename the object. @@ -640,10 +601,8 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { } func (t *StaleHandleTest) WritingToFileAfterObjectRenamedFailsWithStaleHandle() { - var err error - // Create an object on bucket - _, err = storageutil.CreateObject( + _, err := storageutil.CreateObject( ctx, bucket, "foo", From 52784d9ca13bfe34ecf21953a5c333db829357db Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 3 Dec 2024 05:35:32 +0000 Subject: [PATCH 0038/1298] fixed lint --- internal/fs/stale_file_handle_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go index e07433d3c7..aab3e78fe8 100644 --- a/internal/fs/stale_file_handle_test.go +++ b/internal/fs/stale_file_handle_test.go @@ -388,6 +388,7 @@ func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleH // Open the read handle t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, @@ -417,6 +418,7 @@ func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStal // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, @@ -449,6 +451,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleH // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) @@ -488,6 +491,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("foobar")) AssertEq(nil, err) @@ -524,6 +528,7 @@ func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Delete the object. err = os.Remove(t.f1.Name()) AssertEq(nil, err) @@ -575,6 +580,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("foobar")) AssertEq(nil, err) @@ -584,8 +590,8 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { AssertEq(nil, err) // Attempt to write to file should not give any error. n, err = t.f1.Write([]byte("taco")) - AssertEq(4, n) AssertEq(nil, err) + AssertEq(4, n) // Attempt to sync the file should result in clobbered error. err = t.f1.Sync() @@ -611,6 +617,7 @@ func (t *StaleHandleTest) WritingToFileAfterObjectRenamedFailsWithStaleHandle() // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Rename the object. err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) AssertEq(nil, err) From f3a2fdf3e4c4f2f0afaf3d5b0a89db5e81662fb4 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 3 Dec 2024 12:11:53 +0000 Subject: [PATCH 0039/1298] Formatted tests according to AAA. --- internal/fs/stale_file_handle_test.go | 49 +++++++++++---------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go index aab3e78fe8..18cdb84e07 100644 --- a/internal/fs/stale_file_handle_test.go +++ b/internal/fs/stale_file_handle_test.go @@ -97,6 +97,7 @@ func (t *StaleHandleTest) validateObjectNotFoundErr(fileName string) { func (t *StaleHandleTest) validateNoFileOrDirError(filename string) { _, err := os.Stat(path.Join(mntDir, filename)) + AssertNe(nil, err) AssertTrue(strings.Contains(err.Error(), "no such file or directory")) } @@ -200,7 +201,7 @@ func (t *StaleHandleTest) TestUnlinkOfLocalFile() { err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Verify unlinked is not present on GCS. + // Verify unlinked file is not present on GCS. t.validateObjectNotFoundErr(FileName) } @@ -215,6 +216,7 @@ func (t *StaleHandleTest) TestWriteOnUnlinkedLocalFileSucceeds() { // Write to unlinked local file. _, err = t.f1.WriteString(FileContents) + AssertEq(nil, err) // Validate flushing unlinked local file throws stale NFS file handle error. err = t.closeLocalFile(&t.f1) @@ -228,20 +230,21 @@ func (t *StaleHandleTest) TestSyncOnUnlinkedLocalFile() { // Create local file. var filePath string filePath, t.f1 = t.createLocalFile(FileName) - // Attempt to unlink local file. err := os.Remove(filePath) - // Verify unlink operation succeeds. AssertEq(nil, err) t.validateNoFileOrDirError(FileName) + // Validate sync operation throws stale NFS file handle error // and does not write to GCS after unlink. err = t.f1.Sync() + AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) t.validateObjectNotFoundErr(FileName) - // Validate flushing unlinked local file throws stale NFS file handle error. + // Validate closing unlinked local file handle also throws stale NFS file + // handle error. err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) @@ -331,44 +334,40 @@ func (t *StaleHandleTest) TestReadSymlinkForDeletedLocalFile() { // Attempt to unlink local file. err = os.Remove(filePath) + // Verify unlink operation succeeds. AssertEq(nil, err) - // Validate flushing local unlinked file throws stale NFS file handle error // and does not create object on GCS. err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) t.validateObjectNotFoundErr(FileName) - // Reading symlink should fail. _, err = os.Stat(symlinkName) AssertTrue(strings.Contains(err.Error(), "no such file or directory")) } -func (t *StaleHandleTest) SyncClobberedLocalInode() { +func (t *StaleHandleTest) SyncClobberedLocalInodeFailsWithStaleHandle() { // Create a local file. _, t.f1 = t.createLocalFile("foo") - // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) - // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, bucket, "foo", []byte("foobar")) - AssertEq(nil, err) - // Attempt to sync the file should result in clobbered error. err = t.f1.Sync() + AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Validate closing the file also throws stale NFS file handle error + // Validate closing the file also throws error err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) @@ -385,7 +384,6 @@ func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("bar")) AssertEq(nil, err) - // Open the read handle t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) @@ -396,7 +394,7 @@ func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("foobar")) AssertEq(nil, err) - // Attempt to read the file should result in stale NFS file handle error. + buffer := make([]byte, 6) _, err = t.f1.Read(buffer) @@ -415,7 +413,6 @@ func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStal "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) @@ -426,7 +423,7 @@ func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStal "foo", []byte("foobar")) AssertEq(nil, err) - // Attempt to write to file should result in stale NFS file handle error. + _, err = t.f1.Write([]byte("taco")) AssertNe(nil, err) @@ -448,7 +445,6 @@ func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) @@ -463,12 +459,12 @@ func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("foobar")) AssertEq(nil, err) - // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Validate closing the file also throws stale NFS file handle error + // Validate closing the file also throws error err = t.f1.Close() AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) @@ -488,7 +484,6 @@ func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) @@ -503,7 +498,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { n, err = t.f1.Write([]byte("taco")) AssertEq(4, n) AssertEq(nil, err) - // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() AssertNe(nil, err) @@ -525,14 +520,13 @@ func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) // Delete the object. err = os.Remove(t.f1.Name()) AssertEq(nil, err) - // Attempt to write to file should result in stale NFS file handle error. + _, err = t.f1.Write([]byte("taco")) AssertNe(nil, err) @@ -546,7 +540,6 @@ func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandle() { // Create a local file. _, t.f1 = t.createLocalFile("foo") - // Delete the object. err := os.Remove(t.f1.Name()) AssertEq(nil, err) @@ -555,7 +548,7 @@ func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandl n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) - // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() AssertNe(nil, err) @@ -577,7 +570,6 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) @@ -592,7 +584,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { n, err = t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) - // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() AssertNe(nil, err) @@ -614,14 +606,13 @@ func (t *StaleHandleTest) WritingToFileAfterObjectRenamedFailsWithStaleHandle() "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) // Rename the object. err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) AssertEq(nil, err) - // Attempt to write to file should result in stale NFS file handle error. + _, err = t.f1.Write([]byte("taco")) AssertNe(nil, err) From d460e4fd0ddfaef40af577aa773f1ffcc210a774 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 3 Dec 2024 05:16:41 +0000 Subject: [PATCH 0040/1298] Removed global err variable --- internal/fs/stale_file_handle_test.go | 58 ++++++++++++++------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go index 18cdb84e07..e07433d3c7 100644 --- a/internal/fs/stale_file_handle_test.go +++ b/internal/fs/stale_file_handle_test.go @@ -97,7 +97,6 @@ func (t *StaleHandleTest) validateObjectNotFoundErr(fileName string) { func (t *StaleHandleTest) validateNoFileOrDirError(filename string) { _, err := os.Stat(path.Join(mntDir, filename)) - AssertNe(nil, err) AssertTrue(strings.Contains(err.Error(), "no such file or directory")) } @@ -201,7 +200,7 @@ func (t *StaleHandleTest) TestUnlinkOfLocalFile() { err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Verify unlinked file is not present on GCS. + // Verify unlinked is not present on GCS. t.validateObjectNotFoundErr(FileName) } @@ -216,7 +215,6 @@ func (t *StaleHandleTest) TestWriteOnUnlinkedLocalFileSucceeds() { // Write to unlinked local file. _, err = t.f1.WriteString(FileContents) - AssertEq(nil, err) // Validate flushing unlinked local file throws stale NFS file handle error. err = t.closeLocalFile(&t.f1) @@ -230,21 +228,20 @@ func (t *StaleHandleTest) TestSyncOnUnlinkedLocalFile() { // Create local file. var filePath string filePath, t.f1 = t.createLocalFile(FileName) + // Attempt to unlink local file. err := os.Remove(filePath) + // Verify unlink operation succeeds. AssertEq(nil, err) t.validateNoFileOrDirError(FileName) - // Validate sync operation throws stale NFS file handle error // and does not write to GCS after unlink. err = t.f1.Sync() - AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) t.validateObjectNotFoundErr(FileName) - // Validate closing unlinked local file handle also throws stale NFS file - // handle error. + // Validate flushing unlinked local file throws stale NFS file handle error. err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) @@ -334,40 +331,44 @@ func (t *StaleHandleTest) TestReadSymlinkForDeletedLocalFile() { // Attempt to unlink local file. err = os.Remove(filePath) - // Verify unlink operation succeeds. AssertEq(nil, err) + // Validate flushing local unlinked file throws stale NFS file handle error // and does not create object on GCS. err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) t.validateObjectNotFoundErr(FileName) + // Reading symlink should fail. _, err = os.Stat(symlinkName) AssertTrue(strings.Contains(err.Error(), "no such file or directory")) } -func (t *StaleHandleTest) SyncClobberedLocalInodeFailsWithStaleHandle() { +func (t *StaleHandleTest) SyncClobberedLocalInode() { // Create a local file. _, t.f1 = t.createLocalFile("foo") + // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) + // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, bucket, "foo", []byte("foobar")) + AssertEq(nil, err) + // Attempt to sync the file should result in clobbered error. err = t.f1.Sync() - AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Validate closing the file also throws error + // Validate closing the file also throws stale NFS file handle error err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) @@ -384,9 +385,9 @@ func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("bar")) AssertEq(nil, err) + // Open the read handle t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, @@ -394,7 +395,7 @@ func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("foobar")) AssertEq(nil, err) - + // Attempt to read the file should result in stale NFS file handle error. buffer := make([]byte, 6) _, err = t.f1.Read(buffer) @@ -413,9 +414,9 @@ func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStal "foo", []byte("bar")) AssertEq(nil, err) + // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, @@ -423,7 +424,7 @@ func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStal "foo", []byte("foobar")) AssertEq(nil, err) - + // Attempt to write to file should result in stale NFS file handle error. _, err = t.f1.Write([]byte("taco")) AssertNe(nil, err) @@ -445,9 +446,9 @@ func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("bar")) AssertEq(nil, err) + // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) @@ -459,12 +460,12 @@ func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("foobar")) AssertEq(nil, err) - + // Attempt to sync the file should result in clobbered error. err = t.f1.Sync() AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Validate closing the file also throws error + // Validate closing the file also throws stale NFS file handle error err = t.f1.Close() AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) @@ -484,9 +485,9 @@ func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { "foo", []byte("bar")) AssertEq(nil, err) + // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("foobar")) AssertEq(nil, err) @@ -498,7 +499,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { n, err = t.f1.Write([]byte("taco")) AssertEq(4, n) AssertEq(nil, err) - + // Attempt to sync the file should result in clobbered error. err = t.f1.Sync() AssertNe(nil, err) @@ -520,13 +521,13 @@ func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() "foo", []byte("bar")) AssertEq(nil, err) + // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) // Delete the object. err = os.Remove(t.f1.Name()) AssertEq(nil, err) - + // Attempt to write to file should result in stale NFS file handle error. _, err = t.f1.Write([]byte("taco")) AssertNe(nil, err) @@ -540,6 +541,7 @@ func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandle() { // Create a local file. _, t.f1 = t.createLocalFile("foo") + // Delete the object. err := os.Remove(t.f1.Name()) AssertEq(nil, err) @@ -548,7 +550,7 @@ func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandl n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) - + // Attempt to sync the file should result in clobbered error. err = t.f1.Sync() AssertNe(nil, err) @@ -570,9 +572,9 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { "foo", []byte("bar")) AssertEq(nil, err) + // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("foobar")) AssertEq(nil, err) @@ -582,9 +584,9 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { AssertEq(nil, err) // Attempt to write to file should not give any error. n, err = t.f1.Write([]byte("taco")) - AssertEq(nil, err) AssertEq(4, n) - + AssertEq(nil, err) + // Attempt to sync the file should result in clobbered error. err = t.f1.Sync() AssertNe(nil, err) @@ -606,13 +608,13 @@ func (t *StaleHandleTest) WritingToFileAfterObjectRenamedFailsWithStaleHandle() "foo", []byte("bar")) AssertEq(nil, err) + // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) // Rename the object. err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) AssertEq(nil, err) - + // Attempt to write to file should result in stale NFS file handle error. _, err = t.f1.Write([]byte("taco")) AssertNe(nil, err) From 9611ec7a0f5fb37d4d8469ff7e1212aab8facc13 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 3 Dec 2024 05:35:32 +0000 Subject: [PATCH 0041/1298] fixed lint --- internal/fs/stale_file_handle_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go index e07433d3c7..aab3e78fe8 100644 --- a/internal/fs/stale_file_handle_test.go +++ b/internal/fs/stale_file_handle_test.go @@ -388,6 +388,7 @@ func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleH // Open the read handle t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, @@ -417,6 +418,7 @@ func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStal // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, @@ -449,6 +451,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleH // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) @@ -488,6 +491,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("foobar")) AssertEq(nil, err) @@ -524,6 +528,7 @@ func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Delete the object. err = os.Remove(t.f1.Name()) AssertEq(nil, err) @@ -575,6 +580,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("foobar")) AssertEq(nil, err) @@ -584,8 +590,8 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { AssertEq(nil, err) // Attempt to write to file should not give any error. n, err = t.f1.Write([]byte("taco")) - AssertEq(4, n) AssertEq(nil, err) + AssertEq(4, n) // Attempt to sync the file should result in clobbered error. err = t.f1.Sync() @@ -611,6 +617,7 @@ func (t *StaleHandleTest) WritingToFileAfterObjectRenamedFailsWithStaleHandle() // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) + AssertEq(nil, err) // Rename the object. err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) AssertEq(nil, err) From a716129876de9772ac3fa4bff69acaf6d823407e Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 6 Dec 2024 11:09:33 +0000 Subject: [PATCH 0042/1298] updated flags --- internal/fs/stale_file_handle_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go index aab3e78fe8..3c0b0ff4ea 100644 --- a/internal/fs/stale_file_handle_test.go +++ b/internal/fs/stale_file_handle_test.go @@ -43,12 +43,7 @@ func init() { } func (t *StaleHandleTest) SetUpTestSuite() { - t.serverCfg.ImplicitDirectories = true - t.serverCfg.LocalFileCache = false t.serverCfg.NewConfig = &cfg.Config{ - Write: cfg.WriteConfig{ - CreateEmptyFile: false, - }, FileSystem: cfg.FileSystemConfig{ PreconditionErrors: true, }, From ca59944cf3d051cd565fd87238baad87c87f4a52 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 9 Dec 2024 06:56:59 +0000 Subject: [PATCH 0043/1298] fixed formatting --- internal/fs/stale_file_handle_test.go | 53 ++++++++++++--------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go index 3c0b0ff4ea..816417fa8d 100644 --- a/internal/fs/stale_file_handle_test.go +++ b/internal/fs/stale_file_handle_test.go @@ -79,7 +79,6 @@ func (t *StaleHandleTest) createLocalFile(fileName string) (filePath string, f * AssertEq(nil, err) t.validateObjectNotFoundErr(fileName) - return } @@ -92,6 +91,7 @@ func (t *StaleHandleTest) validateObjectNotFoundErr(fileName string) { func (t *StaleHandleTest) validateNoFileOrDirError(filename string) { _, err := os.Stat(path.Join(mntDir, filename)) + AssertNe(nil, err) AssertTrue(strings.Contains(err.Error(), "no such file or directory")) } @@ -104,6 +104,7 @@ func (t *StaleHandleTest) closeLocalFile(f **os.File) error { func (t *StaleHandleTest) readDirectory(dirPath string) (entries []os.DirEntry) { entries, err := os.ReadDir(dirPath) + AssertEq(nil, err) return } @@ -113,6 +114,7 @@ func (t *StaleHandleTest) verifyLocalFileEntry(entry os.DirEntry, fileName strin AssertEq(fileName, entry.Name()) fileInfo, err := entry.Info() + AssertEq(nil, err) AssertEq(size, fileInfo.Size()) } @@ -144,7 +146,7 @@ func (t *StaleHandleTest) StatOnUnlinkedLocalFile() { // Stat the local file and validate error. t.validateNoFileOrDirError(FileName) - // Validate that flushing local unlinked file throws stale NFS file handle + // Validate that closing local unlinked file throws stale NFS file handle // error and the object is not created on GCS. err = t.closeLocalFile(&t.f1) AssertNe(nil, err) @@ -172,7 +174,7 @@ func (t *StaleHandleTest) TestReadDirContainingUnlinkedLocalFiles() { // Close the local files. t.closeFileAndValidateObjectContents(&t.f1, FileName+"1", "") t.closeFileAndValidateObjectContents(&t.f2, FileName+"2", "") - // Validate flushing unlinked local file throws stale NFS file handle error. + // Validate closing unlinked local file throws stale NFS file handle error. err = t.closeLocalFile(&t.f3) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) @@ -191,11 +193,11 @@ func (t *StaleHandleTest) TestUnlinkOfLocalFile() { // Verify unlink operation succeeds. AssertEq(nil, err) t.validateNoFileOrDirError(FileName) - // Validate flushing unlinked local file throws stale NFS file handle error. + // Validate closing unlinked local file throws stale NFS file handle error. err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Verify unlinked is not present on GCS. + // Verify unlinked object is not present on GCS. t.validateObjectNotFoundErr(FileName) } @@ -210,8 +212,9 @@ func (t *StaleHandleTest) TestWriteOnUnlinkedLocalFileSucceeds() { // Write to unlinked local file. _, err = t.f1.WriteString(FileContents) + AssertEq(nil, err) - // Validate flushing unlinked local file throws stale NFS file handle error. + // Validate closing unlinked local file throws stale NFS file handle error. err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) @@ -236,7 +239,7 @@ func (t *StaleHandleTest) TestSyncOnUnlinkedLocalFile() { AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) t.validateObjectNotFoundErr(FileName) - // Validate flushing unlinked local file throws stale NFS file handle error. + // Validate closing unlinked local file also throws stale NFS file handle error. err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) @@ -267,7 +270,7 @@ func (t *StaleHandleTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { // Validate writing content to unlinked local file does not throw error _, err = t.f1.WriteString(FileContents) AssertEq(nil, err) - // Validate flush file throws stale NFS file handle error and does not create + // Validate close file throws stale NFS file handle error and does not create // object on GCS. err = t.closeLocalFile(&t.f1) AssertNe(nil, err) @@ -293,7 +296,7 @@ func (t *StaleHandleTest) TestRmDirOfDirectoryContainingOnlyLocalFiles() { t.validateNoFileOrDirError("explicit/" + explicitLocalFileName) t.validateNoFileOrDirError("explicit/" + FileName) t.validateNoFileOrDirError("explicit") - // Validate flushing local unlinked files throw stale NFS file handle errors + // Validate closing local unlinked files throw stale NFS file handle errors // and do not create objects on GCS. err = t.closeLocalFile(&t.f1) AssertNe(nil, err) @@ -323,12 +326,10 @@ func (t *StaleHandleTest) TestReadSymlinkForDeletedLocalFile() { target, err := os.Readlink(symlinkName) AssertEq(nil, err) ExpectEq(filePath, target) - // Attempt to unlink local file. err = os.Remove(filePath) // Verify unlink operation succeeds. AssertEq(nil, err) - // Validate flushing local unlinked file throws stale NFS file handle error // and does not create object on GCS. err = t.closeLocalFile(&t.f1) @@ -338,32 +339,31 @@ func (t *StaleHandleTest) TestReadSymlinkForDeletedLocalFile() { // Reading symlink should fail. _, err = os.Stat(symlinkName) + AssertTrue(strings.Contains(err.Error(), "no such file or directory")) } func (t *StaleHandleTest) SyncClobberedLocalInode() { // Create a local file. _, t.f1 = t.createLocalFile("foo") - // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) - // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, bucket, "foo", []byte("foobar")) - AssertEq(nil, err) // Attempt to sync the file should result in clobbered error. err = t.f1.Sync() + AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Validate closing the file also throws stale NFS file handle error + // Validate closing the file also throws error err = t.closeLocalFile(&t.f1) AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) @@ -380,7 +380,6 @@ func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("bar")) AssertEq(nil, err) - // Open the read handle t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) @@ -391,6 +390,7 @@ func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("foobar")) AssertEq(nil, err) + // Attempt to read the file should result in stale NFS file handle error. buffer := make([]byte, 6) _, err = t.f1.Read(buffer) @@ -410,7 +410,6 @@ func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStal "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) @@ -421,6 +420,7 @@ func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStal "foo", []byte("foobar")) AssertEq(nil, err) + // Attempt to write to file should result in stale NFS file handle error. _, err = t.f1.Write([]byte("taco")) @@ -443,7 +443,6 @@ func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) @@ -458,12 +457,12 @@ func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleH "foo", []byte("foobar")) AssertEq(nil, err) - // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Validate closing the file also throws stale NFS file handle error + // Validate closing the file also throws error err = t.f1.Close() AssertNe(nil, err) ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) @@ -483,7 +482,6 @@ func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) @@ -498,7 +496,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { n, err = t.f1.Write([]byte("taco")) AssertEq(4, n) AssertEq(nil, err) - // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() AssertNe(nil, err) @@ -520,14 +518,13 @@ func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) // Delete the object. err = os.Remove(t.f1.Name()) AssertEq(nil, err) - // Attempt to write to file should result in stale NFS file handle error. + _, err = t.f1.Write([]byte("taco")) AssertNe(nil, err) @@ -550,7 +547,7 @@ func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandl n, err := t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) - // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() AssertNe(nil, err) @@ -572,7 +569,6 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) @@ -587,7 +583,7 @@ func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { n, err = t.f1.Write([]byte("taco")) AssertEq(nil, err) AssertEq(4, n) - // Attempt to sync the file should result in clobbered error. + err = t.f1.Sync() AssertNe(nil, err) @@ -609,14 +605,13 @@ func (t *StaleHandleTest) WritingToFileAfterObjectRenamedFailsWithStaleHandle() "foo", []byte("bar")) AssertEq(nil, err) - // Open file handle to write t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) AssertEq(nil, err) // Rename the object. err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) AssertEq(nil, err) - // Attempt to write to file should result in stale NFS file handle error. + _, err = t.f1.Write([]byte("taco")) AssertNe(nil, err) From 7f0bcc19cd562d4d02817e899814d9cfb95b8d15 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 9 Dec 2024 07:00:19 +0000 Subject: [PATCH 0044/1298] fixed formatting --- internal/fs/stale_file_handle_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go index 816417fa8d..1fca6c2265 100644 --- a/internal/fs/stale_file_handle_test.go +++ b/internal/fs/stale_file_handle_test.go @@ -114,7 +114,7 @@ func (t *StaleHandleTest) verifyLocalFileEntry(entry os.DirEntry, fileName strin AssertEq(fileName, entry.Name()) fileInfo, err := entry.Info() - + AssertEq(nil, err) AssertEq(size, fileInfo.Size()) } From e3c885d0c8103787a81a0b28664d801757e35e0e Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 10 Dec 2024 05:41:08 +0000 Subject: [PATCH 0045/1298] Used testify framework for testing and refactored code. --- internal/fs/stale_file_handle_test.go | 623 +++++------------- internal/storage/storageutil/delete_object.go | 33 + .../util/operations/file_operations.go | 25 + .../util/operations/validation_helper.go | 15 + 4 files changed, 249 insertions(+), 447 deletions(-) create mode 100644 internal/storage/storageutil/delete_object.go diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go index 1fca6c2265..7f5e54823f 100644 --- a/internal/fs/stale_file_handle_test.go +++ b/internal/fs/stale_file_handle_test.go @@ -15,17 +15,16 @@ package fs_test import ( - "errors" "os" "path" - "strings" "syscall" + "testing" "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - . "github.com/jacobsa/oglematchers" - . "github.com/jacobsa/ogletest" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) // ////////////////////////////////////////////////////////////////////// @@ -36,13 +35,14 @@ type StaleHandleTest struct { // fsTest has f1 *osFile and f2 *osFile which we will reuse here. f3 *os.File fsTest + suite.Suite } -func init() { - RegisterTestSuite(&StaleHandleTest{}) +func TestFileTestSuite(t *testing.T) { + suite.Run(t, new(StaleHandleTest)) } -func (t *StaleHandleTest) SetUpTestSuite() { +func (t *StaleHandleTest) SetupTest() { t.serverCfg.NewConfig = &cfg.Config{ FileSystem: cfg.FileSystemConfig{ PreconditionErrors: true, @@ -54,10 +54,10 @@ func (t *StaleHandleTest) SetUpTestSuite() { t.fsTest.SetUpTestSuite() } -func (t *StaleHandleTest) TearDown() { +func (t *StaleHandleTest) TearDownTest() { // Close t.f3 in case of test failure. if t.f3 != nil { - AssertEq(nil, t.f3.Close()) + assert.Equal(t.T(), nil, t.f3.Close()) t.f3 = nil } @@ -68,556 +68,285 @@ func (t *StaleHandleTest) TearDown() { // ////////////////////////////////////////////////////////////////////// // Helpers // ////////////////////////////////////////////////////////////////////// - -func (t *StaleHandleTest) createLocalFile(fileName string) (filePath string, f *os.File) { - // Creating a file shouldn't create file on GCS. - filePath = path.Join(mntDir, fileName) - _, err := os.Stat(mntDir) - AssertEq(nil, err) - - f, err = os.Create(filePath) - - AssertEq(nil, err) - t.validateObjectNotFoundErr(fileName) - return -} - -func (t *StaleHandleTest) validateObjectNotFoundErr(fileName string) { - var notFoundErr *gcs.NotFoundError - _, err := storageutil.ReadObject(ctx, bucket, fileName) - - ExpectTrue(errors.As(err, ¬FoundErr)) -} - -func (t *StaleHandleTest) validateNoFileOrDirError(filename string) { - _, err := os.Stat(path.Join(mntDir, filename)) - - AssertNe(nil, err) - AssertTrue(strings.Contains(err.Error(), "no such file or directory")) -} - -func (t *StaleHandleTest) closeLocalFile(f **os.File) error { - err := (*f).Close() - *f = nil - return err -} - -func (t *StaleHandleTest) readDirectory(dirPath string) (entries []os.DirEntry) { - entries, err := os.ReadDir(dirPath) - - AssertEq(nil, err) - return -} - -func (t *StaleHandleTest) verifyLocalFileEntry(entry os.DirEntry, fileName string, size int) { - AssertEq(false, entry.IsDir()) - AssertEq(fileName, entry.Name()) - - fileInfo, err := entry.Info() - - AssertEq(nil, err) - AssertEq(size, fileInfo.Size()) -} - -func (t *StaleHandleTest) closeFileAndValidateObjectContents(f **os.File, fileName string, contents string) { - err := t.closeLocalFile(f) - AssertEq(nil, err) - t.validateObjectContents(fileName, contents) -} - -func (t *StaleHandleTest) validateObjectContents(fileName string, contents string) { - contentBytes, err := storageutil.ReadObject(ctx, bucket, fileName) - AssertEq(nil, err) - ExpectEq(contents, string(contentBytes)) +func (t *StaleHandleTest) validateStaleNFSFileHandleError(err error) { + assert.NotEqual(t.T(), nil, err) + assert.Regexp(t.T(), "stale NFS file handle", err.Error()) } -//////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////// // Tests -//////////////////////////////////////////////////////////////////////// - -func (t *StaleHandleTest) StatOnUnlinkedLocalFile() { - // Create a local file. - var filePath string - filePath, t.f1 = t.createLocalFile(FileName) - // unlink the local file. - err := os.Remove(filePath) - AssertEq(nil, err) - - // Stat the local file and validate error. - t.validateNoFileOrDirError(FileName) - - // Validate that closing local unlinked file throws stale NFS file handle - // error and the object is not created on GCS. - err = t.closeLocalFile(&t.f1) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - t.validateObjectNotFoundErr(FileName) -} - -func (t *StaleHandleTest) TestReadDirContainingUnlinkedLocalFiles() { - // Create local files. - _, t.f1 = t.createLocalFile(FileName + "1") - _, t.f2 = t.createLocalFile(FileName + "2") - var filePath3 string - filePath3, t.f3 = t.createLocalFile(FileName + "3") - // Unlink local file 3 - err := os.Remove(filePath3) - AssertEq(nil, err) - - // Attempt to list mntDir. - entries := t.readDirectory(mntDir) - - // Verify unlinked entries are not listed. - AssertEq(2, len(entries)) - t.verifyLocalFileEntry(entries[0], FileName+"1", 0) - t.verifyLocalFileEntry(entries[1], FileName+"2", 0) - // Close the local files. - t.closeFileAndValidateObjectContents(&t.f1, FileName+"1", "") - t.closeFileAndValidateObjectContents(&t.f2, FileName+"2", "") - // Validate closing unlinked local file throws stale NFS file handle error. - err = t.closeLocalFile(&t.f3) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Verify unlinked file is not written to GCS - t.validateObjectNotFoundErr(FileName + "3") -} - -func (t *StaleHandleTest) TestUnlinkOfLocalFile() { - // Create empty local file. - var filePath string - filePath, t.f1 = t.createLocalFile(FileName) - - // Attempt to unlink local file. - err := os.Remove(filePath) - - // Verify unlink operation succeeds. - AssertEq(nil, err) - t.validateNoFileOrDirError(FileName) - // Validate closing unlinked local file throws stale NFS file handle error. - err = t.closeLocalFile(&t.f1) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Verify unlinked object is not present on GCS. - t.validateObjectNotFoundErr(FileName) -} - -func (t *StaleHandleTest) TestWriteOnUnlinkedLocalFileSucceeds() { - // Create local file and unlink. - var filePath string - filePath, t.f1 = t.createLocalFile(FileName) - err := os.Remove(filePath) - // Verify unlink operation succeeds. - AssertEq(nil, err) - t.validateNoFileOrDirError(FileName) - - // Write to unlinked local file. - _, err = t.f1.WriteString(FileContents) - - AssertEq(nil, err) - // Validate closing unlinked local file throws stale NFS file handle error. - err = t.closeLocalFile(&t.f1) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Verify unlinked file is not written to GCS - t.validateObjectNotFoundErr(FileName) -} - -func (t *StaleHandleTest) TestSyncOnUnlinkedLocalFile() { - // Create local file. - var filePath string - filePath, t.f1 = t.createLocalFile(FileName) - - // Attempt to unlink local file. - err := os.Remove(filePath) - - // Verify unlink operation succeeds. - AssertEq(nil, err) - t.validateNoFileOrDirError(FileName) - // Validate sync operation throws stale NFS file handle error - // and does not write to GCS after unlink. - err = t.f1.Sync() - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - t.validateObjectNotFoundErr(FileName) - // Validate closing unlinked local file also throws stale NFS file handle error. - err = t.closeLocalFile(&t.f1) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Verify unlinked file is not present on GCS. - t.validateObjectNotFoundErr(FileName) -} - -func (t *StaleHandleTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { - // Create explicit directory with one synced and one local file. - AssertEq( - nil, - t.createObjects( - map[string]string{ - // File - "explicit/": "", - "explicit/foo": "", - })) - _, t.f1 = t.createLocalFile("explicit/" + explicitLocalFileName) - - // Attempt to remove explicit directory. - err := os.RemoveAll(path.Join(mntDir, "explicit")) - - // Verify rmDir operation succeeds. - AssertEq(nil, err) - t.validateNoFileOrDirError("explicit/" + explicitLocalFileName) - t.validateNoFileOrDirError("explicit/foo") - t.validateNoFileOrDirError("explicit") - // Validate writing content to unlinked local file does not throw error - _, err = t.f1.WriteString(FileContents) - AssertEq(nil, err) - // Validate close file throws stale NFS file handle error and does not create - // object on GCS. - err = t.closeLocalFile(&t.f1) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - t.validateObjectNotFoundErr("explicit/" + explicitLocalFileName) - // Validate synced files are also deleted. - t.validateObjectNotFoundErr("explicit/foo") - t.validateObjectNotFoundErr("explicit/") -} - -func (t *StaleHandleTest) TestRmDirOfDirectoryContainingOnlyLocalFiles() { - // Create a directory with two local files. - err := os.Mkdir(path.Join(mntDir, "explicit"), dirPerms) - AssertEq(nil, err) - _, t.f1 = t.createLocalFile("explicit/" + explicitLocalFileName) - _, t.f2 = t.createLocalFile("explicit/" + FileName) - - // Attempt to remove explicit directory. - err = os.RemoveAll(path.Join(mntDir, "explicit")) - - // Verify rmDir operation succeeds. - AssertEq(nil, err) - t.validateNoFileOrDirError("explicit/" + explicitLocalFileName) - t.validateNoFileOrDirError("explicit/" + FileName) - t.validateNoFileOrDirError("explicit") - // Validate closing local unlinked files throw stale NFS file handle errors - // and do not create objects on GCS. - err = t.closeLocalFile(&t.f1) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - t.validateObjectNotFoundErr("explicit/" + explicitLocalFileName) - err = t.closeLocalFile(&t.f2) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - t.validateObjectNotFoundErr("explicit/" + FileName) - // Validate directory is also deleted. - t.validateObjectNotFoundErr("explicit/") -} - -func (t *StaleHandleTest) TestReadSymlinkForDeletedLocalFile() { - // Create a local file. - var filePath string - filePath, t.f1 = t.createLocalFile(FileName) - // Writing contents to local file shouldn't create file on GCS. - _, err := t.f1.Write([]byte(FileContents)) - AssertEq(nil, err) - t.validateObjectNotFoundErr(FileName) - // Create the symlink. - symlinkName := path.Join(mntDir, "bar") - err = os.Symlink(filePath, symlinkName) - AssertEq(nil, err) - // Read the link. - target, err := os.Readlink(symlinkName) - AssertEq(nil, err) - ExpectEq(filePath, target) - // Attempt to unlink local file. - err = os.Remove(filePath) - // Verify unlink operation succeeds. - AssertEq(nil, err) - // Validate flushing local unlinked file throws stale NFS file handle error - // and does not create object on GCS. - err = t.closeLocalFile(&t.f1) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - t.validateObjectNotFoundErr(FileName) - - // Reading symlink should fail. - _, err = os.Stat(symlinkName) - - AssertTrue(strings.Contains(err.Error(), "no such file or directory")) -} - -func (t *StaleHandleTest) SyncClobberedLocalInode() { - // Create a local file. - _, t.f1 = t.createLocalFile("foo") - // Dirty the file by giving it some contents. - n, err := t.f1.Write([]byte("taco")) - AssertEq(nil, err) - AssertEq(4, n) - // Replace the underlying object with a new generation. - _, err = storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("foobar")) - AssertEq(nil, err) - - // Attempt to sync the file should result in clobbered error. - err = t.f1.Sync() - - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Validate closing the file also throws error - err = t.closeLocalFile(&t.f1) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - contents, err := storageutil.ReadObject(ctx, bucket, "foo") - AssertEq(nil, err) - ExpectEq("foobar", string(contents)) -} - -func (t *StaleHandleTest) ReadingFileAfterObjectClobberedRemotelyFailsWithStaleHandle() { - // Create an object on bucket +// ////////////////////////////////////////////////////////////////////// +func (t *StaleHandleTest) TestSyncedObjectClobberedRemotely_Read_ThrowsStaleFileHandleError() { + // Create an object on bucket. _, err := storageutil.CreateObject( ctx, bucket, "foo", []byte("bar")) - AssertEq(nil, err) - // Open the read handle + assert.Equal(t.T(), nil, err) + // Open file handle to read. t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) + assert.NotEqual(t.T(), nil, err) // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, bucket, "foo", []byte("foobar")) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) - // Attempt to read the file should result in stale NFS file handle error. buffer := make([]byte, 6) _, err = t.f1.Read(buffer) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + t.validateStaleNFSFileHandleError(err) + // Validate that object is updated with new content. contents, err := storageutil.ReadObject(ctx, bucket, "foo") - AssertEq(nil, err) - ExpectEq("foobar", string(contents)) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "foobar", string(contents)) } -func (t *StaleHandleTest) WritingToFileAfterObjectClobberedRemotelyFailsWithStaleHandle() { - // Create an object on bucket +func (t *StaleHandleTest) TestSyncedObjectClobberedRemotely_FirstWrite_ThrowsStaleFileHandleError() { + // Create an object on bucket. _, err := storageutil.CreateObject( ctx, bucket, "foo", []byte("bar")) - AssertEq(nil, err) - // Open file handle to write + assert.Equal(t.T(), nil, err) + // Open file handle to write. t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, bucket, "foo", []byte("foobar")) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) - // Attempt to write to file should result in stale NFS file handle error. _, err = t.f1.Write([]byte("taco")) - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Attempt to sync to file should not result in error as content written is - // nil. + t.validateStaleNFSFileHandleError(err) + // Attempt to sync to file should not result in error as we first check if the + // content has been dirtied before clobbered check in Sync flow. err = t.f1.Sync() - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) + // Validate that object is not updated with new content as write failed. contents, err := storageutil.ReadObject(ctx, bucket, "foo") - AssertEq(nil, err) - ExpectEq("foobar", string(contents)) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "foobar", string(contents)) } -func (t *StaleHandleTest) SyncingFileAfterObjectClobberedRemotelyFailsWithStaleHandle() { - // Create an object on bucket +func (t *StaleHandleTest) TestLocalInodeClobberedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { + // Create a local file. + _, t.f1 = operations.CreateLocalFile(ctx, mntDir, bucket, "foo", t.T()) + // Dirty the file by giving it some contents. + n, err := t.f1.Write([]byte("taco")) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 4, n) + // Replace the underlying object with a new generation. + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("foobar")) + assert.Equal(t.T(), nil, err) + + err = t.f1.Sync() + + t.validateStaleNFSFileHandleError(err) + err = operations.CloseLocalFile(&t.f1, t.T()) + t.validateStaleNFSFileHandleError(err) + // Validate that local file content is not synced to GCS. + contents, err := storageutil.ReadObject(ctx, bucket, "foo") + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "foobar", string(contents)) +} + +func (t *StaleHandleTest) TestSyncedObjectClobberedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { + // Create an object on bucket. _, err := storageutil.CreateObject( ctx, bucket, "foo", []byte("bar")) - AssertEq(nil, err) - // Open file handle to write + assert.Equal(t.T(), nil, err) + // Open file handle to write. t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("taco")) - AssertEq(nil, err) - AssertEq(4, n) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 4, n) // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, bucket, "foo", []byte("foobar")) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) err = t.f1.Sync() - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Validate closing the file also throws error + t.validateStaleNFSFileHandleError(err) err = t.f1.Close() - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + t.validateStaleNFSFileHandleError(err) // Make f1 nil, so that another attempt is not taken in TearDown to close the - // file + // file. t.f1 = nil + // Validate that object is not updated with un-synced content. contents, err := storageutil.ReadObject(ctx, bucket, "foo") - AssertEq(nil, err) - ExpectEq("foobar", string(contents)) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "foobar", string(contents)) } -func (t *StaleHandleTest) SyncingFileAfterObjectDeletedFailsWithStaleHandle() { - // Create an object on bucket +func (t *StaleHandleTest) TestUnlinkedLocalInode_SyncAndClose_ThrowsStaleFileHandleError() { + // Create local file and unlink. + var filePath string + filePath, t.f1 = operations.CreateLocalFile(ctx, mntDir, bucket, FileName, t.T()) + err := os.Remove(filePath) + // Verify unlink operation succeeds. + assert.Equal(t.T(), nil, err) + operations.ValidateNoFileOrDirError(path.Join(mntDir, FileName), t.T()) + // Write to unlinked local file. + _, err = t.f1.WriteString(FileContents) + assert.Equal(t.T(), nil, err) + + err = t.f1.Sync() + + t.validateStaleNFSFileHandleError(err) + err = operations.CloseLocalFile(&t.f1, t.T()) + t.validateStaleNFSFileHandleError(err) + // Verify unlinked file is not present on GCS. + operations.ValidateObjectNotFoundErr(ctx, bucket, FileName, t.T()) +} + +func (t *StaleHandleTest) TestSyncedObjectDeletedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { + // Create an object on bucket. _, err := storageutil.CreateObject( ctx, bucket, "foo", []byte("bar")) - AssertEq(nil, err) - // Open file handle to write + assert.Equal(t.T(), nil, err) + // Open file handle to write. t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("foobar")) - AssertEq(nil, err) - AssertEq(6, n) - // Delete the object. - err = os.Remove(t.f1.Name()) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 6, n) + // Delete the object remotely. + err = storageutil.DeleteObject(ctx, bucket, "foo") + assert.Equal(t.T(), nil, err) // Attempt to write to file should not give any error. n, err = t.f1.Write([]byte("taco")) - AssertEq(4, n) - AssertEq(nil, err) + assert.Equal(t.T(), 4, n) + assert.Equal(t.T(), nil, err) err = t.f1.Sync() - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Closing file should also give error + t.validateStaleNFSFileHandleError(err) err = t.f1.Close() - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + t.validateStaleNFSFileHandleError(err) // Make f1 nil, so that another attempt is not taken in TearDown to close the - // file + // file. t.f1 = nil } -func (t *StaleHandleTest) WritingToFileAfterObjectDeletedFailsWithStaleHandle() { - // Create an object on bucket +func (t *StaleHandleTest) TestSyncedObjectDeletedLocally_SyncAndClose_ThrowsStaleFileHandleError() { + // Create an object on bucket. _, err := storageutil.CreateObject( ctx, bucket, "foo", []byte("bar")) - AssertEq(nil, err) - // Open file handle to write + assert.Equal(t.T(), nil, err) + // Open file handle to write. t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) - // Delete the object. + assert.Equal(t.T(), nil, err) + // Dirty the file by giving it some contents. + n, err := t.f1.Write([]byte("foobar")) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 6, n) + // Delete the object locally. err = os.Remove(t.f1.Name()) - AssertEq(nil, err) - - _, err = t.f1.Write([]byte("taco")) - - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Attempt to sync to file should not result in error as content written is - // nil. - err = t.f1.Sync() - AssertEq(nil, err) -} - -func (t *StaleHandleTest) SyncingLocalInodeAfterObjectDeletedFailsWithStaleHandle() { - // Create a local file. - _, t.f1 = t.createLocalFile("foo") - - // Delete the object. - err := os.Remove(t.f1.Name()) - AssertEq(nil, err) - // Attempt to write to file should not give any error as for local inode data - // is written to buffer, and we don't check for object on GCS. - n, err := t.f1.Write([]byte("taco")) - AssertEq(nil, err) - AssertEq(4, n) + assert.Equal(t.T(), nil, err) + // Attempt to write to file should not give any error. + n, err = t.f1.Write([]byte("taco")) + assert.Equal(t.T(), 4, n) + assert.Equal(t.T(), nil, err) err = t.f1.Sync() - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Closing file should also give error + t.validateStaleNFSFileHandleError(err) err = t.f1.Close() - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + t.validateStaleNFSFileHandleError(err) // Make f1 nil, so that another attempt is not taken in TearDown to close the - // file + // file. t.f1 = nil } -func (t *StaleHandleTest) SyncingFileAfterObjectRenamedFailsWithStaleHandle() { - // Create an object on bucket +func (t *StaleHandleTest) TestUnlinkedDirectoryContainingGCSAndLocalFiles_Close_ThrowsStaleFileHandleError() { + // Create explicit directory with one synced and one local file. + assert.Equal(t.T(), + nil, + t.createObjects( + map[string]string{ + // File + "explicit/": "", + "explicit/foo": "", + })) + _, t.f1 = operations.CreateLocalFile(ctx, mntDir, bucket, "explicit/"+explicitLocalFileName, t.T()) + // Attempt to remove explicit directory. + err := os.RemoveAll(path.Join(mntDir, "explicit")) + // Verify rmDir operation succeeds. + assert.Equal(t.T(), nil, err) + operations.ValidateNoFileOrDirError(path.Join(mntDir, "explicit/"+explicitLocalFileName), t.T()) + operations.ValidateNoFileOrDirError(path.Join(mntDir, "explicit/foo"), t.T()) + operations.ValidateNoFileOrDirError(path.Join(mntDir, "explicit"), t.T()) + // Validate writing content to unlinked local file does not throw error. + _, err = t.f1.WriteString(FileContents) + assert.Equal(t.T(), nil, err) + + err = operations.CloseLocalFile(&t.f1, t.T()) + + t.validateStaleNFSFileHandleError(err) + // Validate both local and synced files are deleted. + operations.ValidateObjectNotFoundErr(ctx, bucket, "explicit/"+explicitLocalFileName, t.T()) + operations.ValidateObjectNotFoundErr(ctx, bucket, "explicit/foo", t.T()) + operations.ValidateObjectNotFoundErr(ctx, bucket, "explicit/", t.T()) +} + +func (t *StaleHandleTest) TestRenamedSyncedObject_Sync_ThrowsStaleFileHandleError() { + // Create an object on bucket. _, err := storageutil.CreateObject( ctx, bucket, "foo", []byte("bar")) - AssertEq(nil, err) - // Open file handle to write + assert.Equal(t.T(), nil, err) + // Open file handle to write. t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("foobar")) - AssertEq(nil, err) - AssertEq(6, n) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 6, n) // Rename the object. err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // Attempt to write to file should not give any error. n, err = t.f1.Write([]byte("taco")) - AssertEq(nil, err) - AssertEq(4, n) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 4, n) err = t.f1.Sync() - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Closing file should also give error + t.validateStaleNFSFileHandleError(err) err = t.f1.Close() - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) + t.validateStaleNFSFileHandleError(err) // Make f1 nil, so that another attempt is not taken in TearDown to close the - // file + // file. t.f1 = nil } - -func (t *StaleHandleTest) WritingToFileAfterObjectRenamedFailsWithStaleHandle() { - // Create an object on bucket - _, err := storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("bar")) - AssertEq(nil, err) - // Open file handle to write - t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - AssertEq(nil, err) - // Rename the object. - err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) - AssertEq(nil, err) - - _, err = t.f1.Write([]byte("taco")) - - AssertNe(nil, err) - ExpectThat(err, Error(HasSubstr("stale NFS file handle"))) - // Attempt to sync to file should not result in error as content written is - // nil. - err = t.f1.Sync() - AssertEq(nil, err) -} diff --git a/internal/storage/storageutil/delete_object.go b/internal/storage/storageutil/delete_object.go new file mode 100644 index 0000000000..31deb5953d --- /dev/null +++ b/internal/storage/storageutil/delete_object.go @@ -0,0 +1,33 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package storageutil + +import ( + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "golang.org/x/net/context" +) + +// DeleteObject deletes an object in the given bucket with the given name. +func DeleteObject( + ctx context.Context, + bucket gcs.Bucket, + name string) error { + req := &gcs.DeleteObjectRequest{ + Name: name, + Generation: 0, + } + + return bucket.DeleteObject(ctx, req) +} diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 5ada65f988..a3dd327779 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -31,6 +31,10 @@ import ( "syscall" "testing" "time" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" ) const ( @@ -683,3 +687,24 @@ func CreateLocalTempFile(content string, gzipCompress bool) (string, error) { return writeTextToFile(f, f.Name(), content, len(content)) } + +func CreateLocalFile(ctx context.Context, mntDir string, bucket gcs.Bucket, fileName string, t *testing.T) (filePath string, f *os.File) { + t.Helper() + // Creating a file shouldn't create file on GCS. + filePath = path.Join(mntDir, fileName) + _, err := os.Stat(mntDir) + assert.Equal(t, nil, err) + + f, err = os.Create(filePath) + + assert.Equal(t, nil, err) + ValidateObjectNotFoundErr(ctx, bucket, fileName, t) + return +} + +func CloseLocalFile(f **os.File, t *testing.T) error { + t.Helper() + err := (*f).Close() + *f = nil + return err +} diff --git a/tools/integration_tests/util/operations/validation_helper.go b/tools/integration_tests/util/operations/validation_helper.go index 9fa2a7dce2..1c42c72e72 100644 --- a/tools/integration_tests/util/operations/validation_helper.go +++ b/tools/integration_tests/util/operations/validation_helper.go @@ -15,12 +15,19 @@ package operations import ( + "context" + "errors" "os" "strings" "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/stretchr/testify/assert" ) func ValidateNoFileOrDirError(path string, t *testing.T) { + t.Helper() _, err := os.Stat(path) if err == nil || !strings.Contains(err.Error(), "no such file or directory") { t.Fatalf("os.Stat(%s). Expected: %s, Got: %v", path, @@ -28,6 +35,14 @@ func ValidateNoFileOrDirError(path string, t *testing.T) { } } +func ValidateObjectNotFoundErr(ctx context.Context, bucket gcs.Bucket, fileName string, t *testing.T) { + t.Helper() + var notFoundErr *gcs.NotFoundError + _, err := storageutil.ReadObject(ctx, bucket, fileName) + + assert.True(t, errors.As(err, ¬FoundErr)) +} + func CheckErrorForReadOnlyFileSystem(err error, t *testing.T) { if err == nil { t.Error("permission denied error expected but got nil error.") From e0722fc867961cd242a860c6a0ac13212c8784db Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 10 Dec 2024 09:21:16 +0000 Subject: [PATCH 0046/1298] Added t.TearDownTestSuite() --- internal/fs/stale_file_handle_test.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go index 7f5e54823f..c651d40656 100644 --- a/internal/fs/stale_file_handle_test.go +++ b/internal/fs/stale_file_handle_test.go @@ -33,7 +33,6 @@ import ( type StaleHandleTest struct { // fsTest has f1 *osFile and f2 *osFile which we will reuse here. - f3 *os.File fsTest suite.Suite } @@ -55,14 +54,9 @@ func (t *StaleHandleTest) SetupTest() { } func (t *StaleHandleTest) TearDownTest() { - // Close t.f3 in case of test failure. - if t.f3 != nil { - assert.Equal(t.T(), nil, t.f3.Close()) - t.f3 = nil - } - // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. t.fsTest.TearDown() + t.TearDownTestSuite() } // ////////////////////////////////////////////////////////////////////// @@ -86,7 +80,7 @@ func (t *StaleHandleTest) TestSyncedObjectClobberedRemotely_Read_ThrowsStaleFile assert.Equal(t.T(), nil, err) // Open file handle to read. t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDONLY|syscall.O_DIRECT, filePerms) - assert.NotEqual(t.T(), nil, err) + assert.Equal(t.T(), nil, err) // Replace the underlying object with a new generation. _, err = storageutil.CreateObject( ctx, From 9c10d53ddecbd4af8b734b72e1bf218c28224665 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 10 Dec 2024 15:53:07 +0530 Subject: [PATCH 0047/1298] Prevent unit test failure due to flag parse (#2764) * prevent unit test failure due to flag parse * add return * small fix --- .../emulator_tests/proxy_server/main_test.go | 10 ++++++++++ tools/integration_tests/util/setup/setup.go | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/tools/integration_tests/emulator_tests/proxy_server/main_test.go b/tools/integration_tests/emulator_tests/proxy_server/main_test.go index 4006cd1014..d5b71664c3 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/main_test.go +++ b/tools/integration_tests/emulator_tests/proxy_server/main_test.go @@ -20,6 +20,7 @@ import ( "net/http/httptest" "testing" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" ) @@ -49,3 +50,12 @@ func TestAddRetryID(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "test-id-123", req.Header.Get("x-retry-test-id"), "Unexpected x-retry-test-id header value") } + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + // Skip running unit tests as part of the integration tests. Although running these tests will not cause any failures. + if setup.IsIntegrationTest() { + return + } +} diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index eacad09a37..3fedf1ad04 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -75,6 +75,10 @@ func IsPresubmitRun() bool { return *isPresubmitRun } +func IsIntegrationTest() bool { + return *integrationTest +} + func TestBucket() string { return *testBucket } From 4331b8fddf2bb5f7d6cded6d163cad746b1bc4d7 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 10 Dec 2024 13:00:38 +0000 Subject: [PATCH 0048/1298] Created two test suites. --- .../fs/stale_file_handle_local_file_test.go | 151 ++++++++ .../fs/stale_file_handle_synced_file_test.go | 221 +++++++++++ internal/fs/stale_file_handle_test.go | 346 ------------------ .../local_file/remove_dir_test.go | 4 +- .../local_file/unlinked_file_test.go | 6 +- .../managed_folders/admin_permissions_test.go | 4 +- .../managed_folders/test_helper.go | 6 +- .../managed_folders/view_permissions_test.go | 6 +- .../readonly/create_object_test.go | 4 +- .../readonly/delete_object_test.go | 2 +- .../readonly/rename_object_test.go | 4 +- .../integration_tests/readonly/write_test.go | 4 +- .../util/operations/file_operations.go | 10 +- .../util/operations/validation_helper.go | 11 +- 14 files changed, 404 insertions(+), 375 deletions(-) create mode 100644 internal/fs/stale_file_handle_local_file_test.go create mode 100644 internal/fs/stale_file_handle_synced_file_test.go delete mode 100644 internal/fs/stale_file_handle_test.go diff --git a/internal/fs/stale_file_handle_local_file_test.go b/internal/fs/stale_file_handle_local_file_test.go new file mode 100644 index 0000000000..f4df02a68d --- /dev/null +++ b/internal/fs/stale_file_handle_local_file_test.go @@ -0,0 +1,151 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs_test + +import ( + "os" + "path" + "path/filepath" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type StaleFileHandleLocalFile struct { + // fsTest has f1 *osFile and f2 *osFile which we will reuse here. + fsTest + suite.Suite +} + +func TestStaleFileHandleLocalFile(t *testing.T) { + suite.Run(t, new(StaleFileHandleLocalFile)) +} + +func (t *StaleFileHandleLocalFile) SetupSuite() { + t.serverCfg.NewConfig = &cfg.Config{ + FileSystem: cfg.FileSystemConfig{ + PreconditionErrors: true, + }, + MetadataCache: cfg.MetadataCacheConfig{ + TtlSecs: 0, + }, + } + t.fsTest.SetUpTestSuite() +} + +func (t *StaleFileHandleLocalFile) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} + +func (t *StaleFileHandleLocalFile) SetupTest() { + // Create a local file. + _, t.f1 = operations.CreateLocalFile(ctx, t.T(), mntDir, bucket, "foo") +} + +func (t *StaleFileHandleLocalFile) TearDownTest() { + filePath := filepath.Join(mntDir, "foo") + if _, err := os.Stat(filePath); err == nil { + err = os.Remove(filePath) + assert.Equal(t.T(), nil, err) + } + + // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. + t.fsTest.TearDown() +} + +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// +func (t *StaleFileHandleLocalFile) TestLocalInodeClobberedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { + // Dirty the local file by giving it some contents. + n, err := t.f1.Write([]byte("taco")) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 4, n) + // Replace the underlying object with a new generation. + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("foobar")) + assert.Equal(t.T(), nil, err) + + err = t.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = operations.CloseLocalFile(t.T(), &t.f1) + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Validate that local file content is not synced to GCS. + contents, err := storageutil.ReadObject(ctx, bucket, "foo") + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "foobar", string(contents)) +} + +func (t *StaleFileHandleLocalFile) TestUnlinkedLocalInode_SyncAndClose_ThrowsStaleFileHandleError() { + // Unlink the local file. + err := os.Remove(t.f1.Name()) + // Verify unlink operation succeeds. + assert.Equal(t.T(), nil, err) + operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, FileName)) + // Write to unlinked local file. + _, err = t.f1.WriteString(FileContents) + assert.Equal(t.T(), nil, err) + + err = t.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = operations.CloseLocalFile(t.T(), &t.f1) + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Verify unlinked file is not present on GCS. + operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, FileName) +} + +func (t *StaleFileHandleLocalFile) TestUnlinkedDirectoryContainingSyncedAndLocalFiles_Close_ThrowsStaleFileHandleError() { + // Create explicit directory with one synced and one local file. + assert.Equal(t.T(), + nil, + t.createObjects( + map[string]string{ + // File + "explicit/": "", + "explicit/foo": "", + })) + _, t.f1 = operations.CreateLocalFile(ctx, t.T(), mntDir, bucket, "explicit/"+explicitLocalFileName) + // Attempt to remove explicit directory. + err := os.RemoveAll(path.Join(mntDir, "explicit")) + // Verify rmDir operation succeeds. + assert.Equal(t.T(), nil, err) + operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, "explicit/"+explicitLocalFileName)) + operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, "explicit/foo")) + operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, "explicit")) + // Validate writing content to unlinked local file does not throw error. + _, err = t.f1.WriteString(FileContents) + assert.Equal(t.T(), nil, err) + + err = operations.CloseLocalFile(t.T(), &t.f1) + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Validate both local and synced files are deleted. + operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, "explicit/"+explicitLocalFileName) + operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, "explicit/foo") + operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, "explicit/") +} diff --git a/internal/fs/stale_file_handle_synced_file_test.go b/internal/fs/stale_file_handle_synced_file_test.go new file mode 100644 index 0000000000..93616a0bef --- /dev/null +++ b/internal/fs/stale_file_handle_synced_file_test.go @@ -0,0 +1,221 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs_test + +import ( + "os" + "path" + "syscall" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type StaleFileHandleSyncedFile struct { + // fsTest has f1 *osFile and f2 *osFile which we will reuse here. + fsTest + suite.Suite +} + +func TestStaleFileHandleSyncedFile(t *testing.T) { + suite.Run(t, new(StaleFileHandleSyncedFile)) +} + +func (t *StaleFileHandleSyncedFile) SetupSuite() { + t.serverCfg.NewConfig = &cfg.Config{ + FileSystem: cfg.FileSystemConfig{ + PreconditionErrors: true, + }, + MetadataCache: cfg.MetadataCacheConfig{ + TtlSecs: 0, + }, + } + t.fsTest.SetUpTestSuite() +} + +func (t *StaleFileHandleSyncedFile) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} +func (t *StaleFileHandleSyncedFile) SetupTest() { + // Create an object on bucket. + _, err := storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("bar")) + assert.Equal(t.T(), nil, err) + // Open file handle to read or write. + t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDWR|syscall.O_DIRECT, filePerms) + assert.Equal(t.T(), nil, err) +} + +func (t *StaleFileHandleSyncedFile) TearDownTest() { + err := storageutil.DeleteObject( + ctx, + bucket, + "foo") + assert.Equal(t.T(), nil, err) + + // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. + t.fsTest.TearDown() +} + +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// +func (t *StaleFileHandleSyncedFile) TestSyncedObjectClobberedRemotely_Read_ThrowsStaleFileHandleError() { + // Replace the underlying object with a new generation. + _, err := storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("foobar")) + assert.Equal(t.T(), nil, err) + + buffer := make([]byte, 6) + _, err = t.f1.Read(buffer) + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Validate that object is updated with new content. + contents, err := storageutil.ReadObject(ctx, bucket, "foo") + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "foobar", string(contents)) +} + +func (t *StaleFileHandleSyncedFile) TestSyncedObjectClobberedRemotely_FirstWrite_ThrowsStaleFileHandleError() { + // Replace the underlying object with a new generation. + _, err := storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("foobar")) + assert.Equal(t.T(), nil, err) + + _, err = t.f1.Write([]byte("taco")) + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Attempt to sync to file should not result in error as we first check if the + // content has been dirtied before clobbered check in Sync flow. + err = t.f1.Sync() + assert.Equal(t.T(), nil, err) + // Validate that object is not updated with new content as write failed. + contents, err := storageutil.ReadObject(ctx, bucket, "foo") + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "foobar", string(contents)) +} + +func (t *StaleFileHandleSyncedFile) TestSyncedObjectClobberedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + n, err := t.f1.Write([]byte("taco")) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 4, n) + // Replace the underlying object with a new generation. + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("foobar")) + assert.Equal(t.T(), nil, err) + + err = t.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = t.f1.Close() + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file. + t.f1 = nil + // Validate that object is not updated with un-synced content. + contents, err := storageutil.ReadObject(ctx, bucket, "foo") + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "foobar", string(contents)) +} + +func (t *StaleFileHandleSyncedFile) TestSyncedObjectDeletedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + n, err := t.f1.Write([]byte("foobar")) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 6, n) + // Delete the object remotely. + err = storageutil.DeleteObject(ctx, bucket, "foo") + assert.Equal(t.T(), nil, err) + // Attempt to write to file should not give any error. + n, err = t.f1.Write([]byte("taco")) + assert.Equal(t.T(), 4, n) + assert.Equal(t.T(), nil, err) + + err = t.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = t.f1.Close() + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file. + t.f1 = nil +} + +func (t *StaleFileHandleSyncedFile) TestSyncedObjectDeletedLocally_SyncAndClose_ThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + n, err := t.f1.Write([]byte("foobar")) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 6, n) + // Delete the object locally. + err = os.Remove(t.f1.Name()) + assert.Equal(t.T(), nil, err) + // Attempt to write to file should not give any error. + n, err = t.f1.Write([]byte("taco")) + assert.Equal(t.T(), 4, n) + assert.Equal(t.T(), nil, err) + + err = t.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = t.f1.Close() + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file. + t.f1 = nil +} + +func (t *StaleFileHandleSyncedFile) TestRenamedSyncedObject_Sync_ThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + n, err := t.f1.Write([]byte("foobar")) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 6, n) + // Rename the object. + err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) + assert.Equal(t.T(), nil, err) + // Attempt to write to file should not give any error. + n, err = t.f1.Write([]byte("taco")) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), 4, n) + + err = t.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = t.f1.Close() + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file. + t.f1 = nil +} diff --git a/internal/fs/stale_file_handle_test.go b/internal/fs/stale_file_handle_test.go deleted file mode 100644 index c651d40656..0000000000 --- a/internal/fs/stale_file_handle_test.go +++ /dev/null @@ -1,346 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package fs_test - -import ( - "os" - "path" - "syscall" - "testing" - - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" -) - -// ////////////////////////////////////////////////////////////////////// -// Boilerplate -// ////////////////////////////////////////////////////////////////////// - -type StaleHandleTest struct { - // fsTest has f1 *osFile and f2 *osFile which we will reuse here. - fsTest - suite.Suite -} - -func TestFileTestSuite(t *testing.T) { - suite.Run(t, new(StaleHandleTest)) -} - -func (t *StaleHandleTest) SetupTest() { - t.serverCfg.NewConfig = &cfg.Config{ - FileSystem: cfg.FileSystemConfig{ - PreconditionErrors: true, - }, - MetadataCache: cfg.MetadataCacheConfig{ - TtlSecs: 0, - }, - } - t.fsTest.SetUpTestSuite() -} - -func (t *StaleHandleTest) TearDownTest() { - // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. - t.fsTest.TearDown() - t.TearDownTestSuite() -} - -// ////////////////////////////////////////////////////////////////////// -// Helpers -// ////////////////////////////////////////////////////////////////////// -func (t *StaleHandleTest) validateStaleNFSFileHandleError(err error) { - assert.NotEqual(t.T(), nil, err) - assert.Regexp(t.T(), "stale NFS file handle", err.Error()) -} - -// ////////////////////////////////////////////////////////////////////// -// Tests -// ////////////////////////////////////////////////////////////////////// -func (t *StaleHandleTest) TestSyncedObjectClobberedRemotely_Read_ThrowsStaleFileHandleError() { - // Create an object on bucket. - _, err := storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("bar")) - assert.Equal(t.T(), nil, err) - // Open file handle to read. - t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDONLY|syscall.O_DIRECT, filePerms) - assert.Equal(t.T(), nil, err) - // Replace the underlying object with a new generation. - _, err = storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("foobar")) - assert.Equal(t.T(), nil, err) - - buffer := make([]byte, 6) - _, err = t.f1.Read(buffer) - - t.validateStaleNFSFileHandleError(err) - // Validate that object is updated with new content. - contents, err := storageutil.ReadObject(ctx, bucket, "foo") - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), "foobar", string(contents)) -} - -func (t *StaleHandleTest) TestSyncedObjectClobberedRemotely_FirstWrite_ThrowsStaleFileHandleError() { - // Create an object on bucket. - _, err := storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("bar")) - assert.Equal(t.T(), nil, err) - // Open file handle to write. - t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - assert.Equal(t.T(), nil, err) - // Replace the underlying object with a new generation. - _, err = storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("foobar")) - assert.Equal(t.T(), nil, err) - - _, err = t.f1.Write([]byte("taco")) - - t.validateStaleNFSFileHandleError(err) - // Attempt to sync to file should not result in error as we first check if the - // content has been dirtied before clobbered check in Sync flow. - err = t.f1.Sync() - assert.Equal(t.T(), nil, err) - // Validate that object is not updated with new content as write failed. - contents, err := storageutil.ReadObject(ctx, bucket, "foo") - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), "foobar", string(contents)) -} - -func (t *StaleHandleTest) TestLocalInodeClobberedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { - // Create a local file. - _, t.f1 = operations.CreateLocalFile(ctx, mntDir, bucket, "foo", t.T()) - // Dirty the file by giving it some contents. - n, err := t.f1.Write([]byte("taco")) - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), 4, n) - // Replace the underlying object with a new generation. - _, err = storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("foobar")) - assert.Equal(t.T(), nil, err) - - err = t.f1.Sync() - - t.validateStaleNFSFileHandleError(err) - err = operations.CloseLocalFile(&t.f1, t.T()) - t.validateStaleNFSFileHandleError(err) - // Validate that local file content is not synced to GCS. - contents, err := storageutil.ReadObject(ctx, bucket, "foo") - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), "foobar", string(contents)) -} - -func (t *StaleHandleTest) TestSyncedObjectClobberedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { - // Create an object on bucket. - _, err := storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("bar")) - assert.Equal(t.T(), nil, err) - // Open file handle to write. - t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - assert.Equal(t.T(), nil, err) - // Dirty the file by giving it some contents. - n, err := t.f1.Write([]byte("taco")) - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), 4, n) - // Replace the underlying object with a new generation. - _, err = storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("foobar")) - assert.Equal(t.T(), nil, err) - - err = t.f1.Sync() - - t.validateStaleNFSFileHandleError(err) - err = t.f1.Close() - t.validateStaleNFSFileHandleError(err) - // Make f1 nil, so that another attempt is not taken in TearDown to close the - // file. - t.f1 = nil - // Validate that object is not updated with un-synced content. - contents, err := storageutil.ReadObject(ctx, bucket, "foo") - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), "foobar", string(contents)) -} - -func (t *StaleHandleTest) TestUnlinkedLocalInode_SyncAndClose_ThrowsStaleFileHandleError() { - // Create local file and unlink. - var filePath string - filePath, t.f1 = operations.CreateLocalFile(ctx, mntDir, bucket, FileName, t.T()) - err := os.Remove(filePath) - // Verify unlink operation succeeds. - assert.Equal(t.T(), nil, err) - operations.ValidateNoFileOrDirError(path.Join(mntDir, FileName), t.T()) - // Write to unlinked local file. - _, err = t.f1.WriteString(FileContents) - assert.Equal(t.T(), nil, err) - - err = t.f1.Sync() - - t.validateStaleNFSFileHandleError(err) - err = operations.CloseLocalFile(&t.f1, t.T()) - t.validateStaleNFSFileHandleError(err) - // Verify unlinked file is not present on GCS. - operations.ValidateObjectNotFoundErr(ctx, bucket, FileName, t.T()) -} - -func (t *StaleHandleTest) TestSyncedObjectDeletedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { - // Create an object on bucket. - _, err := storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("bar")) - assert.Equal(t.T(), nil, err) - // Open file handle to write. - t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - assert.Equal(t.T(), nil, err) - // Dirty the file by giving it some contents. - n, err := t.f1.Write([]byte("foobar")) - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), 6, n) - // Delete the object remotely. - err = storageutil.DeleteObject(ctx, bucket, "foo") - assert.Equal(t.T(), nil, err) - // Attempt to write to file should not give any error. - n, err = t.f1.Write([]byte("taco")) - assert.Equal(t.T(), 4, n) - assert.Equal(t.T(), nil, err) - - err = t.f1.Sync() - - t.validateStaleNFSFileHandleError(err) - err = t.f1.Close() - t.validateStaleNFSFileHandleError(err) - // Make f1 nil, so that another attempt is not taken in TearDown to close the - // file. - t.f1 = nil -} - -func (t *StaleHandleTest) TestSyncedObjectDeletedLocally_SyncAndClose_ThrowsStaleFileHandleError() { - // Create an object on bucket. - _, err := storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("bar")) - assert.Equal(t.T(), nil, err) - // Open file handle to write. - t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - assert.Equal(t.T(), nil, err) - // Dirty the file by giving it some contents. - n, err := t.f1.Write([]byte("foobar")) - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), 6, n) - // Delete the object locally. - err = os.Remove(t.f1.Name()) - assert.Equal(t.T(), nil, err) - // Attempt to write to file should not give any error. - n, err = t.f1.Write([]byte("taco")) - assert.Equal(t.T(), 4, n) - assert.Equal(t.T(), nil, err) - - err = t.f1.Sync() - - t.validateStaleNFSFileHandleError(err) - err = t.f1.Close() - t.validateStaleNFSFileHandleError(err) - // Make f1 nil, so that another attempt is not taken in TearDown to close the - // file. - t.f1 = nil -} - -func (t *StaleHandleTest) TestUnlinkedDirectoryContainingGCSAndLocalFiles_Close_ThrowsStaleFileHandleError() { - // Create explicit directory with one synced and one local file. - assert.Equal(t.T(), - nil, - t.createObjects( - map[string]string{ - // File - "explicit/": "", - "explicit/foo": "", - })) - _, t.f1 = operations.CreateLocalFile(ctx, mntDir, bucket, "explicit/"+explicitLocalFileName, t.T()) - // Attempt to remove explicit directory. - err := os.RemoveAll(path.Join(mntDir, "explicit")) - // Verify rmDir operation succeeds. - assert.Equal(t.T(), nil, err) - operations.ValidateNoFileOrDirError(path.Join(mntDir, "explicit/"+explicitLocalFileName), t.T()) - operations.ValidateNoFileOrDirError(path.Join(mntDir, "explicit/foo"), t.T()) - operations.ValidateNoFileOrDirError(path.Join(mntDir, "explicit"), t.T()) - // Validate writing content to unlinked local file does not throw error. - _, err = t.f1.WriteString(FileContents) - assert.Equal(t.T(), nil, err) - - err = operations.CloseLocalFile(&t.f1, t.T()) - - t.validateStaleNFSFileHandleError(err) - // Validate both local and synced files are deleted. - operations.ValidateObjectNotFoundErr(ctx, bucket, "explicit/"+explicitLocalFileName, t.T()) - operations.ValidateObjectNotFoundErr(ctx, bucket, "explicit/foo", t.T()) - operations.ValidateObjectNotFoundErr(ctx, bucket, "explicit/", t.T()) -} - -func (t *StaleHandleTest) TestRenamedSyncedObject_Sync_ThrowsStaleFileHandleError() { - // Create an object on bucket. - _, err := storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("bar")) - assert.Equal(t.T(), nil, err) - // Open file handle to write. - t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_WRONLY|syscall.O_DIRECT, filePerms) - assert.Equal(t.T(), nil, err) - // Dirty the file by giving it some contents. - n, err := t.f1.Write([]byte("foobar")) - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), 6, n) - // Rename the object. - err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) - assert.Equal(t.T(), nil, err) - // Attempt to write to file should not give any error. - n, err = t.f1.Write([]byte("taco")) - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), 4, n) - - err = t.f1.Sync() - - t.validateStaleNFSFileHandleError(err) - err = t.f1.Close() - t.validateStaleNFSFileHandleError(err) - // Make f1 nil, so that another attempt is not taken in TearDown to close the - // file. - t.f1 = nil -} diff --git a/tools/integration_tests/local_file/remove_dir_test.go b/tools/integration_tests/local_file/remove_dir_test.go index b93c6e074c..bba007655e 100644 --- a/tools/integration_tests/local_file/remove_dir_test.go +++ b/tools/integration_tests/local_file/remove_dir_test.go @@ -38,7 +38,7 @@ func TestRmDirOfDirectoryContainingGCSAndLocalFiles(t *testing.T) { operations.RemoveDir(path.Join(testDirPath, ExplicitDirName)) // Verify that directory is removed. - operations.ValidateNoFileOrDirError(path.Join(testDirPath, ExplicitDirName), t) + operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, ExplicitDirName)) // Validate writing content to unlinked local file does not throw error. operations.WriteWithoutClose(fh2, FileContents, t) // Validate flush file does not throw error and does not create object on GCS. @@ -62,7 +62,7 @@ func TestRmDirOfDirectoryContainingOnlyLocalFiles(t *testing.T) { operations.RemoveDir(path.Join(testDirPath, ExplicitDirName)) // Verify rmDir operation succeeds. - operations.ValidateNoFileOrDirError(path.Join(testDirPath, ExplicitDirName), t) + operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, ExplicitDirName)) // Close the local files and validate they are not present on GCS. operations.CloseFileShouldNotThrowError(fh1, t) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile1, t) diff --git a/tools/integration_tests/local_file/unlinked_file_test.go b/tools/integration_tests/local_file/unlinked_file_test.go index d1db7ae3c4..f9bac017a6 100644 --- a/tools/integration_tests/local_file/unlinked_file_test.go +++ b/tools/integration_tests/local_file/unlinked_file_test.go @@ -32,7 +32,7 @@ func TestStatOnUnlinkedLocalFile(t *testing.T) { operations.RemoveFile(filePath) // Stat the local file and validate error. - operations.ValidateNoFileOrDirError(path.Join(testDirPath, FileName1), t) + operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) // Close the file and validate that file is not created on GCS. operations.CloseFileShouldNotThrowError(fh, t) @@ -71,7 +71,7 @@ func TestWriteOnUnlinkedLocalFileSucceeds(t *testing.T) { filepath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) // Verify unlink operation succeeds. operations.RemoveFile(filepath) - operations.ValidateNoFileOrDirError(path.Join(testDirPath, FileName1), t) + operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) // Write to unlinked local file. operations.WriteWithoutClose(fh, FileContents, t) @@ -91,7 +91,7 @@ func TestSyncOnUnlinkedLocalFile(t *testing.T) { operations.RemoveFile(filepath) // Verify unlink operation succeeds. - operations.ValidateNoFileOrDirError(path.Join(testDirPath, FileName1), t) + operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) // Validate sync operation does not write to GCS after unlink. operations.SyncFile(fh, t) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) diff --git a/tools/integration_tests/managed_folders/admin_permissions_test.go b/tools/integration_tests/managed_folders/admin_permissions_test.go index 8f20bb96c8..57cff598d8 100644 --- a/tools/integration_tests/managed_folders/admin_permissions_test.go +++ b/tools/integration_tests/managed_folders/admin_permissions_test.go @@ -137,7 +137,7 @@ func (s *managedFoldersAdminPermission) TestCopyManagedFolder(t *testing.T) { err := operations.CopyDir(srcDirPath, destDirPath) if s.bucketPermission == ViewPermission { - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) } else { _, err = os.Stat(destDirPath) if err != nil { @@ -173,7 +173,7 @@ func (s *managedFoldersAdminPermission) TestMoveManagedFolder(t *testing.T) { err := operations.Move(srcDirPath, destDirPath) if s.bucketPermission == ViewPermission { - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) } else { _, err = os.Stat(destDirPath) if err != nil { diff --git a/tools/integration_tests/managed_folders/test_helper.go b/tools/integration_tests/managed_folders/test_helper.go index 9f23fee1c3..edfe4afd84 100644 --- a/tools/integration_tests/managed_folders/test_helper.go +++ b/tools/integration_tests/managed_folders/test_helper.go @@ -231,7 +231,7 @@ func copyDirAndCheckErrForViewPermission(src, dest string, t *testing.T) { t.Errorf(" Managed folder unexpectedly got copied with view only permission.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) } func copyObjectAndCheckErrForViewPermission(src, dest string, t *testing.T) { @@ -240,7 +240,7 @@ func copyObjectAndCheckErrForViewPermission(src, dest string, t *testing.T) { t.Errorf("Objects in managed folder unexpectedly got copied with view only permission.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) } func moveAndCheckErrForViewPermission(src, dest string, t *testing.T) { @@ -249,7 +249,7 @@ func moveAndCheckErrForViewPermission(src, dest string, t *testing.T) { t.Errorf("Objects in managed folder unexpectedly got moved with view only permission.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) } func createFileForTest(filePath string, t *testing.T) { diff --git a/tools/integration_tests/managed_folders/view_permissions_test.go b/tools/integration_tests/managed_folders/view_permissions_test.go index 52d81db849..a18b3cadc4 100644 --- a/tools/integration_tests/managed_folders/view_permissions_test.go +++ b/tools/integration_tests/managed_folders/view_permissions_test.go @@ -64,7 +64,7 @@ func (s *managedFoldersViewPermission) TestCreateObjectInManagedFolder(t *testin } t.Cleanup(func() { err = file.Close() - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) }) } @@ -75,7 +75,7 @@ func (s *managedFoldersViewPermission) TestDeleteObjectFromManagedFolder(t *test t.Errorf("File from managed folder gets deleted with view only permission.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) } func (s *managedFoldersViewPermission) TestDeleteNonEmptyManagedFolder(t *testing.T) { @@ -85,7 +85,7 @@ func (s *managedFoldersViewPermission) TestDeleteNonEmptyManagedFolder(t *testin t.Errorf("Managed folder deleted with view only permission.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) } func (s *managedFoldersViewPermission) TestMoveManagedFolder(t *testing.T) { diff --git a/tools/integration_tests/readonly/create_object_test.go b/tools/integration_tests/readonly/create_object_test.go index 9797e8c4fd..e5d25aa2f9 100644 --- a/tools/integration_tests/readonly/create_object_test.go +++ b/tools/integration_tests/readonly/create_object_test.go @@ -33,7 +33,7 @@ func checkIfFileCreationFailed(filePath string, t *testing.T) { t.Errorf("File is created in read-only file system.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) defer file.Close() } @@ -57,7 +57,7 @@ func checkIfDirCreationFailed(dirPath string, t *testing.T) { t.Errorf("Directory is created in read-only file system.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) } func TestCreateDir(t *testing.T) { diff --git a/tools/integration_tests/readonly/delete_object_test.go b/tools/integration_tests/readonly/delete_object_test.go index b548bdb4eb..d874e4eec9 100644 --- a/tools/integration_tests/readonly/delete_object_test.go +++ b/tools/integration_tests/readonly/delete_object_test.go @@ -32,7 +32,7 @@ func checkIfObjDeletionFailed(objPath string, t *testing.T) { t.Errorf("Objects are deleted in read-only file system.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) } func TestDeleteDir(t *testing.T) { diff --git a/tools/integration_tests/readonly/rename_object_test.go b/tools/integration_tests/readonly/rename_object_test.go index 759ac863eb..16bb3aff07 100644 --- a/tools/integration_tests/readonly/rename_object_test.go +++ b/tools/integration_tests/readonly/rename_object_test.go @@ -31,7 +31,7 @@ func checkIfRenameFileFailed(oldFilePath string, newFilePath string, t *testing. t.Errorf("File renamed in read-only file system.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) if _, err := os.Stat(oldFilePath); err != nil { t.Errorf("Old file is deleted in read-only file system.") @@ -48,7 +48,7 @@ func checkIfRenameDirFailed(oldDirPath string, newDirPath string, t *testing.T) t.Errorf("Directory renamed in read-only file system.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) if _, err := os.Stat(oldDirPath); err != nil { t.Errorf("Old directory is deleted in read-only file system.") diff --git a/tools/integration_tests/readonly/write_test.go b/tools/integration_tests/readonly/write_test.go index 9032957aa8..37707316a1 100644 --- a/tools/integration_tests/readonly/write_test.go +++ b/tools/integration_tests/readonly/write_test.go @@ -32,7 +32,7 @@ func checkIfFileFailedToOpenForWrite(filePath string, t *testing.T) { t.Errorf("File opened for writing in read-only mount.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) } // testBucket/testDirForReadOnlyTest/Test1.txt @@ -91,7 +91,7 @@ func checkIfFileFailedToOpenForAppend(filePath string, t *testing.T) { t.Errorf("File opened for appending content in read-only mount.") } - operations.CheckErrorForReadOnlyFileSystem(err, t) + operations.CheckErrorForReadOnlyFileSystem(t, err) } func TestOpenFileWithAppendAccess(t *testing.T) { diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index a3dd327779..6dadddf87d 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -688,21 +688,19 @@ func CreateLocalTempFile(content string, gzipCompress bool) (string, error) { return writeTextToFile(f, f.Name(), content, len(content)) } -func CreateLocalFile(ctx context.Context, mntDir string, bucket gcs.Bucket, fileName string, t *testing.T) (filePath string, f *os.File) { +func CreateLocalFile(ctx context.Context, t *testing.T, mntDir string, bucket gcs.Bucket, fileName string) (filePath string, f *os.File) { t.Helper() // Creating a file shouldn't create file on GCS. filePath = path.Join(mntDir, fileName) - _, err := os.Stat(mntDir) - assert.Equal(t, nil, err) - f, err = os.Create(filePath) + f, err := os.Create(filePath) assert.Equal(t, nil, err) - ValidateObjectNotFoundErr(ctx, bucket, fileName, t) + ValidateObjectNotFoundErr(ctx, t, bucket, fileName) return } -func CloseLocalFile(f **os.File, t *testing.T) error { +func CloseLocalFile(t *testing.T, f **os.File) error { t.Helper() err := (*f).Close() *f = nil diff --git a/tools/integration_tests/util/operations/validation_helper.go b/tools/integration_tests/util/operations/validation_helper.go index 1c42c72e72..61c58299d1 100644 --- a/tools/integration_tests/util/operations/validation_helper.go +++ b/tools/integration_tests/util/operations/validation_helper.go @@ -26,7 +26,7 @@ import ( "github.com/stretchr/testify/assert" ) -func ValidateNoFileOrDirError(path string, t *testing.T) { +func ValidateNoFileOrDirError(t *testing.T, path string) { t.Helper() _, err := os.Stat(path) if err == nil || !strings.Contains(err.Error(), "no such file or directory") { @@ -35,7 +35,7 @@ func ValidateNoFileOrDirError(path string, t *testing.T) { } } -func ValidateObjectNotFoundErr(ctx context.Context, bucket gcs.Bucket, fileName string, t *testing.T) { +func ValidateObjectNotFoundErr(ctx context.Context, t *testing.T, bucket gcs.Bucket, fileName string) { t.Helper() var notFoundErr *gcs.NotFoundError _, err := storageutil.ReadObject(ctx, bucket, fileName) @@ -43,7 +43,12 @@ func ValidateObjectNotFoundErr(ctx context.Context, bucket gcs.Bucket, fileName assert.True(t, errors.As(err, ¬FoundErr)) } -func CheckErrorForReadOnlyFileSystem(err error, t *testing.T) { +func ValidateStaleNFSFileHandleError(t *testing.T, err error) { + assert.NotEqual(t, nil, err) + assert.Regexp(t, "stale NFS file handle", err.Error()) +} + +func CheckErrorForReadOnlyFileSystem(t *testing.T, err error) { if err == nil { t.Error("permission denied error expected but got nil error.") return From 94a9357fce70096f37a334511bb6d3d58f50148c Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:35:01 +0530 Subject: [PATCH 0049/1298] [Write-stall] Create chunk transfer timeout flag (#2755) * add hidden flag * add chunk transfer timeout flag with default 10s value * small fix * remove redudunt line * fix validation * remove redudunt changes * fix unit tests * fix unit tests * nit fix * review comments * review comments * fix unit tests * fix unit tests * fix unit tests * remove from write config and add it in gcs retries * remove from write config and add it in gcs retries * remove from write config and add it in gcs retries * Add unit tests * Add unit tests * small fix * review comment --- cfg/config.go | 12 +++++++++ cfg/params.yaml | 11 ++++++++ cfg/validate.go | 11 ++++++++ cfg/validate_test.go | 28 +++++++++++++++++++++ cmd/config_validation_test.go | 17 +++++++++---- cmd/root_test.go | 46 ++++++++++++++++++++++++++++++++++ cmd/testdata/valid_config.yaml | 1 + 7 files changed, 121 insertions(+), 5 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 37d4351d44..890194d286 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -152,6 +152,8 @@ type GcsConnectionConfig struct { } type GcsRetriesConfig struct { + ChunkTransferTimeoutSecs int64 `yaml:"chunk-transfer-timeout-secs"` + MaxRetryAttempts int64 `yaml:"max-retry-attempts"` MaxRetrySleep time.Duration `yaml:"max-retry-sleep"` @@ -255,6 +257,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.StringP("cache-dir", "", "", "Enables file-caching. Specifies the directory to use for file-cache.") + flagSet.IntP("chunk-transfer-timeout-secs", "", 10, "We send larger file uploads in 16 MiB chunks. This flag controls the duration that the HTTP client will wait for a response after making a request to upload a chunk. The default value of 10s indicates that the client will wait 10 seconds for upload completion; otherwise, it cancels the request and retries for that chunk till chunkRetryDeadline(32s). 0 means no timeout.") + + if err := flagSet.MarkHidden("chunk-transfer-timeout-secs"); err != nil { + return err + } + flagSet.StringP("client-protocol", "", "http1", "The protocol used for communicating with the GCS backend. Value can be 'http1' (HTTP/1.1), 'http2' (HTTP/2) or 'grpc'.") flagSet.IntP("cloud-metrics-export-interval-secs", "", 0, "Specifies the interval at which the metrics are uploaded to cloud monitoring") @@ -584,6 +592,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("gcs-retries.chunk-transfer-timeout-secs", flagSet.Lookup("chunk-transfer-timeout-secs")); err != nil { + return err + } + if err := v.BindPFlag("gcs-connection.client-protocol", flagSet.Lookup("client-protocol")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 078ef0d448..5c90a9b221 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -341,6 +341,17 @@ usage: "File chunk size to read from GCS in one call. Need to specify the value in MB. ChunkSize less than 1MB is not supported" default: "200" +- config-path: "gcs-retries.chunk-transfer-timeout-secs" + flag-name: "chunk-transfer-timeout-secs" + type: "int" + usage: >- + We send larger file uploads in 16 MiB chunks. This flag controls the duration + that the HTTP client will wait for a response after making a request to upload a chunk. + The default value of 10s indicates that the client will wait 10 seconds for upload completion; + otherwise, it cancels the request and retries for that chunk till chunkRetryDeadline(32s). 0 means no timeout. + default: "10" + hide-flag: true + - config-path: "gcs-retries.max-retry-attempts" flag-name: "max-retry-attempts" type: "int" diff --git a/cfg/validate.go b/cfg/validate.go index ffad35daac..a634b832e9 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -184,6 +184,13 @@ func isValidMetricsConfig(m *MetricsConfig) error { return nil } +func isValidChunkTransferTimeoutForRetriesConfig(chunkTransferTimeoutSecs int64) error { + if chunkTransferTimeoutSecs < 0 || chunkTransferTimeoutSecs > maxSupportedTTLInSeconds { + return fmt.Errorf("invalid value of ChunkTransferTimeout: %d; should be > 0 or 0 (for infinite)", chunkTransferTimeoutSecs) + } + return nil +} + // ValidateConfig returns a non-nil error if the config is invalid. func ValidateConfig(v isSet, config *Config) error { var err error @@ -228,6 +235,10 @@ func ValidateConfig(v isSet, config *Config) error { return fmt.Errorf("error parsing read-stall-gcs-retries config: %w", err) } + if err = isValidChunkTransferTimeoutForRetriesConfig(config.GcsRetries.ChunkTransferTimeoutSecs); err != nil { + return fmt.Errorf("error parsing chunk-transfer-timeout-secs config: %w", err) + } + if err = isValidMetricsConfig(&config.Metrics); err != nil { return fmt.Errorf("error parsing metrics config: %w", err) } diff --git a/cfg/validate_test.go b/cfg/validate_test.go index ff7b1d0fc2..550695f669 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -156,6 +156,21 @@ func TestValidateConfigSuccessful(t *testing.T) { FileSystem: FileSystemConfig{KernelListCacheTtlSecs: 30}, }, }, + { + name: "valid_chunk_transfer_timeout_secs", + config: &Config{ + Logging: LoggingConfig{LogRotate: validLogRotateConfig()}, + FileCache: validFileCacheConfig(t), + GcsConnection: GcsConnectionConfig{ + SequentialReadSizeMb: 10, + }, + MetadataCache: MetadataCacheConfig{ + ExperimentalMetadataPrefetchOnMount: "sync", + }, + FileSystem: FileSystemConfig{KernelListCacheTtlSecs: 30}, + GcsRetries: GcsRetriesConfig{ChunkTransferTimeoutSecs: 15}, + }, + }, } for _, tc := range testCases { @@ -326,6 +341,19 @@ func TestValidateConfig_ErrorScenarios(t *testing.T) { }, }, }, + { + name: "chunk_transfer_timeout_in_negative", + config: &Config{ + Logging: LoggingConfig{LogRotate: validLogRotateConfig()}, + FileCache: validFileCacheConfig(t), + MetadataCache: MetadataCacheConfig{ + ExperimentalMetadataPrefetchOnMount: "sync", + }, + GcsRetries: GcsRetriesConfig{ + ChunkTransferTimeoutSecs: -5, + }, + }, + }, } for _, tc := range testCases { diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 4d901a37de..0fef30bcb6 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -693,8 +693,8 @@ func TestValidateConfigFile_MetadataCacheConfigSuccessful(t *testing.T) { } } -func TestValidateConfigFile_ReadStallConfigSuccessful(t *testing.T) { - testCases := []struct { +func TestValidateConfigFile_GCSRetries(t *testing.T) { + tests := []struct { name string configFile string expectedConfig *cfg.Config @@ -705,6 +705,10 @@ func TestValidateConfigFile_ReadStallConfigSuccessful(t *testing.T) { configFile: "testdata/empty_file.yaml", expectedConfig: &cfg.Config{ GcsRetries: cfg.GcsRetriesConfig{ + ChunkTransferTimeoutSecs: 10, + MaxRetryAttempts: 0, + MaxRetrySleep: 30 * time.Second, + Multiplier: 2, ReadStall: cfg.ReadStallGcsRetriesConfig{ Enable: false, MinReqTimeout: 1500 * time.Millisecond, @@ -721,6 +725,10 @@ func TestValidateConfigFile_ReadStallConfigSuccessful(t *testing.T) { configFile: "testdata/valid_config.yaml", expectedConfig: &cfg.Config{ GcsRetries: cfg.GcsRetriesConfig{ + ChunkTransferTimeoutSecs: 20, + MaxRetryAttempts: 0, + MaxRetrySleep: 30 * time.Second, + Multiplier: 2, ReadStall: cfg.ReadStallGcsRetriesConfig{ Enable: true, MinReqTimeout: 10 * time.Second, @@ -733,13 +741,12 @@ func TestValidateConfigFile_ReadStallConfigSuccessful(t *testing.T) { }, }, } - - for _, tc := range testCases { + for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { gotConfig, err := getConfigObjectWithConfigFile(t, tc.configFile) if assert.NoError(t, err) { - assert.EqualValues(t, tc.expectedConfig.GcsRetries.ReadStall, gotConfig.GcsRetries.ReadStall) + assert.EqualValues(t, tc.expectedConfig.GcsRetries, gotConfig.GcsRetries) } }) } diff --git a/cmd/root_test.go b/cmd/root_test.go index 5aa75e9a36..fef6ba8360 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1017,3 +1017,49 @@ func TestArgsParsing_MetadataCacheFlags(t *testing.T) { }) } } + +func TestArgParsing_GCSRetries(t *testing.T) { + tests := []struct { + name string + args []string + expectedConfig *cfg.Config + }{ + { + name: "Test with non default chunkTransferTimeout", + args: []string{"gcsfuse", "--chunk-transfer-timeout-secs=30", "abc", "pqr"}, + expectedConfig: &cfg.Config{ + GcsRetries: cfg.GcsRetriesConfig{ + ChunkTransferTimeoutSecs: 30, + MaxRetryAttempts: 0, + MaxRetrySleep: 30 * time.Second, + Multiplier: 2, + ReadStall: cfg.ReadStallGcsRetriesConfig{ + Enable: false, + InitialReqTimeout: 20 * time.Second, + MinReqTimeout: 1500 * time.Millisecond, + MaxReqTimeout: 1200 * time.Second, + ReqIncreaseRate: 15, + ReqTargetPercentile: 0.99, + }, + }, + }, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var gotConfig *cfg.Config + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { + gotConfig = cfg + return nil + }) + require.Nil(t, err) + cmd.SetArgs(convertToPosixArgs(tc.args, cmd)) + + err = cmd.Execute() + + if assert.NoError(t, err) { + assert.Equal(t, tc.expectedConfig.GcsRetries, gotConfig.GcsRetries) + } + }) + } +} diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 3033023116..134685b527 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -33,6 +33,7 @@ gcs-connection: max-idle-conns-per-host: 20 sequential-read-size-mb: 450 gcs-retries: + chunk-transfer-timeout-secs: 20 read-stall: enable: true min-req-timeout: 10s From f75b45499577ae41e118b20afa4731b7d091a54c Mon Sep 17 00:00:00 2001 From: codechanges Date: Wed, 11 Dec 2024 10:29:36 +0530 Subject: [PATCH 0050/1298] =?UTF-8?q?Enforce=20file=20cache=20availability?= =?UTF-8?q?=20for=20parallel=20downloads=20through=20config=E2=80=A6=20(#2?= =?UTF-8?q?772)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Enforce file cache availability for parallel downloads through config validation * update valid config yaml to disable parallel download * Added requisite import * Amend code to match with the latest commit changes * fix broken tests * Update cfg/validate_test.go Accepted Co-authored-by: Prince Kumar * resolve merge conflict * Used utility function to check whether file cache is enabled --------- Co-authored-by: Prince Kumar --- cfg/validate.go | 39 ++++++++++++++++++++----------- cfg/validate_test.go | 42 ++++++++++++++++++++++++++++++++++ cmd/config_validation_test.go | 2 +- cmd/root_test.go | 3 ++- cmd/testdata/valid_config.yaml | 2 +- 5 files changed, 72 insertions(+), 16 deletions(-) diff --git a/cfg/validate.go b/cfg/validate.go index a634b832e9..161a0c55de 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -17,6 +17,7 @@ package cfg import ( "errors" "fmt" + "math" ) @@ -43,6 +44,25 @@ func isValidURL(u string) error { return err } +func isValidParallelDownloadConfig(config *Config) error { + if config.FileCache.EnableParallelDownloads { + if !IsFileCacheEnabled(config) { + return errors.New("file cache should be enabled for parallel download support") + } + if config.FileCache.MaxParallelDownloads == 0 { + return errors.New("the value of max-parallel-downloads for file-cache must not be 0 when enable-parallel-downloads is true") + } + if config.FileCache.WriteBufferSize < CacheUtilMinimumAlignSizeForWriting { + return errors.New("the value of write-buffer-size for file-cache can't be less than 4096") + } + if (config.FileCache.WriteBufferSize % CacheUtilMinimumAlignSizeForWriting) != 0 { + return errors.New("the value of write-buffer-size for file-cache should be in multiple of 4096") + } + } + + return nil +} + func isValidFileCacheConfig(config *FileCacheConfig) error { if config.MaxSizeMb < -1 { return errors.New(FileCacheMaxSizeMBInvalidValueError) @@ -50,22 +70,11 @@ func isValidFileCacheConfig(config *FileCacheConfig) error { if config.MaxParallelDownloads < -1 { return errors.New(MaxParallelDownloadsInvalidValueError) } - if config.EnableParallelDownloads { - if config.MaxParallelDownloads == 0 { - return errors.New(MaxParallelDownloadsCantBeZeroError) - } - if config.WriteBufferSize < CacheUtilMinimumAlignSizeForWriting { - return errors.New("the value of write-buffer-size for file-cache can't be less than 4096") - } - if (config.WriteBufferSize % CacheUtilMinimumAlignSizeForWriting) != 0 { - return errors.New("the value of write-buffer-size for file-cache should be in multiple of 4096") - } - } if config.ParallelDownloadsPerFile < 1 { - return fmt.Errorf(ParallelDownloadsPerFileInvalidValueError) + return errors.New(ParallelDownloadsPerFileInvalidValueError) } if config.DownloadChunkSizeMb < 1 { - return fmt.Errorf(DownloadChunkSizeMBInvalidValueError) + return errors.New(DownloadChunkSizeMBInvalidValueError) } return nil @@ -243,5 +252,9 @@ func ValidateConfig(v isSet, config *Config) error { return fmt.Errorf("error parsing metrics config: %w", err) } + if err = isValidParallelDownloadConfig(config); err != nil { + return fmt.Errorf("error parsing parallel download config: %w", err) + } + return nil } diff --git a/cfg/validate_test.go b/cfg/validate_test.go index 550695f669..dafbb19127 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -156,6 +156,28 @@ func TestValidateConfigSuccessful(t *testing.T) { FileSystem: FileSystemConfig{KernelListCacheTtlSecs: 30}, }, }, + { + name: "valid_parallel_download_config_with_file_cache_enabled", + config: &Config{ + Logging: LoggingConfig{LogRotate: validLogRotateConfig()}, + CacheDir: "/some/valid/path", + FileCache: FileCacheConfig{ + DownloadChunkSizeMb: 50, + EnableParallelDownloads: true, + MaxParallelDownloads: 4, + ParallelDownloadsPerFile: 16, + MaxSizeMb: -1, + WriteBufferSize: 4 * 1024 * 1024, + }, + GcsConnection: GcsConnectionConfig{ + CustomEndpoint: "https://bing.com/search?q=dotnet", + SequentialReadSizeMb: 200, + }, + MetadataCache: MetadataCacheConfig{ + ExperimentalMetadataPrefetchOnMount: "disabled", + }, + }, + }, { name: "valid_chunk_transfer_timeout_secs", config: &Config{ @@ -341,6 +363,26 @@ func TestValidateConfig_ErrorScenarios(t *testing.T) { }, }, }, + { + name: "parallel_download_config_without_file_cache_enabled", + config: &Config{ + Logging: LoggingConfig{LogRotate: validLogRotateConfig()}, + FileCache: FileCacheConfig{ + DownloadChunkSizeMb: 50, + EnableParallelDownloads: true, + MaxParallelDownloads: 4, + ParallelDownloadsPerFile: 16, + WriteBufferSize: 4 * 1024 * 1024, + }, + GcsConnection: GcsConnectionConfig{ + CustomEndpoint: "https://bing.com/search?q=dotnet", + SequentialReadSizeMb: 200, + }, + MetadataCache: MetadataCacheConfig{ + ExperimentalMetadataPrefetchOnMount: "disabled", + }, + }, + }, { name: "chunk_transfer_timeout_in_negative", config: &Config{ diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 0fef30bcb6..e5501e49ff 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -355,7 +355,7 @@ func TestValidateConfigFile_FileCacheConfigSuccessful(t *testing.T) { CacheFileForRangeRead: true, DownloadChunkSizeMb: 300, EnableCrc: true, - EnableParallelDownloads: true, + EnableParallelDownloads: false, MaxParallelDownloads: 200, MaxSizeMb: 40, ParallelDownloadsPerFile: 10, diff --git a/cmd/root_test.go b/cmd/root_test.go index fef6ba8360..40a28412ad 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -306,8 +306,9 @@ func TestArgsParsing_FileCacheFlags(t *testing.T) { }{ { name: "Test file cache flags.", - args: []string{"gcsfuse", "--file-cache-cache-file-for-range-read", "--file-cache-download-chunk-size-mb=20", "--file-cache-enable-crc", "--file-cache-enable-parallel-downloads", "--file-cache-max-parallel-downloads=40", "--file-cache-max-size-mb=100", "--file-cache-parallel-downloads-per-file=2", "--file-cache-enable-o-direct=false", "abc", "pqr"}, + args: []string{"gcsfuse", "--file-cache-cache-file-for-range-read", "--file-cache-download-chunk-size-mb=20", "--file-cache-enable-crc", "--cache-dir=/some/valid/dir", "--file-cache-enable-parallel-downloads", "--file-cache-max-parallel-downloads=40", "--file-cache-max-size-mb=100", "--file-cache-parallel-downloads-per-file=2", "--file-cache-enable-o-direct=false", "abc", "pqr"}, expectedConfig: &cfg.Config{ + CacheDir: "/some/valid/dir", FileCache: cfg.FileCacheConfig{ CacheFileForRangeRead: true, DownloadChunkSizeMb: 20, diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 134685b527..e8414deee8 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -9,7 +9,7 @@ file-cache: cache-file-for-range-read: true download-chunk-size-mb: 300 enable-crc: true - enable-parallel-downloads: true + enable-parallel-downloads: false max-parallel-downloads: 200 max-size-mb: 40 parallel-downloads-per-file: 10 From 1b8139e675cd240e07ec6e3ebb52037dd8fde691 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:30:15 +0530 Subject: [PATCH 0051/1298] Update Flush call to return an object (#2770) * update Flush call to return an object * review comments --- .../bufferedwrites/buffered_write_handler.go | 17 +++++-- .../buffered_write_handler_test.go | 21 ++++---- internal/bufferedwrites/upload_handler.go | 13 +++-- .../bufferedwrites/upload_handler_test.go | 50 ++++++++++++------- internal/storage/mock/testify_mock_bucket.go | 2 +- 5 files changed, 64 insertions(+), 39 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index c21b7d251c..d3eed43991 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -124,11 +124,11 @@ func (wh *BufferedWriteHandler) Sync() (err error) { } // Flush finalizes the upload. -func (wh *BufferedWriteHandler) Flush() (err error) { +func (wh *BufferedWriteHandler) Flush() (*gcs.Object, error) { // Fail early if the uploadHandler has failed. select { case <-wh.uploadHandler.SignalUploadFailure(): - return ErrUploadFailure + return nil, ErrUploadFailure default: break } @@ -136,11 +136,20 @@ func (wh *BufferedWriteHandler) Flush() (err error) { if wh.current != nil { err := wh.uploadHandler.Upload(wh.current) if err != nil { - return err + return nil, err } wh.current = nil } - return wh.uploadHandler.Finalize() + obj, err := wh.uploadHandler.Finalize() + if err != nil { + return nil, fmt.Errorf("BufferedWriteHandler.Flush(): %w", err) + } + err = wh.blockPool.ClearFreeBlockChannel() + if err != nil { + // Only logging an error in case of resource leak as upload succeeded. + logger.Errorf("blockPool.ClearFreeBlockChannel() failed: %v", err) + } + return obj, nil } // SetMtime stores the mtime with the bufferedWriteHandler. diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 7f61c665e8..ab8d09d058 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -152,26 +152,28 @@ func (testSuite *BufferedWriteTest) TestWrite_SignalUploadFailureInBetween() { func (testSuite *BufferedWriteTest) TestFlushWithNonNilCurrentBlock() { err := testSuite.bwh.Write([]byte("hi"), 0) - currentBlock := testSuite.bwh.current require.Nil(testSuite.T(), err) - err = testSuite.bwh.Flush() + obj, err := testSuite.bwh.Flush() require.NoError(testSuite.T(), err) assert.Equal(testSuite.T(), nil, testSuite.bwh.current) - // The current block should be available on the free channel as flush triggers - // an upload before finalize. - freeCh := testSuite.bwh.blockPool.FreeBlocksChannel() - got := <-freeCh - assert.Equal(testSuite.T(), ¤tBlock, &got) + // Validate object. + assert.NotNil(testSuite.T(), obj) + assert.Equal(testSuite.T(), uint64(2), obj.Size) + // Validate that all blocks have been freed up. + assert.Equal(testSuite.T(), 0, len(testSuite.bwh.blockPool.FreeBlocksChannel())) } func (testSuite *BufferedWriteTest) TestFlushWithNilCurrentBlock() { require.Nil(testSuite.T(), testSuite.bwh.current) - err := testSuite.bwh.Flush() + obj, err := testSuite.bwh.Flush() assert.NoError(testSuite.T(), err) + // Validate empty object created. + assert.NotNil(testSuite.T(), obj) + assert.Equal(testSuite.T(), uint64(0), obj.Size) } func (testSuite *BufferedWriteTest) TestFlush_SignalUploadFailureDuringWrite() { @@ -181,7 +183,8 @@ func (testSuite *BufferedWriteTest) TestFlush_SignalUploadFailureDuringWrite() { // Close the channel to simulate failure in uploader. close(testSuite.bwh.uploadHandler.SignalUploadFailure()) - err = testSuite.bwh.Flush() + obj, err := testSuite.bwh.Flush() require.Error(testSuite.T(), err) assert.Equal(testSuite.T(), err, ErrUploadFailure) + assert.Nil(testSuite.T(), obj) } diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index a1d0b2c7cd..1ab488201a 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -38,7 +38,7 @@ type UploadHandler struct { freeBlocksCh chan block.Block // writer to resumable upload the blocks to GCS. - writer io.WriteCloser + writer gcs.Writer // signalUploadFailure channel will propagate the upload error to file // inode. This signals permanent failure in the buffered write job. @@ -116,7 +116,7 @@ func (uh *UploadHandler) uploader() { } // Finalize finalizes the upload. -func (uh *UploadHandler) Finalize() error { +func (uh *UploadHandler) Finalize() (*gcs.Object, error) { uh.wg.Wait() close(uh.uploadCh) @@ -125,16 +125,15 @@ func (uh *UploadHandler) Finalize() error { // small writes of size less than 1 block. err := uh.createObjectWriter() if err != nil { - return fmt.Errorf("createObjectWriter failed for object %s: %w", uh.objectName, err) + return nil, fmt.Errorf("createObjectWriter failed for object %s: %w", uh.objectName, err) } } - err := uh.writer.Close() + obj, err := uh.bucket.FinalizeUpload(context.Background(), uh.writer) if err != nil { - logger.Errorf("UploadHandler.Finalize(%s): %v", uh.objectName, err) - return fmt.Errorf("writer.Close failed for object %s: %w", uh.objectName, err) + return nil, fmt.Errorf("FinalizeUpload failed for object %s: %w", uh.objectName, err) } - return nil + return obj, nil } func (uh *UploadHandler) SignalUploadFailure() chan error { diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 3a5231402d..c16792d292 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -22,6 +22,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/internal/block" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" storagemock "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -64,8 +65,9 @@ func (t *UploadHandlerTest) TestMultipleBlockUpload() { } // CreateObjectChunkWriter -- should be called once. writer := &storagemock.Writer{} + mockObj := &gcs.Object{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) - writer.On("Close").Return(nil) + t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, nil) // Upload the blocks. for _, b := range blocks { @@ -74,8 +76,10 @@ func (t *UploadHandlerTest) TestMultipleBlockUpload() { } // Finalize. - err := t.uh.Finalize() + obj, err := t.uh.Finalize() require.NoError(t.T(), err) + require.NotNil(t.T(), obj) + assert.Equal(t.T(), mockObj, obj) // The blocks should be available on the free channel for reuse. for _, expect := range blocks { got := <-t.uh.freeBlocksCh @@ -94,7 +98,7 @@ func (t *UploadHandlerTest) TestMultipleBlockUpload() { } } -func (t *UploadHandlerTest) TestUpload_CreateObjectWriterFails() { +func (t *UploadHandlerTest) TestUploadWhenCreateObjectWriterFails() { // Create a block. b, err := t.blockPool.Get() require.NoError(t.T(), err) @@ -111,49 +115,59 @@ func (t *UploadHandlerTest) TestUpload_CreateObjectWriterFails() { func (t *UploadHandlerTest) TestFinalizeWithWriterAlreadyPresent() { writer := &storagemock.Writer{} - writer.On("Close").Return(nil) + mockObj := &gcs.Object{} + t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, nil) t.uh.writer = writer - err := t.uh.Finalize() + obj, err := t.uh.Finalize() - assert.NoError(t.T(), err) + require.NoError(t.T(), err) + require.NotNil(t.T(), obj) + assert.Equal(t.T(), mockObj, obj) } func (t *UploadHandlerTest) TestFinalizeWithNoWriter() { writer := &storagemock.Writer{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) - writer.On("Close").Return(nil) + mockObj := &gcs.Object{} + t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, nil) - err := t.uh.Finalize() + obj, err := t.uh.Finalize() - assert.NoError(t.T(), err) + require.NoError(t.T(), err) + require.NotNil(t.T(), obj) + assert.Equal(t.T(), mockObj, obj) } -func (t *UploadHandlerTest) TestFinalizeWithNoWriter_CreateObjectWriterFails() { +func (t *UploadHandlerTest) TestFinalizeWithNoWriterWhenCreateObjectWriterFails() { t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("taco")) assert.Nil(t.T(), t.uh.writer) - err := t.uh.Finalize() + obj, err := t.uh.Finalize() require.Error(t.T(), err) assert.ErrorContains(t.T(), err, "taco") assert.ErrorContains(t.T(), err, "createObjectWriter") + assert.Nil(t.T(), obj) } -func (t *UploadHandlerTest) TestFinalize_WriterCloseFails() { +func (t *UploadHandlerTest) TestFinalizeWhenFinalizeUploadFails() { writer := &storagemock.Writer{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) - writer.On("Close").Return(fmt.Errorf("taco")) + mockObj := &gcs.Object{} + t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, fmt.Errorf("taco")) - err := t.uh.Finalize() + obj, err := t.uh.Finalize() require.Error(t.T(), err) - assert.ErrorContains(t.T(), err, "writer.Close") + assert.Nil(t.T(), obj) + assert.ErrorContains(t.T(), err, "taco") + assert.ErrorContains(t.T(), err, "FinalizeUpload failed for object") } -func (t *UploadHandlerTest) TestUploadHandler_singleBlock_ErrorInCopy() { +func (t *UploadHandlerTest) TestUploadSingleBlockThrowsErrorInCopy() { // Create a block with test data. b, err := t.blockPool.Get() require.NoError(t.T(), err) @@ -173,7 +187,7 @@ func (t *UploadHandlerTest) TestUploadHandler_singleBlock_ErrorInCopy() { assertUploadFailureSignal(t.T(), t.uh) } -func (t *UploadHandlerTest) TestUploadHandler_multipleBlocks_ErrorInCopy() { +func (t *UploadHandlerTest) TestUploadMultipleBlocksThrowsErrorInCopy() { // Create some blocks. var blocks []block.Block for i := 0; i < 4; i++ { @@ -211,7 +225,7 @@ func assertUploadFailureSignal(t *testing.T, handler *UploadHandler) { } } -func TestBufferedWriteHandler_SignalUploadFailure(t *testing.T) { +func TestSignalUploadFailure(t *testing.T) { mockSignalUploadFailure := make(chan error) uploadHandler := &UploadHandler{ signalUploadFailure: mockSignalUploadFailure, diff --git a/internal/storage/mock/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go index 6abeaef9e1..b15a528a50 100644 --- a/internal/storage/mock/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -59,7 +59,7 @@ func (m *TestifyMockBucket) CreateObjectChunkWriter(ctx context.Context, req *gc } func (m *TestifyMockBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.Object, error) { - args := m.Called(ctx, w.ObjectName()) + args := m.Called(ctx, w) return args.Get(0).(*gcs.Object), args.Error(1) } From b3cd2b91056a9b671e04254abbe260417f9cb8fb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:52:41 +0000 Subject: [PATCH 0052/1298] Bump golang.org/x/text from 0.20.0 to 0.21.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.20.0 to 0.21.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.20.0...v0.21.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 3976f516b1..63938e7736 100644 --- a/go.mod +++ b/go.mod @@ -40,9 +40,9 @@ require ( go.opentelemetry.io/otel/trace v1.32.0 golang.org/x/net v0.31.0 golang.org/x/oauth2 v0.24.0 - golang.org/x/sync v0.9.0 + golang.org/x/sync v0.10.0 golang.org/x/sys v0.27.0 - golang.org/x/text v0.20.0 + golang.org/x/text v0.21.0 golang.org/x/time v0.8.0 google.golang.org/api v0.210.0 google.golang.org/grpc v1.67.2 diff --git a/go.sum b/go.sum index 8199d91fa4..fa8990510a 100644 --- a/go.sum +++ b/go.sum @@ -1467,8 +1467,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1615,8 +1615,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 2519b5a6c66a9f2a5799916a375c76fb45d7278d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:52:21 +0000 Subject: [PATCH 0053/1298] Bump golang.org/x/sys from 0.27.0 to 0.28.0 Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.27.0 to 0.28.0. - [Commits](https://github.com/golang/sys/compare/v0.27.0...v0.28.0) --- updated-dependencies: - dependency-name: golang.org/x/sys dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 63938e7736..e15539fdb0 100644 --- a/go.mod +++ b/go.mod @@ -40,9 +40,9 @@ require ( go.opentelemetry.io/otel/trace v1.32.0 golang.org/x/net v0.31.0 golang.org/x/oauth2 v0.24.0 - golang.org/x/sync v0.10.0 - golang.org/x/sys v0.27.0 - golang.org/x/text v0.21.0 + golang.org/x/sync v0.9.0 + golang.org/x/sys v0.28.0 + golang.org/x/text v0.20.0 golang.org/x/time v0.8.0 google.golang.org/api v0.210.0 google.golang.org/grpc v1.67.2 diff --git a/go.sum b/go.sum index fa8990510a..31502a7365 100644 --- a/go.sum +++ b/go.sum @@ -1597,8 +1597,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= From 25093e6863c5fc597c2600df6a046007587d8488 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:52:44 +0000 Subject: [PATCH 0054/1298] Bump golang.org/x/net from 0.31.0 to 0.32.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.31.0 to 0.32.0. - [Commits](https://github.com/golang/net/compare/v0.31.0...v0.32.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index e15539fdb0..afd83486f1 100644 --- a/go.mod +++ b/go.mod @@ -38,11 +38,11 @@ require ( go.opentelemetry.io/otel/sdk v1.32.0 go.opentelemetry.io/otel/sdk/metric v1.32.0 go.opentelemetry.io/otel/trace v1.32.0 - golang.org/x/net v0.31.0 + golang.org/x/net v0.32.0 golang.org/x/oauth2 v0.24.0 - golang.org/x/sync v0.9.0 + golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 - golang.org/x/text v0.20.0 + golang.org/x/text v0.21.0 golang.org/x/time v0.8.0 google.golang.org/api v0.210.0 google.golang.org/grpc v1.67.2 @@ -111,7 +111,7 @@ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect go.opentelemetry.io/otel/metric v1.32.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.29.0 // indirect + golang.org/x/crypto v0.30.0 // indirect golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect diff --git a/go.sum b/go.sum index 31502a7365..9819f2fc9f 100644 --- a/go.sum +++ b/go.sum @@ -1318,8 +1318,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1431,8 +1431,8 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= From 17c6c0e06eff04d73a91273c8a18a7513a41b875 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:11:59 +0000 Subject: [PATCH 0055/1298] Bump alpine from 3.20 to 3.21 Bumps alpine from 3.20 to 3.21. --- updated-dependencies: - dependency-name: alpine dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f915c96c1e..c91903e4e8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ WORKDIR ${GCSFUSE_REPO} RUN go install ./tools/build_gcsfuse RUN build_gcsfuse . /tmp $(git log -1 --format=format:"%H") -FROM alpine:3.20 +FROM alpine:3.21 RUN apk add --update --no-cache bash ca-certificates fuse From ec4ceb79c480485f0c0ca7ba0cd4cfe935a33f3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:12:01 +0000 Subject: [PATCH 0056/1298] Bump golang from 1.23.3-alpine to 1.23.4-alpine Bumps golang from 1.23.3-alpine to 1.23.4-alpine. --- updated-dependencies: - dependency-name: golang dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c91903e4e8..23e7b2c152 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ # Mount the gcsfuse to /mnt/gcs: # > docker run --privileged --device /fuse -v /mnt/gcs:/gcs:rw,rshared gcsfuse -FROM golang:1.23.3-alpine AS builder +FROM golang:1.23.4-alpine AS builder RUN apk add git From 8efb49e015a61f65908c6368b975207accf90427 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 09:52:36 +0000 Subject: [PATCH 0057/1298] Bump cloud.google.com/go/iam from 1.2.2 to 1.3.0 Bumps [cloud.google.com/go/iam](https://github.com/googleapis/google-cloud-go) from 1.2.2 to 1.3.0. - [Release notes](https://github.com/googleapis/google-cloud-go/releases) - [Changelog](https://github.com/googleapis/google-cloud-go/blob/main/documentai/CHANGES.md) - [Commits](https://github.com/googleapis/google-cloud-go/compare/iam/v1.2.2...iam/v1.3.0) --- updated-dependencies: - dependency-name: cloud.google.com/go/iam dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index afd83486f1..98b16b3efe 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.23.3 require ( cloud.google.com/go/compute/metadata v0.5.2 - cloud.google.com/go/iam v1.2.2 + cloud.google.com/go/iam v1.3.0 cloud.google.com/go/secretmanager v1.14.2 cloud.google.com/go/storage v1.48.0 contrib.go.opencensus.io/exporter/ocagent v0.7.0 diff --git a/go.sum b/go.sum index 9819f2fc9f..6b67e5eda1 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/yb cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= -cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/iam v1.3.0 h1:4Wo2qTaGKFtajbLpF6I4mywg900u3TLlHDb6mriLDPU= +cloud.google.com/go/iam v1.3.0/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= cloud.google.com/go/kms v1.20.1 h1:og29Wv59uf2FVaZlesaiDAqHFzHaoUyHI3HYp9VUHVg= cloud.google.com/go/kms v1.20.1/go.mod h1:LywpNiVCvzYNJWS9JUcGJSVTNSwPwi0vBAotzDqn2nc= cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= From 3482e80c68c1d689a54669d2123349b0cdfc2ece Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 09:54:43 +0000 Subject: [PATCH 0058/1298] Bump github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric Bumps [github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric](https://github.com/GoogleCloudPlatform/opentelemetry-operations-go) from 0.48.1 to 0.49.0. - [Release notes](https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/releases) - [Commits](https://github.com/GoogleCloudPlatform/opentelemetry-operations-go/compare/v0.48.1...v0.49.0) --- updated-dependencies: - dependency-name: github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 98b16b3efe..1c911a1f3d 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( contrib.go.opencensus.io/exporter/ocagent v0.7.0 contrib.go.opencensus.io/exporter/prometheus v0.4.2 contrib.go.opencensus.io/exporter/stackdriver v0.13.14 - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0 github.com/fsouza/fake-gcs-server v1.50.2 github.com/google/uuid v1.6.0 diff --git a/go.sum b/go.sum index 6b67e5eda1..cb342d34c9 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,8 @@ github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0/go.mod h1:6fTWu4m3jocfUZLYF5KsZC1TUfRvEjs7lM4crme/irw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0 h1:4PoDbd/9/06IpwLGxSfvfNoEr9urvfkrN6mmJangGCg= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0/go.mod h1:EycllQ1gupHbjqbcmfCr/H6FKSGSmEUONJ2ivb86qeY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0 h1:jJKWl98inONJAr/IZrdFQUWcwUO95DLY1XMD1ZIut+g= From 2fcb83aebb555b72296241f0e42863f549bf99b0 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 11 Dec 2024 09:11:55 +0000 Subject: [PATCH 0059/1298] rebase --- tools/integration_tests/list_large_dir/testFile.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tools/integration_tests/list_large_dir/testFile.txt diff --git a/tools/integration_tests/list_large_dir/testFile.txt b/tools/integration_tests/list_large_dir/testFile.txt new file mode 100644 index 0000000000..e69de29bb2 From f69fc4a322b316cf352a4de062304cc3439307d7 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 11 Dec 2024 09:12:10 +0000 Subject: [PATCH 0060/1298] rebase --- tools/integration_tests/list_large_dir/testFile.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 tools/integration_tests/list_large_dir/testFile.txt diff --git a/tools/integration_tests/list_large_dir/testFile.txt b/tools/integration_tests/list_large_dir/testFile.txt deleted file mode 100644 index e69de29bb2..0000000000 From 4a36886ff5a48f183705ca3134a6db3a1bf4bbe9 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:54:51 +0000 Subject: [PATCH 0061/1298] Fixing read for bufferedwrites and handling it for empty GCS File (#2769) * fixing conflicts * fixing nil assert # Conflicts: # internal/fs/inode/file_test.go * not nil fix * Handle read * PR review comments --- internal/fs/inode/file.go | 13 +++++-- internal/fs/inode/file_test.go | 68 ++++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 12 deletions(-) diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index fca7729b17..f3c60c2de5 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -347,7 +347,9 @@ func (f *FileInode) Source() *gcs.MinObject { // // LOCKS_REQUIRED(f.mu) func (f *FileInode) SourceGenerationIsAuthoritative() bool { - return f.content == nil + // When streaming writes are enabled, writes are done via bufferedWritesHandler(bwh). + // Hence checking both f.content & f.bwh to be nil + return f.content == nil && f.bwh == nil } // Equivalent to the generation returned by f.Source(). @@ -466,8 +468,11 @@ func (f *FileInode) Read( ctx context.Context, dst []byte, offset int64) (n int, err error) { - if f.IsLocal() && f.writeConfig.ExperimentalEnableStreamingWrites { - err = fmt.Errorf("cannot read a local file when upload in progress") + // It is not nil when streaming writes are enabled in 2 scenarios: + // 1. Local file + // 2. Empty GCS files and writes are triggered via buffered flow. + if f.bwh != nil { + err = fmt.Errorf("cannot read a file when upload in progress") return } @@ -499,7 +504,7 @@ func (f *FileInode) Write( ctx context.Context, data []byte, offset int64) (err error) { - // For empty GCS files also we will triggered bufferedWrites flow. + // For empty GCS files also we will trigger bufferedWrites flow. if f.src.Size == 0 && f.writeConfig.ExperimentalEnableStreamingWrites { err = f.ensureBufferedWriteHandler() if err != nil { diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index f6975372a3..4acb451d90 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -53,6 +53,8 @@ const fileInodeID = 17 const fileName = "foo/bar" const fileMode os.FileMode = 0641 const Delta = 30 * time.Minute +const LocalFile = "Local" +const EmptyGCSFile = "EmptyGCS" type FileTest struct { suite.Suite @@ -1028,20 +1030,70 @@ func (t *FileTest) TestUnlinkLocalFile() { assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) } -func (t *FileTest) TestReadLocalFileWhenStreamingWritesAreEnabled() { - // Create a local file inode. - t.createInodeWithLocalParam("test", true) +func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { + tbl := []struct { + name string + fileType string + performWrite bool + }{ + { + name: "LocalFileWithWrite", + fileType: LocalFile, + performWrite: true, + }, + { + name: "LocalFileWithOutWrite", + fileType: LocalFile, + performWrite: false, + }, + { + name: "EmptyGCSFileWithWrite", + fileType: EmptyGCSFile, + performWrite: true, + }, + } + for _, tc := range tbl { + t.Run(tc.name, func() { + if tc.fileType == LocalFile { + // Create a local file inode. + t.createInodeWithLocalParam("test", true) + t.in.writeConfig = getWriteConfig() + err := t.in.CreateBufferedOrTempWriter() + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.bwh) + } + + if tc.fileType == EmptyGCSFile { + t.createInodeWithEmptyObject() + t.in.writeConfig = getWriteConfig() + } + + if tc.performWrite { + err := t.in.Write(t.ctx, []byte("hi"), 0) + assert.Nil(t.T(), err) + assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) + } + + data := make([]byte, 10) + + n, err := t.in.Read(t.ctx, data, 0) + + assert.Equal(t.T(), 0, n) + assert.NotNil(t.T(), err) + assert.Equal(t.T(), "cannot read a file when upload in progress", err.Error()) + }) + } +} + +func (t *FileTest) TestReadEmptyGCSFileWhenStreamingWritesAreNotInProgress() { + t.createInodeWithEmptyObject() t.in.writeConfig = getWriteConfig() - err := t.in.Write(t.ctx, []byte("hi"), 0) - assert.Nil(t.T(), err) - assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) data := make([]byte, 10) n, err := t.in.Read(t.ctx, data, 0) assert.Equal(t.T(), 0, n) - assert.NotNil(t.T(), err) - assert.Equal(t.T(), "cannot read a local file when upload in progress", err.Error()) + assert.Contains(t.T(), err.Error(), "EOF") } func (t *FileTest) TestWriteToLocalFileWithInvalidConfigWhenStreamingWritesAreEnabled() { From 5c8e89ed61ad521b44ae6b20f7add85df41cf264 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:03:27 +0530 Subject: [PATCH 0062/1298] Increasing mount timeout threshold and taking minimum of 10 iterations (#2780) * using minimum of 10 mount operations to compare with threshold * increased threshold * nits --- .../gcsfuse_mount_timeout_test.go | 33 +++++++++++++------ .../mount_timeout/mount_timeout_test.go | 12 +++---- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go index 83e5b074f6..b05a917a1e 100644 --- a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go +++ b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go @@ -16,6 +16,7 @@ package mount_timeout import ( "fmt" + "math" "os" "path" "testing" @@ -29,6 +30,10 @@ import ( "github.com/stretchr/testify/suite" ) +const ( + iterations int = 10 +) + func TestMountTimeout(t *testing.T) { if os.Getenv("TEST_ENV") == testEnvGCEUSCentral { // Set strict region based timeout values if testing environment is GCE VM in us-central. @@ -97,19 +102,27 @@ func (testSuite *MountTimeoutTest) TearDownTest() { // mountOrTimeout mounts the bucket with the given client protocol. If the time taken // exceeds the expected for the particular test case , an error is thrown and test will fail. func (testSuite *MountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientProtocol string, expectedMountTime time.Duration) error { - args := []string{"--client-protocol", clientProtocol, bucketName, testSuite.dir} - start := time.Now() - if err := mounting.MountGcsfuse(testSuite.gcsfusePath, args); err != nil { - return err - } - defer func() { + minMountTime := time.Duration(math.MaxInt64) + + // Iterating 10 times to account for randomness in time taken to mount. + for i := 0; i < iterations; i++ { + args := []string{"--client-protocol", clientProtocol, bucketName, testSuite.dir} + start := time.Now() + if err := mounting.MountGcsfuse(testSuite.gcsfusePath, args); err != nil { + return err + } + mountTime := time.Since(start) + + minMountTime = time.Duration(math.Min(float64(minMountTime), float64(mountTime))) + if err := util.Unmount(mountDir); err != nil { - fmt.Fprintf(os.Stderr, "Warning: unmount failed: %v\n", err) + err = fmt.Errorf("Warning: unmount failed: %v\n", err) + return err } - }() + } - if mountTime := time.Since(start); mountTime > expectedMountTime { - return fmt.Errorf("[Client Protocol: %s]Mounting failed due to timeout(exceeding %f seconds).Time taken for the mounting %s: %f sec", clientProtocol, expectedMountTime.Seconds(), bucketName, mountTime.Seconds()) + if minMountTime > expectedMountTime { + return fmt.Errorf("[Client Protocol: %s] Mounting failed due to timeout (exceeding %f seconds). Time taken for mounting %s: %f sec", clientProtocol, expectedMountTime.Seconds(), bucketName, minMountTime.Seconds()) } return nil } diff --git a/tools/integration_tests/mount_timeout/mount_timeout_test.go b/tools/integration_tests/mount_timeout/mount_timeout_test.go index c322b2c533..727e37125a 100644 --- a/tools/integration_tests/mount_timeout/mount_timeout_test.go +++ b/tools/integration_tests/mount_timeout/mount_timeout_test.go @@ -49,12 +49,12 @@ const ( dualRegionAsiaBucket string = "mount_timeout_test_bucket_asia1" singleRegionUSCentralBucket string = "mount_timeout_test_bucket_us-central1" singleRegionAsiaEastBucket string = "mount_timeout_test_bucket_asia-east1" - singleRegionAsiaEastExpectedMountTime time.Duration = 3200 * time.Millisecond - multiRegionUSExpectedMountTime time.Duration = 2000 * time.Millisecond - multiRegionAsiaExpectedMountTime time.Duration = 4500 * time.Millisecond - dualRegionUSExpectedMountTime time.Duration = 2700 * time.Millisecond - dualRegionAsiaExpectedMountTime time.Duration = 3750 * time.Millisecond - singleRegionUSCentralExpectedMountTime time.Duration = 2000 * time.Millisecond + singleRegionAsiaEastExpectedMountTime time.Duration = 5500 * time.Millisecond + multiRegionUSExpectedMountTime time.Duration = 4500 * time.Millisecond + multiRegionAsiaExpectedMountTime time.Duration = 7500 * time.Millisecond + dualRegionUSExpectedMountTime time.Duration = 4500 * time.Millisecond + dualRegionAsiaExpectedMountTime time.Duration = 6250 * time.Millisecond + singleRegionUSCentralExpectedMountTime time.Duration = 2500 * time.Millisecond relaxedExpectedMountTime time.Duration = 8000 * time.Millisecond logfilePathPrefix string = "/tmp/gcsfuse_mount_timeout_" ) From b6e4c5b51cd34e72ee94d0bd595411bbaab5a44e Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 11 Dec 2024 15:24:06 +0000 Subject: [PATCH 0063/1298] Updated go-land version from 1.23.3 to 1.23.4 --- go.mod | 2 +- perfmetrics/scripts/ml_tests/pytorch/run_model.sh | 2 +- .../ml_tests/tf/resnet/setup_scripts/setup_container.sh | 4 ++-- perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh | 4 ++-- perfmetrics/scripts/read_cache/setup.sh | 2 +- tools/cd_scripts/e2e_test.sh | 2 +- tools/containerize_gcsfuse_docker/Dockerfile | 2 +- tools/integration_tests/run_e2e_tests.sh | 4 ++-- tools/package_gcsfuse_docker/Dockerfile | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 1c911a1f3d..7fe7308edd 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/googlecloudplatform/gcsfuse/v2 -go 1.23.3 +go 1.23.4 require ( cloud.google.com/go/compute/metadata v0.5.2 diff --git a/perfmetrics/scripts/ml_tests/pytorch/run_model.sh b/perfmetrics/scripts/ml_tests/pytorch/run_model.sh index cdd2689f94..35f65a5120 100755 --- a/perfmetrics/scripts/ml_tests/pytorch/run_model.sh +++ b/perfmetrics/scripts/ml_tests/pytorch/run_model.sh @@ -20,7 +20,7 @@ NUM_EPOCHS=80 TEST_BUCKET="gcsfuse-ml-data" # Install golang -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.3.linux-amd64.tar.gz -q +wget -O go_tar.tar.gz https://go.dev/dl/go1.23.4.linux-amd64.tar.gz -q rm -rf /usr/local/go && tar -C /usr/local -xzf go_tar.tar.gz export PATH=$PATH:/usr/local/go/bin diff --git a/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh b/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh index e5f5ed85d1..4ab2e1477e 100755 --- a/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh +++ b/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh @@ -13,13 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Installs go1.23.3 on the container, builds gcsfuse using log_rotation file +# Installs go1.23.4 on the container, builds gcsfuse using log_rotation file # and installs tf-models-official v2.13.2, makes update to include clear_kernel_cache # and epochs functionality, and runs the model # Install go lang BUCKET_TYPE=$1 -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.3.linux-amd64.tar.gz -q +wget -O go_tar.tar.gz https://go.dev/dl/go1.23.4.linux-amd64.tar.gz -q sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local export PATH=$PATH:/usr/local/go/bin diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index 4e4a69ebe7..df3d104813 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -43,8 +43,8 @@ set -e sudo apt-get update echo Installing git sudo apt-get install git -echo Installing go-lang 1.23.3 -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.3.linux-amd64.tar.gz -q +echo Installing go-lang 1.23.4 +wget -O go_tar.tar.gz https://go.dev/dl/go1.23.4.linux-amd64.tar.gz -q sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local export PATH=$PATH:/usr/local/go/bin export CGO_ENABLED=0 diff --git a/perfmetrics/scripts/read_cache/setup.sh b/perfmetrics/scripts/read_cache/setup.sh index 1cd1665a5a..fa0847f36e 100755 --- a/perfmetrics/scripts/read_cache/setup.sh +++ b/perfmetrics/scripts/read_cache/setup.sh @@ -64,7 +64,7 @@ sed -i 's/define \+FIO_IO_U_PLAT_GROUP_NR \+\([0-9]\+\)/define FIO_IO_U_PLAT_GRO cd - # Install and validate go. -version=1.23.3 +version=1.23.4 wget -O go_tar.tar.gz https://go.dev/dl/go${version}.linux-amd64.tar.gz -q sudo rm -rf /usr/local/go tar -xzf go_tar.tar.gz && sudo mv go /usr/local diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 2606cadda1..2556147ec1 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -112,7 +112,7 @@ else fi # install go -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.3.linux-${architecture}.tar.gz +wget -O go_tar.tar.gz https://go.dev/dl/go1.23.4.linux-${architecture}.tar.gz sudo tar -C /usr/local -xzf go_tar.tar.gz export PATH=${PATH}:/usr/local/go/bin #Write gcsfuse and go version to log file diff --git a/tools/containerize_gcsfuse_docker/Dockerfile b/tools/containerize_gcsfuse_docker/Dockerfile index 8afd60fe03..c0e7732c71 100644 --- a/tools/containerize_gcsfuse_docker/Dockerfile +++ b/tools/containerize_gcsfuse_docker/Dockerfile @@ -34,7 +34,7 @@ ARG OS_VERSION ARG OS_NAME # Image with gcsfuse installed and its package (.deb) -FROM golang:1.23.3 as gcsfuse-package +FROM golang:1.23.4 as gcsfuse-package RUN apt-get update -qq && apt-get install -y ruby ruby-dev rubygems build-essential rpm fuse && gem install --no-document bundler diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 3404f94688..665f86ef15 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -112,8 +112,8 @@ function upgrade_gcloud_version() { function install_packages() { # e.g. architecture=arm64 or amd64 architecture=$(dpkg --print-architecture) - echo "Installing go-lang 1.23.3..." - wget -O go_tar.tar.gz https://go.dev/dl/go1.23.3.linux-${architecture}.tar.gz -q + echo "Installing go-lang 1.23.4..." + wget -O go_tar.tar.gz https://go.dev/dl/go1.23.4.linux-${architecture}.tar.gz -q sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local export PATH=$PATH:/usr/local/go/bin sudo apt-get install -y python3 diff --git a/tools/package_gcsfuse_docker/Dockerfile b/tools/package_gcsfuse_docker/Dockerfile index b338363201..325bf2ece6 100644 --- a/tools/package_gcsfuse_docker/Dockerfile +++ b/tools/package_gcsfuse_docker/Dockerfile @@ -17,7 +17,7 @@ # Copy the gcsfuse packages to the host: # > docker run -it -v /tmp:/output gcsfuse-release cp -r /packages /output -FROM golang:1.23.3 as builder +FROM golang:1.23.4 as builder RUN apt-get update -qq && apt-get install -y ruby ruby-dev rubygems build-essential rpm && gem install --no-document bundler From 090ab5c9049171c1e954b5210c408d5bb0ba53fc Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:02:03 +0530 Subject: [PATCH 0064/1298] [Emulator tests] Automate tests in kokoro and add helper to run proxy server (#2777) * helper to start proxy server * automate tests * helper to start proxy server * lint fix * provide execute permission to script * provide prot in function * provide prot in function * remove redundunt line * review comment * remove comment * remove comment * review comment * remove unnecessary files * removing port from argument as not require for now --- .../gcp_ubuntu/e2e_tests/e2e-tests-master.cfg | 1 + .../e2e_tests/e2e-tests-release.cfg | 1 + .../emulator_tests/emulator_tests.sh | 2 +- .../emulator_tests/util/test_helper.go | 86 +++++++++++++++++++ tools/integration_tests/run_e2e_tests.sh | 29 +++++-- 5 files changed, 110 insertions(+), 9 deletions(-) mode change 100644 => 100755 tools/integration_tests/emulator_tests/emulator_tests.sh create mode 100644 tools/integration_tests/emulator_tests/util/test_helper.go diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-master.cfg b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-master.cfg index d954cc48f0..e7accaef8f 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-master.cfg +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-master.cfg @@ -16,6 +16,7 @@ action { define_artifacts { regex: "gcsfuse-failed-integration-test-logs-*" strip_prefix: "github/gcsfuse/perfmetrics/scripts" + regex: "proxy*" } } diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-release.cfg b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-release.cfg index d954cc48f0..e7accaef8f 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-release.cfg +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-release.cfg @@ -16,6 +16,7 @@ action { define_artifacts { regex: "gcsfuse-failed-integration-test-logs-*" strip_prefix: "github/gcsfuse/perfmetrics/scripts" + regex: "proxy*" } } diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh old mode 100644 new mode 100755 index 8d80ca3c69..2d66529fef --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -73,4 +73,4 @@ curl -X POST --data-binary @test.json \ rm test.json # Run specific test suite -go test --integrationTest -v --testbucket=test-bucket -timeout 10m +go test ./tools/integration_tests/emulator_tests/... --integrationTest -v --testbucket=test-bucket -timeout 10m diff --git a/tools/integration_tests/emulator_tests/util/test_helper.go b/tools/integration_tests/emulator_tests/util/test_helper.go new file mode 100644 index 0000000000..47f53c557e --- /dev/null +++ b/tools/integration_tests/emulator_tests/util/test_helper.go @@ -0,0 +1,86 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package emulator_tests + +import ( + "fmt" + "log" + "os" + "os/exec" + "path" + "strconv" + "strings" + "syscall" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +// StartProxyServer starts a proxy server as a background process and handles its lifecycle. +// +// It launches the proxy server with the specified configuration and port, logs its output to a file. +func StartProxyServer(configPath string) { + // Start the proxy in the background + cmd := exec.Command("go", "run", "../proxy_server/.", "--config-path="+configPath) + logFileForProxyServer, err := os.Create(path.Join(os.Getenv("KOKORO_ARTIFACTS_DIR"), "proxy-"+setup.GenerateRandomString(5))) + if err != nil { + log.Fatal("Error in creating log file for proxy server.") + } + log.Printf("Proxy server logs are generated with specific filename %s: ", logFileForProxyServer.Name()) + cmd.Stdout = logFileForProxyServer + cmd.Stderr = logFileForProxyServer + err = cmd.Start() + if err != nil { + log.Fatal(err) + } +} + +// KillProxyServerProcess kills all processes listening on the specified port. +// +// It uses the `lsof` command to identify the processes and sends SIGINT to each of them. +func KillProxyServerProcess(port int) error { + // Use lsof to find processes listening on the specified port + cmd := exec.Command("lsof", "-i", fmt.Sprintf(":%d", port)) + output, err := cmd.Output() + if err != nil { + return fmt.Errorf("error running lsof: %w", err) + } + + // Parse the lsof output to get the process IDs + lines := strings.Split(string(output), "\n") + for _, line := range lines[1:] { + fields := strings.Fields(line) + if len(fields) > 1 { + pidStr := fields[1] + pid, err := strconv.Atoi(pidStr) + if err != nil { + log.Println("Error parsing process ID:", err) + continue + } + + // Send SIGINT to the process + process, err := os.FindProcess(pid) + if err != nil { + log.Println("Error finding process:", err) + continue + } + err = process.Signal(syscall.SIGINT) + if err != nil { + log.Println("Error sending SIGINT to process:", err) + } + } + } + + return nil +} diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 3404f94688..ad460416eb 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -304,6 +304,10 @@ function run_e2e_tests_for_tpc() { exit $exit_code } +function run_e2e_tests_for_emulator() { + ./tools/integration_tests/emulator_tests/emulator_tests.sh +} + #commenting it so cleanup and failure check happens for both #set -e @@ -342,6 +346,12 @@ function main(){ run_e2e_tests_for_flat_bucket & e2e_tests_flat_bucket_pid=$! + run_e2e_tests_for_emulator & + e2e_tests_emulator_pid=$! + + wait $e2e_tests_emulator_pid + e2e_tests_emulator_status=$? + wait $e2e_tests_flat_bucket_pid e2e_tests_flat_bucket_status=$? @@ -352,23 +362,26 @@ function main(){ print_test_logs - if [ $e2e_tests_flat_bucket_status != 0 ] && [ $e2e_tests_hns_bucket_status != 0 ]; - then - echo "The e2e tests for both flat and hns bucket failed.." - exit 1 - fi - + exit_code=0 if [ $e2e_tests_flat_bucket_status != 0 ]; then echo "The e2e tests for flat bucket failed.." - exit 1 + exit_code=1 fi if [ $e2e_tests_hns_bucket_status != 0 ]; then echo "The e2e tests for hns bucket failed.." - exit 1 + exit_code=1 + fi + + if [ $e2e_tests_emulator_status != 0 ]; + then + echo "The e2e tests for emulator failed.." + exit_code=1 fi + + exit $exit_code } #Main method to run script From fc175858874433f2aeb10801924846d2de313009 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 12 Dec 2024 12:06:07 +0530 Subject: [PATCH 0065/1298] Remove Version test (#2785) * Remove Version test It's failing in CD pipeline. We'll reenable it after fixing the issues. --- .../mounting/gcsfuse_test.go | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/tools/integration_tests/mounting/gcsfuse_test.go b/tools/integration_tests/mounting/gcsfuse_test.go index 1f97ac4afc..6eb4f7d94c 100644 --- a/tools/integration_tests/mounting/gcsfuse_test.go +++ b/tools/integration_tests/mounting/gcsfuse_test.go @@ -21,14 +21,11 @@ import ( "os/exec" "path" "path/filepath" - "strings" "syscall" "testing" "time" "github.com/googlecloudplatform/gcsfuse/v2/internal/canned" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/util" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/oglematchers" @@ -225,27 +222,6 @@ func (t *GcsfuseTest) KeyFile() { } } -func (t *GcsfuseTest) Version() { - for _, arg := range []string{"-v", "--v", "--version", "-version"} { - cmd := t.gcsfuseCommand([]string{arg}, nil) - - output, err := cmd.CombinedOutput() - - AssertEq(nil, err) - if setup.TestInstalledPackage() { - assertContains("0.0.0", string(output)) - } else { - assertContains("fake_version", string(output)) - } - } -} - -func assertContains(expected, actual string) { - if !strings.Contains(actual, expected) { - logger.Fatal("Actual: %s does not contain expected: %s", actual, expected) - } -} - func (t *GcsfuseTest) CannedContents() { var err error var fi os.FileInfo From 84f808f04b559a625c8fbfb0b825478afcf3ee66 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 12 Dec 2024 15:38:06 +0530 Subject: [PATCH 0066/1298] [Write-stall] Set chunk transfer timeout to writer (#2776) * add hidden flag * add chunk transfer timeout flag with default 10s value * small fix * remove redudunt line * fix validation * remove redudunt changes * fix unit tests * fix unit tests * review comments * review comments * fix unit tests * fix unit tests * fix unit tests * remove from write config and add it in gcs retries * remove from write config and add it in gcs retries * remove from write config and add it in gcs retries * Add unit tests * small fix * test changes * remove coverage file * test changes * remove coverage file * remove coverage file * fix unit tetss * fix lint * kokor tests * create const * rename variable * remove unnecessary changes * add doc comment --- cmd/mount.go | 1 + internal/fs/fs_test.go | 13 +++++--- internal/fs/handle/dir_handle_test.go | 2 +- internal/fs/inode/base_dir_test.go | 4 +++ internal/fs/inode/core_test.go | 2 +- internal/fs/inode/dir_test.go | 1 + internal/fs/inode/file_test.go | 1 + internal/fs/inode/hns_dir_test.go | 1 + internal/gcsx/append_object_creator.go | 8 +++-- internal/gcsx/append_object_creator_test.go | 1 + internal/gcsx/bucket_manager.go | 6 ++-- internal/gcsx/integration_test.go | 2 ++ internal/gcsx/syncer.go | 36 +++++++++++++-------- internal/gcsx/syncer_bucket.go | 3 +- internal/gcsx/syncer_test.go | 7 ++++ internal/storage/bucket_handle.go | 1 + internal/storage/gcs/request.go | 9 ++++++ 17 files changed, 71 insertions(+), 27 deletions(-) diff --git a/cmd/mount.go b/cmd/mount.go index 9187752195..013034cdc7 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -95,6 +95,7 @@ be interacting with the file system.`) StatCacheTTL: time.Duration(newConfig.MetadataCache.TtlSecs) * time.Second, EnableMonitoring: cfg.IsMetricsEnabled(&newConfig.Metrics), AppendThreshold: 1 << 21, // 2 MiB, a total guess. + ChunkTransferTimeoutSecs: newConfig.GcsRetries.ChunkTransferTimeoutSecs, TmpObjectPrefix: ".gcsfuse_tmp/", } bm := gcsx.NewBucketManager(bucketCfg, storageHandle) diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index 8f713f81b9..ebb9e4b482 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -150,8 +150,9 @@ func (t *fsTest) SetUpTestSuite() { // This bucket manager is allowed to open these buckets buckets: buckets, // Configs for the syncer when setting up buckets - appendThreshold: 0, - tmpObjectPrefix: ".gcsfuse_tmp/", + appendThreshold: 0, + chunkTransferTimeoutSecs: 10, + tmpObjectPrefix: ".gcsfuse_tmp/", } t.serverCfg.RenameDirLimit = RenameDirLimit t.serverCfg.SequentialReadSizeMb = SequentialReadSizeMb @@ -363,9 +364,10 @@ func currentGid() uint32 { } type fakeBucketManager struct { - buckets map[string]gcs.Bucket - appendThreshold int64 - tmpObjectPrefix string + buckets map[string]gcs.Bucket + appendThreshold int64 + chunkTransferTimeoutSecs int64 + tmpObjectPrefix string } func (bm *fakeBucketManager) ShutDown() {} @@ -377,6 +379,7 @@ func (bm *fakeBucketManager) SetUpBucket( if ok { sb = gcsx.NewSyncerBucket( bm.appendThreshold, + bm.chunkTransferTimeoutSecs, bm.tmpObjectPrefix, gcsx.NewContentTypeBucket(bucket), ) diff --git a/internal/fs/handle/dir_handle_test.go b/internal/fs/handle/dir_handle_test.go index fc17b0f7ab..448b46a479 100644 --- a/internal/fs/handle/dir_handle_test.go +++ b/internal/fs/handle/dir_handle_test.go @@ -56,7 +56,7 @@ func init() { RegisterTestSuite(&DirHandleTest{}) } func (t *DirHandleTest) SetUp(ti *TestInfo) { t.ctx = ti.Ctx t.bucket = gcsx.NewSyncerBucket( - 1, ".gcsfuse_tmp/", fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical)) + 1, 10, ".gcsfuse_tmp/", fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical)) t.clock.SetTime(time.Date(2022, 8, 15, 22, 56, 0, 0, time.Local)) t.resetDirHandle() } diff --git a/internal/fs/inode/base_dir_test.go b/internal/fs/inode/base_dir_test.go index e830042db7..34f57781fb 100644 --- a/internal/fs/inode/base_dir_test.go +++ b/internal/fs/inode/base_dir_test.go @@ -32,6 +32,8 @@ import ( "github.com/jacobsa/timeutil" ) +const ChunkTransferTimeoutSecs = 10 + func TestBaseDir(t *testing.T) { RunTests(t) } //////////////////////////////////////////////////////////////////////// @@ -60,11 +62,13 @@ func (t *BaseDirTest) SetUp(ti *TestInfo) { } t.bm.buckets["bucketA"] = gcsx.NewSyncerBucket( 1, // Append threshold + ChunkTransferTimeoutSecs, ".gcsfuse_tmp/", fake.NewFakeBucket(&t.clock, "bucketA", gcs.NonHierarchical), ) t.bm.buckets["bucketB"] = gcsx.NewSyncerBucket( 1, // Append threshold + ChunkTransferTimeoutSecs, ".gcsfuse_tmp/", fake.NewFakeBucket(&t.clock, "bucketB", gcs.NonHierarchical), ) diff --git a/internal/fs/inode/core_test.go b/internal/fs/inode/core_test.go index 5e484a4ad8..f761c353b1 100644 --- a/internal/fs/inode/core_test.go +++ b/internal/fs/inode/core_test.go @@ -49,7 +49,7 @@ func init() { RegisterTestSuite(&CoreTest{}) } func (t *CoreTest) SetUp(ti *TestInfo) { t.ctx = ti.Ctx t.bucket = gcsx.NewSyncerBucket( - 1, ".gcsfuse_tmp/", fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical)) + 1, 10, ".gcsfuse_tmp/", fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical)) t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)) } diff --git a/internal/fs/inode/dir_test.go b/internal/fs/inode/dir_test.go index 714c364503..16b67e5652 100644 --- a/internal/fs/inode/dir_test.go +++ b/internal/fs/inode/dir_test.go @@ -73,6 +73,7 @@ func (t *DirTest) SetUp(ti *TestInfo) { bucket := fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical) t.bucket = gcsx.NewSyncerBucket( 1, // Append threshold + ChunkTransferTimeoutSecs, ".gcsfuse_tmp/", bucket) // Create the inode. No implicit dirs by default. diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 4acb451d90..b799b6487d 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -124,6 +124,7 @@ func (t *FileTest) createInodeWithLocalParam(fileName string, local bool) { ) syncerBucket := gcsx.NewSyncerBucket( 1, // Append threshold + ChunkTransferTimeoutSecs, ".gcsfuse_tmp/", t.bucket) diff --git a/internal/fs/inode/hns_dir_test.go b/internal/fs/inode/hns_dir_test.go index 56644c1eaf..ec46c3202d 100644 --- a/internal/fs/inode/hns_dir_test.go +++ b/internal/fs/inode/hns_dir_test.go @@ -54,6 +54,7 @@ func (t *HNSDirTest) SetupTest() { t.mockBucket = new(storagemock.TestifyMockBucket) t.bucket = gcsx.NewSyncerBucket( 1, + ChunkTransferTimeoutSecs, ".gcsfuse_tmp/", t.mockBucket) t.resetDirInode(false, false, true) diff --git a/internal/gcsx/append_object_creator.go b/internal/gcsx/append_object_creator.go index b82216de5d..8aede97eda 100644 --- a/internal/gcsx/append_object_creator.go +++ b/internal/gcsx/append_object_creator.go @@ -86,6 +86,7 @@ func (oc *appendObjectCreator) Create( objectName string, srcObject *gcs.Object, mtime *time.Time, + chunkTransferTimeoutSecs int64, r io.Reader) (o *gcs.Object, err error) { // Choose a name for a temporary object. tmpName, err := oc.chooseName() @@ -99,9 +100,10 @@ func (oc *appendObjectCreator) Create( tmp, err := oc.bucket.CreateObject( ctx, &gcs.CreateObjectRequest{ - Name: tmpName, - GenerationPrecondition: &zero, - Contents: r, + Name: tmpName, + GenerationPrecondition: &zero, + Contents: r, + ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, }) if err != nil { err = fmt.Errorf("CreateObject: %w", err) diff --git a/internal/gcsx/append_object_creator_test.go b/internal/gcsx/append_object_creator_test.go index 4d03e7c6ec..0e60868ae0 100644 --- a/internal/gcsx/append_object_creator_test.go +++ b/internal/gcsx/append_object_creator_test.go @@ -93,6 +93,7 @@ func (t *AppendObjectCreatorTest) call() (o *gcs.Object, err error) { t.srcObject.Name, &t.srcObject, &t.mtime, + chunkTransferTimeoutSecs, strings.NewReader(t.srcContents)) return diff --git a/internal/gcsx/bucket_manager.go b/internal/gcsx/bucket_manager.go index b17a1fd335..f817b874a7 100644 --- a/internal/gcsx/bucket_manager.go +++ b/internal/gcsx/bucket_manager.go @@ -59,8 +59,9 @@ type BucketConfig struct { // Note that if the process fails or is interrupted the temporary object will // not be cleaned up, so the user must ensure that TmpObjectPrefix is // periodically garbage collected. - AppendThreshold int64 - TmpObjectPrefix string + AppendThreshold int64 + ChunkTransferTimeoutSecs int64 + TmpObjectPrefix string } // BucketManager manages the lifecycle of buckets. @@ -220,6 +221,7 @@ func (bm *bucketManager) SetUpBucket( } sb = NewSyncerBucket( bm.config.AppendThreshold, + bm.config.ChunkTransferTimeoutSecs, bm.config.TmpObjectPrefix, b) diff --git a/internal/gcsx/integration_test.go b/internal/gcsx/integration_test.go index 7addfb20fc..c0a19a2ba9 100644 --- a/internal/gcsx/integration_test.go +++ b/internal/gcsx/integration_test.go @@ -86,10 +86,12 @@ func (t *IntegrationTest) SetUp(ti *TestInfo) { // Set up the syncer. const appendThreshold = 0 + const chunkTransferTimeoutSecs = 10 const tmpObjectPrefix = ".gcsfuse_tmp/" t.syncer = gcsx.NewSyncer( appendThreshold, + chunkTransferTimeoutSecs, tmpObjectPrefix, t.bucket) } diff --git a/internal/gcsx/syncer.go b/internal/gcsx/syncer.go index 96804ad21c..aa32e05e24 100644 --- a/internal/gcsx/syncer.go +++ b/internal/gcsx/syncer.go @@ -55,6 +55,7 @@ type Syncer interface { // to do so. Therefore the user should arrange for garbage collection. func NewSyncer( appendThreshold int64, + chunkTransferTimeoutSecs int64, tmpObjectPrefix string, bucket gcs.Bucket) (os Syncer) { // Create the object creators. @@ -67,7 +68,7 @@ func NewSyncer( bucket) // And the syncer. - os = newSyncer(appendThreshold, fullCreator, appendCreator) + os = newSyncer(appendThreshold, chunkTransferTimeoutSecs, fullCreator, appendCreator) return } @@ -85,6 +86,7 @@ func (oc *fullObjectCreator) Create( objectName string, srcObject *gcs.Object, mtime *time.Time, + chunkTransferTimeoutSecs int64, r io.Reader) (o *gcs.Object, err error) { metadataMap := make(map[string]string) @@ -92,10 +94,11 @@ func (oc *fullObjectCreator) Create( if srcObject == nil { var precond int64 req = &gcs.CreateObjectRequest{ - Name: objectName, - Contents: r, - GenerationPrecondition: &precond, - Metadata: metadataMap, + Name: objectName, + Contents: r, + GenerationPrecondition: &precond, + Metadata: metadataMap, + ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, } } else { for key, value := range srcObject.Metadata { @@ -115,6 +118,7 @@ func (oc *fullObjectCreator) Create( CustomTime: srcObject.CustomTime, EventBasedHold: srcObject.EventBasedHold, StorageClass: srcObject.StorageClass, + ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, } } @@ -143,6 +147,7 @@ type objectCreator interface { objectName string, srcObject *gcs.Object, mtime *time.Time, + chunkTransferTimeoutSecs int64, r io.Reader) (o *gcs.Object, err error) } @@ -161,21 +166,24 @@ type objectCreator interface { // to GCS (for a small create, a compose, and a delete). func newSyncer( appendThreshold int64, + chunkTransferTimeoutSecs int64, fullCreator objectCreator, appendCreator objectCreator) (os Syncer) { os = &syncer{ - appendThreshold: appendThreshold, - fullCreator: fullCreator, - appendCreator: appendCreator, + appendThreshold: appendThreshold, + chunkTransferTimeoutSecs: chunkTransferTimeoutSecs, + fullCreator: fullCreator, + appendCreator: appendCreator, } return } type syncer struct { - appendThreshold int64 - fullCreator objectCreator - appendCreator objectCreator + appendThreshold int64 + chunkTransferTimeoutSecs int64 + fullCreator objectCreator + appendCreator objectCreator } func (os *syncer) SyncObject( @@ -200,7 +208,7 @@ func (os *syncer) SyncObject( err = fmt.Errorf("error in seeking: %w", err) return } - return os.fullCreator.Create(ctx, objectName, srcObject, sr.Mtime, content) + return os.fullCreator.Create(ctx, objectName, srcObject, sr.Mtime, os.chunkTransferTimeoutSecs, content) } // Make sure the dirty threshold makes sense. @@ -240,7 +248,7 @@ func (os *syncer) SyncObject( return } - o, err = os.appendCreator.Create(ctx, objectName, srcObject, sr.Mtime, content) + o, err = os.appendCreator.Create(ctx, objectName, srcObject, sr.Mtime, os.chunkTransferTimeoutSecs, content) } else { _, err = content.Seek(0, 0) if err != nil { @@ -248,7 +256,7 @@ func (os *syncer) SyncObject( return } - o, err = os.fullCreator.Create(ctx, objectName, srcObject, sr.Mtime, content) + o, err = os.fullCreator.Create(ctx, objectName, srcObject, sr.Mtime, os.chunkTransferTimeoutSecs, content) } // Deal with errors. diff --git a/internal/gcsx/syncer_bucket.go b/internal/gcsx/syncer_bucket.go index ae77832285..9e5654a740 100644 --- a/internal/gcsx/syncer_bucket.go +++ b/internal/gcsx/syncer_bucket.go @@ -27,9 +27,10 @@ type SyncerBucket struct { // a gcs.Bucket, or as a Syncer. func NewSyncerBucket( appendThreshold int64, + chunkTransferTimeoutSecs int64, tmpObjectPrefix string, bucket gcs.Bucket, ) SyncerBucket { - syncer := NewSyncer(appendThreshold, tmpObjectPrefix, bucket) + syncer := NewSyncer(appendThreshold, chunkTransferTimeoutSecs, tmpObjectPrefix, bucket) return SyncerBucket{bucket, syncer} } diff --git a/internal/gcsx/syncer_test.go b/internal/gcsx/syncer_test.go index b587542d46..0d42059ca7 100644 --- a/internal/gcsx/syncer_test.go +++ b/internal/gcsx/syncer_test.go @@ -67,6 +67,7 @@ func (t *FullObjectCreatorTest) call() (o *gcs.Object, err error) { t.srcObject.Name, &t.srcObject, &t.mtime, + chunkTransferTimeoutSecs, strings.NewReader(t.srcContents)) return @@ -175,6 +176,7 @@ func (t *FullObjectCreatorTest) CallsCreateObjectWhenSrcObjectIsNil() { t.srcObject.Name, nil, &t.mtime, + chunkTransferTimeoutSecs, strings.NewReader(t.srcContents)) t.validateEmptyProperties(req) @@ -194,6 +196,7 @@ func (t *FullObjectCreatorTest) CallsCreateObjectWhenSrcObjectAndMtimeAreNil() { t.srcObject.Name, nil, nil, + chunkTransferTimeoutSecs, strings.NewReader(t.srcContents)) t.validateEmptyProperties(req) @@ -243,6 +246,7 @@ func (oc *fakeObjectCreator) Create( fileName string, srcObject *gcs.Object, mtime *time.Time, + chunkTransferTimeoutSecs int64, r io.Reader) (o *gcs.Object, err error) { // Have we been called more than once? AssertFalse(oc.called) @@ -267,6 +271,7 @@ func (oc *fakeObjectCreator) Create( const srcObjectContents = "taco" const appendThreshold = int64(len(srcObjectContents)) +const chunkTransferTimeoutSecs = 10 type SyncerTest struct { ctx context.Context @@ -294,6 +299,7 @@ func (t *SyncerTest) SetUp(ti *TestInfo) { t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical) t.syncer = newSyncer( appendThreshold, + chunkTransferTimeoutSecs, &t.fullCreator, &t.appendCreator) @@ -413,6 +419,7 @@ func (t *SyncerTest) SourceTooShortForAppend() { // Recreate the syncer with a higher append threshold. t.syncer = newSyncer( int64(len(srcObjectContents)+1), + chunkTransferTimeoutSecs, &t.fullCreator, &t.appendCreator) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index f59a7a2026..9f1ad6b4ca 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -221,6 +221,7 @@ func (bh *bucketHandle) CreateObject(ctx context.Context, req *gcs.CreateObjectR // Creating a NewWriter with requested attributes, using Go Storage Client. // Chuck size for resumable upload is default i.e. 16MB. wc := obj.NewWriter(ctx) + wc.ChunkTransferTimeout = time.Duration(req.ChunkTransferTimeoutSecs) * time.Second wc = storageutil.SetAttrsInWriter(wc, req) wc.ProgressFunc = func(bytesUploadedSoFar int64) { logger.Tracef("gcs: Req %#16x: -- CreateObject(%q): %20v bytes uploaded so far", ctx.Value(gcs.ReqIdField), req.Name, bytesUploadedSoFar) diff --git a/internal/storage/gcs/request.go b/internal/storage/gcs/request.go index 30eef0bd2c..d5ab2375dd 100644 --- a/internal/storage/gcs/request.go +++ b/internal/storage/gcs/request.go @@ -54,6 +54,15 @@ type CreateObjectRequest struct { StorageClass string Acl []*storagev1.ObjectAccessControl + // ChunkTransferTimeout sets a per-chunk request timeout for resumable uploads. + // + // For resumable uploads, the Writer will terminate the request and attempt a retry + // if the request to upload a particular chunk stalls for longer than this duration. Retries + // may continue until the ChunkRetryDeadline(32s) is reached. + // + // The default value is 10 seconds. + ChunkTransferTimeoutSecs int64 + // A reader from which to obtain the contents of the object. Must be non-nil. Contents io.Reader From 8bf3207fbe0488c4dcf9ee1dd1b1b41921eb1de4 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 12 Dec 2024 11:02:36 +0000 Subject: [PATCH 0067/1298] Removing unnecessary noises while deleting bucket --- tools/integration_tests/run_e2e_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 5e012c9cef..21662a987b 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -320,7 +320,7 @@ function clean_up() { # Empty bucket name may cause deletions of all the buckets. if [ "$bucket" != "" ]; then - gcloud alpha storage rm --recursive gs://$bucket + gcloud alpha storage rm --recursive gs://$bucket 2>&1 | grep "ERROR" fi done } From 872b99d5845ff674e0c66067459595b180a5be58 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 13 Dec 2024 09:09:04 +0000 Subject: [PATCH 0068/1298] increasing timeout as some tests take longer to run. --- tools/cd_scripts/e2e_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 2556147ec1..36b9550758 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -172,7 +172,7 @@ TEST_DIR_NON_PARALLEL=( # Create a temporary file to store the log file name. TEST_LOGS_FILE=$(mktemp) -INTEGRATION_TEST_TIMEOUT=180m +INTEGRATION_TEST_TIMEOUT=240m function run_non_parallel_tests() { local exit_code=0 From ed4181d39937e2e46a0f84c081e5a901c6b70974 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:36:28 +0530 Subject: [PATCH 0069/1298] update buffered write sync and flush methods (#2794) * update bw sync and flush methods * review comments --- .../bufferedwrites/buffered_write_handler.go | 25 ++++--- .../buffered_write_handler_test.go | 67 +++++++++++++++++-- internal/bufferedwrites/upload_handler.go | 20 ++++-- .../bufferedwrites/upload_handler_test.go | 63 +++++++++++++---- 4 files changed, 142 insertions(+), 33 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index d3eed43991..c529f8cf41 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -119,20 +119,18 @@ func (wh *BufferedWriteHandler) Write(data []byte, offset int64) (err error) { // Sync uploads all the pending full buffers to GCS. func (wh *BufferedWriteHandler) Sync() (err error) { - // TODO: Will be added after uploadHandler changes are done. - return fmt.Errorf("not implemented") -} + wh.uploadHandler.AwaitBlocksUpload() -// Flush finalizes the upload. -func (wh *BufferedWriteHandler) Flush() (*gcs.Object, error) { - // Fail early if the uploadHandler has failed. select { case <-wh.uploadHandler.SignalUploadFailure(): - return nil, ErrUploadFailure + return ErrUploadFailure default: - break + return nil } +} +// Flush finalizes the upload. +func (wh *BufferedWriteHandler) Flush() (*gcs.Object, error) { if wh.current != nil { err := wh.uploadHandler.Upload(wh.current) if err != nil { @@ -140,15 +138,26 @@ func (wh *BufferedWriteHandler) Flush() (*gcs.Object, error) { } wh.current = nil } + obj, err := wh.uploadHandler.Finalize() if err != nil { return nil, fmt.Errorf("BufferedWriteHandler.Flush(): %w", err) } + err = wh.blockPool.ClearFreeBlockChannel() if err != nil { // Only logging an error in case of resource leak as upload succeeded. logger.Errorf("blockPool.ClearFreeBlockChannel() failed: %v", err) } + + // Return an error along with object if the uploadHandler failed in between. + select { + case <-wh.uploadHandler.SignalUploadFailure(): + return obj, ErrUploadFailure + default: + break + } + return obj, nil } diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index ab8d09d058..4fb265aa00 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -21,6 +21,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/jacobsa/timeutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -39,7 +40,7 @@ func TestBufferedWriteTestSuite(t *testing.T) { func (testSuite *BufferedWriteTest) SetupTest() { bucket := fake.NewFakeBucket(timeutil.RealClock(), "FakeBucketName", gcs.NonHierarchical) - bwh, err := NewBWHandler("testObject", bucket, 1024, 10, semaphore.NewWeighted(10)) + bwh, err := NewBWHandler("testObject", bucket, blockSize, 10, semaphore.NewWeighted(10)) require.Nil(testSuite.T(), err) testSuite.bwh = bwh } @@ -135,7 +136,7 @@ func (testSuite *BufferedWriteTest) TestMultipleWrites() { assert.Equal(testSuite.T(), int64(13), fileInfo.TotalSize) } -func (testSuite *BufferedWriteTest) TestWrite_SignalUploadFailureInBetween() { +func (testSuite *BufferedWriteTest) TestWriteWithSignalUploadFailureInBetween() { err := testSuite.bwh.Write([]byte("hello"), 0) require.Nil(testSuite.T(), err) fileInfo := testSuite.bwh.WriteFileInfo() @@ -176,7 +177,7 @@ func (testSuite *BufferedWriteTest) TestFlushWithNilCurrentBlock() { assert.Equal(testSuite.T(), uint64(0), obj.Size) } -func (testSuite *BufferedWriteTest) TestFlush_SignalUploadFailureDuringWrite() { +func (testSuite *BufferedWriteTest) TestFlushWithSignalUploadFailureDuringWrite() { err := testSuite.bwh.Write([]byte("hi"), 0) require.Nil(testSuite.T(), err) @@ -186,5 +187,63 @@ func (testSuite *BufferedWriteTest) TestFlush_SignalUploadFailureDuringWrite() { obj, err := testSuite.bwh.Flush() require.Error(testSuite.T(), err) assert.Equal(testSuite.T(), err, ErrUploadFailure) - assert.Nil(testSuite.T(), obj) + // Whatever could be finalized, got finalized (empty object in this case). + assert.NotNil(testSuite.T(), obj) + assert.Equal(testSuite.T(), uint64(0), obj.Size) +} + +func (testSuite *BufferedWriteTest) TestFlushWithMultiBlockWritesAndSignalUploadFailureInBetween() { + buffer, err := operations.GenerateRandomData(blockSize) + assert.NoError(testSuite.T(), err) + // Upload and sync 5 blocks. + testSuite.TestSync5InProgressBlocks() + // Close the channel to simulate failure in uploader. + close(testSuite.bwh.uploadHandler.SignalUploadFailure()) + // Write 5 more blocks. + for i := 0; i < 5; i++ { + err := testSuite.bwh.Write(buffer, int64(blockSize*(i+5))) + require.Error(testSuite.T(), err) + } + + obj, err := testSuite.bwh.Flush() + + require.Error(testSuite.T(), err) + assert.Equal(testSuite.T(), err, ErrUploadFailure) + // Whatever could be finalized, got finalized. + assert.NotNil(testSuite.T(), obj) + assert.Equal(testSuite.T(), uint64(5*blockSize), obj.Size) +} + +func (testSuite *BufferedWriteTest) TestSync5InProgressBlocks() { + buffer, err := operations.GenerateRandomData(blockSize) + assert.NoError(testSuite.T(), err) + // Write 5 blocks. + for i := 0; i < 5; i++ { + err = testSuite.bwh.Write(buffer, int64(blockSize*i)) + require.Nil(testSuite.T(), err) + } + + // Wait for 5 blocks to upload successfully. + err = testSuite.bwh.Sync() + + assert.NoError(testSuite.T(), err) + assert.Equal(testSuite.T(), 0, len(testSuite.bwh.uploadHandler.uploadCh)) + assert.Equal(testSuite.T(), 5, len(testSuite.bwh.blockPool.FreeBlocksChannel())) +} + +func (testSuite *BufferedWriteTest) TestSyncBlocksWithError() { + buffer, err := operations.GenerateRandomData(blockSize) + assert.NoError(testSuite.T(), err) + // Write 5 blocks. + for i := 0; i < 5; i++ { + err = testSuite.bwh.Write(buffer, int64(blockSize*i)) + require.Nil(testSuite.T(), err) + } + // Close the channel to simulate failure in uploader. + close(testSuite.bwh.uploadHandler.SignalUploadFailure()) + + err = testSuite.bwh.Sync() + + assert.Error(testSuite.T(), err) + assert.Equal(testSuite.T(), ErrUploadFailure, err) } diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 1ab488201a..4dcc651a2e 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -101,15 +101,17 @@ func (uh *UploadHandler) createObjectWriter() (err error) { // uploader is the single-threaded goroutine that uploads blocks. func (uh *UploadHandler) uploader() { for currBlock := range uh.uploadCh { - _, err := io.Copy(uh.writer, currBlock.Reader()) - if err != nil { - logger.Errorf("buffered write upload failed for object %s: error in io.Copy: %v", uh.objectName, err) - // Close the channel to signal upload failure. - close(uh.signalUploadFailure) - return + select { + case <-uh.signalUploadFailure: + default: + _, err := io.Copy(uh.writer, currBlock.Reader()) + if err != nil { + logger.Errorf("buffered write upload failed for object %s: error in io.Copy: %v", uh.objectName, err) + // Close the channel to signal upload failure. + close(uh.signalUploadFailure) + } } uh.wg.Done() - // Put back the uploaded block on the freeBlocksChannel for re-use. uh.freeBlocksCh <- currBlock } @@ -139,3 +141,7 @@ func (uh *UploadHandler) Finalize() (*gcs.Object, error) { func (uh *UploadHandler) SignalUploadFailure() chan error { return uh.signalUploadFailure } + +func (uh *UploadHandler) AwaitBlocksUpload() { + uh.wg.Wait() +} diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index c16792d292..280841e396 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -32,7 +32,8 @@ import ( ) const ( - blockSize = 1024 + blockSize = 1024 + maxBlocks int64 = 5 ) type UploadHandlerTest struct { @@ -47,10 +48,9 @@ func TestUploadHandlerTestSuite(t *testing.T) { } func (t *UploadHandlerTest) SetupTest() { - var maxBlocks int64 = 5 t.mockBucket = new(storagemock.TestifyMockBucket) var err error - t.blockPool, err = block.NewBlockPool(blockSize, maxBlocks, semaphore.NewWeighted(5)) + t.blockPool, err = block.NewBlockPool(blockSize, maxBlocks, semaphore.NewWeighted(maxBlocks)) require.NoError(t.T(), err) t.uh = newUploadHandler("testObject", t.mockBucket, maxBlocks, t.blockPool.FreeBlocksChannel(), blockSize) } @@ -85,17 +85,7 @@ func (t *UploadHandlerTest) TestMultipleBlockUpload() { got := <-t.uh.freeBlocksCh assert.Equal(t.T(), expect, got) } - // All goroutines for upload should have exited. - done := make(chan struct{}) - go func() { - t.uh.wg.Wait() - close(done) - }() - select { - case <-done: - case <-time.After(100 * time.Millisecond): - t.T().Error("Timeout waiting for WaitGroup") - } + assertAllBlocksProcessed(t.T(), t.uh) } func (t *UploadHandlerTest) TestUploadWhenCreateObjectWriterFails() { @@ -185,6 +175,8 @@ func (t *UploadHandlerTest) TestUploadSingleBlockThrowsErrorInCopy() { require.NoError(t.T(), err) // Expect an error on the signalUploadFailure channel due to error while copying content to GCS writer. assertUploadFailureSignal(t.T(), t.uh) + assertAllBlocksProcessed(t.T(), t.uh) + assert.Equal(t.T(), 1, len(t.uh.freeBlocksCh)) } func (t *UploadHandlerTest) TestUploadMultipleBlocksThrowsErrorInCopy() { @@ -212,6 +204,8 @@ func (t *UploadHandlerTest) TestUploadMultipleBlocksThrowsErrorInCopy() { } assertUploadFailureSignal(t.T(), t.uh) + assertAllBlocksProcessed(t.T(), t.uh) + assert.Equal(t.T(), 4, len(t.uh.freeBlocksCh)) } func assertUploadFailureSignal(t *testing.T, handler *UploadHandler) { @@ -225,6 +219,22 @@ func assertUploadFailureSignal(t *testing.T, handler *UploadHandler) { } } +func assertAllBlocksProcessed(t *testing.T, handler *UploadHandler) { + t.Helper() + + // All blocks for upload should have been processed. + done := make(chan struct{}) + go func() { + handler.wg.Wait() + close(done) + }() + select { + case <-done: + case <-time.After(100 * time.Millisecond): + t.Error("Timeout waiting for WaitGroup") + } +} + func TestSignalUploadFailure(t *testing.T) { mockSignalUploadFailure := make(chan error) uploadHandler := &UploadHandler{ @@ -235,3 +245,28 @@ func TestSignalUploadFailure(t *testing.T) { assert.Equal(t, mockSignalUploadFailure, actualChannel) } + +func (t *UploadHandlerTest) TestMultipleBlockAwaitBlocksUpload() { + // Create some blocks. + var blocks []block.Block + for i := 0; i < 5; i++ { + b, err := t.blockPool.Get() + require.NoError(t.T(), err) + blocks = append(blocks, b) + } + // CreateObjectChunkWriter -- should be called once. + writer := &storagemock.Writer{} + t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) + // Upload the blocks. + for _, b := range blocks { + err := t.uh.Upload(b) + require.NoError(t.T(), err) + } + + // AwaitBlocksUpload. + t.uh.AwaitBlocksUpload() + + assert.Equal(t.T(), 5, len(t.uh.freeBlocksCh)) + assert.Equal(t.T(), 0, len(t.uh.uploadCh)) + assertAllBlocksProcessed(t.T(), t.uh) +} From 2838d8015d7c35019828ab8b65e731c3946f22e4 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 13 Dec 2024 19:16:49 +0530 Subject: [PATCH 0070/1298] [testing-on-gke] Enable gVNIC in new cluster/node-pool (#2796) --- perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index b5471a37fc..332b8988f6 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -389,7 +389,7 @@ function createNewNodePool() { local machine_type=${4} local num_nodes=${5} local num_ssd=${6} - gcloud container node-pools create ${node_pool} --project=${project_id} --cluster ${cluster_name} --ephemeral-storage-local-ssd count=${num_ssd} --network-performance-configs=total-egress-bandwidth-tier=TIER_1 --machine-type ${machine_type} --zone ${zone} --num-nodes ${num_nodes} --workload-metadata=GKE_METADATA + gcloud container node-pools create ${node_pool} --project=${project_id} --cluster ${cluster_name} --ephemeral-storage-local-ssd count=${num_ssd} --network-performance-configs=total-egress-bandwidth-tier=TIER_1 --machine-type ${machine_type} --zone ${zone} --num-nodes ${num_nodes} --workload-metadata=GKE_METADATA --enable-gvnic } function getMachineTypeInNodePool() { @@ -429,7 +429,7 @@ function ensureGkeCluster() { fi gcloud container clusters update ${cluster_name} --project=${project_id} --location=${zone} --workload-pool=${project_id}.svc.id.goog else - gcloud container clusters create ${cluster_name} --project=${project_id} --zone "${zone}" --workload-pool=${project_id}.svc.id.goog --machine-type "${machine_type}" --image-type "COS_CONTAINERD" --num-nodes ${num_nodes} --ephemeral-storage-local-ssd count=${num_ssd} --network-performance-configs=total-egress-bandwidth-tier=TIER_1 --workload-metadata=GKE_METADATA + gcloud container clusters create ${cluster_name} --project=${project_id} --zone "${zone}" --workload-pool=${project_id}.svc.id.goog --machine-type "${machine_type}" --image-type "COS_CONTAINERD" --num-nodes ${num_nodes} --ephemeral-storage-local-ssd count=${num_ssd} --network-performance-configs=total-egress-bandwidth-tier=TIER_1 --workload-metadata=GKE_METADATA --enable-gvnic fi } From 5195af79b2c9ca68e9c1729adb435adbd219395b Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Sat, 14 Dec 2024 14:38:48 +0530 Subject: [PATCH 0071/1298] [Write-stall] Add e2e tests - 1 (#2767) * automate tests * separating list and stat request * separating list and stat request * add unit tests * add unit tests * update unit tests * rebase * add unit tests * linux test fix * rebasing * rebase * add vendor dir * test changes * test changes * update poc according to comments * remove print * remove print * rebase * rebase * rebase * rebase * lint fix * lint fix * undo unnecessary changes * undo unnecessary changes * add bash script and readme * add bash script and readme * test changes * test * lint fix * test changes * rebase * add e2e tests * rebase * add e2e tests * remove unnecessary files * remove unnecessary files * remove unnecessary files * remove unnecessary files * lint fix * rename tests * rename tests * add log * rebase * small fix * remove redundunt line * rebase * rebase * add comment * add comment * remove unnecessary changes * create var * small fix * use proxy port * adding testBucket variable in endpoint * lint fix * lint fix * update comment * fix typo * rebase * create separate test dir * create common function and review comment * review comment * small fix * small fix --- .../emulator_tests/emulator_test.go | 54 +++++++++++ .../emulator_tests/emulator_tests.sh | 3 +- .../proxy_server/configs/write_stall_40s.yaml | 10 +++ .../emulator_tests/util/test_helper.go | 49 +++++++++- .../emulator_tests/write_stall_test.go | 90 +++++++++++++++++++ tools/integration_tests/run_e2e_tests.sh | 2 +- 6 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 tools/integration_tests/emulator_tests/emulator_test.go create mode 100644 tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_40s.yaml create mode 100644 tools/integration_tests/emulator_tests/write_stall_test.go diff --git a/tools/integration_tests/emulator_tests/emulator_test.go b/tools/integration_tests/emulator_tests/emulator_test.go new file mode 100644 index 0000000000..06a4cde1ff --- /dev/null +++ b/tools/integration_tests/emulator_tests/emulator_test.go @@ -0,0 +1,54 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package emulator_tests + +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +const port = 8020 + +var ( + testDirPath string + mountFunc func([]string) error + // root directory is the directory to be unmounted. + rootDir string + proxyEndpoint = fmt.Sprintf("http://localhost:%d/storage/v1/b?project=test-project", port) +) + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + if setup.MountedDirectory() != "" { + log.Printf("These tests will not run with mounted directory..") + return + } + + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + + rootDir = setup.MntDir() + + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() + os.Exit(successCode) +} diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index 2d66529fef..1ea7b2fad3 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -19,6 +19,7 @@ set -eo pipefail # Display commands being run set -x +RUN_E2E_TESTS_ON_PACKAGE=$1 # Only run on Go 1.17+ min_minor_ver=17 @@ -73,4 +74,4 @@ curl -X POST --data-binary @test.json \ rm test.json # Run specific test suite -go test ./tools/integration_tests/emulator_tests/... --integrationTest -v --testbucket=test-bucket -timeout 10m +go test ./tools/integration_tests/emulator_tests/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_40s.yaml b/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_40s.yaml new file mode 100644 index 0000000000..89dc3ade0e --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_40s.yaml @@ -0,0 +1,10 @@ +targetHost: http://localhost:9000 +retryConfig: +- method: JsonCreate + retryInstruction: "stall-for-40s-after-15360K" + retryCount: 1 + # To add forced error scenarios for resumable uploads, we need to define skipCount two. + # This is because the first POST request creates the file in our tests, and the second POST request only initiates + # the resumable upload request. Subsequent requests actually upload the data, and it's + # these requests we want to stall for testing. + skipCount: 2 diff --git a/tools/integration_tests/emulator_tests/util/test_helper.go b/tools/integration_tests/emulator_tests/util/test_helper.go index 47f53c557e..cbe8ed8389 100644 --- a/tools/integration_tests/emulator_tests/util/test_helper.go +++ b/tools/integration_tests/emulator_tests/util/test_helper.go @@ -15,7 +15,9 @@ package emulator_tests import ( + "crypto/rand" "fmt" + "io" "log" "os" "os/exec" @@ -23,6 +25,7 @@ import ( "strconv" "strings" "syscall" + "time" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) @@ -32,7 +35,7 @@ import ( // It launches the proxy server with the specified configuration and port, logs its output to a file. func StartProxyServer(configPath string) { // Start the proxy in the background - cmd := exec.Command("go", "run", "../proxy_server/.", "--config-path="+configPath) + cmd := exec.Command("go", "run", "./proxy_server/.", "--config-path="+configPath) logFileForProxyServer, err := os.Create(path.Join(os.Getenv("KOKORO_ARTIFACTS_DIR"), "proxy-"+setup.GenerateRandomString(5))) if err != nil { log.Fatal("Error in creating log file for proxy server.") @@ -84,3 +87,47 @@ func KillProxyServerProcess(port int) error { return nil } + +// WriteFileAndSync creates a file at the given path, writes random data to it, +// and then syncs the file to GCS. It returns the time taken for the sync operation +// and any error encountered. +// +// This function is useful for testing scenarios where file write and sync operations +// might be subject to delays or timeouts. +// +// Parameters: +// - filePath: The path where the file should be created. +// - fileSize: The size of the random data to be written to the file. +// +// Returns: +// - time.Duration: The elapsed time for the file.Sync() operation. +// - error: Any error encountered during file creation, writing, or syncing. +func WriteFileAndSync(filePath string, fileSize int) (time.Duration, error) { + // Create a file for writing + file, err := os.Create(filePath) + if err != nil { + return 0, err + } + defer file.Close() + + // Generate random data + data := make([]byte, fileSize) + if _, err := io.ReadFull(rand.Reader, data); err != nil { + return 0, err + } + + // Write the data to the file + if _, err := file.Write(data); err != nil { + return 0, err + } + + startTime := time.Now() + err = file.Sync() + endTime := time.Now() + + if err != nil { + return 0, err + } + + return endTime.Sub(startTime), nil +} diff --git a/tools/integration_tests/emulator_tests/write_stall_test.go b/tools/integration_tests/emulator_tests/write_stall_test.go new file mode 100644 index 0000000000..53f48a3c03 --- /dev/null +++ b/tools/integration_tests/emulator_tests/write_stall_test.go @@ -0,0 +1,90 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package emulator_tests + +import ( + "log" + "path" + "testing" + "time" + + emulator_tests "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/emulator_tests/util" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/assert" +) + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +const ( + fileSize = 50 * 1024 * 1024 + stallTime = 40 * time.Second +) + +type chunkTransferTimeoutInfinity struct { + flags []string +} + +func (s *chunkTransferTimeoutInfinity) Setup(t *testing.T) { + configPath := "./proxy_server/configs/write_stall_40s.yaml" + emulator_tests.StartProxyServer(configPath) + setup.MountGCSFuseWithGivenMountFunc(s.flags, mountFunc) +} + +func (s *chunkTransferTimeoutInfinity) Teardown(t *testing.T) { + setup.UnmountGCSFuse(rootDir) + assert.NoError(t, emulator_tests.KillProxyServerProcess(port)) +} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +// This test verifies that write operations stall for the expected duration +// when write stall is induced while uploading first chunk. +// It creates a file, writes data to it, and then calls Sync() to ensure +// the data is written to GCS. The test measures the time taken for the Sync() +// operation and asserts that it is greater than or equal to the configured stall time. +func (s *chunkTransferTimeoutInfinity) TestWriteStallCausesDelay(t *testing.T) { + testDir := "TestWriteStallCausesDelay" + testDirPath = setup.SetupTestDirectory(testDir) + filePath := path.Join(testDirPath, "file.txt") + + elapsedTime, err := emulator_tests.WriteFileAndSync(filePath, fileSize) + + assert.NoError(t, err) + assert.GreaterOrEqual(t, elapsedTime, stallTime) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestChunkTransferTimeoutInfinity(t *testing.T) { + ts := &chunkTransferTimeoutInfinity{} + // Define flag set to run the tests. + flagsSet := [][]string{ + {"--custom-endpoint=" + proxyEndpoint, "--chunk-transfer-timeout-secs=0"}, + } + + // Run tests. + for _, flags := range flagsSet { + ts.flags = flags + log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) + } +} diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 21662a987b..6d460b8e7a 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -305,7 +305,7 @@ function run_e2e_tests_for_tpc() { } function run_e2e_tests_for_emulator() { - ./tools/integration_tests/emulator_tests/emulator_tests.sh + ./tools/integration_tests/emulator_tests/emulator_tests.sh $RUN_E2E_TESTS_ON_PACKAGE } #commenting it so cleanup and failure check happens for both From de832bf852ea107635c05c1538539966865007e9 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:44:48 +0530 Subject: [PATCH 0072/1298] Fix emulator based kokoro tests (#2799) * fix kokoro test * review comment --- tools/integration_tests/emulator_tests/emulator_tests.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index 1ea7b2fad3..f3247dad5e 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -19,6 +19,13 @@ set -eo pipefail # Display commands being run set -x +architecture=$(dpkg --print-architecture) +if [ $architecture == "arm64" ];then + # TODO: Remove this when we have an ARM64 image for the storage test bench.(b/384388821) + echo "These tests will not run for arm64 machine..." + exit 0 +fi + RUN_E2E_TESTS_ON_PACKAGE=$1 # Only run on Go 1.17+ min_minor_ver=17 From 78b69e8a72444037fd37684e170acd938e2dac6b Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 16 Dec 2024 12:11:17 +0530 Subject: [PATCH 0073/1298] use min object in buffered writes (#2795) --- internal/bufferedwrites/buffered_write_handler.go | 2 +- internal/bufferedwrites/upload_handler.go | 2 +- internal/bufferedwrites/upload_handler_test.go | 8 ++++---- internal/gcsx/prefix_bucket.go | 2 +- internal/gcsx/prefix_bucket_test.go | 4 ++-- internal/monitor/bucket.go | 2 +- internal/ratelimit/throttled_bucket.go | 2 +- internal/storage/bucket_handle.go | 6 +++--- internal/storage/caching/fast_stat_bucket.go | 8 ++++++-- internal/storage/caching/fast_stat_bucket_test.go | 6 +++--- internal/storage/debug_bucket.go | 2 +- internal/storage/fake/bucket.go | 2 +- internal/storage/fake/fake_object_writer.go | 5 +++-- internal/storage/gcs/bucket.go | 2 +- internal/storage/mock/testify_mock_bucket.go | 4 ++-- internal/storage/mock_bucket.go | 4 ++-- 16 files changed, 33 insertions(+), 28 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index c529f8cf41..ebe4989e81 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -130,7 +130,7 @@ func (wh *BufferedWriteHandler) Sync() (err error) { } // Flush finalizes the upload. -func (wh *BufferedWriteHandler) Flush() (*gcs.Object, error) { +func (wh *BufferedWriteHandler) Flush() (*gcs.MinObject, error) { if wh.current != nil { err := wh.uploadHandler.Upload(wh.current) if err != nil { diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 4dcc651a2e..a539bd7896 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -118,7 +118,7 @@ func (uh *UploadHandler) uploader() { } // Finalize finalizes the upload. -func (uh *UploadHandler) Finalize() (*gcs.Object, error) { +func (uh *UploadHandler) Finalize() (*gcs.MinObject, error) { uh.wg.Wait() close(uh.uploadCh) diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 280841e396..f068e82574 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -65,7 +65,7 @@ func (t *UploadHandlerTest) TestMultipleBlockUpload() { } // CreateObjectChunkWriter -- should be called once. writer := &storagemock.Writer{} - mockObj := &gcs.Object{} + mockObj := &gcs.MinObject{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, nil) @@ -105,7 +105,7 @@ func (t *UploadHandlerTest) TestUploadWhenCreateObjectWriterFails() { func (t *UploadHandlerTest) TestFinalizeWithWriterAlreadyPresent() { writer := &storagemock.Writer{} - mockObj := &gcs.Object{} + mockObj := &gcs.MinObject{} t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, nil) t.uh.writer = writer @@ -120,7 +120,7 @@ func (t *UploadHandlerTest) TestFinalizeWithNoWriter() { writer := &storagemock.Writer{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) - mockObj := &gcs.Object{} + mockObj := &gcs.MinObject{} t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, nil) obj, err := t.uh.Finalize() @@ -146,7 +146,7 @@ func (t *UploadHandlerTest) TestFinalizeWhenFinalizeUploadFails() { writer := &storagemock.Writer{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) - mockObj := &gcs.Object{} + mockObj := &gcs.MinObject{} t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, fmt.Errorf("taco")) obj, err := t.uh.Finalize() diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index ff3a1259ec..2dacccc0ef 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -111,7 +111,7 @@ func (b *prefixBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cre return wc, err } -func (b *prefixBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs.Object, err error) { +func (b *prefixBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { o, err = b.wrapped.FinalizeUpload(ctx, w) // Modify the returned object. if o != nil { diff --git a/internal/gcsx/prefix_bucket_test.go b/internal/gcsx/prefix_bucket_test.go index 6b0aaa55f1..cab3588b3d 100644 --- a/internal/gcsx/prefix_bucket_test.go +++ b/internal/gcsx/prefix_bucket_test.go @@ -128,7 +128,7 @@ func (t *PrefixBucketTest) CreateObjectChunkWriterAndFinalizeUpload() { t.ctx, &gcs.CreateObjectRequest{ Name: suffix, - ContentLanguage: "en-GB", + ContentEncoding: "gzip", Contents: nil, }, 1024, nil) @@ -139,7 +139,7 @@ func (t *PrefixBucketTest) CreateObjectChunkWriterAndFinalizeUpload() { AssertEq(nil, err) ExpectEq(suffix, o.Name) - ExpectEq("en-GB", o.ContentLanguage) + ExpectEq("gzip", o.ContentEncoding) // Read it through the back door. actual, err := storageutil.ReadObject(t.ctx, t.wrapped, t.prefix+suffix) AssertEq(nil, err) diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index f22b329044..08df7fff63 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -84,7 +84,7 @@ func (mb *monitoringBucket) CreateObjectChunkWriter(ctx context.Context, req *gc return wc, err } -func (mb *monitoringBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.Object, error) { +func (mb *monitoringBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { startTime := time.Now() o, err := mb.wrapped.FinalizeUpload(ctx, w) recordRequest(ctx, mb.metricHandle, "FinalizeUpload", startTime) diff --git a/internal/ratelimit/throttled_bucket.go b/internal/ratelimit/throttled_bucket.go index 886944f7b2..7938c8ac9f 100644 --- a/internal/ratelimit/throttled_bucket.go +++ b/internal/ratelimit/throttled_bucket.go @@ -107,7 +107,7 @@ func (b *throttledBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs. return } -func (b *throttledBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.Object, error) { +func (b *throttledBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { // FinalizeUpload is not throttled to prevent permanent data loss in case the // limiter's burst size is exceeded. // Note: CreateObjectChunkWriter, a prerequisite for FinalizeUpload, diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 9f1ad6b4ca..ff48adc793 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -268,7 +268,7 @@ func (bh *bucketHandle) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cr return wc, nil } -func (bh *bucketHandle) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs.Object, err error) { +func (bh *bucketHandle) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { if err = w.Close(); err != nil { var gErr *googleapi.Error if errors.As(err, &gErr) { @@ -282,8 +282,8 @@ func (bh *bucketHandle) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gc } attrs := w.Attrs() // Retrieving the attributes of the created object. - // Converting attrs to type *Object. - o = storageutil.ObjectAttrsToBucketObject(attrs) + // Converting attrs to type *MinObject. + o = storageutil.ObjectAttrsToMinObject(attrs) return } diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index 58a1cf56b5..37ce1d2d49 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -138,6 +138,10 @@ func (b *fastStatBucket) insert(o *gcs.Object) { b.insertMultiple([]*gcs.Object{o}) } +func (b *fastStatBucket) insertMinObject(o *gcs.MinObject) { + b.insertMultipleMinObjects([]*gcs.MinObject{o}) +} + // LOCKS_EXCLUDED(b.mu) func (b *fastStatBucket) insertFolder(f *gcs.Folder) { b.mu.Lock() @@ -231,7 +235,7 @@ func (b *fastStatBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.C return b.wrapped.CreateObjectChunkWriter(ctx, req, chunkSize, callBack) } -func (b *fastStatBucket) FinalizeUpload(ctx context.Context, writer gcs.Writer) (*gcs.Object, error) { +func (b *fastStatBucket) FinalizeUpload(ctx context.Context, writer gcs.Writer) (*gcs.MinObject, error) { name := writer.ObjectName() // Throw away any existing record for this object. b.invalidate(name) @@ -240,7 +244,7 @@ func (b *fastStatBucket) FinalizeUpload(ctx context.Context, writer gcs.Writer) // Record the new object if err is nil. if err == nil { - b.insert(o) + b.insertMinObject(o) } return o, err diff --git a/internal/storage/caching/fast_stat_bucket_test.go b/internal/storage/caching/fast_stat_bucket_test.go index 4e670d813c..6f0f057116 100644 --- a/internal/storage/caching/fast_stat_bucket_test.go +++ b/internal/storage/caching/fast_stat_bucket_test.go @@ -228,7 +228,7 @@ func (t *FinalizeUploadTest) CallsEraseAndWrappedWithExpectedParameter() { // Wrapped var wrappedWriter gcs.Writer ExpectCall(t.wrapped, "FinalizeUpload")(Any(), Any()). - WillOnce(DoAll(SaveArg(1, &wrappedWriter), Return(&gcs.Object{}, errors.New("")))) + WillOnce(DoAll(SaveArg(1, &wrappedWriter), Return(&gcs.MinObject{}, errors.New("")))) // Call _, _ = t.bucket.FinalizeUpload(context.TODO(), writer) @@ -246,7 +246,7 @@ func (t *FinalizeUploadTest) WrappedFails() { ExpectCall(t.cache, "Erase")(Any()) // Wrapped ExpectCall(t.wrapped, "FinalizeUpload")(Any(), Any()). - WillOnce(Return(&gcs.Object{}, errors.New("taco"))) + WillOnce(Return(&gcs.MinObject{}, errors.New("taco"))) // Call o, err := t.bucket.FinalizeUpload(context.TODO(), writer) @@ -265,7 +265,7 @@ func (t *FinalizeUploadTest) WrappedSucceeds() { ExpectCall(t.cache, "Erase")(Any()) // Wrapped ExpectCall(t.wrapped, "FinalizeUpload")(Any(), Any()). - WillOnce(Return(&gcs.Object{}, nil)) + WillOnce(Return(&gcs.MinObject{}, nil)) // Insert ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))) diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index a3404e8767..7c80c4681a 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -171,7 +171,7 @@ func (b *debugBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.Crea return } -func (b *debugBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs.Object, err error) { +func (b *debugBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { id, desc, start := b.startRequest("FinalizeUpload(%q)", w.ObjectName()) defer b.finishRequest(id, desc, start, &err) diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index cc913db477..6da81e2c21 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -689,7 +689,7 @@ func (b *bucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.CreateObj return NewFakeObjectWriter(b, req) } -func (b *bucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.Object, error) { +func (b *bucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { b.mu.Lock() defer b.mu.Unlock() diff --git a/internal/storage/fake/fake_object_writer.go b/internal/storage/fake/fake_object_writer.go index efd94e7f38..c213d975ee 100644 --- a/internal/storage/fake/fake_object_writer.go +++ b/internal/storage/fake/fake_object_writer.go @@ -25,6 +25,7 @@ import ( "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" ) // FakeObjectWriter is a mock implementation of storage.Writer used by FakeBucket. @@ -34,7 +35,7 @@ type FakeObjectWriter struct { storage.ObjectAttrs bkt *bucket req *gcs.CreateObjectRequest - Object *gcs.Object // Object created by writer + Object *gcs.MinObject // Object created by writer } func (w *FakeObjectWriter) Write(p []byte) (n int, err error) { @@ -51,7 +52,7 @@ func (w *FakeObjectWriter) Close() error { o, err := createOrUpdateFakeObject(w.bkt, w.req, contents) if err == nil { - w.Object = o + w.Object = storageutil.ConvertObjToMinObject(o) } return err diff --git a/internal/storage/gcs/bucket.go b/internal/storage/gcs/bucket.go index 746c0050c9..1cd020f1a4 100644 --- a/internal/storage/gcs/bucket.go +++ b/internal/storage/gcs/bucket.go @@ -99,7 +99,7 @@ type Bucket interface { // FinalizeUpload closes the storage.Writer which completes the write // operation and creates an object on GCS. - FinalizeUpload(ctx context.Context, writer Writer) (*Object, error) + FinalizeUpload(ctx context.Context, writer Writer) (*MinObject, error) // Copy an object to a new name, preserving all metadata. Any existing // generation of the destination name will be overwritten. diff --git a/internal/storage/mock/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go index b15a528a50..65d6fd916c 100644 --- a/internal/storage/mock/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -58,9 +58,9 @@ func (m *TestifyMockBucket) CreateObjectChunkWriter(ctx context.Context, req *gc return args.Get(0).(gcs.Writer), nil } -func (m *TestifyMockBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.Object, error) { +func (m *TestifyMockBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { args := m.Called(ctx, w) - return args.Get(0).(*gcs.Object), args.Error(1) + return args.Get(0).(*gcs.MinObject), args.Error(1) } func (m *TestifyMockBucket) CopyObject(ctx context.Context, req *gcs.CopyObjectRequest) (*gcs.Object, error) { diff --git a/internal/storage/mock_bucket.go b/internal/storage/mock_bucket.go index 46309c1636..57cad5acea 100644 --- a/internal/storage/mock_bucket.go +++ b/internal/storage/mock_bucket.go @@ -161,7 +161,7 @@ func (m *mockBucket) CreateObjectChunkWriter(p0 context.Context, p1 *gcs.CreateO return } -func (m *mockBucket) FinalizeUpload(p0 context.Context, p1 gcs.Writer) (o0 *gcs.Object, o1 error) { +func (m *mockBucket) FinalizeUpload(p0 context.Context, p1 gcs.Writer) (o0 *gcs.MinObject, o1 error) { // Get a file name and line number for the caller. _, file, line, _ := runtime.Caller(1) @@ -179,7 +179,7 @@ func (m *mockBucket) FinalizeUpload(p0 context.Context, p1 gcs.Writer) (o0 *gcs. // o0 *gcs.Object if retVals[0] != nil { - o0 = retVals[0].(*gcs.Object) + o0 = retVals[0].(*gcs.MinObject) } // o1 error if retVals[1] != nil { From 840605f954807bb81baafc2fef51ab569cdb31bc Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 16 Dec 2024 12:25:02 +0530 Subject: [PATCH 0074/1298] Compute OTel metrics (#2744) * Compute OTel metrics GCSFuse metrics have been implemented in OTel. This means that one can now use the available OTel exporters to export these metrics. --- cmd/legacy_main.go | 3 + common/oc_metrics.go | 19 ++ common/otel_metrics.go | 139 ++++++++++ common/telemetry.go | 6 + internal/monitor/otelexporters.go | 16 ++ .../integration_tests/monitoring/prom_test.go | 250 ++++++++++++++++++ tools/integration_tests/run_e2e_tests.sh | 1 + tools/integration_tests/util/setup/setup.go | 11 + 8 files changed, 445 insertions(+) create mode 100644 common/otel_metrics.go create mode 100644 tools/integration_tests/monitoring/prom_test.go diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 896f3472d5..68e64ac2fd 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -378,6 +378,9 @@ func Mount(newConfig *cfg.Config, bucketName, mountPoint string) (err error) { if cfg.IsMetricsEnabled(&newConfig.Metrics) { if newConfig.Metrics.EnableOtel { metricExporterShutdownFn = monitor.SetupOTelMetricExporters(ctx, newConfig) + if metricHandle, err = common.NewOTelMetrics(); err != nil { + metricHandle = common.NewNoopMetrics() + } } else { metricExporterShutdownFn = monitor.SetupOpenCensusExporters(newConfig) if metricHandle, err = common.NewOCMetrics(); err != nil { diff --git a/common/oc_metrics.go b/common/oc_metrics.go index 3e2cff1a2a..2bcf633615 100644 --- a/common/oc_metrics.go +++ b/common/oc_metrics.go @@ -23,6 +23,8 @@ import ( "go.opencensus.io/stats" "go.opencensus.io/stats/view" "go.opencensus.io/tag" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" ) const ( @@ -72,6 +74,23 @@ func attrsToTags(attrs []MetricAttr) []tag.Mutator { } return mutators } + +func attrsToRecordOption(attrs []MetricAttr) []metric.RecordOption { + otelOptions := make([]metric.RecordOption, 0, len(attrs)) + for _, attr := range attrs { + otelOptions = append(otelOptions, metric.WithAttributes(attribute.String(attr.Key, attr.Value))) + } + return otelOptions +} + +func attrsToAddOption(attrs []MetricAttr) []metric.AddOption { + otelOptions := make([]metric.AddOption, 0, len(attrs)) + for _, attr := range attrs { + otelOptions = append(otelOptions, metric.WithAttributes(attribute.String(attr.Key, attr.Value))) + } + return otelOptions +} + func (o *ocMetrics) GCSReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { recordOCMetric(ctx, o.gcsReadBytesCount, inc, attrs, "GCS read bytes count") } diff --git a/common/otel_metrics.go b/common/otel_metrics.go new file mode 100644 index 0000000000..7a1c5eb9de --- /dev/null +++ b/common/otel_metrics.go @@ -0,0 +1,139 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "context" + "errors" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/metric" +) + +var ( + fsOpsMeter = otel.Meter("fs_op") + gcsMeter = otel.Meter("gcs") + fileCacheMeter = otel.Meter("file_cache") +) + +// otelMetrics maintains the list of all metrics computed in GCSFuse. +type otelMetrics struct { + fsOpsCount metric.Int64Counter + fsOpsErrorCount metric.Int64Counter + fsOpsLatency metric.Float64Histogram + + gcsReadCount metric.Int64Counter + gcsReadBytesCount metric.Int64Counter + gcsReaderCount metric.Int64Counter + gcsRequestCount metric.Int64Counter + gcsRequestLatency metric.Float64Histogram + gcsDownloadBytesCount metric.Int64Counter + + fileCacheReadCount metric.Int64Counter + fileCacheReadBytesCount metric.Int64Counter + fileCacheReadLatency metric.Float64Histogram +} + +func (o *otelMetrics) GCSReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { + o.gcsReadBytesCount.Add(ctx, inc, attrsToAddOption(attrs)...) +} + +func (o *otelMetrics) GCSReaderCount(ctx context.Context, inc int64, attrs []MetricAttr) { + o.gcsReaderCount.Add(ctx, inc, attrsToAddOption(attrs)...) +} + +func (o *otelMetrics) GCSRequestCount(ctx context.Context, inc int64, attrs []MetricAttr) { + o.gcsRequestCount.Add(ctx, inc, attrsToAddOption(attrs)...) +} + +func (o *otelMetrics) GCSRequestLatency(ctx context.Context, value float64, attrs []MetricAttr) { + o.gcsRequestLatency.Record(ctx, value, attrsToRecordOption(attrs)...) +} + +func (o *otelMetrics) GCSReadCount(ctx context.Context, inc int64, attrs []MetricAttr) { + o.gcsReadCount.Add(ctx, inc, attrsToAddOption(attrs)...) +} + +func (o *otelMetrics) GCSDownloadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { + o.gcsDownloadBytesCount.Add(ctx, inc, attrsToAddOption(attrs)...) +} + +func (o *otelMetrics) OpsCount(ctx context.Context, inc int64, attrs []MetricAttr) { + o.fsOpsCount.Add(ctx, inc, attrsToAddOption(attrs)...) +} + +func (o *otelMetrics) OpsLatency(ctx context.Context, value float64, attrs []MetricAttr) { + o.fsOpsLatency.Record(ctx, value, attrsToRecordOption(attrs)...) +} + +func (o *otelMetrics) OpsErrorCount(ctx context.Context, inc int64, attrs []MetricAttr) { + o.fsOpsErrorCount.Add(ctx, inc, attrsToAddOption(attrs)...) +} + +func (o *otelMetrics) FileCacheReadCount(ctx context.Context, inc int64, attrs []MetricAttr) { + o.fileCacheReadCount.Add(ctx, inc, attrsToAddOption(attrs)...) +} + +func (o *otelMetrics) FileCacheReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { + o.fileCacheReadBytesCount.Add(ctx, inc, attrsToAddOption(attrs)...) +} + +func (o *otelMetrics) FileCacheReadLatency(ctx context.Context, value float64, attrs []MetricAttr) { + o.fileCacheReadLatency.Record(ctx, value, attrsToRecordOption(attrs)...) +} + +func NewOTelMetrics() (MetricHandle, error) { + fsOpsCount, err1 := fsOpsMeter.Int64Counter("fs/ops_count", metric.WithDescription("The number of ops processed by the file system.")) + fsOpsLatency, err2 := fsOpsMeter.Float64Histogram("fs/ops_latency", metric.WithDescription("The latency of a file system operation."), metric.WithUnit("us"), + defaultLatencyDistribution) + fsOpsErrorCount, err3 := fsOpsMeter.Int64Counter("fs/ops_error_count", metric.WithDescription("The number of errors generated by file system operation.")) + + gcsReadCount, err4 := gcsMeter.Int64Counter("gcs/read_count", metric.WithDescription("Specifies the number of gcs reads made along with type - Sequential/Random")) + gcsDownloadBytesCount, err5 := gcsMeter.Int64Counter("gcs/download_bytes_count", + metric.WithDescription("The cumulative number of bytes downloaded from GCS along with type - Sequential/Random"), + metric.WithUnit("By")) + gcsReadBytesCount, err6 := gcsMeter.Int64Counter("gcs/read_bytes_count", metric.WithDescription("The number of bytes read from GCS objects."), metric.WithUnit("By")) + gcsReaderCount, err7 := gcsMeter.Int64Counter("gcs/reader_count", metric.WithDescription("The number of GCS object readers opened or closed.")) + gcsRequestCount, err8 := gcsMeter.Int64Counter("gcs/request_count", metric.WithDescription("The cumulative number of GCS requests processed.")) + gcsRequestLatency, err9 := gcsMeter.Float64Histogram("gcs/request_latency", metric.WithDescription("The latency of a GCS request."), metric.WithUnit("ms")) + + fileCacheReadCount, err10 := fileCacheMeter.Int64Counter("file_cache/read_count", + metric.WithDescription("Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false")) + fileCacheReadBytesCount, err11 := fileCacheMeter.Int64Counter("file_cache/read_bytes_count", + metric.WithDescription("The cumulative number of bytes read from file cache along with read type - Sequential/Random"), + metric.WithUnit("By")) + fileCacheReadLatency, err12 := fileCacheMeter.Float64Histogram("file_cache/read_latencies", + metric.WithDescription("Latency of read from file cache along with cache hit - true/false"), + metric.WithUnit("us"), + defaultLatencyDistribution) + + if err := errors.Join(err1, err2, err3, err4, err5, err6, err7, err8, err9, err10, err11, err12); err != nil { + return nil, err + } + return &otelMetrics{ + fsOpsCount: fsOpsCount, + fsOpsErrorCount: fsOpsErrorCount, + fsOpsLatency: fsOpsLatency, + gcsReadCount: gcsReadCount, + gcsReadBytesCount: gcsReadBytesCount, + gcsReaderCount: gcsReaderCount, + gcsRequestCount: gcsRequestCount, + gcsRequestLatency: gcsRequestLatency, + gcsDownloadBytesCount: gcsDownloadBytesCount, + fileCacheReadCount: fileCacheReadCount, + fileCacheReadBytesCount: fileCacheReadBytesCount, + fileCacheReadLatency: fileCacheReadLatency, + }, nil +} diff --git a/common/telemetry.go b/common/telemetry.go index c23dfa2680..ac42554dcc 100644 --- a/common/telemetry.go +++ b/common/telemetry.go @@ -18,10 +18,16 @@ import ( "context" "errors" "fmt" + + "go.opentelemetry.io/otel/metric" ) type ShutdownFn func(ctx context.Context) error +// The default time buckets for latency metrics. +// The unit can however change for different units i.e. for one metric the unit could be microseconds and for another it could be milliseconds. +var defaultLatencyDistribution = metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000) + // JoinShutdownFunc combines the provided shutdown functions into a single function. func JoinShutdownFunc(shutdownFns ...ShutdownFn) ShutdownFn { return func(ctx context.Context) error { diff --git a/internal/monitor/otelexporters.go b/internal/monitor/otelexporters.go index eea69fb8ed..bf04364a90 100644 --- a/internal/monitor/otelexporters.go +++ b/internal/monitor/otelexporters.go @@ -39,6 +39,8 @@ import ( const serviceName = "gcsfuse" const cloudMonitoringMetricPrefix = "custom.googleapis.com/gcsfuse/" +var allowedMetricPrefixes = []string{"fs/", "gcs/", "file_cache/"} + // SetupOTelMetricExporters sets up the metrics exporters func SetupOTelMetricExporters(ctx context.Context, c *cfg.Config) (shutdownFn common.ShutdownFn) { shutdownFns := make([]common.ShutdownFn, 0) @@ -59,6 +61,8 @@ func SetupOTelMetricExporters(ctx context.Context, c *cfg.Config) (shutdownFn co options = append(options, metric.WithResource(res)) } + options = append(options, metric.WithView(dropDisallowedMetricsView)) + meterProvider := metric.NewMeterProvider(options...) shutdownFns = append(shutdownFns, meterProvider.Shutdown) @@ -67,6 +71,18 @@ func SetupOTelMetricExporters(ctx context.Context, c *cfg.Config) (shutdownFn co return common.JoinShutdownFunc(shutdownFns...) } +// dropUnwantedMetricsView is an OTel View that drops the metrics that don't match the allowed prefixes. +func dropDisallowedMetricsView(i metric.Instrument) (metric.Stream, bool) { + s := metric.Stream{Name: i.Name, Description: i.Description, Unit: i.Unit} + for _, prefix := range allowedMetricPrefixes { + if strings.HasPrefix(i.Name, prefix) { + return s, true + } + } + s.Aggregation = metric.AggregationDrop{} + return s, true +} + func setupCloudMonitoring(secs int64) ([]metric.Option, common.ShutdownFn) { if secs <= 0 { return nil, nil diff --git a/tools/integration_tests/monitoring/prom_test.go b/tools/integration_tests/monitoring/prom_test.go new file mode 100644 index 0000000000..ce462de8f7 --- /dev/null +++ b/tools/integration_tests/monitoring/prom_test.go @@ -0,0 +1,250 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package monitoring + +import ( + "context" + "fmt" + "net/http" + "os" + "os/exec" + "path" + "strings" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + promclient "github.com/prometheus/client_model/go" + "github.com/prometheus/common/expfmt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +const ( + testBucket = "gcsfuse_monitoring_test_bucket" + portNonHNSRun = 9191 + portHNSRun = 9192 +) + +var prometheusPort = portNonHNSRun + +func isPortOpen(port int) bool { + c := exec.Command("lsof", "-t", fmt.Sprintf("-i:%d", port)) + output, _ := c.CombinedOutput() + return len(output) == 0 +} + +type PromTest struct { + suite.Suite + // Path to the gcsfuse binary. + gcsfusePath string + + // A temporary directory into which a file system may be mounted. Removed in + // TearDown. + mountPoint string + + enableOTEL bool +} + +// isHNSTestRun returns true if the bucket is an HNS bucket. +func isHNSTestRun(t *testing.T) bool { + storageClient, err := client.CreateStorageClient(context.Background()) + require.NoError(t, err, "error while creating storage client") + defer storageClient.Close() + return setup.IsHierarchicalBucket(context.Background(), storageClient) +} + +func (testSuite *PromTest) SetupSuite() { + setup.IgnoreTestIfIntegrationTestFlagIsNotSet(testSuite.T()) + if isHNSTestRun(testSuite.T()) { + // sets different Prometheus ports for HNS and non-HNS presubmit runs. + // This ensures that there is no port contention if both HNS and non-HNS test runs are happening simultaneously. + prometheusPort = portHNSRun + } + + err := setup.SetUpTestDir() + require.NoErrorf(testSuite.T(), err, "error while building GCSFuse: %p", err) +} + +func (testSuite *PromTest) SetupTest() { + var err error + testSuite.gcsfusePath = setup.BinFile() + testSuite.mountPoint, err = os.MkdirTemp("", "gcsfuse_monitoring_tests") + require.NoError(testSuite.T(), err) + + setup.SetLogFile(fmt.Sprintf("%s%s.txt", "/tmp/gcsfuse_monitoring_test_", strings.ReplaceAll(testSuite.T().Name(), "/", "_"))) + err = testSuite.mount(testBucket) + require.NoError(testSuite.T(), err) +} + +func (testSuite *PromTest) TearDownTest() { + if err := util.Unmount(testSuite.mountPoint); err != nil { + fmt.Fprintf(os.Stderr, "Warning: unmount failed: %v\n", err) + } + require.True(testSuite.T(), isPortOpen(prometheusPort)) + + err := os.Remove(testSuite.mountPoint) + assert.NoError(testSuite.T(), err) +} + +func (testSuite *PromTest) TearDownSuite() { + os.RemoveAll(setup.TestDir()) +} + +func (testSuite *PromTest) mount(bucketName string) error { + testSuite.T().Helper() + if portAvailable := isPortOpen(prometheusPort); !portAvailable { + require.Failf(testSuite.T(), "prometheus port is not available.", "port: %d", int64(prometheusPort)) + } + cacheDir, err := os.MkdirTemp("", "gcsfuse-cache") + require.NoError(testSuite.T(), err) + testSuite.T().Cleanup(func() { _ = os.RemoveAll(cacheDir) }) + + flags := []string{fmt.Sprintf("--prometheus-port=%d", prometheusPort), "--cache-dir", cacheDir} + if testSuite.enableOTEL { + flags = append(flags, "--enable-otel=true") + } else { + flags = append(flags, "--enable-otel=false") + } + args := append(flags, bucketName, testSuite.mountPoint) + + if err := mounting.MountGcsfuse(testSuite.gcsfusePath, args); err != nil { + return err + } + return nil +} + +func parsePromFormat(testSuite *PromTest) (map[string]*promclient.MetricFamily, error) { + testSuite.T().Helper() + + resp, err := http.Get(fmt.Sprintf("http://localhost:%d/metrics", prometheusPort)) + require.NoError(testSuite.T(), err) + var parser expfmt.TextParser + return parser.TextToMetricFamilies(resp.Body) +} + +// assertNonZeroCountMetric asserts that the specified count metric is present and is positive in the Prometheus export +func assertNonZeroCountMetric(testSuite *PromTest, metricName, labelName, labelValue string) { + testSuite.T().Helper() + mf, err := parsePromFormat(testSuite) + require.NoError(testSuite.T(), err) + for k, v := range mf { + if k != metricName || *v.Type != promclient.MetricType_COUNTER { + continue + } + for _, m := range v.Metric { + if *m.Counter.Value <= 0 { + continue + } + if labelName == "" { + return + } + for _, l := range m.GetLabel() { + if *l.Name == labelName && *l.Value == labelValue { + return + } + } + } + + } + assert.Fail(testSuite.T(), "Didn't find the metric with name: %s, labelName: %s and labelValue: %s", metricName, labelName, labelValue) +} + +// assertNonZeroHistogramMetric asserts that the specified histogram metric is present and is positive for at least one of the buckets in the Prometheus export. +func assertNonZeroHistogramMetric(testSuite *PromTest, metricName, labelName, labelValue string) { + testSuite.T().Helper() + mf, err := parsePromFormat(testSuite) + require.NoError(testSuite.T(), err) + + for k, v := range mf { + if k != metricName || *v.Type != promclient.MetricType_HISTOGRAM { + continue + } + for _, m := range v.Metric { + for _, bkt := range m.GetHistogram().Bucket { + if bkt.CumulativeCount == nil || *bkt.CumulativeCount == 0 { + continue + } + if labelName == "" { + return + } + for _, l := range m.GetLabel() { + if *l.Name == labelName && *l.Value == labelValue { + return + } + } + } + } + } +} + +func (testSuite *PromTest) TestStatMetrics() { + _, err := os.Stat(path.Join(testSuite.mountPoint, "hello/hello.txt")) + + require.NoError(testSuite.T(), err) + assertNonZeroCountMetric(testSuite, "fs_ops_count", "fs_op", "LookUpInode") + assertNonZeroHistogramMetric(testSuite, "fs_ops_latency", "fs_op", "LookUpInode") + assertNonZeroCountMetric(testSuite, "gcs_request_count", "gcs_method", "StatObject") + assertNonZeroHistogramMetric(testSuite, "gcs_request_latencies", "gcs_method", "StatObject") +} + +func (testSuite *PromTest) TestFsOpsErrorMetrics() { + _, err := os.Stat(path.Join(testSuite.mountPoint, "non_existent_path.txt")) + require.Error(testSuite.T(), err) + + assertNonZeroCountMetric(testSuite, "fs_ops_error_count", "fs_op", "LookUpInode") + assertNonZeroHistogramMetric(testSuite, "fs_ops_latency", "fs_op", "LookUpInode") +} + +func (testSuite *PromTest) TestListMetrics() { + _, err := os.ReadDir(path.Join(testSuite.mountPoint, "hello")) + + require.NoError(testSuite.T(), err) + assertNonZeroCountMetric(testSuite, "fs_ops_count", "fs_op", "ReadDir") + assertNonZeroCountMetric(testSuite, "fs_ops_count", "fs_op", "OpenDir") + assertNonZeroCountMetric(testSuite, "gcs_request_count", "gcs_method", "ListObjects") + assertNonZeroHistogramMetric(testSuite, "gcs_request_latencies", "gcs_method", "ListObjects") +} + +func (testSuite *PromTest) TestReadMetrics() { + _, err := os.ReadFile(path.Join(testSuite.mountPoint, "hello/hello.txt")) + + require.NoError(testSuite.T(), err) + assertNonZeroCountMetric(testSuite, "file_cache_read_count", "cache_hit", "false") + assertNonZeroCountMetric(testSuite, "file_cache_read_count", "read_type", "Sequential") + assertNonZeroCountMetric(testSuite, "file_cache_read_bytes_count", "read_type", "Sequential") + assertNonZeroHistogramMetric(testSuite, "file_cache_read_latencies", "cache_hit", "false") + assertNonZeroCountMetric(testSuite, "fs_ops_count", "fs_op", "OpenFile") + assertNonZeroCountMetric(testSuite, "fs_ops_count", "fs_op", "ReadFile") + assertNonZeroCountMetric(testSuite, "fs_ops_count", "fs_op", "ReadFile") + assertNonZeroCountMetric(testSuite, "gcs_request_count", "gcs_method", "NewReader") + assertNonZeroCountMetric(testSuite, "gcs_reader_count", "io_method", "opened") + assertNonZeroCountMetric(testSuite, "gcs_reader_count", "io_method", "closed") + assertNonZeroCountMetric(testSuite, "gcs_read_count", "read_type", "Sequential") + assertNonZeroCountMetric(testSuite, "gcs_download_bytes_count", "", "") + assertNonZeroCountMetric(testSuite, "gcs_read_bytes_count", "", "") + assertNonZeroHistogramMetric(testSuite, "gcs_request_latencies", "gcs_method", "NewReader") +} + +func TestPromOCSuite(t *testing.T) { + suite.Run(t, &PromTest{enableOTEL: false}) +} + +func TestPromOTELSuite(t *testing.T) { + suite.Run(t, &PromTest{enableOTEL: true}) +} diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 6d460b8e7a..b06a08ce9d 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -64,6 +64,7 @@ echo "Setting the integration test timeout to: $INTEGRATION_TEST_TIMEOUT" readonly RANDOM_STRING_LENGTH=5 # Test directory arrays TEST_DIR_PARALLEL=( + "monitoring" "local_file" "log_rotation" "mounting" diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 3fedf1ad04..66c919ce87 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -273,6 +273,17 @@ func IgnoreTestIfIntegrationTestFlagIsSet(t *testing.T) { } } +// IgnoreTestIfIntegrationTestFlagIsNotSet helps skip a test if --integrationTest flag is not set. +// If the test uses TestMain, then one usually calls os.Exit() to skip the test, +// but for non-TestMain tests, this helps skip integration tests if --integrationTest has not been passed. +func IgnoreTestIfIntegrationTestFlagIsNotSet(t *testing.T) { + flag.Parse() + + if !*integrationTest { + t.SkipNow() + } +} + func ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() { ParseSetUpFlags() From 6c3060a963f374ffc60aea89c9f8e5b40e854026 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Mon, 16 Dec 2024 07:34:21 +0000 Subject: [PATCH 0075/1298] Handling truncate in buffered writes flow (#2775) * Truncate handling * added comment * fixing truncate * PR review comments * PR review comments --- .../bufferedwrites/buffered_write_handler.go | 63 ++++++- .../buffered_write_handler_test.go | 89 +++++++++ internal/fs/inode/file.go | 12 ++ internal/fs/inode/file_test.go | 173 +++++++++++++++++- 4 files changed, 332 insertions(+), 5 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index ebe4989e81..3a2ecfe2ca 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -36,10 +36,18 @@ type BufferedWriteHandler struct { blockPool *block.BlockPool uploadHandler *UploadHandler // Total size of data buffered so far. Some part of buffered data might have - // been uploaded to GCS as well. + // been uploaded to GCS as well. Depending on the state we are in, it might or + // might not include truncatedSize. totalSize int64 // Stores the mtime value updated by kernel as part of setInodeAttributes call. mtime time.Time + // Stores the size to truncate. No action is made when truncate is called. + // Will be used as mentioned below: + // 1. During flush if totalSize != truncatedSize, additional dummy data is + // added before flush and uploaded. + // 2. If write is started after the truncate offset, dummy data is created + // as per the truncatedSize and then new data is appended to it. + truncatedSize int64 } // WriteFileInfo is used as part of serving fileInode attributes (GetInodeAttributes call). @@ -71,12 +79,20 @@ func NewBWHandler(objectName string, bucket gcs.Bucket, blockSize int64, maxBloc // Write writes the given data to the buffer. It writes to an existing buffer if // the capacity is available otherwise writes to a new buffer. func (wh *BufferedWriteHandler) Write(data []byte, offset int64) (err error) { - if offset != wh.totalSize { + if offset != wh.totalSize && offset != wh.truncatedSize { logger.Errorf("BufferedWriteHandler.OutOfOrderError for object: %s, expectedOffset: %d, actualOffset: %d", wh.uploadHandler.objectName, wh.totalSize, offset) return ErrOutOfOrderWrite } + if offset == wh.truncatedSize { + // Check and update if any data filling has to be done. + err = wh.writeDataForTruncatedSize() + if err != nil { + return + } + } + // Fail early if the uploadHandler has failed. select { case <-wh.uploadHandler.SignalUploadFailure(): @@ -85,6 +101,10 @@ func (wh *BufferedWriteHandler) Write(data []byte, offset int64) (err error) { break } + return wh.appendBuffer(data) +} + +func (wh *BufferedWriteHandler) appendBuffer(data []byte) (err error) { dataWritten := 0 for dataWritten < len(data) { if wh.current == nil { @@ -131,6 +151,12 @@ func (wh *BufferedWriteHandler) Sync() (err error) { // Flush finalizes the upload. func (wh *BufferedWriteHandler) Flush() (*gcs.MinObject, error) { + // In case it is a truncated file, upload empty blocks as required. + err := wh.writeDataForTruncatedSize() + if err != nil { + return nil, err + } + if wh.current != nil { err := wh.uploadHandler.Upload(wh.current) if err != nil { @@ -166,11 +192,42 @@ func (wh *BufferedWriteHandler) SetMtime(mtime time.Time) { wh.mtime = mtime } +func (wh *BufferedWriteHandler) Truncate(size int64) error { + if size < wh.totalSize { + return fmt.Errorf("cannot truncate to lesser size when upload is in progress") + } + + wh.truncatedSize = size + return nil +} + // WriteFileInfo returns the file info i.e, how much data has been buffered so far // and the mtime. func (wh *BufferedWriteHandler) WriteFileInfo() WriteFileInfo { return WriteFileInfo{ - TotalSize: wh.totalSize, + TotalSize: int64(math.Max(float64(wh.totalSize), float64(wh.truncatedSize))), Mtime: wh.mtime, } } + +func (wh *BufferedWriteHandler) writeDataForTruncatedSize() error { + // If totalSize is greater than truncatedSize, that means user has + // written more data than they actually truncated in the beginning. + if wh.totalSize >= wh.truncatedSize { + return nil + } + + // Otherwise append dummy data to match truncatedSize. + diff := wh.truncatedSize - wh.totalSize + // Create 1MB of data at a time to avoid OOM + chunkSize := 1024 * 1024 + for i := 0; i < int(diff); i += chunkSize { + size := math.Min(float64(chunkSize), float64(int(diff)-i)) + err := wh.appendBuffer(make([]byte, int(size))) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 4fb265aa00..0c814e995c 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -151,6 +151,39 @@ func (testSuite *BufferedWriteTest) TestWriteWithSignalUploadFailureInBetween() assert.Equal(testSuite.T(), err, ErrUploadFailure) } +func (testSuite *BufferedWriteTest) TestWriteAtTruncatedOffset() { + // Truncate + err := testSuite.bwh.Truncate(2) + require.NoError(testSuite.T(), err) + require.Equal(testSuite.T(), int64(2), testSuite.bwh.truncatedSize) + + // Write at offset = truncatedSize + err = testSuite.bwh.Write([]byte("hello"), 2) + + require.Nil(testSuite.T(), err) + fileInfo := testSuite.bwh.WriteFileInfo() + assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + assert.Equal(testSuite.T(), int64(7), fileInfo.TotalSize) +} + +func (testSuite *BufferedWriteTest) TestWriteAfterTruncateAtCurrentSize() { + err := testSuite.bwh.Write([]byte("hello"), 0) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), int64(5), testSuite.bwh.totalSize) + // Truncate + err = testSuite.bwh.Truncate(20) + require.NoError(testSuite.T(), err) + require.Equal(testSuite.T(), int64(20), testSuite.bwh.truncatedSize) + require.Equal(testSuite.T(), int64(20), testSuite.bwh.WriteFileInfo().TotalSize) + + // Write at offset=bwh.totalSize + err = testSuite.bwh.Write([]byte("abcde"), 5) + + require.Nil(testSuite.T(), err) + assert.Equal(testSuite.T(), int64(10), testSuite.bwh.totalSize) + assert.Equal(testSuite.T(), int64(20), testSuite.bwh.WriteFileInfo().TotalSize) +} + func (testSuite *BufferedWriteTest) TestFlushWithNonNilCurrentBlock() { err := testSuite.bwh.Write([]byte("hi"), 0) require.Nil(testSuite.T(), err) @@ -247,3 +280,59 @@ func (testSuite *BufferedWriteTest) TestSyncBlocksWithError() { assert.Error(testSuite.T(), err) assert.Equal(testSuite.T(), ErrUploadFailure, err) } + +func (testSuite *BufferedWriteTest) TestFlushWithNonZeroTruncatedLengthForEmptyObject() { + require.Nil(testSuite.T(), testSuite.bwh.current) + testSuite.bwh.truncatedSize = 10 + + _, err := testSuite.bwh.Flush() + + assert.NoError(testSuite.T(), err) + assert.Equal(testSuite.T(), testSuite.bwh.truncatedSize, testSuite.bwh.totalSize) +} + +func (testSuite *BufferedWriteTest) TestFlushWithTruncatedLengthGreaterThanObjectSize() { + err := testSuite.bwh.Write([]byte("hi"), 0) + require.Nil(testSuite.T(), err) + testSuite.bwh.truncatedSize = 10 + + _, err = testSuite.bwh.Flush() + + assert.NoError(testSuite.T(), err) + assert.Equal(testSuite.T(), testSuite.bwh.truncatedSize, testSuite.bwh.totalSize) +} + +func (testSuite *BufferedWriteTest) TestTruncateWithLesserSize() { + testSuite.bwh.totalSize = 10 + + err := testSuite.bwh.Truncate(2) + + assert.Error(testSuite.T(), err) +} + +func (testSuite *BufferedWriteTest) TestTruncateWithSizeGreaterThanCurrentObjectSize() { + testSuite.bwh.totalSize = 10 + + err := testSuite.bwh.Truncate(12) + + assert.NoError(testSuite.T(), err) + assert.Equal(testSuite.T(), int64(12), testSuite.bwh.truncatedSize) +} + +func (testSuite *BufferedWriteTest) TestWriteFileInfoWithTruncatedLengthLessThanTotalSize() { + testSuite.bwh.totalSize = 10 + testSuite.bwh.truncatedSize = 5 + + fileInfo := testSuite.bwh.WriteFileInfo() + + assert.Equal(testSuite.T(), testSuite.bwh.totalSize, fileInfo.TotalSize) +} + +func (testSuite *BufferedWriteTest) TestWriteFileInfoWithTruncatedLengthGreaterThanTotalSize() { + testSuite.bwh.totalSize = 10 + testSuite.bwh.truncatedSize = 20 + + fileInfo := testSuite.bwh.WriteFileInfo() + + assert.Equal(testSuite.T(), testSuite.bwh.truncatedSize, fileInfo.TotalSize) +} diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index f3c60c2de5..fe0a0394f8 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -691,6 +691,18 @@ func (f *FileInode) Sync(ctx context.Context) (err error) { func (f *FileInode) Truncate( ctx context.Context, size int64) (err error) { + // For empty GCS files also, we will trigger bufferedWrites flow. + if f.src.Size == 0 && f.writeConfig.ExperimentalEnableStreamingWrites { + err = f.ensureBufferedWriteHandler() + if err != nil { + return + } + } + + if f.bwh != nil { + return f.bwh.Truncate(size) + } + // Make sure f.content != nil. err = f.ensureContent(ctx) if err != nil { diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index b799b6487d..731b14e32b 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -624,7 +624,7 @@ func (t *FileTest) TestTruncateUpwardThenSync() { assert.Equal(t.T(), attrs.Mtime, truncateTime.UTC()) } -func (t *FileTest) TestTestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes() { +func (t *FileTest) TestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes() { var err error var attrs fuseops.InodeAttributes // Create a local file inode. @@ -650,7 +650,7 @@ func (t *FileTest) TestTestTruncateUpwardForLocalFileShouldUpdateLocalFileAttrib assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) } -func (t *FileTest) TestTestTruncateDownwardForLocalFileShouldUpdateLocalFileAttributes() { +func (t *FileTest) TestTruncateDownwardForLocalFileShouldUpdateLocalFileAttributes() { var err error var attrs fuseops.InodeAttributes // Create a local file inode. @@ -679,6 +679,175 @@ func (t *FileTest) TestTestTruncateDownwardForLocalFileShouldUpdateLocalFileAttr assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) } +func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() { + tbl := []struct { + name string + performWrite bool + }{ + { + name: "WithWrite", + performWrite: true, + }, + { + name: "WithOutWrite", + performWrite: false, + }, + } + for _, tc := range tbl { + t.Run(tc.name, func() { + // Create a local file inode. + t.createInodeWithLocalParam("test", true) + t.in.writeConfig = getWriteConfig() + err := t.in.CreateBufferedOrTempWriter() + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.bwh) + + // Fetch the attributes and check if the file is empty. + attrs, err := t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(0), attrs.Size) + + if tc.performWrite { + err := t.in.Write(t.ctx, []byte("hi"), 0) + assert.Nil(t.T(), err) + assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) + // Fetch the attributes and check if the file size reflects the write. + attrs, err := t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(2), attrs.Size) + } + + err = t.in.Truncate(t.ctx, 10) + + assert.Nil(t.T(), err) + // The inode should return the new size. + attrs, err = t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(10), attrs.Size) + // Data shouldn't be updated to GCS. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + _, _, err = t.bucket.StatObject(t.ctx, statReq) + assert.NotNil(t.T(), err) + assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) + }) + } +} + +func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnabled() { + tbl := []struct { + name string + performWrite bool + }{ + { + name: "WithWrite", + performWrite: true, + }, + { + name: "WithOutWrite", + performWrite: false, + }, + } + for _, tc := range tbl { + t.Run(tc.name, func() { + t.createInodeWithEmptyObject() + t.in.writeConfig = getWriteConfig() + assert.Nil(t.T(), t.in.bwh) + // Fetch the attributes and check if the file is empty. + attrs, err := t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(0), attrs.Size) + + if tc.performWrite { + err := t.in.Write(t.ctx, []byte("hi"), 0) + assert.Nil(t.T(), err) + assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) + // Fetch the attributes and check if the file size reflects the write. + attrs, err := t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(2), attrs.Size) + } + + err = t.in.Truncate(t.ctx, 10) + + assert.Nil(t.T(), err) + // The inode should return the new size. + attrs, err = t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(10), attrs.Size) + // Data shouldn't be updated to GCS. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + minObject, _, err := t.bucket.StatObject(t.ctx, statReq) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(0), minObject.Size) + }) + } +} + +func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { + tbl := []struct { + name string + fileType string + performWrite bool + truncateSize int64 + }{ + { + name: "LocalFileWithWrite", + fileType: LocalFile, + performWrite: true, + truncateSize: 2, + }, + { + name: "LocalFileWithOutWrite", + fileType: LocalFile, + performWrite: false, + truncateSize: -1, + }, + { + name: "EmptyGCSFileWithWrite", + fileType: EmptyGCSFile, + performWrite: true, + truncateSize: 2, + }, + { + name: "EmptyGCSFileWithOutWrite", + fileType: EmptyGCSFile, + performWrite: false, + truncateSize: -1, + }, + } + for _, tc := range tbl { + t.Run(tc.name, func() { + if tc.fileType == LocalFile { + t.createInodeWithLocalParam("test", true) + } + if tc.fileType == EmptyGCSFile { + t.createInodeWithEmptyObject() + } + t.in.writeConfig = getWriteConfig() + assert.Nil(t.T(), t.in.bwh) + // Fetch the attributes and check if the file is empty. + attrs, err := t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(0), attrs.Size) + + if tc.performWrite { + err := t.in.Write(t.ctx, []byte("hihello"), 0) + assert.Nil(t.T(), err) + assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) + // Fetch the attributes and check if the file size reflects the write. + attrs, err := t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(7), attrs.Size) + } + + err = t.in.Truncate(t.ctx, tc.truncateSize) + + assert.NotNil(t.T(), err) + assert.ErrorContains(t.T(), err, "cannot truncate") + }) + } +} + func (t *FileTest) TestSync_Clobbered() { var err error From a78121a23f48de9fdd4d2de4aedcfa6ea9f780c9 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Mon, 16 Dec 2024 09:53:37 +0000 Subject: [PATCH 0076/1298] Dependency upgrade Crypto for CVE --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7fe7308edd..beab278de6 100644 --- a/go.mod +++ b/go.mod @@ -111,7 +111,7 @@ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect go.opentelemetry.io/otel/metric v1.32.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.30.0 // indirect + golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 // indirect google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect diff --git a/go.sum b/go.sum index cb342d34c9..c18a8b8d92 100644 --- a/go.sum +++ b/go.sum @@ -1318,8 +1318,8 @@ golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= -golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= From cb481293f59c6b57fb4d64f9135d6ee9c7924d67 Mon Sep 17 00:00:00 2001 From: Kenneth Hill Date: Mon, 16 Dec 2024 23:56:08 -0600 Subject: [PATCH 0077/1298] Lock bundler version (#2812) builds using 'package_gcsfuse_docker/Dockerfile' fail without specifying 'bundler' version to match 'tools/gem_dependency/Gemfile.lock' --- tools/package_gcsfuse_docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/package_gcsfuse_docker/Dockerfile b/tools/package_gcsfuse_docker/Dockerfile index 325bf2ece6..8d3715d7f4 100644 --- a/tools/package_gcsfuse_docker/Dockerfile +++ b/tools/package_gcsfuse_docker/Dockerfile @@ -19,7 +19,7 @@ FROM golang:1.23.4 as builder -RUN apt-get update -qq && apt-get install -y ruby ruby-dev rubygems build-essential rpm && gem install --no-document bundler +RUN apt-get update -qq && apt-get install -y ruby ruby-dev rubygems build-essential rpm && gem install --no-document bundler -v "2.4.12" ENV CGO_ENABLED=0 ENV GOOS=linux From 92233762e77fd132c8aa17c55c43a46495d24719 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:41:26 +0530 Subject: [PATCH 0078/1298] Allow upload handler to create chunk writer for empty synced object (#2797) * allow buffered writes to create writer for empty object * review comments * rename ResolveCreateObjectRequest to NewCreateObjectRequest * fix flaky test --- .../bufferedwrites/buffered_write_handler.go | 48 +++++--- .../buffered_write_handler_test.go | 13 ++- internal/bufferedwrites/upload_handler.go | 45 +++++--- .../bufferedwrites/upload_handler_test.go | 72 +++++++++++- internal/fs/fs.go | 15 +-- internal/fs/handle/dir_handle_test.go | 4 +- internal/fs/inode/dir_test.go | 7 +- internal/fs/inode/file.go | 101 ++++++++++------- internal/fs/inode/file_test.go | 67 ++++++----- internal/fs/local_modifications_test.go | 5 +- internal/gcsx/append_object_creator.go | 14 +-- internal/gcsx/syncer.go | 46 +------- internal/storage/gcs/request_helper.go | 64 +++++++++++ internal/storage/gcs/request_helper_test.go | 105 ++++++++++++++++++ .../operations/write_test.go | 5 +- 15 files changed, 420 insertions(+), 191 deletions(-) create mode 100644 internal/storage/gcs/request_helper.go create mode 100644 internal/storage/gcs/request_helper_test.go diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 3a2ecfe2ca..97a9a77851 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -59,19 +59,37 @@ type WriteFileInfo struct { var ErrOutOfOrderWrite = errors.New("outOfOrder write detected") var ErrUploadFailure = errors.New("error while uploading object to GCS") +type CreateBWHandlerRequest struct { + Object *gcs.Object + ObjectName string + Bucket gcs.Bucket + BlockSize int64 + MaxBlocksPerFile int64 + GlobalMaxBlocksSem *semaphore.Weighted + ChunkTransferTimeoutSecs int64 +} + // NewBWHandler creates the bufferedWriteHandler struct. -func NewBWHandler(objectName string, bucket gcs.Bucket, blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphore.Weighted) (bwh *BufferedWriteHandler, err error) { - bp, err := block.NewBlockPool(blockSize, maxBlocks, globalMaxBlocksSem) +func NewBWHandler(req *CreateBWHandlerRequest) (bwh *BufferedWriteHandler, err error) { + bp, err := block.NewBlockPool(req.BlockSize, req.MaxBlocksPerFile, req.GlobalMaxBlocksSem) if err != nil { return } bwh = &BufferedWriteHandler{ - current: nil, - blockPool: bp, - uploadHandler: newUploadHandler(objectName, bucket, maxBlocks, bp.FreeBlocksChannel(), blockSize), - totalSize: 0, - mtime: time.Now(), + current: nil, + blockPool: bp, + uploadHandler: newUploadHandler(&CreateUploadHandlerRequest{ + Object: req.Object, + ObjectName: req.ObjectName, + Bucket: req.Bucket, + FreeBlocksCh: bp.FreeBlocksChannel(), + MaxBlocksPerFile: req.MaxBlocksPerFile, + BlockSize: req.BlockSize, + ChunkTransferTimeoutSecs: req.ChunkTransferTimeoutSecs, + }), + totalSize: 0, + mtime: time.Now(), } return } @@ -79,6 +97,14 @@ func NewBWHandler(objectName string, bucket gcs.Bucket, blockSize int64, maxBloc // Write writes the given data to the buffer. It writes to an existing buffer if // the capacity is available otherwise writes to a new buffer. func (wh *BufferedWriteHandler) Write(data []byte, offset int64) (err error) { + // Fail early if the uploadHandler has failed. + select { + case <-wh.uploadHandler.SignalUploadFailure(): + return ErrUploadFailure + default: + break + } + if offset != wh.totalSize && offset != wh.truncatedSize { logger.Errorf("BufferedWriteHandler.OutOfOrderError for object: %s, expectedOffset: %d, actualOffset: %d", wh.uploadHandler.objectName, wh.totalSize, offset) @@ -93,14 +119,6 @@ func (wh *BufferedWriteHandler) Write(data []byte, offset int64) (err error) { } } - // Fail early if the uploadHandler has failed. - select { - case <-wh.uploadHandler.SignalUploadFailure(): - return ErrUploadFailure - default: - break - } - return wh.appendBuffer(data) } diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 0c814e995c..2498113dd4 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -29,6 +29,8 @@ import ( "golang.org/x/sync/semaphore" ) +const chunkTransferTimeoutSecs int64 = 10 + type BufferedWriteTest struct { bwh *BufferedWriteHandler suite.Suite @@ -40,7 +42,15 @@ func TestBufferedWriteTestSuite(t *testing.T) { func (testSuite *BufferedWriteTest) SetupTest() { bucket := fake.NewFakeBucket(timeutil.RealClock(), "FakeBucketName", gcs.NonHierarchical) - bwh, err := NewBWHandler("testObject", bucket, blockSize, 10, semaphore.NewWeighted(10)) + bwh, err := NewBWHandler(&CreateBWHandlerRequest{ + Object: nil, + ObjectName: "testObject", + Bucket: bucket, + BlockSize: blockSize, + MaxBlocksPerFile: 10, + GlobalMaxBlocksSem: semaphore.NewWeighted(10), + ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, + }) require.Nil(testSuite.T(), err) testSuite.bwh = bwh } @@ -236,6 +246,7 @@ func (testSuite *BufferedWriteTest) TestFlushWithMultiBlockWritesAndSignalUpload for i := 0; i < 5; i++ { err := testSuite.bwh.Write(buffer, int64(blockSize*(i+5))) require.Error(testSuite.T(), err) + assert.Equal(testSuite.T(), ErrUploadFailure, err) } obj, err := testSuite.bwh.Flush() diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index a539bd7896..651ba59e32 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -45,21 +45,35 @@ type UploadHandler struct { signalUploadFailure chan error // Parameters required for creating a new GCS chunk writer. - bucket gcs.Bucket - objectName string - blockSize int64 + bucket gcs.Bucket + objectName string + obj *gcs.Object + chunkTransferTimeout int64 + blockSize int64 +} + +type CreateUploadHandlerRequest struct { + Object *gcs.Object + ObjectName string + Bucket gcs.Bucket + FreeBlocksCh chan block.Block + MaxBlocksPerFile int64 + BlockSize int64 + ChunkTransferTimeoutSecs int64 } // newUploadHandler creates the UploadHandler struct. -func newUploadHandler(objectName string, bucket gcs.Bucket, maxBlocks int64, freeBlocksCh chan block.Block, blockSize int64) *UploadHandler { +func newUploadHandler(req *CreateUploadHandlerRequest) *UploadHandler { uh := &UploadHandler{ - uploadCh: make(chan block.Block, maxBlocks), - wg: sync.WaitGroup{}, - freeBlocksCh: freeBlocksCh, - bucket: bucket, - objectName: objectName, - blockSize: blockSize, - signalUploadFailure: make(chan error, 1), + uploadCh: make(chan block.Block, req.MaxBlocksPerFile), + wg: sync.WaitGroup{}, + freeBlocksCh: req.FreeBlocksCh, + bucket: req.Bucket, + objectName: req.ObjectName, + obj: req.Object, + blockSize: req.BlockSize, + signalUploadFailure: make(chan error, 1), + chunkTransferTimeout: req.ChunkTransferTimeoutSecs, } return uh } @@ -86,12 +100,7 @@ func (uh *UploadHandler) Upload(block block.Block) error { // createObjectWriter creates a GCS object writer. func (uh *UploadHandler) createObjectWriter() (err error) { - var preCond int64 - req := &gcs.CreateObjectRequest{ - Name: uh.objectName, - GenerationPrecondition: &preCond, - Metadata: make(map[string]string), - } + req := gcs.NewCreateObjectRequest(uh.obj, uh.objectName, nil, uh.chunkTransferTimeout) // We need a new context here, since the first writeFile() call will be complete // (and context will be cancelled) by the time complete upload is done. uh.writer, err = uh.bucket.CreateObjectChunkWriter(context.Background(), req, int(uh.blockSize), nil) @@ -111,9 +120,9 @@ func (uh *UploadHandler) uploader() { close(uh.signalUploadFailure) } } - uh.wg.Done() // Put back the uploaded block on the freeBlocksChannel for re-use. uh.freeBlocksCh <- currBlock + uh.wg.Done() } } diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index f068e82574..3f28e4d382 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -52,7 +52,15 @@ func (t *UploadHandlerTest) SetupTest() { var err error t.blockPool, err = block.NewBlockPool(blockSize, maxBlocks, semaphore.NewWeighted(maxBlocks)) require.NoError(t.T(), err) - t.uh = newUploadHandler("testObject", t.mockBucket, maxBlocks, t.blockPool.FreeBlocksChannel(), blockSize) + t.uh = newUploadHandler(&CreateUploadHandlerRequest{ + Object: nil, + ObjectName: "testObject", + Bucket: t.mockBucket, + FreeBlocksCh: t.blockPool.FreeBlocksChannel(), + MaxBlocksPerFile: maxBlocks, + BlockSize: blockSize, + ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, + }) } func (t *UploadHandlerTest) TestMultipleBlockUpload() { @@ -270,3 +278,65 @@ func (t *UploadHandlerTest) TestMultipleBlockAwaitBlocksUpload() { assert.Equal(t.T(), 0, len(t.uh.uploadCh)) assertAllBlocksProcessed(t.T(), t.uh) } + +func (t *UploadHandlerTest) TestCreateObjectChunkWriterIsCalledWithCorrectRequestParametersForEmptyGCSObject() { + t.uh.obj = &gcs.Object{ + Name: t.uh.objectName, + ContentType: "image/png", + Size: 0, + ContentEncoding: "gzip", + Generation: 10, + MetaGeneration: 20, + Acl: nil, + } + + // CreateObjectChunkWriter -- should be called once with correct request parameters. + writer := &storagemock.Writer{} + mockObj := &gcs.Object{} + t.mockBucket.On("CreateObjectChunkWriter", + mock.Anything, + mock.MatchedBy(func(req *gcs.CreateObjectRequest) bool { + return req.Name == t.uh.objectName && + *req.GenerationPrecondition == t.uh.obj.Generation && + *req.MetaGenerationPrecondition == t.uh.obj.MetaGeneration && + req.ContentEncoding == t.uh.obj.ContentEncoding && + req.ContentType == t.uh.obj.ContentType && + req.ChunkTransferTimeoutSecs == chunkTransferTimeoutSecs + }), + mock.Anything, + mock.Anything).Return(writer, nil) + t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, nil) + + // Create a block. + b, err := t.blockPool.Get() + require.NoError(t.T(), err) + // Upload the block. + err = t.uh.Upload(b) + require.NoError(t.T(), err) +} + +func (t *UploadHandlerTest) TestCreateObjectChunkWriterIsCalledWithCorrectRequestParametersForLocalInode() { + assert.Nil(t.T(), t.uh.obj) + + // CreateObjectChunkWriter -- should be called once with correct request parameters. + writer := &storagemock.Writer{} + mockObj := &gcs.Object{} + t.mockBucket.On("CreateObjectChunkWriter", + mock.Anything, + mock.MatchedBy(func(req *gcs.CreateObjectRequest) bool { + return req.Name == t.uh.objectName && + *req.GenerationPrecondition == 0 && + req.MetaGenerationPrecondition == nil && + req.ChunkTransferTimeoutSecs == chunkTransferTimeoutSecs + }), + mock.Anything, + mock.Anything).Return(writer, nil) + t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, nil) + + // Create a block. + b, err := t.blockPool.Get() + require.NoError(t.T(), err) + // Upload the block. + err = t.uh.Upload(b) + require.NoError(t.T(), err) +} diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 92828588bf..922bb0c65e 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -48,7 +48,6 @@ import ( "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/timeutil" - "golang.org/x/sync/semaphore" ) type ServerConfig struct { @@ -194,7 +193,6 @@ func NewFileSystem(ctx context.Context, serverCfg *ServerConfig) (fuseutil.FileS newConfig: serverCfg.NewConfig, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: serverCfg.NewConfig.FileCache.CacheFileForRangeRead, - globalMaxBlocksSem: semaphore.NewWeighted(serverCfg.NewConfig.Write.GlobalMaxBlocks), metricHandle: serverCfg.MetricHandle, } @@ -485,8 +483,6 @@ type fileSystem struct { // random file access. cacheFileForRangeRead bool - globalMaxBlocksSem *semaphore.Weighted - metricHandle common.MetricHandle } @@ -808,8 +804,7 @@ func (fs *fileSystem) mintInode(ic inode.Core) (in inode.Inode) { fs.contentCache, fs.mtimeClock, ic.Local, - &fs.newConfig.Write, - fs.globalMaxBlocksSem) + fs.newConfig) } // Place it in our map of IDs to inodes. @@ -1661,9 +1656,7 @@ func (fs *fileSystem) createFile( // LOCKS_EXCLUDED(fs.mu) // UNLOCK_FUNCTION(fs.mu) // LOCK_FUNCTION(child) -func (fs *fileSystem) createLocalFile( - parentID fuseops.InodeID, - name string) (child inode.Inode, err error) { +func (fs *fileSystem) createLocalFile(ctx context.Context, parentID fuseops.InodeID, name string) (child inode.Inode, err error) { // Find the parent. fs.mu.Lock() parent := fs.dirInodeOrDie(parentID) @@ -1699,7 +1692,7 @@ func (fs *fileSystem) createLocalFile( fs.localFileInodes[child.Name()] = child // Empty file is created to be able to set attributes on the file. fileInode := child.(*inode.FileInode) - if err := fileInode.CreateBufferedOrTempWriter(); err != nil { + if err := fileInode.CreateBufferedOrTempWriter(ctx); err != nil { return nil, err } fs.mu.Unlock() @@ -1729,7 +1722,7 @@ func (fs *fileSystem) CreateFile( if fs.newConfig.Write.CreateEmptyFile { child, err = fs.createFile(ctx, op.Parent, op.Name, op.Mode) } else { - child, err = fs.createLocalFile(op.Parent, op.Name) + child, err = fs.createLocalFile(ctx, op.Parent, op.Name) } if err != nil { diff --git a/internal/fs/handle/dir_handle_test.go b/internal/fs/handle/dir_handle_test.go index 448b46a479..e9d0b46bec 100644 --- a/internal/fs/handle/dir_handle_test.go +++ b/internal/fs/handle/dir_handle_test.go @@ -31,7 +31,6 @@ import ( "github.com/jacobsa/fuse/fuseutil" . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" - "golang.org/x/sync/semaphore" ) func TestDirHandle(t *testing.T) { RunTests(t) } @@ -106,8 +105,7 @@ func (t *DirHandleTest) createLocalFileInode(name string, id fuseops.InodeID) (i contentcache.New("", &t.clock), &t.clock, true, // localFile - &cfg.WriteConfig{}, - semaphore.NewWeighted(math.MaxInt64)) + &cfg.Config{Write: cfg.WriteConfig{GlobalMaxBlocks: math.MaxInt64}}) return } diff --git a/internal/fs/inode/dir_test.go b/internal/fs/inode/dir_test.go index 16b67e5652..6f966ed144 100644 --- a/internal/fs/inode/dir_test.go +++ b/internal/fs/inode/dir_test.go @@ -24,14 +24,12 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" - "golang.org/x/sync/semaphore" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "golang.org/x/net/context" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" @@ -208,8 +206,7 @@ func (t *DirTest) createLocalFileInode(parent Name, name string, id fuseops.Inod contentcache.New("", &t.clock), &t.clock, true, //localFile - &cfg.WriteConfig{}, - semaphore.NewWeighted(math.MaxInt64)) + &cfg.Config{Write: cfg.WriteConfig{GlobalMaxBlocks: math.MaxInt64}}) return } diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index fe0a0394f8..71a219eb80 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -38,7 +38,7 @@ import ( // A GCS object metadata key for file mtimes. mtimes are UTC, and are stored in // the format defined by time.RFC3339Nano. -const FileMtimeMetadataKey = gcsx.MtimeMetadataKey +const FileMtimeMetadataKey = gcs.MtimeMetadataKey type FileInode struct { ///////////////////////// @@ -93,9 +93,8 @@ type FileInode struct { // Represents if local file has been unlinked. unlinked bool - bwh *bufferedwrites.BufferedWriteHandler - writeConfig *cfg.WriteConfig - globalMaxBlocksSem *semaphore.Weighted + bwh *bufferedwrites.BufferedWriteHandler + config *cfg.Config } var _ Inode = &FileInode{} @@ -118,26 +117,24 @@ func NewFileInode( contentCache *contentcache.ContentCache, mtimeClock timeutil.Clock, localFile bool, - writeConfig *cfg.WriteConfig, - globalMaxBlocksSem *semaphore.Weighted) (f *FileInode) { + cfg *cfg.Config) (f *FileInode) { // Set up the basic struct. var minObj gcs.MinObject if m != nil { minObj = *m } f = &FileInode{ - bucket: bucket, - mtimeClock: mtimeClock, - id: id, - name: name, - attrs: attrs, - localFileCache: localFileCache, - contentCache: contentCache, - src: minObj, - local: localFile, - unlinked: false, - writeConfig: writeConfig, - globalMaxBlocksSem: globalMaxBlocksSem, + bucket: bucket, + mtimeClock: mtimeClock, + id: id, + name: name, + attrs: attrs, + localFileCache: localFileCache, + contentCache: contentCache, + src: minObj, + local: localFile, + unlinked: false, + config: cfg, } f.lc.Init(id) @@ -505,8 +502,8 @@ func (f *FileInode) Write( data []byte, offset int64) (err error) { // For empty GCS files also we will trigger bufferedWrites flow. - if f.src.Size == 0 && f.writeConfig.ExperimentalEnableStreamingWrites { - err = f.ensureBufferedWriteHandler() + if f.src.Size == 0 && f.config.Write.ExperimentalEnableStreamingWrites { + err = f.ensureBufferedWriteHandler(ctx) if err != nil { return } @@ -613,20 +610,7 @@ func (f *FileInode) SetMtime( return } -// Sync writes out contents to GCS. If this fails due to the generation having been -// clobbered, failure is propagated back to the calling function as an error. -// -// After this method succeeds, SourceGeneration will return the new generation -// by which this inode should be known (which may be the same as before). If it -// fails, the generation will not change. -// -// LOCKS_REQUIRED(f.mu) -func (f *FileInode) Sync(ctx context.Context) (err error) { - // If we have not been dirtied, there is nothing to do. - if f.content == nil { - return - } - +func (f *FileInode) fetchLatestGcsObject(ctx context.Context) (*gcs.Object, error) { // When listObjects call is made, we fetch data with projection set as noAcl // which means acls and owner properties are not returned. So the f.src object // here will not have acl information even though there are acls present on @@ -636,13 +620,30 @@ func (f *FileInode) Sync(ctx context.Context) (err error) { // default sets the projection to full, which fetches all the object // properties. latestGcsObj, isClobbered, err := f.clobbered(ctx, true, true) - if isClobbered { err = &gcsfuse_errors.FileClobberedError{ Err: err, } } + return latestGcsObj, err +} + +// Sync writes out contents to GCS. If this fails due to the generation having been +// clobbered, failure is propagated back to the calling function as an error. +// +// After this method succeeds, SourceGeneration will return the new generation +// by which this inode should be known (which may be the same as before). If it +// fails, the generation will not change. +// +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) Sync(ctx context.Context) (err error) { + // If we have not been dirtied, there is nothing to do. + if f.content == nil { + return + } + + latestGcsObj, err := f.fetchLatestGcsObject(ctx) if err != nil { return } @@ -692,8 +693,8 @@ func (f *FileInode) Truncate( ctx context.Context, size int64) (err error) { // For empty GCS files also, we will trigger bufferedWrites flow. - if f.src.Size == 0 && f.writeConfig.ExperimentalEnableStreamingWrites { - err = f.ensureBufferedWriteHandler() + if f.src.Size == 0 && f.config.Write.ExperimentalEnableStreamingWrites { + err = f.ensureBufferedWriteHandler(ctx) if err != nil { return } @@ -725,10 +726,10 @@ func (f *FileInode) CacheEnsureContent(ctx context.Context) (err error) { return } -func (f *FileInode) CreateBufferedOrTempWriter() (err error) { +func (f *FileInode) CreateBufferedOrTempWriter(ctx context.Context) (err error) { // Skip creating empty file when streaming writes are enabled - if f.local && f.writeConfig.ExperimentalEnableStreamingWrites { - err = f.ensureBufferedWriteHandler() + if f.local && f.config.Write.ExperimentalEnableStreamingWrites { + err = f.ensureBufferedWriteHandler(ctx) if err != nil { return } @@ -743,10 +744,26 @@ func (f *FileInode) CreateBufferedOrTempWriter() (err error) { return } -func (f *FileInode) ensureBufferedWriteHandler() error { +func (f *FileInode) ensureBufferedWriteHandler(ctx context.Context) error { var err error + var latestGcsObj *gcs.Object + if !f.local { + latestGcsObj, err = f.fetchLatestGcsObject(ctx) + if err != nil { + return err + } + } + if f.bwh == nil { - f.bwh, err = bufferedwrites.NewBWHandler(f.name.GcsObjectName(), f.bucket, f.writeConfig.BlockSizeMb, f.writeConfig.MaxBlocksPerFile, f.globalMaxBlocksSem) + f.bwh, err = bufferedwrites.NewBWHandler(&bufferedwrites.CreateBWHandlerRequest{ + Object: latestGcsObj, + ObjectName: f.name.GcsObjectName(), + Bucket: f.bucket, + BlockSize: f.config.Write.BlockSizeMb, + MaxBlocksPerFile: f.config.Write.MaxBlocksPerFile, + GlobalMaxBlocksSem: semaphore.NewWeighted(f.config.Write.GlobalMaxBlocks), + ChunkTransferTimeoutSecs: f.config.GcsRetries.ChunkTransferTimeoutSecs, + }) if err != nil { return fmt.Errorf("failed to create bufferedWriteHandler: %w", err) } diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 731b14e32b..3c7f69985c 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -26,20 +26,18 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/syncutil" + "github.com/jacobsa/timeutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "golang.org/x/net/context" - "golang.org/x/sync/semaphore" - - "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/jacobsa/fuse/fuseops" - "github.com/jacobsa/timeutil" ) //////////////////////////////////////////////////////////////////////// @@ -145,8 +143,7 @@ func (t *FileTest) createInodeWithLocalParam(fileName string, local bool) { contentcache.New("", &t.clock), &t.clock, local, - &cfg.WriteConfig{}, - semaphore.NewWeighted(math.MaxInt64)) + &cfg.Config{Write: cfg.WriteConfig{GlobalMaxBlocks: math.MaxInt64}}) t.in.Lock() } @@ -415,7 +412,7 @@ func (t *FileTest) TestWriteToLocalFileThenSync() { // Create a local file inode. t.createInodeWithLocalParam("test", true) // Create a temp file for the local inode created above. - err = t.in.CreateBufferedOrTempWriter() + err = t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) // Write some content to temp file. t.clock.AdvanceTime(time.Second) @@ -459,7 +456,7 @@ func (t *FileTest) TestSyncEmptyLocalFile() { t.createInodeWithLocalParam("test", true) creationTime := t.clock.Now() // Create a temp file for the local inode created above. - err = t.in.CreateBufferedOrTempWriter() + err = t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) // Sync. @@ -629,7 +626,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes var attrs fuseops.InodeAttributes // Create a local file inode. t.createInodeWithLocalParam("test", true) - err = t.in.CreateBufferedOrTempWriter() + err = t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) // Fetch the attributes and check if the file is empty. attrs, err = t.in.Attributes(t.ctx) @@ -655,7 +652,7 @@ func (t *FileTest) TestTruncateDownwardForLocalFileShouldUpdateLocalFileAttribut var attrs fuseops.InodeAttributes // Create a local file inode. t.createInodeWithLocalParam("test", true) - err = t.in.CreateBufferedOrTempWriter() + err = t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) // Write some data to the local file. err = t.in.Write(t.ctx, []byte("burrito"), 0) @@ -697,8 +694,8 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() t.Run(tc.name, func() { // Create a local file inode. t.createInodeWithLocalParam("test", true) - t.in.writeConfig = getWriteConfig() - err := t.in.CreateBufferedOrTempWriter() + t.in.config = &cfg.Config{Write: *getWriteConfig()} + err := t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) @@ -750,7 +747,7 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable for _, tc := range tbl { t.Run(tc.name, func() { t.createInodeWithEmptyObject() - t.in.writeConfig = getWriteConfig() + t.in.config = &cfg.Config{Write: *getWriteConfig()} assert.Nil(t.T(), t.in.bwh) // Fetch the attributes and check if the file is empty. attrs, err := t.in.Attributes(t.ctx) @@ -823,7 +820,7 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { if tc.fileType == EmptyGCSFile { t.createInodeWithEmptyObject() } - t.in.writeConfig = getWriteConfig() + t.in.config = &cfg.Config{Write: *getWriteConfig()} assert.Nil(t.T(), t.in.bwh) // Fetch the attributes and check if the file is empty. attrs, err := t.in.Attributes(t.ctx) @@ -1057,7 +1054,7 @@ func (t *FileTest) TestTestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() // Create a local file inode. t.createInodeWithLocalParam("test", true) createTime := t.in.mtimeClock.Now() - err = t.in.CreateBufferedOrTempWriter() + err = t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) // Validate the attributes on an empty file. attrs, err = t.in.Attributes(t.ctx) @@ -1087,8 +1084,8 @@ func (t *FileTest) TestSetMtimeForLocalFileWhenStreamingWritesAreEnabled() { var attrs fuseops.InodeAttributes // Create a local file inode. t.createInodeWithLocalParam("test", true) - t.in.writeConfig = getWriteConfig() - err = t.in.CreateBufferedOrTempWriter() + t.in.config = &cfg.Config{Write: *getWriteConfig()} + err = t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) // Set mtime. @@ -1144,7 +1141,7 @@ func (t *FileTest) TestTestCheckInvariantsShouldNotThrowExceptionForLocalFiles() } func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateEmptyFile() { - err := t.in.CreateBufferedOrTempWriter() + err := t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.content) @@ -1156,9 +1153,9 @@ func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateEmptyFile() { func (t *FileTest) TestCreateBufferedOrTempWriterShouldNotCreateFileWhenStreamingWritesAreEnabled() { t.createInodeWithLocalParam("test", true) - t.in.writeConfig = getWriteConfig() + t.in.config = &cfg.Config{Write: *getWriteConfig()} - err := t.in.CreateBufferedOrTempWriter() + err := t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) assert.Nil(t.T(), t.in.content) @@ -1167,9 +1164,9 @@ func (t *FileTest) TestCreateBufferedOrTempWriterShouldNotCreateFileWhenStreamin func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateFileForNonLocalFilesForStreamingWrites() { // Enabling buffered writes. - t.in.writeConfig = getWriteConfig() + t.in.config = &cfg.Config{Write: *getWriteConfig()} - err := t.in.CreateBufferedOrTempWriter() + err := t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.content) @@ -1185,7 +1182,7 @@ func (t *FileTest) TestUnlinkLocalFile() { // Create a local file inode. t.createInodeWithLocalParam("test", true) // Create a temp file for the local inode created above. - err = t.in.CreateBufferedOrTempWriter() + err = t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) // Unlink. @@ -1227,15 +1224,15 @@ func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { if tc.fileType == LocalFile { // Create a local file inode. t.createInodeWithLocalParam("test", true) - t.in.writeConfig = getWriteConfig() - err := t.in.CreateBufferedOrTempWriter() + t.in.config = &cfg.Config{Write: *getWriteConfig()} + err := t.in.CreateBufferedOrTempWriter(t.ctx) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) } if tc.fileType == EmptyGCSFile { t.createInodeWithEmptyObject() - t.in.writeConfig = getWriteConfig() + t.in.config = &cfg.Config{Write: *getWriteConfig()} } if tc.performWrite { @@ -1257,7 +1254,7 @@ func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { func (t *FileTest) TestReadEmptyGCSFileWhenStreamingWritesAreNotInProgress() { t.createInodeWithEmptyObject() - t.in.writeConfig = getWriteConfig() + t.in.config = &cfg.Config{Write: *getWriteConfig()} data := make([]byte, 10) n, err := t.in.Read(t.ctx, data, 0) @@ -1269,7 +1266,7 @@ func (t *FileTest) TestReadEmptyGCSFileWhenStreamingWritesAreNotInProgress() { func (t *FileTest) TestWriteToLocalFileWithInvalidConfigWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) - t.in.writeConfig.ExperimentalEnableStreamingWrites = true + t.in.config = &cfg.Config{Write: cfg.WriteConfig{ExperimentalEnableStreamingWrites: true}} assert.Nil(t.T(), t.in.bwh) err := t.in.Write(t.ctx, []byte("hi"), 0) @@ -1281,7 +1278,7 @@ func (t *FileTest) TestWriteToLocalFileWithInvalidConfigWhenStreamingWritesAreEn func (t *FileTest) TestWriteToLocalFileWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) - t.in.writeConfig = getWriteConfig() + t.in.config = &cfg.Config{Write: *getWriteConfig()} assert.Nil(t.T(), t.in.bwh) err := t.in.Write(t.ctx, []byte("hi"), 0) @@ -1296,7 +1293,7 @@ func (t *FileTest) TestMultipleWritesToLocalFileWhenStreamingWritesAreEnabled() // Create a local file inode. t.createInodeWithLocalParam("test", true) createTime := t.in.mtimeClock.Now() - t.in.writeConfig = getWriteConfig() + t.in.config = &cfg.Config{Write: *getWriteConfig()} assert.Nil(t.T(), t.in.bwh) err := t.in.Write(t.ctx, []byte("hi"), 0) @@ -1316,7 +1313,7 @@ func (t *FileTest) TestMultipleWritesToLocalFileWhenStreamingWritesAreEnabled() func (t *FileTest) WriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() - t.in.writeConfig = getWriteConfig() + t.in.config = &cfg.Config{Write: *getWriteConfig()} createTime := t.in.mtimeClock.Now() assert.Nil(t.T(), t.in.bwh) @@ -1335,7 +1332,7 @@ func (t *FileTest) WriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { func (t *FileTest) SetMtimeOnEmptyGCSFileWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() - t.in.writeConfig = getWriteConfig() + t.in.config = &cfg.Config{Write: *getWriteConfig()} assert.Nil(t.T(), t.in.bwh) // This test checks if the mtime is updated to GCS. Since test framework @@ -1347,7 +1344,7 @@ func (t *FileTest) SetMtimeOnEmptyGCSFileWhenStreamingWritesAreEnabled() { func (t *FileTest) SetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() - t.in.writeConfig = getWriteConfig() + t.in.config = &cfg.Config{Write: *getWriteConfig()} assert.Nil(t.T(), t.in.bwh) // Initiate write call. err := t.in.Write(t.ctx, []byte("hi"), 0) diff --git a/internal/fs/local_modifications_test.go b/internal/fs/local_modifications_test.go index 4de361ace7..a1a1fdf440 100644 --- a/internal/fs/local_modifications_test.go +++ b/internal/fs/local_modifications_test.go @@ -33,7 +33,6 @@ import ( "unicode" "unicode/utf8" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" @@ -1538,8 +1537,8 @@ func validateObjectAttributes(extendedAttr1, extendedAttr2 *gcs.ExtendedObjectAt ExpectEq(FileContentsSize, minObject2.Size) ExpectNe(minObject1.Generation, minObject2.Generation) ExpectTrue(minObject1.Updated.Before(minObject2.Updated)) - attr1MTime, _ := time.Parse(time.RFC3339Nano, minObject1.Metadata[gcsx.MtimeMetadataKey]) - attr2MTime, _ := time.Parse(time.RFC3339Nano, minObject2.Metadata[gcsx.MtimeMetadataKey]) + attr1MTime, _ := time.Parse(time.RFC3339Nano, minObject1.Metadata[gcs.MtimeMetadataKey]) + attr2MTime, _ := time.Parse(time.RFC3339Nano, minObject2.Metadata[gcs.MtimeMetadataKey]) ExpectTrue(attr1MTime.Before(attr2MTime)) ExpectEq(minObject1.ContentEncoding, minObject2.ContentEncoding) ExpectNe(nil, minObject1.CRC32C) diff --git a/internal/gcsx/append_object_creator.go b/internal/gcsx/append_object_creator.go index 8aede97eda..ad2782d296 100644 --- a/internal/gcsx/append_object_creator.go +++ b/internal/gcsx/append_object_creator.go @@ -96,15 +96,9 @@ func (oc *appendObjectCreator) Create( } // Create a temporary object containing the additional contents. - var zero int64 - tmp, err := oc.bucket.CreateObject( - ctx, - &gcs.CreateObjectRequest{ - Name: tmpName, - GenerationPrecondition: &zero, - Contents: r, - ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, - }) + req := gcs.NewCreateObjectRequest(nil, tmpName, nil, chunkTransferTimeoutSecs) + req.Contents = r + tmp, err := oc.bucket.CreateObject(ctx, req) if err != nil { err = fmt.Errorf("CreateObject: %w", err) return @@ -132,7 +126,7 @@ func (oc *appendObjectCreator) Create( } if mtime != nil { - MetadataMap[MtimeMetadataKey] = mtime.UTC().Format(time.RFC3339Nano) + MetadataMap[gcs.MtimeMetadataKey] = mtime.UTC().Format(time.RFC3339Nano) } // Compose the old contents plus the new over the old. diff --git a/internal/gcsx/syncer.go b/internal/gcsx/syncer.go index aa32e05e24..3b37fee257 100644 --- a/internal/gcsx/syncer.go +++ b/internal/gcsx/syncer.go @@ -23,11 +23,6 @@ import ( "golang.org/x/net/context" ) -// MtimeMetadataKey objects are created by Syncer.SyncObject and contain a -// metadata field with this key and with a UTC mtime in the format defined -// by time.RFC3339Nano. -const MtimeMetadataKey = "gcsfuse_mtime" - // Syncer is safe for concurrent access. type Syncer interface { // Given an object record and content that was originally derived from that @@ -88,45 +83,8 @@ func (oc *fullObjectCreator) Create( mtime *time.Time, chunkTransferTimeoutSecs int64, r io.Reader) (o *gcs.Object, err error) { - metadataMap := make(map[string]string) - - var req *gcs.CreateObjectRequest - if srcObject == nil { - var precond int64 - req = &gcs.CreateObjectRequest{ - Name: objectName, - Contents: r, - GenerationPrecondition: &precond, - Metadata: metadataMap, - ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, - } - } else { - for key, value := range srcObject.Metadata { - metadataMap[key] = value - } - - req = &gcs.CreateObjectRequest{ - Name: srcObject.Name, - GenerationPrecondition: &srcObject.Generation, - MetaGenerationPrecondition: &srcObject.MetaGeneration, - Contents: r, - Metadata: metadataMap, - CacheControl: srcObject.CacheControl, - ContentDisposition: srcObject.ContentDisposition, - ContentEncoding: srcObject.ContentEncoding, - ContentType: srcObject.ContentType, - CustomTime: srcObject.CustomTime, - EventBasedHold: srcObject.EventBasedHold, - StorageClass: srcObject.StorageClass, - ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, - } - } - - // Any existing mtime value will be overwritten with new value. - if mtime != nil { - metadataMap[MtimeMetadataKey] = mtime.UTC().Format(time.RFC3339Nano) - } - + req := gcs.NewCreateObjectRequest(srcObject, objectName, mtime, chunkTransferTimeoutSecs) + req.Contents = r o, err = oc.bucket.CreateObject(ctx, req) if err != nil { err = fmt.Errorf("CreateObject: %w", err) diff --git a/internal/storage/gcs/request_helper.go b/internal/storage/gcs/request_helper.go new file mode 100644 index 0000000000..09c142c8a5 --- /dev/null +++ b/internal/storage/gcs/request_helper.go @@ -0,0 +1,64 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcs + +import ( + "time" +) + +// MtimeMetadataKey objects are created by Syncer.SyncObject and contain a +// metadata field with this key and with a UTC mtime in the format defined +// by time.RFC3339Nano. +const MtimeMetadataKey = "gcsfuse_mtime" + +func NewCreateObjectRequest(srcObject *Object, objectName string, mtime *time.Time, chunkTransferTimeoutSecs int64) *CreateObjectRequest { + metadataMap := make(map[string]string) + var req *CreateObjectRequest + if srcObject == nil { + var preCond int64 + req = &CreateObjectRequest{ + Name: objectName, + GenerationPrecondition: &preCond, + Metadata: metadataMap, + ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, + } + } else { + for key, value := range srcObject.Metadata { + metadataMap[key] = value + } + + req = &CreateObjectRequest{ + Name: srcObject.Name, + GenerationPrecondition: &srcObject.Generation, + MetaGenerationPrecondition: &srcObject.MetaGeneration, + Metadata: metadataMap, + CacheControl: srcObject.CacheControl, + ContentDisposition: srcObject.ContentDisposition, + ContentEncoding: srcObject.ContentEncoding, + ContentType: srcObject.ContentType, + CustomTime: srcObject.CustomTime, + EventBasedHold: srcObject.EventBasedHold, + StorageClass: srcObject.StorageClass, + ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, + } + } + + // Any existing mtime value will be overwritten with new value. + if mtime != nil { + metadataMap[MtimeMetadataKey] = mtime.UTC().Format(time.RFC3339Nano) + } + + return req +} diff --git a/internal/storage/gcs/request_helper_test.go b/internal/storage/gcs/request_helper_test.go new file mode 100644 index 0000000000..8e938ce52c --- /dev/null +++ b/internal/storage/gcs/request_helper_test.go @@ -0,0 +1,105 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcs + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestCreateObjectRequest(t *testing.T) { + now := time.Now() + + tests := []struct { + name string + srcObject *Object + objectName string + mtime *time.Time + chunkTransferTimeoutSecs int64 + expectedRequest *CreateObjectRequest + }{ + { + name: "nil_srcObject", + objectName: "new-object.txt", + mtime: &now, + chunkTransferTimeoutSecs: 30, + expectedRequest: &CreateObjectRequest{ + Name: "new-object.txt", + GenerationPrecondition: &[]int64{0}[0], // Default precondition + Metadata: map[string]string{ + MtimeMetadataKey: now.UTC().Format(time.RFC3339Nano), + }, + ChunkTransferTimeoutSecs: 30, + }, + }, + { + name: "existing_srcObject", + srcObject: &Object{ + Name: "existing-object.txt", + Generation: 12345, + MetaGeneration: 67890, + Metadata: map[string]string{"key1": "value1", "key2": "value2"}, + CacheControl: "public, max-age=3600", + ContentDisposition: "attachment; filename=\"myfile.txt\"", + ContentEncoding: "gzip", + ContentType: "text/plain", + CustomTime: now.Add(-24 * time.Hour).String(), + EventBasedHold: true, + StorageClass: "STANDARD", + }, + mtime: &now, + chunkTransferTimeoutSecs: 60, + expectedRequest: &CreateObjectRequest{ + Name: "existing-object.txt", + GenerationPrecondition: &[]int64{12345}[0], + MetaGenerationPrecondition: &[]int64{67890}[0], + Metadata: map[string]string{ + "key1": "value1", + "key2": "value2", + MtimeMetadataKey: now.UTC().Format(time.RFC3339Nano), + }, + CacheControl: "public, max-age=3600", + ContentDisposition: "attachment; filename=\"myfile.txt\"", + ContentEncoding: "gzip", + ContentType: "text/plain", + CustomTime: now.Add(-24 * time.Hour).String(), + EventBasedHold: true, + StorageClass: "STANDARD", + ChunkTransferTimeoutSecs: 60, + }, + }, + { + name: "nil_mtime_nil_srcObject", + objectName: "no-mtime.txt", + chunkTransferTimeoutSecs: 30, + expectedRequest: &CreateObjectRequest{ + Name: "no-mtime.txt", + GenerationPrecondition: &[]int64{0}[0], + Metadata: map[string]string{}, + ChunkTransferTimeoutSecs: 30, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := NewCreateObjectRequest(tt.srcObject, tt.objectName, tt.mtime, tt.chunkTransferTimeoutSecs) + + assert.Equal(t, tt.expectedRequest, req) + }) + } +} diff --git a/tools/integration_tests/operations/write_test.go b/tools/integration_tests/operations/write_test.go index 84a9f39e16..2e72ba3e65 100644 --- a/tools/integration_tests/operations/write_test.go +++ b/tools/integration_tests/operations/write_test.go @@ -25,7 +25,6 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -104,8 +103,8 @@ func validateObjectAttributes(attr1, attr2 *storage.ObjectAttrs, t *testing.T) { if attr1.StorageClass != storageClass || attr2.StorageClass != storageClass { t.Errorf("Expected storage class ") } - attr1MTime, _ := time.Parse(time.RFC3339Nano, attr1.Metadata[gcsx.MtimeMetadataKey]) - attr2MTime, _ := time.Parse(time.RFC3339Nano, attr2.Metadata[gcsx.MtimeMetadataKey]) + attr1MTime, _ := time.Parse(time.RFC3339Nano, attr1.Metadata[gcs.MtimeMetadataKey]) + attr2MTime, _ := time.Parse(time.RFC3339Nano, attr2.Metadata[gcs.MtimeMetadataKey]) if attr2MTime.Before(attr1MTime) { t.Errorf("Unexpected MTime received. After operation1: %v, After operation2: %v", attr1MTime, attr2MTime) } From c572bc2e285ef8605a35a5be8e223fd40ee27834 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:02:04 +0530 Subject: [PATCH 0079/1298] skip kernel list cache test for unsupported kernel version (#2814) * skip kernel list cache test for unsupported kernel version * review comment --- .../kernel_list_cache_inifinite_ttl_test.go | 19 +++++++++++++++++++ internal/fs/kernel_list_cache_test.go | 1 + .../fs/kernel_list_cache_zero_ttl_test.go | 1 + .../util/operations/operations.go | 14 ++++++++++++++ 4 files changed, 35 insertions(+) diff --git a/internal/fs/kernel_list_cache_inifinite_ttl_test.go b/internal/fs/kernel_list_cache_inifinite_ttl_test.go index 45fb291acd..47c8bf5036 100644 --- a/internal/fs/kernel_list_cache_inifinite_ttl_test.go +++ b/internal/fs/kernel_list_cache_inifinite_ttl_test.go @@ -20,16 +20,34 @@ import ( "errors" "os" "path" + "regexp" "testing" "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) +func SkipTestForUnsupportedKernelVersion(t *testing.T) { + // TODO: b/384648943 make this part of fsTest.SetUpTestSuite() after post fs + // tests are fully migrated to stretchr/testify. + t.Helper() + UnsupportedKernelVersions := []string{`^6\.9\.\d+`} + + kernelVersion := operations.KernelVersion(t) + for i := 0; i < len(UnsupportedKernelVersions); i++ { + matched, err := regexp.MatchString(UnsupportedKernelVersions[i], kernelVersion) + assert.NoError(t, err) + if matched { + t.SkipNow() + } + } +} + type KernelListCacheTestWithInfiniteTtl struct { suite.Suite fsTest @@ -52,6 +70,7 @@ func (t *KernelListCacheTestWithInfiniteTtl) SetupSuite() { } func TestKernelListCacheTestInfiniteTtlSuite(t *testing.T) { + SkipTestForUnsupportedKernelVersion(t) suite.Run(t, new(KernelListCacheTestWithInfiniteTtl)) } diff --git a/internal/fs/kernel_list_cache_test.go b/internal/fs/kernel_list_cache_test.go index e162f4b2fa..92724a229a 100644 --- a/internal/fs/kernel_list_cache_test.go +++ b/internal/fs/kernel_list_cache_test.go @@ -120,6 +120,7 @@ func (t *KernelListCacheTestWithPositiveTtl) SetupSuite() { } func TestKernelListCacheTestWithPositiveTtlSuite(t *testing.T) { + SkipTestForUnsupportedKernelVersion(t) suite.Run(t, new(KernelListCacheTestWithPositiveTtl)) } diff --git a/internal/fs/kernel_list_cache_zero_ttl_test.go b/internal/fs/kernel_list_cache_zero_ttl_test.go index 74da7cd147..cd56a77a65 100644 --- a/internal/fs/kernel_list_cache_zero_ttl_test.go +++ b/internal/fs/kernel_list_cache_zero_ttl_test.go @@ -50,6 +50,7 @@ func (t *KernelListCacheTestWithZeroTtl) SetupSuite() { } func TestKernelListCacheTestZeroTtlSuite(t *testing.T) { + SkipTestForUnsupportedKernelVersion(t) suite.Run(t, new(KernelListCacheTestWithZeroTtl)) } diff --git a/tools/integration_tests/util/operations/operations.go b/tools/integration_tests/util/operations/operations.go index fdd4d71b62..cb3e0e344d 100644 --- a/tools/integration_tests/util/operations/operations.go +++ b/tools/integration_tests/util/operations/operations.go @@ -20,7 +20,11 @@ import ( "fmt" "math/rand" "os/exec" + "strings" + "testing" "time" + + "github.com/stretchr/testify/assert" ) // GenerateRandomData generates random data that can be used to write to a file. @@ -70,3 +74,13 @@ func runCommand(cmd *exec.Cmd) ([]byte, error) { func ExecuteGcloudCommandf(format string, args ...any) ([]byte, error) { return executeToolCommandf("gcloud", format, args...) } + +func KernelVersion(t *testing.T) string { + t.Helper() + + cmd := exec.Command("uname", "-r") + out, err := cmd.Output() + assert.NoError(t, err) + kernelVersion := strings.TrimSpace(string(out)) + return kernelVersion +} From cf3589306ce706ebc6b98af83f7bba70d6f8bada Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:17:35 +0530 Subject: [PATCH 0080/1298] fix make command (#2815) * fix make command * test fix --- internal/cache/file/downloader/job.go | 2 +- internal/cache/file/downloader/job_test.go | 4 ++-- internal/logger/logger.go | 7 ++++++- .../managed_folders/test_helper.go | 8 ++++---- .../util/operations/dir_operations.go | 12 ++++++------ .../util/operations/operations.go | 15 ++++++++++++++- tools/prefetch_cache_gcsfuse/prefetch.go | 2 +- 7 files changed, 34 insertions(+), 16 deletions(-) diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index be6224fd53..b445857c1a 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -472,7 +472,7 @@ func (job *Job) Download(ctx context.Context, offset int64, waitForDownload bool job.mu.Lock() if int64(job.object.Size) < offset { defer job.mu.Unlock() - err = fmt.Errorf(fmt.Sprintf("Download: the requested offset %d is greater than the size of object %d", offset, job.object.Size)) + err = fmt.Errorf("download: the requested offset %d is greater than the size of object %d", offset, job.object.Size) return job.status, err } diff --git a/internal/cache/file/downloader/job_test.go b/internal/cache/file/downloader/job_test.go index 063078fe38..beffb44fe4 100644 --- a/internal/cache/file/downloader/job_test.go +++ b/internal/cache/file/downloader/job_test.go @@ -631,7 +631,7 @@ func (dt *downloaderTest) Test_Download_InvalidOffset() { jobStatus, err := dt.job.Download(context.Background(), offset, true) AssertNe(nil, err) - AssertTrue(strings.Contains(err.Error(), fmt.Sprintf("Download: the requested offset %d is greater than the size of object %d", offset, dt.object.Size))) + AssertTrue(strings.Contains(err.Error(), fmt.Sprintf("download: the requested offset %d is greater than the size of object %d", offset, dt.object.Size))) expectedJobStatus := JobStatus{NotStarted, nil, 0} AssertTrue(reflect.DeepEqual(expectedJobStatus, jobStatus)) AssertFalse(callbackExecuted.Load()) @@ -681,7 +681,7 @@ func (dt *downloaderTest) Test_Download_Concurrent() { ctx := context.Background() wg := sync.WaitGroup{} offsets := []int64{0, 4 * util.MiB, 16 * util.MiB, 8 * util.MiB, int64(objectSize), int64(objectSize) + 1} - expectedErrs := []error{nil, nil, nil, nil, nil, fmt.Errorf(fmt.Sprintf("Download: the requested offset %d is greater than the size of object %d", int64(objectSize)+1, int64(objectSize)))} + expectedErrs := []error{nil, nil, nil, nil, nil, fmt.Errorf("download: the requested offset %d is greater than the size of object %d", int64(objectSize)+1, int64(objectSize))} downloadFunc := func(expectedOffset int64, expectedErr error) { defer wg.Done() var jobStatus JobStatus diff --git a/internal/logger/logger.go b/internal/logger/logger.go index f84293dcde..641bd3a6a6 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -148,10 +148,15 @@ func Errorf(format string, v ...interface{}) { defaultLogger.Error(fmt.Sprintf(format, v...)) } +// Error prints the message with ERROR severity. +func Error(error string) { + defaultLogger.Error(error) +} + // Fatal prints an error log and exits with non-zero exit code. func Fatal(format string, v ...interface{}) { Errorf(format, v...) - Errorf(string(debug.Stack())) + Error(string(debug.Stack())) os.Exit(1) } diff --git a/tools/integration_tests/managed_folders/test_helper.go b/tools/integration_tests/managed_folders/test_helper.go index edfe4afd84..61cb5b5bb3 100644 --- a/tools/integration_tests/managed_folders/test_helper.go +++ b/tools/integration_tests/managed_folders/test_helper.go @@ -73,7 +73,7 @@ func providePermissionToManagedFolder(bucket, managedFolderPath, serviceAccount, // Indent for readability jsonData, err := json.MarshalIndent(policy, "", " ") if err != nil { - t.Fatalf(fmt.Sprintf("Error in marshal the data into JSON format: %v", err)) + t.Fatalf("Error in marshal the data into JSON format: %v", err) } f, err := os.CreateTemp(os.TempDir(), "iam-policy-*.json") @@ -84,11 +84,11 @@ func providePermissionToManagedFolder(bucket, managedFolderPath, serviceAccount, // Write the JSON to a FileInNonEmptyManagedFoldersTest _, err = f.Write(jsonData) if err != nil { - t.Fatalf(fmt.Sprintf("Error in writing iam policy in json FileInNonEmptyManagedFoldersTest : %v", err)) + t.Fatalf("Error in writing iam policy in json FileInNonEmptyManagedFoldersTest : %v", err) } gcloudProvidePermissionCmd := fmt.Sprintf("alpha storage managed-folders set-iam-policy gs://%s/%s %s", bucket, managedFolderPath, f.Name()) - _, err = operations.ExecuteGcloudCommandf(gcloudProvidePermissionCmd) + _, err = operations.ExecuteGcloudCommand(gcloudProvidePermissionCmd) if err != nil { t.Fatalf("Error in providing permission to managed folder: %v", err) } @@ -97,7 +97,7 @@ func providePermissionToManagedFolder(bucket, managedFolderPath, serviceAccount, func revokePermissionToManagedFolder(bucket, managedFolderPath, serviceAccount, iamRole string, t *testing.T) { gcloudRevokePermissionCmd := fmt.Sprintf("alpha storage managed-folders remove-iam-policy-binding gs://%s/%s --member=%s --role=%s", bucket, managedFolderPath, serviceAccount, iamRole) - _, err := operations.ExecuteGcloudCommandf(gcloudRevokePermissionCmd) + _, err := operations.ExecuteGcloudCommand(gcloudRevokePermissionCmd) if err != nil && !strings.Contains(err.Error(), "Policy binding with the specified principal, role, and condition not found!") && !strings.Contains(err.Error(), "The specified managed folder does not exist.") { t.Fatalf("Error in removing permission to managed folder: %v", err) } diff --git a/tools/integration_tests/util/operations/dir_operations.go b/tools/integration_tests/util/operations/dir_operations.go index a2f73ab978..a9a73167e3 100644 --- a/tools/integration_tests/util/operations/dir_operations.go +++ b/tools/integration_tests/util/operations/dir_operations.go @@ -164,26 +164,26 @@ func DirSizeMiB(dirPath string) (dirSizeMB int64, err error) { func DeleteManagedFoldersInBucket(managedFolderPath, bucket string) { gcloudDeleteManagedFolderCmd := fmt.Sprintf("alpha storage rm -r gs://%s/%s", bucket, managedFolderPath) - _, err := ExecuteGcloudCommandf(gcloudDeleteManagedFolderCmd) + _, err := ExecuteGcloudCommand(gcloudDeleteManagedFolderCmd) if err != nil && !strings.Contains(err.Error(), "The following URLs matched no objects or files") { - log.Fatalf(fmt.Sprintf("Error while deleting managed folder: %v", err)) + log.Fatalf("Error while deleting managed folder: %v", err) } } func CreateManagedFoldersInBucket(managedFolderPath, bucket string) { gcloudCreateManagedFolderCmd := fmt.Sprintf("alpha storage managed-folders create gs://%s/%s", bucket, managedFolderPath) - _, err := ExecuteGcloudCommandf(gcloudCreateManagedFolderCmd) + _, err := ExecuteGcloudCommand(gcloudCreateManagedFolderCmd) if err != nil && !strings.Contains(err.Error(), "The specified managed folder already exists") { - log.Fatalf(fmt.Sprintf("Error while creating managed folder: %v", err)) + log.Fatalf("Error while creating managed folder: %v", err) } } func CopyFileInBucket(srcfilePath, destFilePath, bucket string, t *testing.T) { gcloudCopyFileCmd := fmt.Sprintf("alpha storage cp %s gs://%s/%s/", srcfilePath, bucket, destFilePath) - _, err := ExecuteGcloudCommandf(gcloudCopyFileCmd) + _, err := ExecuteGcloudCommand(gcloudCopyFileCmd) if err != nil { - t.Fatalf(fmt.Sprintf("Error while copying file in bucket: %v", err)) + t.Fatalf("Error while copying file in bucket: %v", err) } } diff --git a/tools/integration_tests/util/operations/operations.go b/tools/integration_tests/util/operations/operations.go index cb3e0e344d..980b97a5e0 100644 --- a/tools/integration_tests/util/operations/operations.go +++ b/tools/integration_tests/util/operations/operations.go @@ -46,6 +46,14 @@ func executeToolCommandf(tool string, format string, args ...any) ([]byte, error return runCommand(cmd) } +// Executes any given tool (e.g. gsutil/gcloud). +func executeToolCommand(tool string, command string) ([]byte, error) { + cmdArgs := tool + " " + command + cmd := exec.Command("/bin/bash", "-c", cmdArgs) + + return runCommand(cmd) +} + // Executes any given tool (e.g. gsutil/gcloud) with given args in specified directory. func ExecuteToolCommandfInDirectory(dirPath, tool, format string, args ...any) ([]byte, error) { cmdArgs := tool + " " + fmt.Sprintf(format, args...) @@ -70,11 +78,16 @@ func runCommand(cmd *exec.Cmd) ([]byte, error) { return stdout.Bytes(), nil } -// Executes any given gcloud command with given args. +// ExecuteGcloudCommandf executes any given gcloud command with given args. func ExecuteGcloudCommandf(format string, args ...any) ([]byte, error) { return executeToolCommandf("gcloud", format, args...) } +// ExecuteGcloudCommand executes any given gcloud command. +func ExecuteGcloudCommand(command string) ([]byte, error) { + return executeToolCommand("gcloud", command) +} + func KernelVersion(t *testing.T) string { t.Helper() diff --git a/tools/prefetch_cache_gcsfuse/prefetch.go b/tools/prefetch_cache_gcsfuse/prefetch.go index 4631da0a95..d2d6ae9227 100644 --- a/tools/prefetch_cache_gcsfuse/prefetch.go +++ b/tools/prefetch_cache_gcsfuse/prefetch.go @@ -33,7 +33,7 @@ import ( const NUM_WORKERS = 10 func downloadFile(ctx context.Context, client *storage.Client, object *storage.ObjectAttrs, cacheDir string) (err error) { - log.Printf(fmt.Sprintf("downloading file %v from bucket %v into dir %v", object.Name, object.Bucket, cacheDir)) + log.Printf("downloading file %v from bucket %v into dir %v", object.Name, object.Bucket, cacheDir) // We may want a way to verify the files are fully downloaded // and either resuming the download or discarding and redownloading the file From 44802212cfdb0b8e7353d89dc7645bb92dfbddf3 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:17:33 +0530 Subject: [PATCH 0081/1298] [gke-automated-test-tool] Create a new custom-driver hash on each build (#2818) This is to handle google internal bug id 384494459 . This creates a new image hash for each new custom csi driver image build to force gke cluster nodes to use them on re-run. --- .../testing_on_gke/examples/run-gke-tests.sh | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index 332b8988f6..bb23784de3 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -513,6 +513,17 @@ function ensureGcsFuseCsiDriverCode() { fi } +uuid() { + if ! which uuidgen; then + # try to install uuidgen + sudo apt-get update && sudo apt-get install -y uuid-runtime + # confirm that it got installed. + which uuidgen + fi + + echo $(uuidgen) | sed -e "s/\-//g" ; +} + function createCustomCsiDriverIfNeeded() { if ${use_custom_csi_driver}; then echo "Disabling managed CSI driver ..." @@ -554,9 +565,10 @@ function createCustomCsiDriverIfNeeded() { make generate-spec-yaml printf "\nBuilding a new custom CSI driver using the above GCSFuse binary ...\n\n" registry=gcr.io/${project_id}/${USER}/${cluster_name} - make build-image-and-push-multi-arch REGISTRY=${registry} GCSFUSE_PATH=gs://${package_bucket} + stagingversion=$(uuid) + make build-image-and-push-multi-arch REGISTRY=${registry} GCSFUSE_PATH=gs://${package_bucket} STAGINGVERSION=${stagingversion} printf "\nInstalling the new custom CSI driver built above ...\n\n" - make install PROJECT=${project_id} REGISTRY=${registry} + make install PROJECT=${project_id} REGISTRY=${registry} STAGINGVERSION=${stagingversion} cd - # Wait some time after csi driver installation before deploying pods From 2739e51d3aebb580e451a29e4192c22a61e8b6b6 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 19 Dec 2024 09:50:51 +0530 Subject: [PATCH 0082/1298] [gke-automated-test-tool] fix bug in csi driver build (#2819) This fixes a bug unintentionally introduced in previous commit . In that commit, the bash function uuid was printing out some more information other than the uuid value itself because of the installation step. In bash, the printed log becomes the output of a function, hence the installation inside this function was changing the output of the uuid function breaking the caller. So, I have moved the installation out to wherever we're invoking this function. --- .../testing_on_gke/examples/run-gke-tests.sh | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index bb23784de3..f2b7f23f71 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -514,13 +514,6 @@ function ensureGcsFuseCsiDriverCode() { } uuid() { - if ! which uuidgen; then - # try to install uuidgen - sudo apt-get update && sudo apt-get install -y uuid-runtime - # confirm that it got installed. - which uuidgen - fi - echo $(uuidgen) | sed -e "s/\-//g" ; } @@ -565,6 +558,12 @@ function createCustomCsiDriverIfNeeded() { make generate-spec-yaml printf "\nBuilding a new custom CSI driver using the above GCSFuse binary ...\n\n" registry=gcr.io/${project_id}/${USER}/${cluster_name} + if ! which uuidgen; then + # try to install uuidgen + sudo apt-get update && sudo apt-get install -y uuid-runtime + # confirm that it got installed. + which uuidgen + fi stagingversion=$(uuid) make build-image-and-push-multi-arch REGISTRY=${registry} GCSFUSE_PATH=gs://${package_bucket} STAGINGVERSION=${stagingversion} printf "\nInstalling the new custom CSI driver built above ...\n\n" From 8e10fc7d1f1e962937bfd9fc3ea974d7f8f991c6 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 19 Dec 2024 11:37:40 +0530 Subject: [PATCH 0083/1298] add 6.10+ to unsupported versions (#2820) --- internal/fs/kernel_list_cache_inifinite_ttl_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/fs/kernel_list_cache_inifinite_ttl_test.go b/internal/fs/kernel_list_cache_inifinite_ttl_test.go index 47c8bf5036..e690ab5df2 100644 --- a/internal/fs/kernel_list_cache_inifinite_ttl_test.go +++ b/internal/fs/kernel_list_cache_inifinite_ttl_test.go @@ -36,7 +36,7 @@ func SkipTestForUnsupportedKernelVersion(t *testing.T) { // TODO: b/384648943 make this part of fsTest.SetUpTestSuite() after post fs // tests are fully migrated to stretchr/testify. t.Helper() - UnsupportedKernelVersions := []string{`^6\.9\.\d+`} + UnsupportedKernelVersions := []string{`^6\.9\.\d+`, `^6\.\d{2}\.\d+`} kernelVersion := operations.KernelVersion(t) for i := 0; i < len(UnsupportedKernelVersions); i++ { From c37377611057afb08df3f1e29b90d37d919bb558 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 19 Dec 2024 12:02:11 +0530 Subject: [PATCH 0084/1298] [Write stall] CD pipeline integration (#2817) * update e2e tests in cd pipeline * update steps * add some installation * move it appropriate place * small fix * small fix * replacing od type * adding sudo while running docker * adding sudo while running docker * test on kokoro --- tools/cd_scripts/e2e_test.sh | 21 ++++++++++ .../emulator_tests/emulator_tests.sh | 42 ++++++++++++++++--- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 36b9550758..80eb6517e5 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -142,6 +142,7 @@ git checkout $(sed -n 2p ~/details.txt) |& tee -a ~/logs.txt set +e # Test directory arrays TEST_DIR_PARALLEL=( + "monitoring" "local_file" "log_rotation" "mounting" @@ -285,6 +286,10 @@ function run_e2e_tests_for_hns_bucket(){ return 0 } +function run_e2e_tests_for_emulator() { + ./tools/integration_tests/emulator_tests/emulator_tests.sh true > ~/logs-emulator.txt +} + function gather_test_logs() { readarray -t test_logs_array < "$TEST_LOGS_FILE" rm "$TEST_LOGS_FILE" @@ -313,6 +318,12 @@ echo "Running integration tests for FLAT bucket..." run_e2e_tests_for_flat_bucket & e2e_tests_flat_bucket_pid=$! +run_e2e_tests_for_emulator & +e2e_tests_emulator_pid=$! + +wait $e2e_tests_emulator_pid +e2e_tests_emulator_status=$? + wait $e2e_tests_flat_bucket_pid e2e_tests_flat_bucket_status=$? @@ -338,4 +349,14 @@ else gsutil cp success-hns.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ fi gsutil cp ~/logs-hns.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + +if [ $e2e_tests_emulator_status != 0 ]; +then + echo "Test failures detected in emulator based tests." &>> ~/logs-emulator.txt +else + touch success-emulator.txt + gsutil cp success-emulator.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ +fi + +gsutil cp ~/logs-emulator.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ ' diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index f3247dad5e..70a7e37b14 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -19,8 +19,8 @@ set -eo pipefail # Display commands being run set -x -architecture=$(dpkg --print-architecture) -if [ $architecture == "arm64" ];then +uname=$(uname -m) +if [ $uname == "aarch64" ];then # TODO: Remove this when we have an ARM64 image for the storage test bench.(b/384388821) echo "These tests will not run for arm64 machine..." exit 0 @@ -39,6 +39,38 @@ if [ "$minor_ver" -lt "$min_minor_ver" ]; then exit 0 fi +# Install dependencies +# Ubuntu/Debian based machine. +if [ -f /etc/debian_version ]; then + if grep -q "Ubuntu" /etc/os-release; then + os="ubuntu" + elif grep -q "Debian" /etc/os-release; then + os="debian" + fi + + sudo apt-get update + sudo apt-get install -y ca-certificates curl + sudo install -m 0755 -d /etc/apt/keyrings + sudo curl -fsSL https://download.docker.com/linux/${os}/gpg -o /etc/apt/keyrings/docker.asc + sudo chmod a+r /etc/apt/keyrings/docker.asc + # Add the repository to Apt sources: + echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${os} \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + sudo apt-get install -y lsof +# RHEL/CentOS based machine. +elif [ -f /etc/redhat-release ]; then + sudo dnf -y install dnf-plugins-core + sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo + sudo dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + sudo usermod -aG docker $USER + sudo systemctl start docker + sudo yum -y install lsof +fi + export STORAGE_EMULATOR_HOST="http://localhost:9000" DEFAULT_IMAGE_NAME='gcr.io/cloud-devrel-public-resources/storage-testbench' @@ -54,17 +86,17 @@ CONTAINER_NAME=storage_testbench DOCKER_NETWORK="--net=host" # Get the docker image for the testbench -docker pull $DOCKER_IMAGE +sudo docker pull $DOCKER_IMAGE # Start the testbench -docker run --name $CONTAINER_NAME --rm -d $DOCKER_NETWORK $DOCKER_IMAGE +sudo docker run --name $CONTAINER_NAME --rm -d $DOCKER_NETWORK $DOCKER_IMAGE echo "Running the Cloud Storage testbench: $STORAGE_EMULATOR_HOST" sleep 5 # Stop the testbench & cleanup environment variables function cleanup() { echo "Cleanup testbench" - docker stop $CONTAINER_NAME + sudo docker stop $CONTAINER_NAME unset STORAGE_EMULATOR_HOST; } trap cleanup EXIT From 9b80d4407308db7c255d4a4de474225c4d6ecdc0 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:53:30 +0530 Subject: [PATCH 0085/1298] [Write-stall] Add e2e tests - 2 (#2798) * add tests for default chunk transfer timeout * add more e2e tests * fix tests * remove proxy log * update readme * lint fix * review comment * update readme * update readme * remove log file * review comment * adding comment * remove unnecessary change * optimize the logic * small fix * small fix --- .../emulator_tests/README.md | 3 +- .../configs/write_stall_twice_40s.yaml | 10 +++ .../emulator_tests/util/test_helper.go | 16 +++++ .../emulator_tests/write_stall_test.go | 62 +++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_twice_40s.yaml diff --git a/tools/integration_tests/emulator_tests/README.md b/tools/integration_tests/emulator_tests/README.md index 9eddd7a775..638af8cf07 100644 --- a/tools/integration_tests/emulator_tests/README.md +++ b/tools/integration_tests/emulator_tests/README.md @@ -26,4 +26,5 @@ go test --integrationTest -v --testbucket=test-bucket -timeout 10m 1. Run ./emulator_tests.sh ### Steps to add new tests in the future: -TODO: Will add in next PR +1. Create _test file [here](https://github.com/GoogleCloudPlatform/gcsfuse/tree/master/tools/integration_tests/emulator_tests). +2. Write tests according to your scenarios. e.g. [write_stall_test](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/tools/integration_tests/emulator_tests/write_stall_test.go) diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_twice_40s.yaml b/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_twice_40s.yaml new file mode 100644 index 0000000000..0f0f3c19bd --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_twice_40s.yaml @@ -0,0 +1,10 @@ +targetHost: http://localhost:9000 +retryConfig: +- method: JsonCreate + retryInstruction: "stall-for-40s-after-15360K" + retryCount: 2 + # To add forced error scenarios for resumable uploads, we need to define skipCount two. + # This is because the first POST request creates the file in our tests, and the second POST request only initiates + # the resumable upload request. Subsequent requests actually upload the data, and it's + # these requests we want to stall for testing. + skipCount: 2 diff --git a/tools/integration_tests/emulator_tests/util/test_helper.go b/tools/integration_tests/emulator_tests/util/test_helper.go index cbe8ed8389..88ed30aaeb 100644 --- a/tools/integration_tests/emulator_tests/util/test_helper.go +++ b/tools/integration_tests/emulator_tests/util/test_helper.go @@ -131,3 +131,19 @@ func WriteFileAndSync(filePath string, fileSize int) (time.Duration, error) { return endTime.Sub(startTime), nil } + +func GetChunkTransferTimeoutFromFlags(flags []string) (int, error) { + timeout := 10 // Default value + for _, flag := range flags { + if strings.HasPrefix(flag, "--chunk-transfer-timeout-secs=") { + valueStr := strings.TrimPrefix(flag, "--chunk-transfer-timeout-secs=") + var err error + timeout, err = strconv.Atoi(valueStr) + if err != nil { + return 0, err + } + break + } + } + return timeout, nil +} diff --git a/tools/integration_tests/emulator_tests/write_stall_test.go b/tools/integration_tests/emulator_tests/write_stall_test.go index 53f48a3c03..0df0ca3f08 100644 --- a/tools/integration_tests/emulator_tests/write_stall_test.go +++ b/tools/integration_tests/emulator_tests/write_stall_test.go @@ -15,6 +15,7 @@ package emulator_tests import ( + "fmt" "log" "path" "testing" @@ -88,3 +89,64 @@ func TestChunkTransferTimeoutInfinity(t *testing.T) { test_setup.RunTests(t, ts) } } + +func TestChunkTransferTimeout(t *testing.T) { + flagSets := [][]string{ + {"--custom-endpoint=" + proxyEndpoint}, + {"--custom-endpoint=" + proxyEndpoint, "--chunk-transfer-timeout-secs=5"}, + } + + stallScenarios := []struct { + name string + configPath string + expectedTimeout func(int) time.Duration + }{ + { + name: "SingleStall", + configPath: "./proxy_server/configs/write_stall_40s.yaml", + expectedTimeout: func(chunkTransferTimeoutSecs int) time.Duration { + return time.Duration(chunkTransferTimeoutSecs) * time.Second + }, + }, + { + name: "MultipleStalls", + configPath: "./proxy_server/configs/write_stall_twice_40s.yaml", // 2 stalls + // Expect total time to be greater than the timeout multiplied by the number of stalls (2 in this case). + expectedTimeout: func(chunkTransferTimeoutSecs int) time.Duration { + return time.Duration(chunkTransferTimeoutSecs*2) * time.Second + }, + }, + } + + for _, flags := range flagSets { + chunkTransferTimeoutSecs, err := emulator_tests.GetChunkTransferTimeoutFromFlags(flags) + if err != nil { + t.Fatalf("Invalid chunk-transfer-timeout-secs flag: %v", err) + } + + t.Run(fmt.Sprintf("Flags_%v", flags), func(t *testing.T) { + for _, scenario := range stallScenarios { + t.Run(scenario.name, func(t *testing.T) { + emulator_tests.StartProxyServer(scenario.configPath) + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) + + defer func() { // Defer unmount and killing the server. + setup.UnmountGCSFuse(rootDir) + assert.NoError(t, emulator_tests.KillProxyServerProcess(port)) + }() + + testDir := scenario.name + setup.GenerateRandomString(3) + testDirPath = setup.SetupTestDirectory(testDir) + filePath := path.Join(testDirPath, "file.txt") + + elapsedTime, err := emulator_tests.WriteFileAndSync(filePath, fileSize) + + assert.NoError(t, err, "failed to write file and sync") + expectedTimeout := scenario.expectedTimeout(chunkTransferTimeoutSecs) + assert.GreaterOrEqual(t, elapsedTime, expectedTimeout) + assert.Less(t, elapsedTime, expectedTimeout+5*time.Second) + }) + } + }) + } +} From 1046474fe5b1c8fd3f2f9224676e8adabe89ceae Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 23 Dec 2024 14:52:40 +0530 Subject: [PATCH 0086/1298] handle out of order writes (#2826) --- .../bufferedwrites/buffered_write_handler.go | 5 +- internal/fs/inode/file.go | 85 +++++-- .../fs/inode/file_streaming_writes_test.go | 210 ++++++++++++++++++ 3 files changed, 284 insertions(+), 16 deletions(-) create mode 100644 internal/fs/inode/file_streaming_writes_test.go diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 97a9a77851..e7bb7a85c6 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -88,8 +88,9 @@ func NewBWHandler(req *CreateBWHandlerRequest) (bwh *BufferedWriteHandler, err e BlockSize: req.BlockSize, ChunkTransferTimeoutSecs: req.ChunkTransferTimeoutSecs, }), - totalSize: 0, - mtime: time.Now(), + totalSize: 0, + mtime: time.Now(), + truncatedSize: -1, } return } diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 71a219eb80..9c60fbf4fd 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -500,19 +500,26 @@ func (f *FileInode) Read( func (f *FileInode) Write( ctx context.Context, data []byte, - offset int64) (err error) { + offset int64) error { // For empty GCS files also we will trigger bufferedWrites flow. if f.src.Size == 0 && f.config.Write.ExperimentalEnableStreamingWrites { - err = f.ensureBufferedWriteHandler(ctx) + err := f.ensureBufferedWriteHandler(ctx) if err != nil { - return + return err } } if f.bwh != nil { - return f.bwh.Write(data, offset) + return f.writeUsingBufferedWrites(ctx, data, offset) } + return f.writeUsingTempFile(ctx, data, offset) +} + +// Helper function to serve write for file using temp file. +// +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) writeUsingTempFile(ctx context.Context, data []byte, offset int64) (err error) { // Make sure f.content != nil. err = f.ensureContent(ctx) if err != nil { @@ -527,6 +534,51 @@ func (f *FileInode) Write( return } +// Helper function to serve write for file using buffered writes handler. +// +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, offset int64) error { + err := f.bwh.Write(data, offset) + if err == bufferedwrites.ErrOutOfOrderWrite || err == bufferedwrites.ErrUploadFailure { + // Finalize the object. + flushErr := f.flushUsingBufferedWriteHandler() + if flushErr != nil { + return fmt.Errorf("bwh.Write failed: %v, could not finalize what has been written so far: %w", err, flushErr) + } + } + + // Fall back to temp file for Out-Of-Order Writes. + if err == bufferedwrites.ErrOutOfOrderWrite { + return f.writeUsingTempFile(ctx, data, offset) + } + + return err +} + +// Helper function to flush buffered writes handler and update inode state with +// new object. +// +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) flushUsingBufferedWriteHandler() error { + obj, err := f.bwh.Flush() + + var preconditionErr *gcs.PreconditionError + if errors.As(err, &preconditionErr) { + return &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("f.bwh.Flush(): %w", err), + } + } + + // bwh can return a partially synced object along with an error so updating + // inode state before returning error. + f.updateInodeStateAfterSync(obj) + if err != nil { + return fmt.Errorf("f.bwh.Flush(): %w", err) + } + + return nil +} + // Set the mtime for this file. May involve a round trip to GCS. // // LOCKS_REQUIRED(f.mu) @@ -666,21 +718,26 @@ func (f *FileInode) Sync(ctx context.Context) (err error) { err = fmt.Errorf("SyncObject: %w", err) return } - + minObj := storageutil.ConvertObjToMinObject(newObj) // If we wrote out a new object, we need to update our state. - if newObj != nil && !f.localFileCache { - var minObj gcs.MinObject - minObjPtr := storageutil.ConvertObjToMinObject(newObj) - if minObjPtr != nil { - minObj = *minObjPtr - } - f.src = minObj + f.updateInodeStateAfterSync(minObj) + return +} + +func (f *FileInode) updateInodeStateAfterSync(minObj *gcs.MinObject) { + if minObj != nil && !f.localFileCache { + f.src = *minObj // Convert localFile to nonLocalFile after it is synced to GCS. if f.IsLocal() { f.local = false } - f.content.Destroy() - f.content = nil + if f.content != nil { + f.content.Destroy() + f.content = nil + } + if f.bwh != nil { + f.bwh = nil + } } return diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go new file mode 100644 index 0000000000..27bc9eaf15 --- /dev/null +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -0,0 +1,210 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package inode + +import ( + "context" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/jacobsa/fuse/fuseops" + "github.com/jacobsa/syncutil" + "github.com/jacobsa/timeutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +const localFile = "local" +const emptyGCSFile = "emptyGCS" + +type FileStreamingWritesTest struct { + suite.Suite + ctx context.Context + bucket gcs.Bucket + clock timeutil.SimulatedClock + backingObj *gcs.MinObject + in *FileInode +} + +func TestFileStreamingWritesTestSuite(t *testing.T) { + suite.Run(t, new(FileStreamingWritesTest)) +} + +func (t *FileStreamingWritesTest) SetupTest() { + // Enabling invariant check for all tests. + syncutil.EnableInvariantChecking() + t.ctx = context.Background() + t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)) + t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical) + + // Create the inode. + t.createInode(fileName, localFile) +} + +func (t *FileStreamingWritesTest) TearDownTest() { + t.in.Unlock() +} + +func (t *FileStreamingWritesTest) createInode(fileName string, fileType string) { + if fileType != emptyGCSFile && fileType != localFile { + t.T().Errorf("fileType should be either local or empty") + } + + name := NewFileName( + NewRootName(""), + fileName, + ) + syncerBucket := gcsx.NewSyncerBucket( + 1, // Append threshold + ChunkTransferTimeoutSecs, + ".gcsfuse_tmp/", + t.bucket) + + isLocal := false + if fileType == localFile { + t.backingObj = nil + isLocal = true + } + + if fileType == emptyGCSFile { + object, err := storageutil.CreateObject( + t.ctx, + t.bucket, + fileName, + []byte{}) + t.backingObj = storageutil.ConvertObjToMinObject(object) + + assert.Nil(t.T(), err) + } + + t.in = NewFileInode( + fileInodeID, + name, + t.backingObj, + fuseops.InodeAttributes{ + Uid: uid, + Gid: gid, + Mode: fileMode, + }, + &syncerBucket, + false, // localFileCache + contentcache.New("", &t.clock), + &t.clock, + isLocal, + &cfg.Config{}) + + // Set buffered write config for created inode. + t.in.config = &cfg.Config{Write: cfg.WriteConfig{ + MaxBlocksPerFile: 5, + BlockSizeMb: 1, + ExperimentalEnableStreamingWrites: true, + GlobalMaxBlocks: 10, + }} + + // Create write handler for the local inode created above. + err := t.in.CreateBufferedOrTempWriter(t.ctx) + assert.Nil(t.T(), err) + + t.in.Lock() +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempFile() { + testCases := []struct { + name string + offset int64 + expectedContent string + }{ + { + name: "ahead_of_current_offset", + offset: 5, + expectedContent: "taco\x00hello", + }, + { + name: "zero_offset", + offset: 0, + expectedContent: "hello", + }, + { + name: "before_current_offset", + offset: 2, + expectedContent: "tahello", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + assert.True(t.T(), t.in.IsLocal()) + createTime := t.in.mtimeClock.Now() + err := t.in.Write(t.ctx, []byte("taco"), 0) + require.Nil(t.T(), err) + require.NotNil(t.T(), t.in.bwh) + assert.Equal(t.T(), int64(4), t.in.bwh.WriteFileInfo().TotalSize) + + err = t.in.Write(t.ctx, []byte("hello"), tc.offset) + require.Nil(t.T(), err) + + // Ensure bwh cleared and temp file created. + assert.Nil(t.T(), t.in.bwh) + assert.NotNil(t.T(), t.in.content) + // The inode should agree about the new mtime and size. + attrs, err := t.in.Attributes(t.ctx) + require.Nil(t.T(), err) + assert.Equal(t.T(), uint64(len(tc.expectedContent)), attrs.Size) + assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) + // sync file and validate content + err = t.in.Sync(t.ctx) + require.Nil(t.T(), err) + // Read the object's contents. + contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + assert.Nil(t.T(), err) + assert.Equal(t.T(), tc.expectedContent, string(contents)) + t.TearDownTest() + t.SetupTest() + }) + } +} + +func (t *FileStreamingWritesTest) TestOutOfOrderWritesOnClobberedFileThrowsError() { + err := t.in.Write(t.ctx, []byte("hi"), 0) + require.Nil(t.T(), err) + require.NotNil(t.T(), t.in.bwh) + assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) + // Clobber the file. + objWritten, err := storageutil.CreateObject(t.ctx, t.bucket, fileName, []byte("taco")) + require.Nil(t.T(), err) + + err = t.in.Write(t.ctx, []byte("hello"), 10) + + require.Error(t.T(), err) + var fileClobberedError *gcsfuse_errors.FileClobberedError + assert.ErrorAs(t.T(), err, &fileClobberedError) + // Validate Object on GCS not updated. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + objGot, _, err := t.bucket.StatObject(t.ctx, statReq) + assert.Nil(t.T(), err) + assert.Equal(t.T(), storageutil.ConvertObjToMinObject(objWritten), objGot) +} From ff4417a02c15d5d8ff35383dbc572e2ccbfe1066 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:50:18 +0530 Subject: [PATCH 0087/1298] [Move Object] Integrating MoveObject in all the bucket layers (#2831) * add MoveObject in all the bucket layers * small fix * remove prefix bucket impementation and add it later * lint fix * add comment --- internal/gcsx/prefix_bucket.go | 5 ++++ internal/monitor/bucket.go | 7 +++++ internal/ratelimit/throttled_bucket.go | 12 ++++++++ internal/storage/bucket_handle.go | 5 ++++ internal/storage/caching/fast_stat_bucket.go | 5 ++++ internal/storage/debug_bucket.go | 11 +++++++ internal/storage/fake/bucket.go | 5 ++++ internal/storage/gcs/bucket.go | 8 +++++ internal/storage/gcs/request.go | 8 +++++ internal/storage/mock/testify_mock_bucket.go | 5 ++++ internal/storage/mock_bucket.go | 31 ++++++++++++++++++++ 11 files changed, 102 insertions(+) diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index 2dacccc0ef..a37ae2f3db 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -235,6 +235,11 @@ func (b *prefixBucket) DeleteObject( return } +func (b *prefixBucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { + // TODO: Implement it. + return nil, nil +} + func (b *prefixBucket) DeleteFolder(ctx context.Context, folderName string) (err error) { mFolderName := b.wrappedName(folderName) return b.wrapped.DeleteFolder(ctx, mFolderName) diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index 08df7fff63..ef60e1804e 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -145,6 +145,13 @@ func (mb *monitoringBucket) DeleteObject( return err } +func (mb *monitoringBucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { + startTime := time.Now() + o, err := mb.wrapped.MoveObject(ctx, req) + recordRequest(ctx, mb.metricHandle, "MoveObject", startTime) + return o, err +} + func (mb *monitoringBucket) DeleteFolder(ctx context.Context, folderName string) error { startTime := time.Now() err := mb.wrapped.DeleteFolder(ctx, folderName) diff --git a/internal/ratelimit/throttled_bucket.go b/internal/ratelimit/throttled_bucket.go index 7938c8ac9f..25899b1c6b 100644 --- a/internal/ratelimit/throttled_bucket.go +++ b/internal/ratelimit/throttled_bucket.go @@ -205,6 +205,18 @@ func (b *throttledBucket) DeleteObject( return } +func (b *throttledBucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { + // Wait for permission to call through. + err := b.opThrottle.Wait(ctx, 1) + if err != nil { + return nil, err + } + + // Call through. + o, err := b.wrapped.MoveObject(ctx, req) + + return o, err +} func (b *throttledBucket) DeleteFolder(ctx context.Context, folderName string) (err error) { // Wait for permission to call through. err = b.opThrottle.Wait(ctx, 1) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index ff48adc793..0b388a430e 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -536,6 +536,11 @@ func (bh *bucketHandle) DeleteFolder(ctx context.Context, folderName string) (er return err } +func (bh *bucketHandle) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { + // TODO: Implement it. + return nil, nil +} + func (bh *bucketHandle) RenameFolder(ctx context.Context, folderName string, destinationFolderId string) (folder *gcs.Folder, err error) { var controlFolder *controlpb.Folder req := &controlpb.RenameFolderRequest{ diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index 37ce1d2d49..df21d3e389 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -372,6 +372,11 @@ func (b *fastStatBucket) DeleteObject( return } +func (b *fastStatBucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { + // TODO: Implement it. + return nil, nil +} + func (b *fastStatBucket) DeleteFolder(ctx context.Context, folderName string) error { err := b.wrapped.DeleteFolder(ctx, folderName) if err != nil { diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index 7c80c4681a..35212d3988 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -246,6 +246,17 @@ func (b *debugBucket) DeleteObject( return } +func (b *debugBucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { + var err error + var o *gcs.Object + id, desc, start := b.startRequest("MoveObject(%q, %q)", req.SrcName, req.DstName) + + defer b.finishRequest(id, desc, start, &err) + + o, err = b.wrapped.MoveObject(ctx, req) + return o, err +} + func (b *debugBucket) DeleteFolder(ctx context.Context, folderName string) (err error) { id, desc, start := b.startRequest("DeleteFolder(%q)", folderName) defer b.finishRequest(id, desc, start, &err) diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index 6da81e2c21..80cb197fbb 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -1007,6 +1007,11 @@ func (b *bucket) DeleteObject( return } +func (b *bucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { + // TODO: Implement it. + return nil, nil +} + func (b *bucket) DeleteFolder(ctx context.Context, folderName string) (err error) { b.mu.Lock() defer b.mu.Unlock() diff --git a/internal/storage/gcs/bucket.go b/internal/storage/gcs/bucket.go index 1cd020f1a4..909c94fbf0 100644 --- a/internal/storage/gcs/bucket.go +++ b/internal/storage/gcs/bucket.go @@ -160,6 +160,14 @@ type Bucket interface { ctx context.Context, req *DeleteObjectRequest) error + // MoveObject moves an object to a new name, preserving all metadata. + // + // This function overwrites any existing object at the destination name. + // + // Returns a record for the newly created object. + // TODO: Add official documentation link whenever it's available. + MoveObject(ctx context.Context, req *MoveObjectRequest) (*Object, error) + DeleteFolder(ctx context.Context, folderName string) error GetFolder(ctx context.Context, folderName string) (*Folder, error) diff --git a/internal/storage/gcs/request.go b/internal/storage/gcs/request.go index d5ab2375dd..7ba6c68913 100644 --- a/internal/storage/gcs/request.go +++ b/internal/storage/gcs/request.go @@ -385,3 +385,11 @@ type DeleteObjectRequest struct { // is not equal to this value. MetaGenerationPrecondition *int64 } + +// MoveObjectRequest represents a request to move or rename an object. +type MoveObjectRequest struct { + SrcName string // Source object name + DstName string // Destination object name + + // TODO: Add other necessary attributes as required +} diff --git a/internal/storage/mock/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go index 65d6fd916c..417a08c8f1 100644 --- a/internal/storage/mock/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -96,6 +96,11 @@ func (m *TestifyMockBucket) DeleteObject(ctx context.Context, req *gcs.DeleteObj return args.Error(0) } +func (m *TestifyMockBucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { + args := m.Called(ctx, req) + return args.Get(0).(*gcs.Object), args.Error(1) +} + func (m *TestifyMockBucket) DeleteFolder(ctx context.Context, folderName string) error { args := m.Called(ctx, folderName) return args.Error(0) diff --git a/internal/storage/mock_bucket.go b/internal/storage/mock_bucket.go index 57cad5acea..9ef891c64f 100644 --- a/internal/storage/mock_bucket.go +++ b/internal/storage/mock_bucket.go @@ -213,6 +213,37 @@ func (m *mockBucket) DeleteObject(p0 context.Context, p1 *gcs.DeleteObjectReques return } +func (m *mockBucket) MoveObject(p0 context.Context, p1 *gcs.MoveObjectRequest) (*gcs.Object, error) { + var o0 *gcs.Object + var o1 error + // Get a file name and line number for the caller. + _, file, line, _ := runtime.Caller(1) + + // Hand the call off to the controller, which does most of the work. + retVals := m.controller.HandleMethodCall( + m, + "MoveObject", + file, + line, + []interface{}{p0, p1}) + + if len(retVals) != 2 { + panic(fmt.Sprintf("mockBucket.MoveObject: invalid return values: %v", retVals)) + } + + // o0 *Object + if retVals[0] != nil { + o0 = retVals[0].(*gcs.Object) + } + + // o1 error + if retVals[1] != nil { + o1 = retVals[1].(error) + } + + return o0, o1 +} + func (m *mockBucket) DeleteFolder(ctx context.Context, folderName string) (o0 error) { // Get a file name and line number for the caller. _, file, line, _ := runtime.Caller(1) From 2eea4f07ac90eb421912463d30ace486d1a74565 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 24 Dec 2024 11:10:30 +0530 Subject: [PATCH 0088/1298] [Move Object] Fake and Prefix bucket implementation (#2834) * fake and prefix bucket implementation * changes bucket type * nits * small fix * using unnecessary variables * using testify * nit --- internal/gcsx/prefix_bucket.go | 16 +++++++- internal/gcsx/prefix_bucket_test.go | 44 +++++++++++++++++++++ internal/storage/fake/bucket.go | 59 ++++++++++++++++++++++++++++- internal/storage/gcs/request.go | 8 +++- 4 files changed, 122 insertions(+), 5 deletions(-) diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index a37ae2f3db..44d2ed4533 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -236,8 +236,20 @@ func (b *prefixBucket) DeleteObject( } func (b *prefixBucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { - // TODO: Implement it. - return nil, nil + // Modify the request and call through. + mReq := new(gcs.MoveObjectRequest) + *mReq = *req + mReq.SrcName = b.wrappedName(req.SrcName) + mReq.DstName = b.wrappedName(req.DstName) + + o, err := b.wrapped.MoveObject(ctx, mReq) + + // Modify the returned object. + if o != nil { + o.Name = b.localName(o.Name) + } + + return o, err } func (b *prefixBucket) DeleteFolder(ctx context.Context, folderName string) (err error) { diff --git a/internal/gcsx/prefix_bucket_test.go b/internal/gcsx/prefix_bucket_test.go index cab3588b3d..09dccf8842 100644 --- a/internal/gcsx/prefix_bucket_test.go +++ b/internal/gcsx/prefix_bucket_test.go @@ -514,3 +514,47 @@ func TestCreateFolder(t *testing.T) { _, err = bucket.GetFolder(ctx, suffix) assert.NoError(t, err) } + +func TestMoveObject(t *testing.T) { + var notFoundErr *gcs.NotFoundError + var err error + prefix := "foo_" + suffix := "test" + wrapped := fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.Hierarchical) + bucket, err := gcsx.NewPrefixBucket(prefix, wrapped) + require.NoError(t, err) + ctx := context.Background() + contents := "foobar" + name := prefix + suffix + // Create an object through the back door. + _, err = storageutil.CreateObject(ctx, wrapped, name, []byte(contents)) + assert.NoError(t, err) + + // Move it to a new name. + newSuffix := "burrito" + o, err := bucket.MoveObject( + ctx, + &gcs.MoveObjectRequest{ + SrcName: suffix, + DstName: newSuffix, + }) + + assert.NoError(t, err) + assert.Equal(t, newSuffix, o.Name) + + newName := prefix + newSuffix + // Read it through the back door. + actual, err := storageutil.ReadObject(ctx, wrapped, newName) + assert.NoError(t, err) + assert.Equal(t, contents, string(actual)) + + // Stat old object. + m, _, err := bucket.StatObject( + ctx, + &gcs.StatObjectRequest{ + Name: suffix, + }) + + assert.True(t, errors.As(err, ¬FoundErr)) + assert.Nil(t, m) +} diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index 80cb197fbb..919c3bed50 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -1008,8 +1008,63 @@ func (b *bucket) DeleteObject( } func (b *bucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { - // TODO: Implement it. - return nil, nil + // Check that the destination name is legal. + err := checkName(req.DstName) + if err != nil { + return nil, err + } + + // Does the source object exist? + srcIndex := b.objects.find(req.SrcName) + if srcIndex == len(b.objects) { + err = &gcs.NotFoundError{ + Err: fmt.Errorf("object %q not found", req.SrcName), + } + return nil, err + } + + // Does it have the correct generation? + if req.SrcGeneration != 0 && + b.objects[srcIndex].metadata.Generation != req.SrcGeneration { + err = &gcs.NotFoundError{ + Err: fmt.Errorf("object %s generation %d not found", req.SrcName, req.SrcGeneration), + } + return nil, err + } + + // Does it have the correct meta-generation? + if req.SrcMetaGenerationPrecondition != nil { + p := *req.SrcMetaGenerationPrecondition + if b.objects[srcIndex].metadata.MetaGeneration != p { + err = &gcs.PreconditionError{ + Err: fmt.Errorf("object %q has meta-generation %d", req.SrcName, b.objects[srcIndex].metadata.MetaGeneration), + } + return nil, err + } + } + + // Move it and assign a new generation number, to ensure that the generation + // number for the destination name is strictly increasing. + dst := b.objects[srcIndex] + dst.metadata.Name = req.DstName + dst.metadata.MediaLink = "http://localhost/download/storage/fake/" + req.DstName + + b.prevGeneration++ + dst.metadata.Generation = b.prevGeneration + + // Remove the source object. + b.objects = append(b.objects[:srcIndex], b.objects[srcIndex+1:]...) + // Insert dest object into our array. + existingIndex := b.objects.find(req.DstName) + if existingIndex < len(b.objects) { + b.objects[existingIndex] = dst + } else { + b.objects = append(b.objects, dst) + sort.Sort(b.objects) + } + + o := copyObject(&dst.metadata) + return o, err } func (b *bucket) DeleteFolder(ctx context.Context, folderName string) (err error) { diff --git a/internal/storage/gcs/request.go b/internal/storage/gcs/request.go index 7ba6c68913..f32bb2e1e6 100644 --- a/internal/storage/gcs/request.go +++ b/internal/storage/gcs/request.go @@ -391,5 +391,11 @@ type MoveObjectRequest struct { SrcName string // Source object name DstName string // Destination object name - // TODO: Add other necessary attributes as required + // The generation of the source object to move, or zero for the latest + // generation. + SrcGeneration int64 + + // If non-nil, the destination object will be created/overwritten only if the + // current meta-generation for the source object is equal to the given value. + SrcMetaGenerationPrecondition *int64 } From 88ff04c3169274659b0ff4b4b082c14b48f8544c Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Tue, 24 Dec 2024 07:34:53 +0000 Subject: [PATCH 0089/1298] Setting bwh to nil after all fileHandles are closed (#2823) * fixing conflicts * fixing nil assert # Conflicts: # internal/fs/inode/file_test.go * not nil fix * storing fileHandle open semantics * Added unit tests for fileInode * Registering and deregistering. * ADDED COMMENTS * fixing panic * fixing tests * PR comment --- internal/fs/fs.go | 15 ++++-- internal/fs/handle/file.go | 11 +++- internal/fs/inode/file.go | 40 ++++++++++++++ internal/fs/inode/file_test.go | 97 ++++++++++++++++++++++++++++++++-- 4 files changed, 154 insertions(+), 9 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 922bb0c65e..c1cafc1524 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1737,7 +1737,8 @@ func (fs *fileSystem) CreateFile( handleID := fs.nextHandleID fs.nextHandleID++ - fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle) + // Creating new file is always a write operation, hence passing readOnly as false. + fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, false) op.Handle = handleID fs.mu.Unlock() @@ -2372,16 +2373,24 @@ func (fs *fileSystem) OpenFile( ctx context.Context, op *fuseops.OpenFileOp) (err error) { fs.mu.Lock() - defer fs.mu.Unlock() // Find the inode. in := fs.fileInodeOrDie(op.Inode) + // Follow lock ordering rules to get inode lock. + // Inode lock is required to register fileHandle with the inode. + fs.mu.Unlock() + in.Lock() + defer in.Unlock() + + // Get the fs lock again. + fs.mu.Lock() + defer fs.mu.Unlock() // Allocate a handle. handleID := fs.nextHandleID fs.nextHandleID++ - fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle) + fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, op.OpenFlags.IsReadOnly()) op.Handle = handleID // When we observe object generations that we didn't create, we assign them diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 0d5c77962c..d2d417ce92 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -47,16 +47,23 @@ type FileHandle struct { // will be downloaded for random reads as well too. cacheFileForRangeRead bool metricHandle common.MetricHandle + // For now, we will consider the files which are open in append mode also as write, + // as we are not doing anything special for append. When required we will + // define an enum instead of boolean to hold the type of open. + readOnly bool } -func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle) (fh *FileHandle) { +// LOCKS_REQUIRED(fh.inode.mu) +func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle, readOnly bool) (fh *FileHandle) { fh = &FileHandle{ inode: inode, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, metricHandle: metricHandle, + readOnly: readOnly, } + fh.inode.RegisterFileHandle(fh.readOnly) fh.mu = syncutil.NewInvariantMutex(fh.checkInvariants) return @@ -65,6 +72,8 @@ func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, // Destroy any resources associated with the handle, which must not be used // again. func (fh *FileHandle) Destroy() { + // Deregister the fileHandle with the inode. + fh.inode.DeRegisterFileHandle(fh.readOnly) if fh.reader != nil { fh.reader.Destroy() } diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 9c60fbf4fd..78aa009c16 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -27,6 +27,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/jacobsa/fuse/fuseops" @@ -95,6 +96,15 @@ type FileInode struct { bwh *bufferedwrites.BufferedWriteHandler config *cfg.Config + + // Once write is started on the file i.e, bwh is initialized, any fileHandles + // opened in write mode before or after this and not yet closed are considered + // as writing to the file even though they are not writing. + // In case of successful flush, we will set bwh to nil. But in case of error, + // we will keep returning that error to all the fileHandles open during that time + // and set bwh to nil after all fileHandlers are closed. + // writeHandleCount tracks the count of open fileHandles in write mode. + writeHandleCount int32 } var _ Inode = &FileInode{} @@ -369,6 +379,31 @@ func (f *FileInode) DecrementLookupCount(n uint64) (destroy bool) { return } +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) RegisterFileHandle(readOnly bool) { + if !readOnly { + f.writeHandleCount++ + } +} + +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) DeRegisterFileHandle(readOnly bool) { + if readOnly { + return + } + + if f.writeHandleCount <= 0 { + logger.Errorf("Mismatch in number of write file handles for inode :%d", f.id) + } + + f.writeHandleCount-- + + // All write fileHandles associated with bwh are closed. So safe to set bwh to nil. + if f.writeHandleCount == 0 { + f.bwh = nil + } +} + // LOCKS_REQUIRED(f.mu) func (f *FileInode) Destroy() (err error) { f.destroyed = true @@ -802,6 +837,11 @@ func (f *FileInode) CreateBufferedOrTempWriter(ctx context.Context) (err error) } func (f *FileInode) ensureBufferedWriteHandler(ctx context.Context) error { + // bwh already initialized, do nothing. + if f.bwh != nil { + return nil + } + var err error var latestGcsObj *gcs.Object if !f.local { diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 3c7f69985c..0666f8846e 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -36,6 +36,7 @@ import ( "github.com/jacobsa/syncutil" "github.com/jacobsa/timeutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "golang.org/x/net/context" ) @@ -1311,7 +1312,7 @@ func (t *FileTest) TestMultipleWritesToLocalFileWhenStreamingWritesAreEnabled() assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) } -func (t *FileTest) WriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { +func (t *FileTest) TestWriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() t.in.config = &cfg.Config{Write: *getWriteConfig()} createTime := t.in.mtimeClock.Now() @@ -1326,11 +1327,11 @@ func (t *FileTest) WriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { // The inode should agree about the new mtime. attrs, err := t.in.Attributes(t.ctx) assert.Nil(t.T(), err) - assert.Equal(t.T(), int64(2), attrs.Size) + assert.Equal(t.T(), uint64(2), attrs.Size) assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) } -func (t *FileTest) SetMtimeOnEmptyGCSFileWhenStreamingWritesAreEnabled() { +func (t *FileTest) TestSetMtimeOnEmptyGCSFileWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() t.in.config = &cfg.Config{Write: *getWriteConfig()} assert.Nil(t.T(), t.in.bwh) @@ -1342,7 +1343,7 @@ func (t *FileTest) SetMtimeOnEmptyGCSFileWhenStreamingWritesAreEnabled() { assert.Nil(t.T(), t.in.bwh) } -func (t *FileTest) SetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEnabled() { +func (t *FileTest) TestSetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() t.in.config = &cfg.Config{Write: *getWriteConfig()} assert.Nil(t.T(), t.in.bwh) @@ -1351,7 +1352,7 @@ func (t *FileTest) SetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEnable assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) writeFileInfo := t.in.bwh.WriteFileInfo() - assert.Equal(t.T(), 2, writeFileInfo.TotalSize) + assert.Equal(t.T(), int64(2), writeFileInfo.TotalSize) // Set mtime. mtime := time.Now().UTC().Add(123 * time.Second) @@ -1366,6 +1367,92 @@ func (t *FileTest) SetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEnable assert.Equal(t.T(), attrs.Atime, mtime) } +func (t *FileTest) TestRegisterFileHandle() { + tbl := []struct { + name string + readonly bool + currentVal int32 + expectedVal int32 + }{ + { + name: "ReadOnlyHandle", + readonly: true, + currentVal: 0, + expectedVal: 0, + }, + { + name: "ZeroCurrentValueForWriteHandle", + readonly: false, + currentVal: 0, + expectedVal: 1, + }, + { + name: "NonZeroCurrentValueForWriteHandle", + readonly: false, + currentVal: 5, + expectedVal: 6, + }, + } + for _, tc := range tbl { + t.Run(tc.name, func() { + t.in.writeHandleCount = tc.currentVal + + t.in.RegisterFileHandle(tc.readonly) + + assert.Equal(t.T(), tc.expectedVal, t.in.writeHandleCount) + }) + } +} + +func (t *FileTest) TestDeRegisterFileHandle() { + tbl := []struct { + name string + readonly bool + currentVal int32 + expectedVal int32 + isBwhNil bool + }{ + { + name: "ReadOnlyHandle", + readonly: true, + currentVal: 10, + expectedVal: 10, + isBwhNil: false, + }, + { + name: "NonZeroCurrentValueForWriteHandle", + readonly: false, + currentVal: 10, + expectedVal: 9, + isBwhNil: false, + }, + { + name: "LastWriteHandleToDeregister", + readonly: false, + currentVal: 1, + expectedVal: 0, + isBwhNil: true, + }, + } + for _, tc := range tbl { + t.Run(tc.name, func() { + t.in.config = &cfg.Config{Write: *getWriteConfig()} + t.in.writeHandleCount = tc.currentVal + err := t.in.ensureBufferedWriteHandler(t.ctx) + require.NoError(t.T(), err) + + t.in.DeRegisterFileHandle(tc.readonly) + + assert.Equal(t.T(), tc.expectedVal, t.in.writeHandleCount) + if tc.isBwhNil { + assert.Nil(t.T(), t.in.bwh) + } else { + assert.NotNil(t.T(), t.in.bwh) + } + }) + } +} + func getWriteConfig() *cfg.WriteConfig { return &cfg.WriteConfig{ MaxBlocksPerFile: 10, From ee9749a61abdd58470bdd520af5d835d8939e8a4 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 24 Dec 2024 15:19:05 +0530 Subject: [PATCH 0090/1298] Upgrade go sdk module - v1.49.0 (#2830) * upgrade go sdk module * linux test fix --- go.mod | 58 +++++++++++++++++++++++++++---------------------------- go.sum | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 29 deletions(-) diff --git a/go.mod b/go.mod index beab278de6..4ded36f8e7 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/googlecloudplatform/gcsfuse/v2 go 1.23.4 require ( - cloud.google.com/go/compute/metadata v0.5.2 + cloud.google.com/go/compute/metadata v0.6.0 cloud.google.com/go/iam v1.3.0 cloud.google.com/go/secretmanager v1.14.2 - cloud.google.com/go/storage v1.48.0 + cloud.google.com/go/storage v1.49.0 contrib.go.opencensus.io/exporter/ocagent v0.7.0 contrib.go.opencensus.io/exporter/prometheus v0.4.2 contrib.go.opencensus.io/exporter/stackdriver v0.13.14 @@ -14,7 +14,7 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0 github.com/fsouza/fake-gcs-server v1.50.2 github.com/google/uuid v1.6.0 - github.com/googleapis/gax-go/v2 v2.14.0 + github.com/googleapis/gax-go/v2 v2.14.1 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec github.com/jacobsa/fuse v0.0.0-20240607092844-7285af0d05b0 github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd @@ -31,34 +31,34 @@ require ( github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 go.opencensus.io v0.24.0 - go.opentelemetry.io/contrib/detectors/gcp v1.32.0 - go.opentelemetry.io/otel v1.32.0 + go.opentelemetry.io/contrib/detectors/gcp v1.33.0 + go.opentelemetry.io/otel v1.33.0 go.opentelemetry.io/otel/exporters/prometheus v0.54.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 - go.opentelemetry.io/otel/sdk v1.32.0 - go.opentelemetry.io/otel/sdk/metric v1.32.0 - go.opentelemetry.io/otel/trace v1.32.0 - golang.org/x/net v0.32.0 + go.opentelemetry.io/otel/sdk v1.33.0 + go.opentelemetry.io/otel/sdk/metric v1.33.0 + go.opentelemetry.io/otel/trace v1.33.0 + golang.org/x/net v0.33.0 golang.org/x/oauth2 v0.24.0 golang.org/x/sync v0.10.0 golang.org/x/sys v0.28.0 golang.org/x/text v0.21.0 golang.org/x/time v0.8.0 - google.golang.org/api v0.210.0 - google.golang.org/grpc v1.67.2 - google.golang.org/protobuf v1.35.2 + google.golang.org/api v0.214.0 + google.golang.org/grpc v1.69.2 + google.golang.org/protobuf v1.36.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 ) require ( - cel.dev/expr v0.16.1 // indirect - cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.11.0 // indirect + cel.dev/expr v0.19.1 // indirect + cloud.google.com/go v0.117.0 // indirect + cloud.google.com/go/auth v0.13.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect - cloud.google.com/go/longrunning v0.6.2 // indirect - cloud.google.com/go/monitoring v1.21.2 // indirect - cloud.google.com/go/pubsub v1.45.1 // indirect + cloud.google.com/go/longrunning v0.6.3 // indirect + cloud.google.com/go/monitoring v1.22.0 // indirect + cloud.google.com/go/pubsub v1.45.3 // indirect cloud.google.com/go/trace v1.11.2 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect @@ -66,9 +66,9 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect + github.com/cncf/xds/go v0.0.0-20241213214725-57cfbe6fad57 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/envoyproxy/go-control-plane v0.13.0 // indirect + github.com/envoyproxy/go-control-plane v0.13.1 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -76,7 +76,7 @@ require ( github.com/go-logfmt/logfmt v0.5.1 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/s2a-go v0.1.8 // indirect @@ -99,7 +99,7 @@ require ( github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/prometheus v0.35.0 // indirect github.com/prometheus/statsd_exporter v0.22.7 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -107,16 +107,16 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect - go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect + go.opentelemetry.io/otel/metric v1.33.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 // indirect - google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect - google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect + google.golang.org/genproto v0.0.0-20241219192143-6b3ec007d9bb // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index c18a8b8d92..6051fe6cba 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,9 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxo bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cel.dev/expr v0.16.1 h1:NR0+oFYzR1CqLFhTAqg3ql59G9VfN8fKq1TCHJ6gq1g= cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= +cel.dev/expr v0.16.2/go.mod h1:gXngZQMkWJoSbE8mOzehJlXQyubn/Vg0vR9/F3W7iw8= +cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= +cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -33,8 +36,12 @@ cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2Z cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go v0.117.0 h1:Z5TNFfQxj7WG2FgOGX1ekC5RiXrYgms6QscOm32M/4s= +cloud.google.com/go v0.117.0/go.mod h1:ZbwhVTb1DBGt2Iwb3tNO6SEK4q+cplHZmLWH+DelYYc= cloud.google.com/go/auth v0.11.0 h1:Ic5SZz2lsvbYcWT5dfjNWgw6tTlGi2Wc8hyQSC9BstA= cloud.google.com/go/auth v0.11.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= +cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= +cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -46,8 +53,12 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7 cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.31.0 h1:NtkEQnSesZDeTM5Hq57CSeeRn1LkW/p+ffg9sxGIUbs= +cloud.google.com/go/compute v1.31.0/go.mod h1:4SCUCDAvOQvMGu4ze3YIJapnY0UQa5+WvJJeYFsQRoo= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= @@ -59,14 +70,20 @@ cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHi cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/longrunning v0.6.3 h1:A2q2vuyXysRcwzqDpMMLSI6mb6o39miS52UEG/Rd2ng= +cloud.google.com/go/longrunning v0.6.3/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/monitoring v1.22.0 h1:mQ0040B7dpuRq1+4YiQD43M2vW9HgoVxY98xhqGT+YI= +cloud.google.com/go/monitoring v1.22.0/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/pubsub v1.45.1 h1:ZC/UzYcrmK12THWn1P72z+Pnp2vu/zCZRXyhAfP1hJY= cloud.google.com/go/pubsub v1.45.1/go.mod h1:3bn7fTmzZFwaUjllitv1WlsNMkqBgGUb3UdMhI54eCc= +cloud.google.com/go/pubsub v1.45.3 h1:prYj8EEAAAwkp6WNoGTE4ahe0DgHoyJd5Pbop931zow= +cloud.google.com/go/pubsub v1.45.3/go.mod h1:cGyloK/hXC4at7smAtxFnXprKEFTqmMXNNd9w+bd94Q= cloud.google.com/go/secretmanager v1.14.2 h1:2XscWCfy//l/qF96YE18/oUaNJynAx749Jg3u0CjQr8= cloud.google.com/go/secretmanager v1.14.2/go.mod h1:Q18wAPMM6RXLC/zVpWTlqq2IBSbbm7pKBlM3lCKsmjw= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= @@ -76,6 +93,8 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.48.0 h1:FhBDHACbVtdPx7S/AbcKujPWiHvfO6F8OXGgCEbB2+o= cloud.google.com/go/storage v1.48.0/go.mod h1:aFoDYNMAjv67lp+xcuZqjUKv/ctmplzQ3wJgodA7b+M= +cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= contrib.go.opencensus.io/exporter/ocagent v0.7.0 h1:BEfdCTXfMV30tLZD8c9n64V/tIZX5+9sXiuFLnrr1k8= @@ -202,6 +221,7 @@ github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMr github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -233,6 +253,8 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20241213214725-57cfbe6fad57 h1:put7Je9ZyxbHtwr7IqGrW4LLVUupJQ2gbsDshKISSgU= +github.com/cncf/xds/go v0.0.0-20241213214725-57cfbe6fad57/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= @@ -410,6 +432,8 @@ github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go. github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les= github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= +github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= +github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= @@ -564,6 +588,8 @@ github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -661,6 +687,8 @@ github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0 github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= @@ -1058,6 +1086,7 @@ github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= @@ -1225,23 +1254,33 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8= go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU= +go.opentelemetry.io/contrib/detectors/gcp v1.33.0 h1:FVPoXEoILwgbZUu4X7YSgsESsAmGRgoYcnXkzgQPhP4= +go.opentelemetry.io/contrib/detectors/gcp v1.33.0/go.mod h1:ZHrLmr4ikK2AwRj9QL+c9s2SOlgoSRyMpNVzUj2fZqI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.31.0/go.mod h1:PFmBsWbldL1kiWZk9+0LBZz2brhByaGsvp6pRICMlPE= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.6.0/go.mod h1:bfJD2DZVw0LBxghOTlgnlI0CV3hLDu9XF/QKOUXMTQQ= go.opentelemetry.io/otel v1.6.1/go.mod h1:blzUabWHkX6LJewxvadmzafgh/wnvBSDBdOuwkAtrWQ= go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.1/go.mod h1:NEu79Xo32iVb+0gVNV8PMd7GoWqnyDXRlj04yFjqz40= @@ -1261,22 +1300,30 @@ go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9deb go.opentelemetry.io/otel/metric v0.28.0/go.mod h1:TrzsfQAmQaB1PDcdhBauLMk7nyyg9hm+GoQq/ekE9Iw= go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk v1.6.1/go.mod h1:IVYrddmFZ+eJqu2k38qD3WezFR2pymCzm8tdxyh3R4E= go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= +go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= +go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU= +go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/otel/trace v1.6.0/go.mod h1:qs7BrU5cZ8dXQHBGxHMOxwME/27YH2qEp4/+tZLLwJE= go.opentelemetry.io/otel/trace v1.6.1/go.mod h1:RkFRM1m0puWIq10oxImnGEduNBzxiN7TXluRBtE+5j0= go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= go.opentelemetry.io/proto/otlp v0.12.1/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= @@ -1433,6 +1480,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1746,6 +1795,8 @@ google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= google.golang.org/api v0.210.0 h1:HMNffZ57OoZCRYSbdWVRoqOa8V8NIHLL0CzdBPLztWk= google.golang.org/api v0.210.0/go.mod h1:B9XDZGnx2NtyjzVkOVTGrFSAVZgPcbedzKg/gTLwqBs= +google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA= +google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -1829,10 +1880,16 @@ google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2 google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto v0.0.0-20241219192143-6b3ec007d9bb h1:JGs+s1Q6osip3cDY197L1HmkuPn8wPp9Hfy9jl+Uz+U= +google.golang.org/genproto v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:o8GgNarfULyZPNaIY8RDfXM7AZcmcKC/tbMWp/ZOFDw= google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f h1:M65LEviCfuZTfrfzwwEoxVtgvfkFkBUbFnRbxCXuXhU= google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= +google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb h1:B7GIB7sr443wZ/EAEl7VZjmh1V6qzkt5V+RYcUYtS1U= +google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:E5//3O5ZIG2l71Xnt+P/CYUY8Bxs8E7WMoZ9tlcMbAY= google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb h1:3oy2tynMOP1QbTC0MsNNAV+Se8M2Bd0A5+x1QHyw+pI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -1869,6 +1926,8 @@ google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.67.2 h1:Lq11HW1nr5m4OYV+ZVy2BjOK78/zqnTx24vyDBP1JcQ= google.golang.org/grpc v1.67.2/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= +google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a h1:UIpYSuWdWHSzjwcAFRLjKcPXFZVVLXGEM23W+NWqipw= google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a/go.mod h1:9i1T9n4ZinTUZGgzENMi8MDDgbGC5mqTS75JAv6xN3A= @@ -1889,6 +1948,8 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= +google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 1732c7a98ab1bee0cca594e5e30e33a26abdde64 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:51:17 +0530 Subject: [PATCH 0091/1298] Fix: Handle gRPC precondition failures while creating object (#2838) * throwing gcs.precondition error in case rpc code 9 * indentations --- internal/storage/bucket_handle.go | 13 +++++++++++++ .../rename_dir_limit/rename_dir_limit_test.go | 1 + 2 files changed, 14 insertions(+) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 0b388a430e..206382ef99 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -37,6 +37,8 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "google.golang.org/api/googleapi" "google.golang.org/api/iterator" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) const FullFolderPathHNS = "projects/_/buckets/%s/folders/%s" @@ -236,6 +238,17 @@ func (bh *bucketHandle) CreateObject(ctx context.Context, req *gcs.CreateObjectR // We can't use defer to close the writer, because we need to close the // writer successfully before calling Attrs() method of writer. if err = wc.Close(); err != nil { + // This checks if the error returned from the RPC call is a gRPC status error. + // If it is, and the error code is `codes.FailedPrecondition`, it means the + // operation failed due to a precondition not being met.The generic gRPC error + // is converted into a more specific `gcs.PreconditionError`. This allows handling + // of precondition failures differently. + if rpcErr, ok := status.FromError(err); ok { + if code := rpcErr.Code(); code == codes.FailedPrecondition { + err = &gcs.PreconditionError{Err: err} + return + } + } var gErr *googleapi.Error if errors.As(err, &gErr) { if gErr.Code == http.StatusPreconditionFailed { diff --git a/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go b/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go index 3e53dcca49..c52cef26e6 100644 --- a/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go +++ b/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go @@ -62,6 +62,7 @@ func TestMain(m *testing.M) { defer storageClient.Close() flags := [][]string{{"--rename-dir-limit=3", "--implicit-dirs"}, {"--rename-dir-limit=3"}} + setup.AppendFlagsToAllFlagsInTheFlagsSet(&flags, "", "--client-protocol=grpc") if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(ctx, storageClient); err == nil { flags = [][]string{hnsFlagSet} } From e7acf94b798dd38de5474ee87aabbe03ccc0e47b Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 24 Dec 2024 19:18:54 +0530 Subject: [PATCH 0092/1298] [Move object] Fast stat bucket implementation (#2835) * fast stat bucket implementation * nits --- internal/storage/caching/fast_stat_bucket.go | 16 ++++++- .../storage/caching/fast_stat_bucket_test.go | 45 +++++++++++++++++++ 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index df21d3e389..1e37385647 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -373,8 +373,20 @@ func (b *fastStatBucket) DeleteObject( } func (b *fastStatBucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { - // TODO: Implement it. - return nil, nil + // Throw away any existing record for the source and destination name. + b.invalidate(req.SrcName) + b.invalidate(req.DstName) + + // Move the object. + o, err := b.wrapped.MoveObject(ctx, req) + if err != nil { + return nil, err + } + + // Record the new version. + b.insert(o) + + return o, nil } func (b *fastStatBucket) DeleteFolder(ctx context.Context, folderName string) error { diff --git a/internal/storage/caching/fast_stat_bucket_test.go b/internal/storage/caching/fast_stat_bucket_test.go index 6f0f057116..1239bbd15f 100644 --- a/internal/storage/caching/fast_stat_bucket_test.go +++ b/internal/storage/caching/fast_stat_bucket_test.go @@ -1058,3 +1058,48 @@ func (t *CreateFolderTest) TestCreateFolderWhenGCSCallFails() { AssertNe(nil, err) AssertEq(nil, result) } + +type MoveObjectTest struct { + fastStatBucketTest +} + +func init() { RegisterTestSuite(&MoveObjectTest{}) } + +func (t *MoveObjectTest) MoveObjectFails() { + const srcName = "taco" + const dstName = "burrito" + + // Erase + ExpectCall(t.cache, "Erase")(dstName) + ExpectCall(t.cache, "Erase")(srcName) + + // Wrapped + ExpectCall(t.wrapped, "MoveObject")(Any(), Any()).WillOnce(Return(nil, errors.New("taco"))) + + // Call + _, err := t.bucket.MoveObject(context.TODO(), &gcs.MoveObjectRequest{SrcName: srcName, DstName: dstName}) + + ExpectThat(err, Error(HasSubstr("taco"))) +} + +func (t *MoveObjectTest) MoveObjectSucceeds() { + const dstName = "burrito" + // Erase + ExpectCall(t.cache, "Erase")(Any()).Times(2) + + // Wrap object + obj := &gcs.Object{ + Name: dstName, + Generation: 1234, + } + ExpectCall(t.wrapped, "MoveObject")(Any(), Any()).WillOnce(Return(obj, nil)) + + // Insert in cache + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))) + + // Call + o, err := t.bucket.MoveObject(context.TODO(), &gcs.MoveObjectRequest{}) + + AssertEq(nil, err) + ExpectEq(obj, o) +} From 394623060f74e9880b7212640ae6e6fc069cd306 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 26 Dec 2024 14:51:41 +0530 Subject: [PATCH 0093/1298] Support Unlink operation when streaming writes in progress (#2810) * unlink changes * fix test * set bwh to nil * rebase fixes * remove unnecessary comment * review comments * test fix * review comments * lint fix * review comments * common test should not run on their own * combine test --- .../bufferedwrites/buffered_write_handler.go | 9 ++ .../buffered_write_handler_test.go | 26 ++++ internal/bufferedwrites/upload_handler.go | 16 ++- .../bufferedwrites/upload_handler_test.go | 9 ++ internal/fs/fs.go | 41 ++++--- internal/fs/inode/file.go | 9 ++ .../fs/inode/file_streaming_writes_test.go | 41 +++++++ internal/fs/inode/file_test.go | 16 +++ internal/fs/inode/inode.go | 3 + internal/fs/inode/symlink.go | 3 + internal/fs/streaming_writes_common_test.go | 57 +++++++++ .../streaming_writes_empty_gcs_object_test.go | 74 ++++++++++++ .../fs/streaming_writes_local_file_test.go | 114 ++++++++++++++++++ .../util/operations/validation_helper.go | 1 + 14 files changed, 404 insertions(+), 15 deletions(-) create mode 100644 internal/fs/streaming_writes_common_test.go create mode 100644 internal/fs/streaming_writes_empty_gcs_object_test.go create mode 100644 internal/fs/streaming_writes_local_file_test.go diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index e7bb7a85c6..6ad09ba515 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -250,3 +250,12 @@ func (wh *BufferedWriteHandler) writeDataForTruncatedSize() error { return nil } + +func (wh *BufferedWriteHandler) Unlink() { + wh.uploadHandler.CancelUpload() + err := wh.blockPool.ClearFreeBlockChannel() + if err != nil { + // Only logging an error in case of resource leak. + logger.Errorf("blockPool.ClearFreeBlockChannel() failed: %v", err) + } +} diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 2498113dd4..15fe9e9707 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -347,3 +347,29 @@ func (testSuite *BufferedWriteTest) TestWriteFileInfoWithTruncatedLengthGreaterT assert.Equal(testSuite.T(), testSuite.bwh.truncatedSize, fileInfo.TotalSize) } + +func (testSuite *BufferedWriteTest) TestUnlinkBeforeWrite() { + testSuite.bwh.Unlink() + + assert.Nil(testSuite.T(), testSuite.bwh.uploadHandler.cancelFunc) + assert.Equal(testSuite.T(), 0, len(testSuite.bwh.uploadHandler.uploadCh)) + assert.Equal(testSuite.T(), 0, len(testSuite.bwh.blockPool.FreeBlocksChannel())) +} + +func (testSuite *BufferedWriteTest) TestUnlinkAfterWrite() { + buffer, err := operations.GenerateRandomData(blockSize) + assert.NoError(testSuite.T(), err) + // Write 5 blocks. + for i := 0; i < 5; i++ { + err = testSuite.bwh.Write(buffer, int64(blockSize*i)) + require.Nil(testSuite.T(), err) + } + cancelCalled := false + testSuite.bwh.uploadHandler.cancelFunc = func() { cancelCalled = true } + + testSuite.bwh.Unlink() + + assert.True(testSuite.T(), cancelCalled) + assert.Equal(testSuite.T(), 0, len(testSuite.bwh.uploadHandler.uploadCh)) + assert.Equal(testSuite.T(), 0, len(testSuite.bwh.blockPool.FreeBlocksChannel())) +} diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 651ba59e32..b9cd3fdb33 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -44,6 +44,9 @@ type UploadHandler struct { // inode. This signals permanent failure in the buffered write job. signalUploadFailure chan error + // CancelFunc persisted to cancel the uploads in case of unlink operation. + cancelFunc context.CancelFunc + // Parameters required for creating a new GCS chunk writer. bucket gcs.Bucket objectName string @@ -103,7 +106,9 @@ func (uh *UploadHandler) createObjectWriter() (err error) { req := gcs.NewCreateObjectRequest(uh.obj, uh.objectName, nil, uh.chunkTransferTimeout) // We need a new context here, since the first writeFile() call will be complete // (and context will be cancelled) by the time complete upload is done. - uh.writer, err = uh.bucket.CreateObjectChunkWriter(context.Background(), req, int(uh.blockSize), nil) + var ctx context.Context + ctx, uh.cancelFunc = context.WithCancel(context.Background()) + uh.writer, err = uh.bucket.CreateObjectChunkWriter(ctx, req, int(uh.blockSize), nil) return } @@ -147,6 +152,15 @@ func (uh *UploadHandler) Finalize() (*gcs.MinObject, error) { return obj, nil } +func (uh *UploadHandler) CancelUpload() { + if uh.cancelFunc != nil { + // cancel the context to cancel the ongoing GCS upload. + uh.cancelFunc() + } + // Wait for all in progress buffers to be added to the free channel. + uh.wg.Wait() +} + func (uh *UploadHandler) SignalUploadFailure() chan error { return uh.signalUploadFailure } diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 3f28e4d382..9908a4ebf6 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -279,6 +279,15 @@ func (t *UploadHandlerTest) TestMultipleBlockAwaitBlocksUpload() { assertAllBlocksProcessed(t.T(), t.uh) } +func (t *UploadHandlerTest) TestUploadHandlerCancelUpload() { + cancelCalled := false + t.uh.cancelFunc = func() { cancelCalled = true } + + t.uh.CancelUpload() + + assert.True(t.T(), cancelCalled) +} + func (t *UploadHandlerTest) TestCreateObjectChunkWriterIsCalledWithCorrectRequestParametersForEmptyGCSObject() { t.uh.obj = &gcs.Object{ Name: t.uh.objectName, diff --git a/internal/fs/fs.go b/internal/fs/fs.go index c1cafc1524..243e30cc02 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2249,30 +2249,43 @@ func (fs *fileSystem) Unlink( ctx, cancel = util.IsolateContextFromParentContext(ctx) defer cancel() } - // Find the parent. + fs.mu.Lock() + + // Find the parent and file name. parent := fs.dirInodeOrDie(op.Parent) + fileName := inode.NewFileName(parent.Name(), op.Name) + + // Get the inode for the given file. + // Files must have an associated inode, which can be found in either: + // - localFileInodes: For files created locally. + // - generationBackedInodes: For files backed by an object. + // We are not checking implicitDirInodes or folderInodes because + // the unlink operation is only applicable to files. + in, isLocalFile := fs.localFileInodes[fileName] + if !isLocalFile { + in = fs.generationBackedInodes[fileName] + } + fs.mu.Unlock() - // if inode is a local file, mark it unlinked. - fileName := inode.NewFileName(parent.Name(), op.Name) - fs.mu.Lock() - fileInode, ok := fs.localFileInodes[fileName] - if ok { - file := fs.fileInodeOrDie(fileInode.ID()) - fs.mu.Unlock() - file.Lock() - defer file.Unlock() - file.Unlink() + if in != nil { + // Perform the unlink operation on the inode. + in.Lock() + in.Unlink() + in.Unlock() + } + + // If the inode represents a local file, we don't need to delete + // the backing object on GCS, so return early. + if isLocalFile { return } - fs.mu.Unlock() - // else delete the backing object present on GCS. + // Delete the backing object present on GCS. parent.Lock() defer parent.Unlock() - // Delete the backing object. err = parent.DeleteChildFile( ctx, op.Name, diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 78aa009c16..e3f7632c3c 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -335,6 +335,10 @@ func (f *FileInode) IsUnlinked() bool { func (f *FileInode) Unlink() { f.unlinked = true + + if f.bwh != nil { + f.bwh.Unlink() + } } // Source returns a record for the GCS object from which this inode is branched. The @@ -620,6 +624,11 @@ func (f *FileInode) flushUsingBufferedWriteHandler() error { func (f *FileInode) SetMtime( ctx context.Context, mtime time.Time) (err error) { + if f.IsUnlinked() { + // No need to update mtime on GCS for unlinked file. + return + } + // When bufferedWritesHandler instance is not nil, set time on bwh. // It will not be nil in 2 cases when bufferedWrites are enabled: // 1. local files diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 27bc9eaf15..be0dd92b91 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -26,6 +26,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/syncutil" "github.com/jacobsa/timeutil" @@ -208,3 +209,43 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesOnClobberedFileThrowsError assert.Nil(t.T(), err) assert.Equal(t.T(), storageutil.ConvertObjToMinObject(objWritten), objGot) } + +func (t *FileStreamingWritesTest) TestUnlinkLocalFileBeforeWrite() { + assert.True(t.T(), t.in.IsLocal()) + + // Unlink. + t.in.Unlink() + + assert.True(t.T(), t.in.unlinked) + // Data shouldn't be updated to GCS. + operations.ValidateObjectNotFoundErr(t.ctx, t.T(), t.bucket, t.in.Name().GcsObjectName()) +} + +func (t *FileStreamingWritesTest) TestUnlinkLocalFileAfterWrite() { + assert.True(t.T(), t.in.IsLocal()) + // Write some content. + err := t.in.Write(t.ctx, []byte("tacos"), 0) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.bwh) + + // Unlink. + t.in.Unlink() + + assert.True(t.T(), t.in.IsUnlinked()) + // Data shouldn't be updated to GCS. + operations.ValidateObjectNotFoundErr(t.ctx, t.T(), t.bucket, t.in.Name().GcsObjectName()) +} + +func (t *FileStreamingWritesTest) TestUnlinkEmptySyncedFile() { + t.createInode(fileName, emptyGCSFile) + assert.False(t.T(), t.in.IsLocal()) + // Write some content to temp file. + err := t.in.Write(t.ctx, []byte("tacos"), 0) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.bwh) + + // Unlink. + t.in.Unlink() + + assert.True(t.T(), t.in.unlinked) +} diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 0666f8846e..dccb6db4c2 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -1049,6 +1049,22 @@ func (t *FileTest) TestSetMtime_SourceObjectMetaGenerationChanged() { assert.Equal(t.T(), newObj.MetaGeneration, m.MetaGeneration) } +func (t *FileTest) TestSetMtimeForUnlinkedFileIsNoOp() { + t.in.unlinked = true + beforeUpdateAttr, err := t.in.Attributes(t.ctx) + require.Nil(t.T(), err) + mtime := beforeUpdateAttr.Mtime.UTC().Add(123 * time.Second) + + // Set mtime. + err = t.in.SetMtime(t.ctx, mtime) + + require.Nil(t.T(), err) + afterUpdateAttr, err := t.in.Attributes(t.ctx) + require.Nil(t.T(), err) + assert.NotEqual(t.T(), mtime, afterUpdateAttr.Mtime) + assert.Equal(t.T(), beforeUpdateAttr.Mtime, afterUpdateAttr.Mtime) +} + func (t *FileTest) TestTestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() { var err error var attrs fuseops.InodeAttributes diff --git a/internal/fs/inode/inode.go b/internal/fs/inode/inode.go index db5bcd6320..b9131645b7 100644 --- a/internal/fs/inode/inode.go +++ b/internal/fs/inode/inode.go @@ -55,6 +55,9 @@ type Inode interface { // // This method may block. Errors are for logging purposes only. Destroy() (err error) + + // Unlink operation marks the inode as unlinked/deleted. + Unlink() } // An inode owned by a gcs bucket. diff --git a/internal/fs/inode/symlink.go b/internal/fs/inode/symlink.go index eaa3a1667d..8a271e976d 100644 --- a/internal/fs/inode/symlink.go +++ b/internal/fs/inode/symlink.go @@ -149,3 +149,6 @@ func (s *SymlinkInode) Target() (target string) { target = s.target return } + +func (s *SymlinkInode) Unlink() { +} diff --git a/internal/fs/streaming_writes_common_test.go b/internal/fs/streaming_writes_common_test.go new file mode 100644 index 0000000000..bec2d460a9 --- /dev/null +++ b/internal/fs/streaming_writes_common_test.go @@ -0,0 +1,57 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Streaming write tests which are common for both local file and synced empty +// object. + +package fs_test + +import ( + "os" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type StreamingWritesCommonTest struct { + suite.Suite + fsTest +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *StreamingWritesCommonTest) TestUnlinkBeforeWrite() { + // unlink the file. + err := os.Remove(t.f1.Name()) + assert.NoError(t.T(), err) + + // Stat the file and validate file is deleted. + operations.ValidateNoFileOrDirError(t.T(), t.f1.Name()) + // Close the file and validate that file is deleted from GCS. + err = t.f1.Close() + assert.NoError(nil, err) + t.f1 = nil + operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, fileName) +} + +func (t *StreamingWritesCommonTest) TestUnlinkAfterWrite() { + // Write content to file. + _, err := t.f1.Write([]byte("tacos")) + assert.NoError(t.T(), err) + + t.TestUnlinkBeforeWrite() +} diff --git a/internal/fs/streaming_writes_empty_gcs_object_test.go b/internal/fs/streaming_writes_empty_gcs_object_test.go new file mode 100644 index 0000000000..82212ae882 --- /dev/null +++ b/internal/fs/streaming_writes_empty_gcs_object_test.go @@ -0,0 +1,74 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Streaming write tests for synced empty object. + +package fs_test + +import ( + "os" + "path" + "syscall" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +type StreamingWritesEmptyGCSObjectTest struct { + StreamingWritesCommonTest +} + +func (t *StreamingWritesEmptyGCSObjectTest) SetupSuite() { + t.serverCfg.NewConfig = &cfg.Config{ + Write: cfg.WriteConfig{ + BlockSizeMb: 10, + CreateEmptyFile: false, + ExperimentalEnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: 10, + }, + } + t.fsTest.SetUpTestSuite() +} + +func (t *StreamingWritesEmptyGCSObjectTest) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} +func (t *StreamingWritesEmptyGCSObjectTest) SetupTest() { + // Create an object on bucket. + _, err := storageutil.CreateObject( + ctx, + bucket, + fileName, + []byte("bar")) + assert.Equal(t.T(), nil, err) + // Open file handle to read or write. + t.f1, err = os.OpenFile(path.Join(mntDir, fileName), os.O_RDWR|syscall.O_DIRECT, filePerms) + assert.Equal(t.T(), nil, err) + + // Validate that file exists on GCS. + _, err = storageutil.ReadObject(ctx, bucket, fileName) + assert.NoError(t.T(), err) +} + +func (t *StreamingWritesEmptyGCSObjectTest) TearDownTest() { + t.fsTest.TearDown() +} + +func TestStreamingWritesEmptyObjectTest(t *testing.T) { + suite.Run(t, new(StreamingWritesEmptyGCSObjectTest)) +} diff --git a/internal/fs/streaming_writes_local_file_test.go b/internal/fs/streaming_writes_local_file_test.go new file mode 100644 index 0000000000..d017d80d4e --- /dev/null +++ b/internal/fs/streaming_writes_local_file_test.go @@ -0,0 +1,114 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Streaming write tests for local file. + +package fs_test + +import ( + "os" + "path" + "syscall" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +const ( + fileName = "foo" +) + +type StreamingWritesLocalFileTest struct { + StreamingWritesCommonTest +} + +func (t *StreamingWritesLocalFileTest) SetupSuite() { + t.serverCfg.NewConfig = &cfg.Config{ + Write: cfg.WriteConfig{ + BlockSizeMb: 10, + CreateEmptyFile: false, + ExperimentalEnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: 10, + }, + MetadataCache: cfg.MetadataCacheConfig{TtlSecs: 0}, + } + t.fsTest.SetUpTestSuite() +} + +func (t *StreamingWritesLocalFileTest) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} +func (t *StreamingWritesLocalFileTest) SetupTest() { + // CreateLocalFile creates a local file and validates that object does not + // exist on GCS. + _, t.f1 = operations.CreateLocalFile(ctx, t.T(), mntDir, bucket, fileName) +} + +func (t *StreamingWritesLocalFileTest) TearDownTest() { + t.fsTest.TearDown() +} + +func TestStreamingWritesLocalFileTestSuite(t *testing.T) { + suite.Run(t, new(StreamingWritesLocalFileTest)) +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *StreamingWritesLocalFileTest) TestRemoveDirectoryContainingLocalAndEmptyObject() { + // Create explicit directory with one synced and one local file. + explicitDirName := "explicit" + emptyFileName := "emptyFile" + nonEmptyFileName := "nonEmptyFile" + assert.Equal(t.T(), + nil, + t.createObjects( + map[string]string{ + // File + explicitDirName + "/": "", + path.Join(explicitDirName, emptyFileName): "", + path.Join(explicitDirName, nonEmptyFileName): "taco", + })) + // Write content to local and empty gcs file. + _, f1 := operations.CreateLocalFile(ctx, t.T(), mntDir, bucket, path.Join(explicitDirName, fileName)) + _, err := f1.WriteString(FileContents) + assert.NoError(t.T(), err) + f2, err := os.OpenFile(path.Join(mntDir, explicitDirName, emptyFileName), os.O_RDWR|syscall.O_DIRECT, filePerms) + assert.Equal(t.T(), nil, err) + _, err = f2.WriteString(FileContents) + assert.NoError(t.T(), err) + + // Attempt to remove explicit directory. + err = os.RemoveAll(path.Join(mntDir, explicitDirName)) + + // Verify rmDir operation succeeds. + assert.NoError(t.T(), err) + operations.ValidateNoFileOrDirError(t.T(), path.Join(explicitDirName, emptyFileName)) + operations.ValidateNoFileOrDirError(t.T(), path.Join(explicitDirName, nonEmptyFileName)) + operations.ValidateNoFileOrDirError(t.T(), path.Join(explicitDirName, fileName)) + operations.ValidateNoFileOrDirError(t.T(), explicitDirName) + err = operations.CloseLocalFile(t.T(), &f1) + assert.NoError(t.T(), err) + err = f2.Close() + assert.NoError(t.T(), err) + operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, path.Join(explicitDirName, emptyFileName)) + operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, path.Join(explicitDirName, nonEmptyFileName)) + operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, path.Join(explicitDirName, fileName)) + operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, explicitDirName) +} diff --git a/tools/integration_tests/util/operations/validation_helper.go b/tools/integration_tests/util/operations/validation_helper.go index 61c58299d1..f0166af127 100644 --- a/tools/integration_tests/util/operations/validation_helper.go +++ b/tools/integration_tests/util/operations/validation_helper.go @@ -40,6 +40,7 @@ func ValidateObjectNotFoundErr(ctx context.Context, t *testing.T, bucket gcs.Buc var notFoundErr *gcs.NotFoundError _, err := storageutil.ReadObject(ctx, bucket, fileName) + assert.Error(t, err) assert.True(t, errors.As(err, ¬FoundErr)) } From a0dc44ecddeaba3fdb465f5e3e756bb3364b7c81 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Fri, 27 Dec 2024 08:56:41 +0000 Subject: [PATCH 0094/1298] Added destroy method to free up the memory buffers (#2839) * added destroy method * added destroy method * fixes * fixes * fixing the lock and closing the channel * Looping over uploadCh and closing it. * removed unused method * fixing comment. * Addressed review comments --- .../bufferedwrites/buffered_write_handler.go | 12 +++ .../buffered_write_handler_test.go | 14 +++- internal/bufferedwrites/upload_handler.go | 20 +++++ .../bufferedwrites/upload_handler_test.go | 80 ++++++++++++++----- internal/fs/fs.go | 14 ++-- internal/fs/handle/file.go | 5 ++ internal/fs/inode/file.go | 6 +- 7 files changed, 124 insertions(+), 27 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 6ad09ba515..28a9b8e851 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -158,7 +158,13 @@ func (wh *BufferedWriteHandler) appendBuffer(data []byte) (err error) { // Sync uploads all the pending full buffers to GCS. func (wh *BufferedWriteHandler) Sync() (err error) { + // Upload all the pending buffers and release the buffers. wh.uploadHandler.AwaitBlocksUpload() + err = wh.blockPool.ClearFreeBlockChannel() + if err != nil { + // Only logging an error in case of resource leak as upload succeeded. + logger.Errorf("blockPool.ClearFreeBlockChannel() failed during sync: %v", err) + } select { case <-wh.uploadHandler.SignalUploadFailure(): @@ -229,6 +235,12 @@ func (wh *BufferedWriteHandler) WriteFileInfo() WriteFileInfo { } } +func (wh *BufferedWriteHandler) Destroy() error { + // Destroy the upload handler and then free up the buffers. + wh.uploadHandler.Destroy() + return wh.blockPool.ClearFreeBlockChannel() +} + func (wh *BufferedWriteHandler) writeDataForTruncatedSize() error { // If totalSize is greater than truncatedSize, that means user has // written more data than they actually truncated in the beginning. diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 15fe9e9707..996eef99be 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -272,7 +272,7 @@ func (testSuite *BufferedWriteTest) TestSync5InProgressBlocks() { assert.NoError(testSuite.T(), err) assert.Equal(testSuite.T(), 0, len(testSuite.bwh.uploadHandler.uploadCh)) - assert.Equal(testSuite.T(), 5, len(testSuite.bwh.blockPool.FreeBlocksChannel())) + assert.Equal(testSuite.T(), 0, len(testSuite.bwh.blockPool.FreeBlocksChannel())) } func (testSuite *BufferedWriteTest) TestSyncBlocksWithError() { @@ -347,6 +347,18 @@ func (testSuite *BufferedWriteTest) TestWriteFileInfoWithTruncatedLengthGreaterT assert.Equal(testSuite.T(), testSuite.bwh.truncatedSize, fileInfo.TotalSize) } +func (testSuite *BufferedWriteTest) TestDestroyShouldClearFreeBlockChannel() { + // Try to write 4 blocks of data. + contents := strings.Repeat("A", blockSize*4) + err := testSuite.bwh.Write([]byte(contents), 0) + require.Nil(testSuite.T(), err) + + err = testSuite.bwh.Destroy() + + require.Nil(testSuite.T(), err) + assert.Equal(testSuite.T(), 0, len(testSuite.bwh.blockPool.FreeBlocksChannel())) + assert.Equal(testSuite.T(), 0, len(testSuite.bwh.uploadHandler.uploadCh)) +} func (testSuite *BufferedWriteTest) TestUnlinkBeforeWrite() { testSuite.bwh.Unlink() diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index b9cd3fdb33..8a72faa27d 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -168,3 +168,23 @@ func (uh *UploadHandler) SignalUploadFailure() chan error { func (uh *UploadHandler) AwaitBlocksUpload() { uh.wg.Wait() } + +func (uh *UploadHandler) Destroy() { + // Move all pending blocks to freeBlockCh and close the channel if not done. + for { + select { + case currBlock, ok := <-uh.uploadCh: + // Not ok means channel closed. Return. + if !ok { + return + } + uh.freeBlocksCh <- currBlock + // Marking as wg.Done to ensure any waiters are unblocked. + uh.wg.Done() + default: + // This will get executed when there are no blocks pending in uploadCh and its not closed. + close(uh.uploadCh) + return + } + } +} diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 9908a4ebf6..55c9b70f18 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -63,14 +63,11 @@ func (t *UploadHandlerTest) SetupTest() { }) } +func (t *UploadHandlerTest) SetupSubTest() { + t.SetupTest() +} + func (t *UploadHandlerTest) TestMultipleBlockUpload() { - // Create some blocks. - var blocks []block.Block - for i := 0; i < 5; i++ { - b, err := t.blockPool.Get() - require.NoError(t.T(), err) - blocks = append(blocks, b) - } // CreateObjectChunkWriter -- should be called once. writer := &storagemock.Writer{} mockObj := &gcs.MinObject{} @@ -78,6 +75,7 @@ func (t *UploadHandlerTest) TestMultipleBlockUpload() { t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, nil) // Upload the blocks. + blocks := t.createBlocks(5) for _, b := range blocks { err := t.uh.Upload(b) require.NoError(t.T(), err) @@ -189,13 +187,10 @@ func (t *UploadHandlerTest) TestUploadSingleBlockThrowsErrorInCopy() { func (t *UploadHandlerTest) TestUploadMultipleBlocksThrowsErrorInCopy() { // Create some blocks. - var blocks []block.Block + blocks := t.createBlocks(4) for i := 0; i < 4; i++ { - b, err := t.blockPool.Get() + err := blocks[i].Write([]byte("testdata" + strconv.Itoa(i) + " ")) require.NoError(t.T(), err) - err = b.Write([]byte("testdata" + strconv.Itoa(i) + " ")) - require.NoError(t.T(), err) - blocks = append(blocks, b) } // CreateObjectChunkWriter -- should be called once. writer := &storagemock.Writer{} @@ -255,18 +250,11 @@ func TestSignalUploadFailure(t *testing.T) { } func (t *UploadHandlerTest) TestMultipleBlockAwaitBlocksUpload() { - // Create some blocks. - var blocks []block.Block - for i := 0; i < 5; i++ { - b, err := t.blockPool.Get() - require.NoError(t.T(), err) - blocks = append(blocks, b) - } // CreateObjectChunkWriter -- should be called once. writer := &storagemock.Writer{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) // Upload the blocks. - for _, b := range blocks { + for _, b := range t.createBlocks(5) { err := t.uh.Upload(b) require.NoError(t.T(), err) } @@ -349,3 +337,55 @@ func (t *UploadHandlerTest) TestCreateObjectChunkWriterIsCalledWithCorrectReques err = t.uh.Upload(b) require.NoError(t.T(), err) } + +func (t *UploadHandlerTest) TestDestroy() { + testCases := []struct { + name string + uploadChClosed bool + }{ + { + name: "UploadChNotClosed", + uploadChClosed: false, + }, + { + name: "UploadChClosed", + uploadChClosed: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + // Add blocks to uploadCh. + for _, b := range t.createBlocks(5) { + t.uh.uploadCh <- b + t.uh.wg.Add(1) + } + if tc.uploadChClosed { + close(t.uh.uploadCh) + } + + t.uh.Destroy() + + assertAllBlocksProcessed(t.T(), t.uh) + assert.Equal(t.T(), 5, len(t.uh.freeBlocksCh)) + assert.Equal(t.T(), 0, len(t.uh.uploadCh)) + // Check if uploadCh is closed. + select { + case <-t.uh.uploadCh: + default: + assert.Fail(t.T(), "uploadCh not closed") + } + }) + } +} + +func (t *UploadHandlerTest) createBlocks(count int) []block.Block { + var blocks []block.Block + for i := 0; i < count; i++ { + b, err := t.blockPool.Get() + require.NoError(t.T(), err) + blocks = append(blocks, b) + } + + return blocks +} diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 243e30cc02..b2c4a7c5b0 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2558,13 +2558,17 @@ func (fs *fileSystem) ReleaseFileHandle( ctx context.Context, op *fuseops.ReleaseFileHandleOp) (err error) { fs.mu.Lock() - defer fs.mu.Unlock() - - // Destroy the handle. - fs.handles[op.Handle].(*handle.FileHandle).Destroy() - // Update the map. + fileHandle := fs.handles[op.Handle].(*handle.FileHandle) + // Update the map. We are okay updating the map before destroy is called + // since destroy is doing only internal cleanup. delete(fs.handles, op.Handle) + fs.mu.Unlock() + + // Destroy the handle. + fileHandle.Lock() + defer fileHandle.Unlock() + fileHandle.Destroy() return } diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index d2d417ce92..88be496cf2 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -71,9 +71,14 @@ func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, // Destroy any resources associated with the handle, which must not be used // again. +// LOCKS_REQUIRED(fh.mu) +// LOCK_FUNCTION(fh.inode.mu) +// UNLOCK_FUNCTION(fh.inode.mu) func (fh *FileHandle) Destroy() { // Deregister the fileHandle with the inode. + fh.inode.Lock() fh.inode.DeRegisterFileHandle(fh.readOnly) + fh.inode.Unlock() if fh.reader != nil { fh.reader.Destroy() } diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index e3f7632c3c..1b415b74ba 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -403,7 +403,11 @@ func (f *FileInode) DeRegisterFileHandle(readOnly bool) { f.writeHandleCount-- // All write fileHandles associated with bwh are closed. So safe to set bwh to nil. - if f.writeHandleCount == 0 { + if f.writeHandleCount == 0 && f.bwh != nil { + err := f.bwh.Destroy() + if err != nil { + logger.Warnf("Error while destroying the bufferedWritesHandler: %v", err) + } f.bwh = nil } } From 92566ec3e2887bf9b361d877f1173257fc2da9d1 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 27 Dec 2024 15:28:31 +0530 Subject: [PATCH 0095/1298] [Move Object] Add rename file method in dir layer (#2841) * add rename file method in dir layer * nits * nits * nits * remove one assert * review comment * review comment * small fix --- internal/fs/inode/base_dir.go | 5 ++ internal/fs/inode/dir.go | 21 ++++++++ internal/fs/inode/hns_dir_test.go | 54 ++++++++++++++++++++ internal/storage/mock/testify_mock_bucket.go | 5 +- 4 files changed, 84 insertions(+), 1 deletion(-) diff --git a/internal/fs/inode/base_dir.go b/internal/fs/inode/base_dir.go index 00dcf22977..9bd89d52c3 100644 --- a/internal/fs/inode/base_dir.go +++ b/internal/fs/inode/base_dir.go @@ -256,6 +256,11 @@ func (d *baseDirInode) ShouldInvalidateKernelListCache(ttl time.Duration) bool { // List operation is not supported for baseDirInode. func (d *baseDirInode) InvalidateKernelListCache() {} +func (d *baseDirInode) RenameFile(ctx context.Context, fileToRename *gcs.MinObject, destinationFileName string) (*gcs.Object, error) { + err := fuse.ENOSYS + return nil, err +} + func (d *baseDirInode) RenameFolder(ctx context.Context, folderName string, destinationFolderId string) (op *gcs.Folder, err error) { err = fuse.ENOSYS return diff --git a/internal/fs/inode/dir.go b/internal/fs/inode/dir.go index cdffed15a4..6d4641ee59 100644 --- a/internal/fs/inode/dir.go +++ b/internal/fs/inode/dir.go @@ -65,6 +65,9 @@ type DirInode interface { // true. LookUpChild(ctx context.Context, name string) (*Core, error) + // Rename the file. + RenameFile(ctx context.Context, fileToRename *gcs.MinObject, destinationFileName string) (*gcs.Object, error) + // Rename the directiory/folder. RenameFolder(ctx context.Context, folderName string, destinationFolderId string) (*gcs.Folder, error) @@ -1038,6 +1041,24 @@ func (d *dirInode) ShouldInvalidateKernelListCache(ttl time.Duration) bool { return cachedDuration >= ttl } +// LOCKS_REQUIRED(d) +// LOCKS_REQUIRED(parent of destinationFileName) +func (d *dirInode) RenameFile(ctx context.Context, fileToRename *gcs.MinObject, destinationFileName string) (*gcs.Object, error) { + req := &gcs.MoveObjectRequest{ + SrcName: fileToRename.Name, + DstName: destinationFileName, + SrcGeneration: fileToRename.Generation, + SrcMetaGenerationPrecondition: &fileToRename.MetaGeneration, + } + + o, err := d.bucket.MoveObject(ctx, req) + + // Invalidate the cache entry for the old object name. + d.cache.Erase(fileToRename.Name) + + return o, err +} + func (d *dirInode) RenameFolder(ctx context.Context, folderName string, destinationFolderName string) (*gcs.Folder, error) { folder, err := d.bucket.RenameFolder(ctx, folderName, destinationFolderName) if err != nil { diff --git a/internal/fs/inode/hns_dir_test.go b/internal/fs/inode/hns_dir_test.go index ec46c3202d..c613b8df5e 100644 --- a/internal/fs/inode/hns_dir_test.go +++ b/internal/fs/inode/hns_dir_test.go @@ -320,6 +320,59 @@ func (t *HNSDirTest) TestRenameFolderWithNonExistentSourceFolder() { assert.Nil(t.T(), f) } +func (t *HNSDirTest) TestRenameFileWithGivenName() { + const ( + fileName = "qux" + renameFileName = "rename" + ) + oldObjName := path.Join(dirInodeName, fileName) + newObjName := path.Join(dirInodeName, renameFileName) + var metaGeneration int64 = 0 + moveObjectReq := gcs.MoveObjectRequest{ + SrcName: oldObjName, + DstName: newObjName, + SrcGeneration: 0, + SrcMetaGenerationPrecondition: &metaGeneration, + } + oldObj := gcs.MinObject{Name: oldObjName} + newObj := gcs.Object{Name: newObjName} + t.mockBucket.On("MoveObject", t.ctx, &moveObjectReq).Return(&newObj, nil) + + // Attempt to rename the file. + f, err := t.in.RenameFile(t.ctx, &oldObj, path.Join(dirInodeName, renameFileName)) + + t.mockBucket.AssertExpectations(t.T()) + // Verify the renamed file exists. + assert.NoError(t.T(), err) + assert.Equal(t.T(), newObjName, f.Name) +} + +func (t *HNSDirTest) TestRenameFileWithNonExistentSourceFile() { + const ( + fileName = "qux" + renameFileName = "rename" + ) + oldObjName := path.Join(dirInodeName, fileName) + newObjName := path.Join(dirInodeName, renameFileName) + var metaGeneration int64 = 0 + moveObjectReq := gcs.MoveObjectRequest{ + SrcName: oldObjName, + DstName: newObjName, + SrcGeneration: 0, + SrcMetaGenerationPrecondition: &metaGeneration, + } + oldObj := gcs.MinObject{Name: oldObjName} + var notFoundErr *gcs.NotFoundError + t.mockBucket.On("MoveObject", t.ctx, &moveObjectReq).Return(nil, &gcs.NotFoundError{}) + + // Attempt to rename the file. + f, err := t.in.RenameFile(t.ctx, &oldObj, newObjName) + + t.mockBucket.AssertExpectations(t.T()) + assert.True(t.T(), errors.As(err, ¬FoundErr)) + assert.Nil(t.T(), f) +} + func (t *HNSDirTest) TestDeleteChildDir_WhenImplicitDirFlagTrueOnNonHNSBucket() { const folderName = "folder" dirName := path.Join(dirInodeName, folderName) + "/" @@ -350,6 +403,7 @@ func (t *HNSDirTest) TestDeleteChildDir_WhenImplicitDirFlagFalseAndNonHNSBucket_ assert.Equal(t.T(), metadata.Type(0), t.typeCache.Get(t.fixedTime.Now(), dirName)) assert.False(t.T(), dirIn.IsUnlinked()) } + func (t *HNSDirTest) TestDeleteChildDir_WithImplicitDirFlagFalseAndNonHNSBucket_DeleteObjectThrowAnError() { const name = "folder" dirName := path.Join(dirInodeName, name) + "/" diff --git a/internal/storage/mock/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go index 417a08c8f1..46565f7092 100644 --- a/internal/storage/mock/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -98,7 +98,10 @@ func (m *TestifyMockBucket) DeleteObject(ctx context.Context, req *gcs.DeleteObj func (m *TestifyMockBucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { args := m.Called(ctx, req) - return args.Get(0).(*gcs.Object), args.Error(1) + if args.Get(0) != nil { + return args.Get(0).(*gcs.Object), nil + } + return nil, args.Error(1) } func (m *TestifyMockBucket) DeleteFolder(ctx context.Context, folderName string) error { From 8f471b4999b166d0ff704bfa9e5a71db7cfa961a Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 27 Dec 2024 16:17:27 +0530 Subject: [PATCH 0096/1298] [Move Object] Bucket handle integration (#2840) * move object api integration * small fix * review comment * formating * review comment * review comment * unit test * small fix * add one condition * add one more assert * formating * formating * formating --- internal/storage/bucket_handle.go | 53 ++++++++++++++++++++++- internal/storage/bucket_handle_test.go | 59 ++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 206382ef99..a8328be2c5 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -549,9 +549,58 @@ func (bh *bucketHandle) DeleteFolder(ctx context.Context, folderName string) (er return err } +func isPreconditionFailed(err error) (bool, error) { + var gapiErr *googleapi.Error + if errors.As(err, &gapiErr) && gapiErr.Code == http.StatusPreconditionFailed { + return true, &gcs.PreconditionError{Err: gapiErr} + } + + var apiErr *apierror.APIError + if errors.As(err, &apiErr) && apiErr.GRPCStatus().Code() == codes.FailedPrecondition { + return true, &gcs.PreconditionError{Err: apiErr} + } + + return false, nil +} + func (bh *bucketHandle) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { - // TODO: Implement it. - return nil, nil + var o *gcs.Object + var err error + + obj := bh.bucket.Object(req.SrcName) + + // Switching to the requested generation of source object. + if req.SrcGeneration != 0 { + obj = obj.Generation(req.SrcGeneration) + } + + // Putting a condition that the metaGeneration of source should match *req.SrcMetaGenerationPrecondition for move operation to occur. + if req.SrcMetaGenerationPrecondition != nil { + obj = obj.If(storage.Conditions{MetagenerationMatch: *req.SrcMetaGenerationPrecondition}) + } + + dstMoveObject := storage.MoveObjectDestination{ + Object: req.DstName, + Conditions: nil, + } + + attrs, err := obj.Move(ctx, dstMoveObject) + if err == nil { + // Converting objAttrs to type *Object + o = storageutil.ObjectAttrsToBucketObject(attrs) + return o, nil + } + + // If storage object does not exist, httpclient is returning ErrObjectNotExist error instead of googleapi error + // https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/vendor/cloud.google.com/go/storage/http_client.go#L516 + if ok, preCondErr := isPreconditionFailed(err); ok { + err = preCondErr + } else if errors.Is(err, storage.ErrObjectNotExist) { + err = &gcs.NotFoundError{Err: storage.ErrObjectNotExist} + } else { + err = fmt.Errorf("error in moving object: %w", err) + } + return nil, err } func (bh *bucketHandle) RenameFolder(ctx context.Context, folderName string, destinationFolderId string) (folder *gcs.Folder, err error) { diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 047ff07d56..33dd8864aa 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -18,6 +18,7 @@ import ( "context" "errors" "fmt" + "net/http" "reflect" "strings" "testing" @@ -26,11 +27,13 @@ import ( "cloud.google.com/go/storage" control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" + "github.com/googleapis/gax-go/v2/apierror" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "google.golang.org/api/googleapi" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -1520,3 +1523,59 @@ func (testSuite *BucketHandleTest) TestCreateFolderWithGivenName() { assert.NoError(testSuite.T(), err) assert.Equal(testSuite.T(), gcs.GCSFolder(TestBucketName, &mockFolder), folder) } + +func TestIsPreconditionFailed(t *testing.T) { + preCondApiError, _ := apierror.FromError(status.New(codes.FailedPrecondition, "Precondition error").Err()) + notFoundApiError, _ := apierror.FromError(status.New(codes.NotFound, "Not Found error").Err()) + + tests := []struct { + name string + err error + expectPreCond bool + }{ + { + name: "googleapi.Error with PreconditionFailed", + err: &googleapi.Error{Code: http.StatusPreconditionFailed}, + expectPreCond: true, + }, + { + name: "googleapi.Error with other code", + err: &googleapi.Error{Code: http.StatusNotFound}, + expectPreCond: false, + }, + { + name: "apierror.APIError with FailedPrecondition", + err: preCondApiError, + expectPreCond: true, + }, + { + name: "apierror.APIError with other code", + err: notFoundApiError, + expectPreCond: false, + }, + { + name: "nil error", + err: nil, + expectPreCond: false, + }, + { + name: "generic error", + err: errors.New("generic error"), + expectPreCond: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + isPreCond, err := isPreconditionFailed(tt.err) + + assert.Equal(t, tt.expectPreCond, isPreCond) + if tt.expectPreCond { + var preCondErr *gcs.PreconditionError + assert.ErrorAs(t, err, &preCondErr) + } else { + assert.NoError(t, err) + } + }) + } +} From 6ae0e391f78638fd108457b8be428111352113c5 Mon Sep 17 00:00:00 2001 From: codechanges Date: Mon, 30 Dec 2024 07:34:00 +0530 Subject: [PATCH 0097/1298] Add negative cache entry after deleting object/folderCache entry update (#2822) * Add negative cache entry on deletion * Add remaining changes * Added TODO * Added explanatory comments * Fixed tests by adding a random directory * Fixed tests by adding a random directory * Rename dirName variable --- internal/storage/caching/fast_stat_bucket.go | 15 +++++++--- .../storage/caching/fast_stat_bucket_test.go | 9 ++++-- .../implicit_dir/local_file_test.go | 29 ++++++++++--------- .../infinite_kernel_list_cache_test.go | 6 +++- .../util/operations/string_operations.go | 10 +++++++ tools/integration_tests/util/setup/setup.go | 12 ++++++++ 6 files changed, 60 insertions(+), 21 deletions(-) diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index 1e37385647..bfe86b5a50 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -367,8 +367,12 @@ func (b *fastStatBucket) UpdateObject( func (b *fastStatBucket) DeleteObject( ctx context.Context, req *gcs.DeleteObjectRequest) (err error) { - b.invalidate(req.Name) err = b.wrapped.DeleteObject(ctx, req) + if err != nil { + b.invalidate(req.Name) + } else { + b.addNegativeEntry(req.Name) + } return } @@ -391,11 +395,14 @@ func (b *fastStatBucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequ func (b *fastStatBucket) DeleteFolder(ctx context.Context, folderName string) error { err := b.wrapped.DeleteFolder(ctx, folderName) + // In case of an error; invalidate the cached entry. This will make sure that + // gcsfuse is not caching possibly erroneous status of the folder and next + // call will hit GCS backend to probe the latest status. if err != nil { - return err + b.invalidate(folderName) + } else { + b.addNegativeEntryForFolder(folderName) } - // TODO: Caching negative entries for both objects and folders will be implemented together due to test failures. - b.invalidate(folderName) return err } diff --git a/internal/storage/caching/fast_stat_bucket_test.go b/internal/storage/caching/fast_stat_bucket_test.go index 1239bbd15f..5ab06fa6e2 100644 --- a/internal/storage/caching/fast_stat_bucket_test.go +++ b/internal/storage/caching/fast_stat_bucket_test.go @@ -906,8 +906,8 @@ func (t *DeleteObjectTest) WrappedSucceeds() { const name = "" var err error - // Erase - ExpectCall(t.cache, "Erase")(Any()) + // AddNegativeEntry + ExpectCall(t.cache, "AddNegativeEntry")(Any(), Any()) // Wrapped ExpectCall(t.wrapped, "DeleteObject")(Any(), Any()). @@ -1003,9 +1003,10 @@ func init() { RegisterTestSuite(&DeleteFolderTest{}) } func (t *DeleteFolderTest) Test_DeleteFolder_Success() { const name = "some-name" + ExpectCall(t.cache, "AddNegativeEntryForFolder")(name, Any()). + WillOnce(Return()) ExpectCall(t.wrapped, "DeleteFolder")(Any(), name). WillOnce(Return(nil)) - ExpectCall(t.cache, "Erase")(name).WillOnce(Return()) err := t.bucket.DeleteFolder(context.TODO(), name) @@ -1014,6 +1015,8 @@ func (t *DeleteFolderTest) Test_DeleteFolder_Success() { func (t *DeleteFolderTest) Test_DeleteFolder_Failure() { const name = "some-name" + // Erase + ExpectCall(t.cache, "Erase")(Any()) ExpectCall(t.wrapped, "DeleteFolder")(Any(), name). WillOnce(Return(fmt.Errorf("mock error"))) diff --git a/tools/integration_tests/implicit_dir/local_file_test.go b/tools/integration_tests/implicit_dir/local_file_test.go index 728221fad0..34bb8448c7 100644 --- a/tools/integration_tests/implicit_dir/local_file_test.go +++ b/tools/integration_tests/implicit_dir/local_file_test.go @@ -37,21 +37,23 @@ var ( // ////////////////////////////////////////////////////////////////////// func TestNewFileUnderImplicitDirectoryShouldNotGetSyncedToGCSTillClose(t *testing.T) { - testDirPath = setup.SetupTestDirectory(testDirName) - CreateImplicitDir(ctx, storageClient, testDirName, t) + testBaseDirName := path.Join(testDirName, operations.GetRandomName(t)) + testDirPath = setup.SetupTestDirectoryRecursive(testBaseDirName) + CreateImplicitDir(ctx, storageClient, testBaseDirName, t) fileName := path.Join(ImplicitDirName, FileName1) _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName, t) operations.WriteWithoutClose(fh, FileContents, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, fileName, t) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testBaseDirName, fileName, t) // Validate. - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, fileName, FileContents, t) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testBaseDirName, fileName, FileContents, t) } func TestReadDirForImplicitDirWithLocalFile(t *testing.T) { - testDirPath = setup.SetupTestDirectory(testDirName) - CreateImplicitDir(ctx, storageClient, testDirName, t) + testBaseDirName := path.Join(testDirName, operations.GetRandomName(t)) + testDirPath = setup.SetupTestDirectoryRecursive(testBaseDirName) + CreateImplicitDir(ctx, storageClient, testBaseDirName, t) fileName1 := path.Join(ImplicitDirName, FileName1) fileName2 := path.Join(ImplicitDirName, FileName2) _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName1, t) @@ -66,8 +68,8 @@ func TestReadDirForImplicitDirWithLocalFile(t *testing.T) { operations.VerifyFileEntry(entries[1], FileName2, 0, t) operations.VerifyFileEntry(entries[2], ImplicitFileName1, GCSFileSize, t) // Close the local files. - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh1, testDirName, fileName1, "", t) - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testDirName, fileName2, "", t) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh1, testBaseDirName, fileName1, "", t) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testBaseDirName, fileName2, "", t) } func TestRecursiveListingWithLocalFiles(t *testing.T) { @@ -80,7 +82,8 @@ func TestRecursiveListingWithLocalFiles(t *testing.T) { // mntDir/implicit/foo2 --- file // mntDir/implicit/implicitFile1 --- file - testDirPath = setup.SetupTestDirectory(testDirName) + testBaseDirName := path.Join(testDirName, operations.GetRandomName(t)) + testDirPath = setup.SetupTestDirectoryRecursive(testBaseDirName) fileName2 := path.Join(ExplicitDirName, ExplicitFileName1) fileName3 := path.Join(ImplicitDirName, FileName2) // Create local file in mnt/ dir. @@ -89,7 +92,7 @@ func TestRecursiveListingWithLocalFiles(t *testing.T) { operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t) _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName2, t) // Create implicit dir with 1 local file1 and 1 synced file. - CreateImplicitDir(ctx, storageClient, testDirName, t) + CreateImplicitDir(ctx, storageClient, testBaseDirName, t) _, fh3 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName3, t) // Recursively list mntDir/ directory. @@ -135,7 +138,7 @@ func TestRecursiveListingWithLocalFiles(t *testing.T) { if err != nil { t.Errorf("filepath.WalkDir() err: %v", err) } - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh1, testDirName, FileName1, "", t) - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testDirName, fileName2, "", t) - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh3, testDirName, fileName3, "", t) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh1, testBaseDirName, FileName1, "", t) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testBaseDirName, fileName2, "", t) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh3, testBaseDirName, fileName3, "", t) } diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go index 87bcafa8b9..9b9a07c053 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go @@ -526,8 +526,12 @@ func TestInfiniteKernelListCacheTest(t *testing.T) { } // Define flag set to run the tests. + // Note: metadata cache is disabled to avoid cache consistency issue between + // gcsfuse cache and kernel cache. As gcsfuse cache might hold the entry which + // already became stale due to delete operation. + // TODO: Replace metadata-cache-ttl-secs with something better flagsSet := [][]string{ - {"--kernel-list-cache-ttl-secs=-1"}, + {"--kernel-list-cache-ttl-secs=-1", "--metadata-cache-ttl-secs=0"}, } // Run tests. diff --git a/tools/integration_tests/util/operations/string_operations.go b/tools/integration_tests/util/operations/string_operations.go index 316e31ff1f..d0dfca0285 100644 --- a/tools/integration_tests/util/operations/string_operations.go +++ b/tools/integration_tests/util/operations/string_operations.go @@ -18,6 +18,8 @@ package operations import ( "strings" "testing" + + "github.com/google/uuid" ) func VerifyExpectedSubstrings(t *testing.T, input string, expectedSubstrings []string) { @@ -35,3 +37,11 @@ func VerifyUnexpectedSubstrings(t *testing.T, input string, unexpectedSubstrings } } } + +func GetRandomName(t *testing.T) string { + id, err := uuid.NewRandom() + if err != nil { + t.Errorf("Error while generating random string, err: %v", err) + } + return id.String() +} diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 66c919ce87..9d8922e9b2 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -382,6 +382,18 @@ func SetupTestDirectory(testDirName string) string { return testDirPath } +// SetupTestDirectoryRecursive recursively creates a testDirectory in the mounted directory and cleans up +// any content present in it. +func SetupTestDirectoryRecursive(testDirName string) string { + testDirPath := path.Join(MntDir(), testDirName) + err := os.MkdirAll(testDirPath, DirPermission_0755) + if err != nil && !strings.Contains(err.Error(), "file exists") { + log.Printf("Error while setting up directory %s for testing: %v", testDirPath, err) + } + CleanUpDir(testDirPath) + return testDirPath +} + // CleanupDirectoryOnGCS cleans up the object/directory path passed in parameter. func CleanupDirectoryOnGCS(ctx context.Context, client *storage.Client, directoryPathOnGCS string) { bucket, dirPath := GetBucketAndObjectBasedOnTypeOfMount(directoryPathOnGCS) From b0ca9c5b2c0a35aeb8a48fe7a36120d7b33216aa Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 30 Dec 2024 17:26:45 +0530 Subject: [PATCH 0098/1298] rename test function so common tests are also run (#2846) --- internal/fs/streaming_writes_local_file_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/fs/streaming_writes_local_file_test.go b/internal/fs/streaming_writes_local_file_test.go index d017d80d4e..c9067ffe1c 100644 --- a/internal/fs/streaming_writes_local_file_test.go +++ b/internal/fs/streaming_writes_local_file_test.go @@ -63,7 +63,7 @@ func (t *StreamingWritesLocalFileTest) TearDownTest() { t.fsTest.TearDown() } -func TestStreamingWritesLocalFileTestSuite(t *testing.T) { +func TestStreamingWritesLocalFileTest(t *testing.T) { suite.Run(t, new(StreamingWritesLocalFileTest)) } From 399b3560d5b693d48ea40c40afc8d0f9503ac248 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 10:24:05 +0530 Subject: [PATCH 0099/1298] Bump go.opentelemetry.io/otel/exporters/stdout/stdouttrace (#2801) Bumps [go.opentelemetry.io/otel/exporters/stdout/stdouttrace](https://github.com/open-telemetry/opentelemetry-go) from 1.32.0 to 1.33.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/v1.32.0...v1.33.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/stdout/stdouttrace dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 9 ++++---- go.sum | 70 +++++----------------------------------------------------- 2 files changed, 9 insertions(+), 70 deletions(-) diff --git a/go.mod b/go.mod index 4ded36f8e7..c6d33e8c33 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,8 @@ require ( github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/mitchellh/mapstructure v1.5.0 github.com/prometheus/client_golang v1.20.5 + github.com/prometheus/client_model v0.6.1 + github.com/prometheus/common v0.60.1 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 @@ -34,7 +36,8 @@ require ( go.opentelemetry.io/contrib/detectors/gcp v1.33.0 go.opentelemetry.io/otel v1.33.0 go.opentelemetry.io/otel/exporters/prometheus v0.54.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0 + go.opentelemetry.io/otel/metric v1.33.0 go.opentelemetry.io/otel/sdk v1.33.0 go.opentelemetry.io/otel/sdk/metric v1.33.0 go.opentelemetry.io/otel/trace v1.33.0 @@ -94,12 +97,9 @@ require ( github.com/pkg/xattr v0.4.10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/prometheus v0.35.0 // indirect github.com/prometheus/statsd_exporter v0.22.7 // indirect - github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect @@ -110,7 +110,6 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect - go.opentelemetry.io/otel/metric v1.33.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 // indirect diff --git a/go.sum b/go.sum index 6051fe6cba..78ac9fa91f 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,5 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= -cel.dev/expr v0.16.1 h1:NR0+oFYzR1CqLFhTAqg3ql59G9VfN8fKq1TCHJ6gq1g= -cel.dev/expr v0.16.1/go.mod h1:AsGA5zb3WruAEQeQng1RZdGEXmBj0jvMWh6l5SnNuC8= -cel.dev/expr v0.16.2/go.mod h1:gXngZQMkWJoSbE8mOzehJlXQyubn/Vg0vR9/F3W7iw8= cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -34,12 +31,8 @@ cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= -cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= cloud.google.com/go v0.117.0 h1:Z5TNFfQxj7WG2FgOGX1ekC5RiXrYgms6QscOm32M/4s= cloud.google.com/go v0.117.0/go.mod h1:ZbwhVTb1DBGt2Iwb3tNO6SEK4q+cplHZmLWH+DelYYc= -cloud.google.com/go/auth v0.11.0 h1:Ic5SZz2lsvbYcWT5dfjNWgw6tTlGi2Wc8hyQSC9BstA= -cloud.google.com/go/auth v0.11.0/go.mod h1:xxA5AqpDrvS+Gkmo9RqrGGRh6WSNKKOXhY3zNOr38tI= cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= @@ -53,10 +46,6 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7 cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.31.0 h1:NtkEQnSesZDeTM5Hq57CSeeRn1LkW/p+ffg9sxGIUbs= -cloud.google.com/go/compute v1.31.0/go.mod h1:4SCUCDAvOQvMGu4ze3YIJapnY0UQa5+WvJJeYFsQRoo= -cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= -cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -64,24 +53,18 @@ cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1 cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/iam v1.3.0 h1:4Wo2qTaGKFtajbLpF6I4mywg900u3TLlHDb6mriLDPU= cloud.google.com/go/iam v1.3.0/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= -cloud.google.com/go/kms v1.20.1 h1:og29Wv59uf2FVaZlesaiDAqHFzHaoUyHI3HYp9VUHVg= -cloud.google.com/go/kms v1.20.1/go.mod h1:LywpNiVCvzYNJWS9JUcGJSVTNSwPwi0vBAotzDqn2nc= +cloud.google.com/go/kms v1.20.2 h1:NGTHOxAyhDVUGVU5KngeyGScrg2D39X76Aphe6NC7S0= +cloud.google.com/go/kms v1.20.2/go.mod h1:LywpNiVCvzYNJWS9JUcGJSVTNSwPwi0vBAotzDqn2nc= cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= -cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= -cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= cloud.google.com/go/longrunning v0.6.3 h1:A2q2vuyXysRcwzqDpMMLSI6mb6o39miS52UEG/Rd2ng= cloud.google.com/go/longrunning v0.6.3/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= -cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= -cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= cloud.google.com/go/monitoring v1.22.0 h1:mQ0040B7dpuRq1+4YiQD43M2vW9HgoVxY98xhqGT+YI= cloud.google.com/go/monitoring v1.22.0/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.45.1 h1:ZC/UzYcrmK12THWn1P72z+Pnp2vu/zCZRXyhAfP1hJY= -cloud.google.com/go/pubsub v1.45.1/go.mod h1:3bn7fTmzZFwaUjllitv1WlsNMkqBgGUb3UdMhI54eCc= cloud.google.com/go/pubsub v1.45.3 h1:prYj8EEAAAwkp6WNoGTE4ahe0DgHoyJd5Pbop931zow= cloud.google.com/go/pubsub v1.45.3/go.mod h1:cGyloK/hXC4at7smAtxFnXprKEFTqmMXNNd9w+bd94Q= cloud.google.com/go/secretmanager v1.14.2 h1:2XscWCfy//l/qF96YE18/oUaNJynAx749Jg3u0CjQr8= @@ -91,8 +74,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.48.0 h1:FhBDHACbVtdPx7S/AbcKujPWiHvfO6F8OXGgCEbB2+o= -cloud.google.com/go/storage v1.48.0/go.mod h1:aFoDYNMAjv67lp+xcuZqjUKv/ctmplzQ3wJgodA7b+M= cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= @@ -221,7 +202,6 @@ github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMr github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -251,8 +231,6 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 h1:QVw89YDxXxEe+l8gU8ETbOasdwEV+avkR75ZzsVV9WI= -github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20241213214725-57cfbe6fad57 h1:put7Je9ZyxbHtwr7IqGrW4LLVUupJQ2gbsDshKISSgU= github.com/cncf/xds/go v0.0.0-20241213214725-57cfbe6fad57/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= @@ -430,8 +408,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= -github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les= -github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -586,7 +562,6 @@ github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= @@ -685,8 +660,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.14.0 h1:f+jMrjBPl+DL9nI4IQzLUxMq7XrAqFYB7hBPqMNIe8o= -github.com/googleapis/gax-go/v2 v2.14.0/go.mod h1:lhBCnjdLrWRaPvLWhmc8IS24m9mr07qSYnHncrgo+zk= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= @@ -1084,8 +1057,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -1257,28 +1229,20 @@ go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= -go.opentelemetry.io/contrib/detectors/gcp v1.32.0 h1:P78qWqkLSShicHmAzfECaTgvslqHxblNE9j62Ws1NK8= -go.opentelemetry.io/contrib/detectors/gcp v1.32.0/go.mod h1:TVqo0Sda4Cv8gCIixd7LuLwW4EylumVWfhjZJjDD4DU= go.opentelemetry.io/contrib/detectors/gcp v1.33.0 h1:FVPoXEoILwgbZUu4X7YSgsESsAmGRgoYcnXkzgQPhP4= go.opentelemetry.io/contrib/detectors/gcp v1.33.0/go.mod h1:ZHrLmr4ikK2AwRj9QL+c9s2SOlgoSRyMpNVzUj2fZqI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 h1:r6I7RJCN86bpD/FQwedZ0vSixDpwuWREjW9oRMsmqDc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0/go.mod h1:B9yO6b04uB80CzjedvewuqDhxJxi11s7/GtiGa8bAjI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.31.0/go.mod h1:PFmBsWbldL1kiWZk9+0LBZz2brhByaGsvp6pRICMlPE= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= go.opentelemetry.io/otel v1.6.0/go.mod h1:bfJD2DZVw0LBxghOTlgnlI0CV3hLDu9XF/QKOUXMTQQ= go.opentelemetry.io/otel v1.6.1/go.mod h1:blzUabWHkX6LJewxvadmzafgh/wnvBSDBdOuwkAtrWQ= -go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= -go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= @@ -1294,34 +1258,26 @@ go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9Xa go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0 h1:cC2yDI3IQd0Udsux7Qmq8ToKAx1XCilTQECZ0KDZyTw= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.32.0/go.mod h1:2PD5Ex6z8CFzDbTdOlwyNIUywRr1DN0ospafJM1wJ+s= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0 h1:W5AWUn/IVe8RFb5pZx1Uh9Laf/4+Qmm4kJL5zPuvR+0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0/go.mod h1:mzKxJywMNBdEX8TSJais3NnsVZUaJ+bAy6UxPTng2vk= go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= go.opentelemetry.io/otel/metric v0.28.0/go.mod h1:TrzsfQAmQaB1PDcdhBauLMk7nyyg9hm+GoQq/ekE9Iw= -go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= -go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= go.opentelemetry.io/otel/sdk v1.6.1/go.mod h1:IVYrddmFZ+eJqu2k38qD3WezFR2pymCzm8tdxyh3R4E= -go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= -go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= -go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU= -go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ= go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU= go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= go.opentelemetry.io/otel/trace v1.6.0/go.mod h1:qs7BrU5cZ8dXQHBGxHMOxwME/27YH2qEp4/+tZLLwJE= go.opentelemetry.io/otel/trace v1.6.1/go.mod h1:RkFRM1m0puWIq10oxImnGEduNBzxiN7TXluRBtE+5j0= -go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= -go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= @@ -1478,8 +1434,6 @@ golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= -golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1793,8 +1747,6 @@ google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQ google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.210.0 h1:HMNffZ57OoZCRYSbdWVRoqOa8V8NIHLL0CzdBPLztWk= -google.golang.org/api v0.210.0/go.mod h1:B9XDZGnx2NtyjzVkOVTGrFSAVZgPcbedzKg/gTLwqBs= google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA= google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -1878,16 +1830,10 @@ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2 google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= -google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= google.golang.org/genproto v0.0.0-20241219192143-6b3ec007d9bb h1:JGs+s1Q6osip3cDY197L1HmkuPn8wPp9Hfy9jl+Uz+U= google.golang.org/genproto v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:o8GgNarfULyZPNaIY8RDfXM7AZcmcKC/tbMWp/ZOFDw= -google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f h1:M65LEviCfuZTfrfzwwEoxVtgvfkFkBUbFnRbxCXuXhU= -google.golang.org/genproto/googleapis/api v0.0.0-20241113202542-65e8d215514f/go.mod h1:Yo94eF2nj7igQt+TiJ49KxjIH8ndLYPZMIRSiRcEbg0= google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb h1:B7GIB7sr443wZ/EAEl7VZjmh1V6qzkt5V+RYcUYtS1U= google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:E5//3O5ZIG2l71Xnt+P/CYUY8Bxs8E7WMoZ9tlcMbAY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb h1:3oy2tynMOP1QbTC0MsNNAV+Se8M2Bd0A5+x1QHyw+pI= google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -1924,13 +1870,9 @@ google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ5 google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.67.2 h1:Lq11HW1nr5m4OYV+ZVy2BjOK78/zqnTx24vyDBP1JcQ= -google.golang.org/grpc v1.67.2/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a h1:UIpYSuWdWHSzjwcAFRLjKcPXFZVVLXGEM23W+NWqipw= -google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a/go.mod h1:9i1T9n4ZinTUZGgzENMi8MDDgbGC5mqTS75JAv6xN3A= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1946,8 +1888,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= -google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= From 5868e217aff8e69cc5f101dadb50b92f279bcd93 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 10:24:57 +0530 Subject: [PATCH 0100/1298] Bump go.opentelemetry.io/otel/exporters/prometheus from 0.54.0 to 0.55.0 (#2800) Bumps [go.opentelemetry.io/otel/exporters/prometheus](https://github.com/open-telemetry/opentelemetry-go) from 0.54.0 to 0.55.0. - [Release notes](https://github.com/open-telemetry/opentelemetry-go/releases) - [Changelog](https://github.com/open-telemetry/opentelemetry-go/blob/main/CHANGELOG.md) - [Commits](https://github.com/open-telemetry/opentelemetry-go/compare/exporters/prometheus/v0.54.0...exporters/prometheus/v0.55.0) --- updated-dependencies: - dependency-name: go.opentelemetry.io/otel/exporters/prometheus dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index c6d33e8c33..63405601ea 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/prometheus/client_golang v1.20.5 github.com/prometheus/client_model v0.6.1 - github.com/prometheus/common v0.60.1 + github.com/prometheus/common v0.61.0 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 @@ -35,7 +35,7 @@ require ( go.opencensus.io v0.24.0 go.opentelemetry.io/contrib/detectors/gcp v1.33.0 go.opentelemetry.io/otel v1.33.0 - go.opentelemetry.io/otel/exporters/prometheus v0.54.0 + go.opentelemetry.io/otel/exporters/prometheus v0.55.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0 go.opentelemetry.io/otel/metric v1.33.0 go.opentelemetry.io/otel/sdk v1.33.0 diff --git a/go.sum b/go.sum index 78ac9fa91f..1ead45e484 100644 --- a/go.sum +++ b/go.sum @@ -1027,8 +1027,8 @@ github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+ github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= -github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= +github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= github.com/prometheus/common/assets v0.1.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI= github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= github.com/prometheus/exporter-toolkit v0.7.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= @@ -1254,8 +1254,8 @@ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1 go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.6.1/go.mod h1:UJJXJj0rltNIemDMwkOJyggsvyMG9QHfJeFH0HS5JjM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.6.1/go.mod h1:DAKwdo06hFLc0U88O10x4xnb5sc7dDRDqRuiN+io8JE= -go.opentelemetry.io/otel/exporters/prometheus v0.54.0 h1:rFwzp68QMgtzu9PgP3jm9XaMICI6TsofWWPcBDKwlsU= -go.opentelemetry.io/otel/exporters/prometheus v0.54.0/go.mod h1:QyjcV9qDP6VeK5qPyKETvNjmaaEc7+gqjh4SS0ZYzDU= +go.opentelemetry.io/otel/exporters/prometheus v0.55.0 h1:sSPw658Lk2NWAv74lkD3B/RSDb+xRFx46GjkrL3VUZo= +go.opentelemetry.io/otel/exporters/prometheus v0.55.0/go.mod h1:nC00vyCmQixoeaxF6KNyP42II/RHa9UdruK02qBmHvI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0 h1:W5AWUn/IVe8RFb5pZx1Uh9Laf/4+Qmm4kJL5zPuvR+0= From 2867cb874e82176055a899abe184fc7c0ceff754 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Dec 2024 10:26:13 +0530 Subject: [PATCH 0101/1298] Bump google.golang.org/protobuf from 1.36.0 to 1.36.1 (#2847) Bumps google.golang.org/protobuf from 1.36.0 to 1.36.1. --- updated-dependencies: - dependency-name: google.golang.org/protobuf dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 63405601ea..426450c3c2 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( golang.org/x/time v0.8.0 google.golang.org/api v0.214.0 google.golang.org/grpc v1.69.2 - google.golang.org/protobuf v1.36.0 + google.golang.org/protobuf v1.36.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 1ead45e484..59f29e681a 100644 --- a/go.sum +++ b/go.sum @@ -1888,8 +1888,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= -google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 5138f6d38137d6ff7f0261162386c134c0e15ea3 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 31 Dec 2024 10:31:28 +0530 Subject: [PATCH 0102/1298] small fix (#2849) --- tools/integration_tests/run_tests_mounted_directory.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index a69e54c0c7..1d67c2b953 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -562,7 +562,7 @@ test_cases=( "TestInfiniteKernelListCacheTest/TestKernelListCache_DeleteAndListDirectory" ) for test_case in "${test_cases[@]}"; do - gcsfuse --kernel-list-cache-ttl-secs=-1 "$TEST_BUCKET_NAME" "$MOUNT_DIR" + gcsfuse --kernel-list-cache-ttl-secs=-1 --metadata-cache-ttl-secs=0 "$TEST_BUCKET_NAME" "$MOUNT_DIR" GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/kernel_list_cache/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run "$test_case" sudo umount "$MOUNT_DIR" done From 84f8195c1f81f5c70d1793c0725c367e85083d08 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 31 Dec 2024 11:15:43 +0530 Subject: [PATCH 0103/1298] Add troubleshooting guide about implicit dir (#2848) * add documentation * small fix --- docs/troubleshooting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 291a208356..dee41d021a 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -7,9 +7,9 @@ discusses potential solutions to the same. Most of the common mount point issues are around permissions on both local mount point and the Cloud Storage bucket. It is highly recommended to retry with --foreground --log-severity=TRACE flags which would provide much more detailed logs to understand the errors better and possibly provide a solution. -### Mount successful but files not visible +### Mount successful but files are not visible -Try mounting the gcsfuse with --implicit-dir flag. Read the [semantics](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/semantics.md) to know the reasoning. +Try mounting the gcsfuse with `--implicit-dir` flag. Read the [semantics](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/semantics.md#files-and-directories) to know the reasoning. ### Mount failed with fusermount3 exit status 1 From 599f7671557f9bb764d936cd0357b21641726d66 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 31 Dec 2024 14:09:17 +0530 Subject: [PATCH 0104/1298] fix typo (#2851) --- docs/troubleshooting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index dee41d021a..af4aefc447 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -9,7 +9,7 @@ Most of the common mount point issues are around permissions on both local mount ### Mount successful but files are not visible -Try mounting the gcsfuse with `--implicit-dir` flag. Read the [semantics](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/semantics.md#files-and-directories) to know the reasoning. +Try mounting the gcsfuse with `--implicit-dirs` flag. Read the [semantics](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/semantics.md#files-and-directories) to know the reasoning. ### Mount failed with fusermount3 exit status 1 From 19f1a0f3a74e230a53b93432f572ffa49f642ff1 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 3 Jan 2025 11:10:36 +0530 Subject: [PATCH 0105/1298] Fix flaky parallel dirOps e2e tests (#2852) * fix flaky parellal dir ops test * remove unnecessary changes * remove unnecessary changes * fix rest of the tests * fix rest of the tests * small fix * fix rest of the tests * create common function --- .../operations/parallel_dirops_test.go | 195 ++++++------------ 1 file changed, 66 insertions(+), 129 deletions(-) diff --git a/tools/integration_tests/operations/parallel_dirops_test.go b/tools/integration_tests/operations/parallel_dirops_test.go index 7d27b7cf75..a94b0cd83c 100644 --- a/tools/integration_tests/operations/parallel_dirops_test.go +++ b/tools/integration_tests/operations/parallel_dirops_test.go @@ -67,14 +67,17 @@ func createDirectoryStructureForParallelDiropsTest(t *testing.T) string { return testDir } +// lookUpFileStat performs a lookup for the given file path and returns the FileInfo and error. +func lookUpFileStat(wg *sync.WaitGroup, filePath string, result *os.FileInfo, err *error) { + defer wg.Done() + fileInfo, lookupErr := os.Stat(filePath) + *result = fileInfo + *err = lookupErr +} + func TestParallelLookUpsForSameFile(t *testing.T) { // Create directory structure for testing. testDir := createDirectoryStructureForParallelDiropsTest(t) - lookUpFunc := func(wg *sync.WaitGroup, filePath string) (os.FileInfo, error) { - defer wg.Done() - fileInfo, err := os.Stat(filePath) - return fileInfo, err - } var stat1, stat2 os.FileInfo var err1, err2 error @@ -82,12 +85,8 @@ func TestParallelLookUpsForSameFile(t *testing.T) { filePath := path.Join(testDir, "file1.txt") wg := sync.WaitGroup{} wg.Add(2) - go func() { - stat1, err1 = lookUpFunc(&wg, filePath) - }() - go func() { - stat2, err2 = lookUpFunc(&wg, filePath) - }() + go lookUpFileStat(&wg, filePath, &stat1, &err1) + go lookUpFileStat(&wg, filePath, &stat2, &err2) wg.Wait() // Assert both stats passed and give correct information @@ -101,12 +100,8 @@ func TestParallelLookUpsForSameFile(t *testing.T) { // Parallel lookups of file under a directory in mount. filePath = path.Join(testDir, "explicitDir1/file2.txt") wg.Add(2) - go func() { - stat1, err1 = lookUpFunc(&wg, filePath) - }() - go func() { - stat2, err2 = lookUpFunc(&wg, filePath) - }() + go lookUpFileStat(&wg, filePath, &stat1, &err1) + go lookUpFileStat(&wg, filePath, &stat2, &err2) wg.Wait() // Assert both stats passed and give correct information @@ -121,10 +116,9 @@ func TestParallelLookUpsForSameFile(t *testing.T) { func TestParallelReadDirs(t *testing.T) { // Create directory structure for testing. testDir := createDirectoryStructureForParallelDiropsTest(t) - readDirFunc := func(wg *sync.WaitGroup, dirPath string) ([]os.DirEntry, error) { + readDirFunc := func(wg *sync.WaitGroup, dirPath string, dirEntries *[]os.DirEntry, err *error) { defer wg.Done() - dirEntries, err := os.ReadDir(dirPath) - return dirEntries, err + *dirEntries, *err = os.ReadDir(dirPath) } var dirEntries1, dirEntries2 []os.DirEntry var err1, err2 error @@ -133,12 +127,9 @@ func TestParallelReadDirs(t *testing.T) { dirPath := path.Join(testDir, "explicitDir1") wg := sync.WaitGroup{} wg.Add(2) - go func() { - dirEntries1, err1 = readDirFunc(&wg, dirPath) - }() - go func() { - dirEntries2, err2 = readDirFunc(&wg, dirPath) - }() + go readDirFunc(&wg, dirPath, &dirEntries1, &err1) + go readDirFunc(&wg, dirPath, &dirEntries2, &err2) + wg.Wait() // Assert both readDirs passed and give correct information @@ -156,12 +147,8 @@ func TestParallelReadDirs(t *testing.T) { parentDirPath := testDir wg = sync.WaitGroup{} wg.Add(2) - go func() { - dirEntries1, err1 = readDirFunc(&wg, dirPath) - }() - go func() { - dirEntries2, err2 = readDirFunc(&wg, parentDirPath) - }() + go readDirFunc(&wg, dirPath, &dirEntries1, &err1) + go readDirFunc(&wg, parentDirPath, &dirEntries2, &err2) wg.Wait() // Assert both readDirs passed and give correct information @@ -180,15 +167,9 @@ func TestParallelReadDirs(t *testing.T) { func TestParallelLookUpAndDeleteSameDir(t *testing.T) { // Create directory structure for testing. testDir := createDirectoryStructureForParallelDiropsTest(t) - lookUpFunc := func(wg *sync.WaitGroup, dirPath string) (os.FileInfo, error) { - defer wg.Done() - fileInfo, err := os.Stat(dirPath) - return fileInfo, err - } - deleteFunc := func(wg *sync.WaitGroup, dirPath string) error { + deleteFunc := func(wg *sync.WaitGroup, dirPath string, err *error) { defer wg.Done() - err := os.RemoveAll(dirPath) - return err + *err = os.RemoveAll(dirPath) } var statInfo os.FileInfo var lookUpErr, deleteErr error @@ -197,12 +178,8 @@ func TestParallelLookUpAndDeleteSameDir(t *testing.T) { dirPath := path.Join(testDir, "explicitDir1") wg := sync.WaitGroup{} wg.Add(2) - go func() { - statInfo, lookUpErr = lookUpFunc(&wg, dirPath) - }() - go func() { - deleteErr = deleteFunc(&wg, dirPath) - }() + go lookUpFileStat(&wg, dirPath, &statInfo, &lookUpErr) + go deleteFunc(&wg, dirPath, &deleteErr) wg.Wait() assert.NoError(t, deleteErr) @@ -210,8 +187,9 @@ func TestParallelLookUpAndDeleteSameDir(t *testing.T) { assert.True(t, os.IsNotExist(err)) // Assert either dir is looked up first or deleted first if lookUpErr == nil { + assert.NotNil(t, statInfo, "statInfo should not be nil when lookUpErr is nil") assert.Contains(t, statInfo.Name(), "explicitDir1") - assert.True(t, statInfo.IsDir()) + assert.True(t, statInfo.IsDir(), "The created path should be a directory") } else { assert.True(t, os.IsNotExist(lookUpErr)) } @@ -220,11 +198,6 @@ func TestParallelLookUpAndDeleteSameDir(t *testing.T) { func TestParallelLookUpsForDifferentFiles(t *testing.T) { // Create directory structure for testing. testDir := createDirectoryStructureForParallelDiropsTest(t) - lookUpFunc := func(wg *sync.WaitGroup, filePath string) (os.FileInfo, error) { - defer wg.Done() - fileInfo, err := os.Stat(filePath) - return fileInfo, err - } var stat1, stat2 os.FileInfo var err1, err2 error @@ -233,12 +206,9 @@ func TestParallelLookUpsForDifferentFiles(t *testing.T) { filePath2 := path.Join(testDir, "file2.txt") wg := sync.WaitGroup{} wg.Add(2) - go func() { - stat1, err1 = lookUpFunc(&wg, filePath1) - }() - go func() { - stat2, err2 = lookUpFunc(&wg, filePath2) - }() + go lookUpFileStat(&wg, filePath1, &stat1, &err1) + go lookUpFileStat(&wg, filePath2, &stat2, &err2) + wg.Wait() // Assert both stats passed and give correct information @@ -254,12 +224,8 @@ func TestParallelLookUpsForDifferentFiles(t *testing.T) { filePath2 = path.Join(testDir, "explicitDir1", "file2.txt") wg = sync.WaitGroup{} wg.Add(2) - go func() { - stat1, err1 = lookUpFunc(&wg, filePath1) - }() - go func() { - stat2, err2 = lookUpFunc(&wg, filePath2) - }() + go lookUpFileStat(&wg, filePath1, &stat1, &err1) + go lookUpFileStat(&wg, filePath2, &stat2, &err2) wg.Wait() // Assert both stats passed and give correct information @@ -274,19 +240,16 @@ func TestParallelLookUpsForDifferentFiles(t *testing.T) { func TestParallelReadDirAndMkdirInsideSameDir(t *testing.T) { // Create directory structure for testing. testDir := createDirectoryStructureForParallelDiropsTest(t) - readDirFunc := func(wg *sync.WaitGroup, dirPath string) ([]os.DirEntry, error) { + readDirFunc := func(wg *sync.WaitGroup, dirPath string, dirEntries *[]os.DirEntry, err *error) { defer wg.Done() - var dirEntries []os.DirEntry - err := filepath.WalkDir(dirPath, func(path string, d fs.DirEntry, err error) error { - dirEntries = append(dirEntries, d) + *err = filepath.WalkDir(dirPath, func(path string, d fs.DirEntry, err error) error { + *dirEntries = append(*dirEntries, d) return nil }) - return dirEntries, err } - mkdirFunc := func(wg *sync.WaitGroup, dirPath string) error { + mkdirFunc := func(wg *sync.WaitGroup, dirPath string, err *error) { defer wg.Done() - err := os.Mkdir(dirPath, setup.DirPermission_0755) - return err + *err = os.Mkdir(dirPath, setup.DirPermission_0755) } var dirEntries []os.DirEntry var readDirErr, mkdirErr error @@ -295,12 +258,8 @@ func TestParallelReadDirAndMkdirInsideSameDir(t *testing.T) { newDirPath := path.Join(testDir, "newDir") wg := sync.WaitGroup{} wg.Add(2) - go func() { - dirEntries, readDirErr = readDirFunc(&wg, testDir) - }() - go func() { - mkdirErr = mkdirFunc(&wg, newDirPath) - }() + go readDirFunc(&wg, testDir, &dirEntries, &readDirErr) + go mkdirFunc(&wg, newDirPath, &mkdirErr) wg.Wait() // Assert both listing and mkdir succeeded @@ -308,7 +267,7 @@ func TestParallelReadDirAndMkdirInsideSameDir(t *testing.T) { assert.NoError(t, mkdirErr) dirStatInfo, err := os.Stat(newDirPath) assert.NoError(t, err) - assert.True(t, dirStatInfo.IsDir()) + assert.True(t, dirStatInfo.IsDir(), "The created path should be a directory") // List should happen either before or after creation of newDir. assert.GreaterOrEqual(t, len(dirEntries), 8) assert.LessOrEqual(t, len(dirEntries), 9) @@ -320,15 +279,9 @@ func TestParallelReadDirAndMkdirInsideSameDir(t *testing.T) { func TestParallelLookUpAndDeleteSameFile(t *testing.T) { // Create directory structure for testing. testDir := createDirectoryStructureForParallelDiropsTest(t) - lookUpFunc := func(wg *sync.WaitGroup, filePath string) (os.FileInfo, error) { - defer wg.Done() - fileInfo, err := os.Stat(filePath) - return fileInfo, err - } - deleteFileFunc := func(wg *sync.WaitGroup, filePath string) error { + deleteFileFunc := func(wg *sync.WaitGroup, filePath string, err *error) { defer wg.Done() - err := os.Remove(filePath) - return err + *err = os.Remove(filePath) } var fileInfo os.FileInfo var lookUpErr, deleteErr error @@ -337,12 +290,10 @@ func TestParallelLookUpAndDeleteSameFile(t *testing.T) { filePath := path.Join(testDir, "explicitDir1", "file1.txt") wg := sync.WaitGroup{} wg.Add(2) - go func() { - fileInfo, lookUpErr = lookUpFunc(&wg, filePath) - }() - go func() { - deleteErr = deleteFileFunc(&wg, filePath) - }() + + go lookUpFileStat(&wg, filePath, &fileInfo, &lookUpErr) + go deleteFileFunc(&wg, filePath, &deleteErr) + wg.Wait() assert.NoError(t, deleteErr) @@ -350,9 +301,10 @@ func TestParallelLookUpAndDeleteSameFile(t *testing.T) { assert.True(t, os.IsNotExist(err)) // Assert either file is looked up first or deleted first if lookUpErr == nil { + assert.NotNil(t, fileInfo, "fileInfo should not be nil when lookUpErr is nil") assert.Equal(t, int64(5), fileInfo.Size()) assert.Contains(t, fileInfo.Name(), "file1.txt") - assert.False(t, fileInfo.IsDir()) + assert.False(t, fileInfo.IsDir(), "The created path should not be a directory") } else { assert.True(t, os.IsNotExist(lookUpErr)) } @@ -361,15 +313,9 @@ func TestParallelLookUpAndDeleteSameFile(t *testing.T) { func TestParallelLookUpAndRenameSameFile(t *testing.T) { // Create directory structure for testing. testDir := createDirectoryStructureForParallelDiropsTest(t) - lookUpFunc := func(wg *sync.WaitGroup, filePath string) (os.FileInfo, error) { - defer wg.Done() - fileInfo, err := os.Stat(filePath) - return fileInfo, err - } - renameFunc := func(wg *sync.WaitGroup, oldFilePath string, newFilePath string) error { + renameFunc := func(wg *sync.WaitGroup, oldFilePath string, newFilePath string, err *error) { defer wg.Done() - err := os.Rename(oldFilePath, newFilePath) - return err + *err = os.Rename(oldFilePath, newFilePath) } var fileInfo os.FileInfo var lookUpErr, renameErr error @@ -379,12 +325,9 @@ func TestParallelLookUpAndRenameSameFile(t *testing.T) { newFilePath := path.Join(testDir, "newFile.txt") wg := sync.WaitGroup{} wg.Add(2) - go func() { - fileInfo, lookUpErr = lookUpFunc(&wg, filePath) - }() - go func() { - renameErr = renameFunc(&wg, filePath, newFilePath) - }() + go lookUpFileStat(&wg, filePath, &fileInfo, &lookUpErr) + go renameFunc(&wg, filePath, newFilePath, &renameErr) + wg.Wait() assert.NoError(t, renameErr) @@ -395,9 +338,10 @@ func TestParallelLookUpAndRenameSameFile(t *testing.T) { assert.Equal(t, int64(5), newFileInfo.Size()) // Assert either file is renamed first or looked up first if lookUpErr == nil { + assert.NotNil(t, fileInfo, "fileInfo should not be nil when lookUpErr is nil") assert.Equal(t, int64(5), fileInfo.Size()) assert.Contains(t, fileInfo.Name(), "file1.txt") - assert.False(t, fileInfo.IsDir()) + assert.False(t, fileInfo.IsDir(), "The created path should not be a directory") } else { assert.True(t, os.IsNotExist(lookUpErr)) } @@ -406,40 +350,33 @@ func TestParallelLookUpAndRenameSameFile(t *testing.T) { func TestParallelLookUpAndMkdirSameDir(t *testing.T) { // Create directory structure for testing. testDir := createDirectoryStructureForParallelDiropsTest(t) - lookUpFunc := func(wg *sync.WaitGroup, dirPath string) (os.FileInfo, error) { - defer wg.Done() - fileInfo, err := os.Stat(dirPath) - return fileInfo, err - } - mkdirFunc := func(wg *sync.WaitGroup, dirPath string) error { + mkdirFunc := func(wg *sync.WaitGroup, dirPath string, err *error) { defer wg.Done() - err := os.Mkdir(dirPath, setup.DirPermission_0755) - return err + *err = os.Mkdir(dirPath, setup.DirPermission_0755) } + var statInfo os.FileInfo var lookUpErr, mkdirErr error - // Parallel lookup and mkdir of a new directory. dirPath := path.Join(testDir, "newDir") - wg := sync.WaitGroup{} + var wg sync.WaitGroup wg.Add(2) - go func() { - statInfo, lookUpErr = lookUpFunc(&wg, dirPath) - }() - go func() { - mkdirErr = mkdirFunc(&wg, dirPath) - }() + + go lookUpFileStat(&wg, dirPath, &statInfo, &lookUpErr) + go mkdirFunc(&wg, dirPath, &mkdirErr) wg.Wait() - assert.NoError(t, mkdirErr) // Assert either directory is created first or looked up first + assert.NoError(t, mkdirErr, "mkdirFunc should not fail") + if lookUpErr == nil { + assert.NotNil(t, statInfo, "statInfo should not be nil when lookUpErr is nil") assert.Contains(t, statInfo.Name(), "newDir") assert.True(t, statInfo.IsDir()) } else { - assert.True(t, os.IsNotExist(lookUpErr)) + assert.True(t, os.IsNotExist(lookUpErr), "lookUpErr should indicate directory does not exist") dirStatInfo, err := os.Stat(dirPath) - assert.NoError(t, err) - assert.True(t, dirStatInfo.IsDir()) + assert.NoError(t, err, "os.Stat should succeed after directory creation") + assert.True(t, dirStatInfo.IsDir(), "The created path should be a directory") } } From 94eddd0fa24614b0c10618e3ae23806143917f25 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:42:36 +0530 Subject: [PATCH 0106/1298] Fix kernel cache ttl test failure for GKE (#2853) * separating directory * add one more test * lint fix * lint fix * review comment --- ...inite_kernel_list_cache_delete_dir_test.go | 134 ++++++++++++++++++ .../infinite_kernel_list_cache_test.go | 66 +-------- .../run_tests_mounted_directory.sh | 12 +- 3 files changed, 145 insertions(+), 67 deletions(-) create mode 100644 tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go new file mode 100644 index 0000000000..c53d3c192c --- /dev/null +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go @@ -0,0 +1,134 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kernel_list_cache + +import ( + "log" + "os" + "path" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type infiniteKernelListCacheDeleteDirTest struct { + flags []string +} + +func (s *infiniteKernelListCacheDeleteDirTest) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(s.flags, ctx, storageClient, testDirName) +} + +func (s *infiniteKernelListCacheDeleteDirTest) Teardown(t *testing.T) { + setup.UnmountGCSFuse(rootDir) +} + +func (s *infiniteKernelListCacheDeleteDirTest) TestKernelListCache_ListAndDeleteDirectory(t *testing.T) { + targetDir := path.Join(testDirPath, "explicit_dir") + operations.CreateDirectory(targetDir, t) + // Create test data + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) + operations.CloseFile(f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) + operations.CloseFile(f2) + + // (a) First read served from GCS, kernel will cache the dir response. + f, err := os.Open(targetDir) + assert.NoError(t, err) + names1, err := f.Readdirnames(-1) + assert.NoError(t, err) + require.Equal(t, 2, len(names1)) + assert.Equal(t, "file1.txt", names1[0]) + assert.Equal(t, "file2.txt", names1[1]) + err = f.Close() + assert.NoError(t, err) + // Adding one object to make sure to change the ReadDir() response. + // All files including file3.txt will be deleted by os.RemoveAll + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + + err = os.RemoveAll(targetDir) + + assert.NoError(t, err) +} + +func (s *infiniteKernelListCacheDeleteDirTest) TestKernelListCache_DeleteAndListDirectory(t *testing.T) { + targetDir := path.Join(testDirPath, "explicit_dir") + operations.CreateDirectory(targetDir, t) + // Create test data + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) + operations.CloseFile(f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) + operations.CloseFile(f2) + + err := os.RemoveAll(targetDir) + assert.NoError(t, err) + + // Adding object to GCS to make sure to change the ReadDir() response. + err = client.CreateObjectOnGCS(ctx, storageClient, path.Join(testDirName, "explicit_dir")+"/", "") + require.NoError(t, err) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + // Read will be served from GCS as removing the directory also deletes the cache. + f, err := os.Open(targetDir) + assert.NoError(t, err) + names1, err := f.Readdirnames(-1) + assert.NoError(t, err) + require.Equal(t, 1, len(names1)) + assert.Equal(t, "file3.txt", names1[0]) + err = f.Close() + assert.NoError(t, err) + + // 2nd RemoveAll call will also succeed. + err = os.RemoveAll(targetDir) + assert.NoError(t, err) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestInfiniteKernelListCacheDeleteDirTest(t *testing.T) { + ts := &infiniteKernelListCacheDeleteDirTest{} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + // Define flag set to run the tests. + // Note: metadata cache is disabled to avoid cache consistency issue between + // gcsfuse cache and kernel cache. As gcsfuse cache might hold the entry which + // already became stale due to delete operation. + // TODO: Replace metadata-cache-ttl-secs with something better + flagsSet := [][]string{ + {"--kernel-list-cache-ttl-secs=-1", "--metadata-cache-ttl-secs=0"}, + } + + // Run tests. + for _, flags := range flagsSet { + ts.flags = flags + log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) + } +} diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go index 9b9a07c053..ea740f1862 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go @@ -453,65 +453,6 @@ func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnDirectoryRe assert.Equal(t, "renamed_sub_dir", names2[3]) } -func (s *infiniteKernelListCacheTest) TestKernelListCache_ListAndDeleteDirectory(t *testing.T) { - targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) - // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) - - // (a) First read served from GCS, kernel will cache the dir response. - f, err := os.Open(targetDir) - assert.NoError(t, err) - names1, err := f.Readdirnames(-1) - assert.NoError(t, err) - require.Equal(t, 2, len(names1)) - assert.Equal(t, "file1.txt", names1[0]) - assert.Equal(t, "file2.txt", names1[1]) - err = f.Close() - assert.NoError(t, err) - // Adding one object to make sure to change the ReadDir() response. - // All files including file3.txt will be deleted by os.RemoveAll - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) - - err = os.RemoveAll(targetDir) - - assert.NoError(t, err) -} - -func (s *infiniteKernelListCacheTest) TestKernelListCache_DeleteAndListDirectory(t *testing.T) { - targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) - // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) - - err := os.RemoveAll(targetDir) - assert.NoError(t, err) - - // Adding object to GCS to make sure to change the ReadDir() response. - err = client.CreateObjectOnGCS(ctx, storageClient, path.Join(testDirName, "explicit_dir")+"/", "") - require.NoError(t, err) - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) - // Read will be served from GCS as removing the directory also deletes the cache. - f, err := os.Open(targetDir) - assert.NoError(t, err) - names1, err := f.Readdirnames(-1) - assert.NoError(t, err) - require.Equal(t, 1, len(names1)) - assert.Equal(t, "file3.txt", names1[0]) - err = f.Close() - assert.NoError(t, err) - - // 2nd RemoveAll call will also succeed. - err = os.RemoveAll(targetDir) - assert.NoError(t, err) -} - //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// @@ -525,13 +466,8 @@ func TestInfiniteKernelListCacheTest(t *testing.T) { return } - // Define flag set to run the tests. - // Note: metadata cache is disabled to avoid cache consistency issue between - // gcsfuse cache and kernel cache. As gcsfuse cache might hold the entry which - // already became stale due to delete operation. - // TODO: Replace metadata-cache-ttl-secs with something better flagsSet := [][]string{ - {"--kernel-list-cache-ttl-secs=-1", "--metadata-cache-ttl-secs=0"}, + {"--kernel-list-cache-ttl-secs=-1"}, } // Run tests. diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index 1d67c2b953..1598dcc6b9 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -558,8 +558,16 @@ test_cases=( "TestInfiniteKernelListCacheTest/TestKernelListCache_CacheMissOnAdditionOfDirectory" "TestInfiniteKernelListCacheTest/TestKernelListCache_CacheMissOnDeletionOfDirectory" "TestInfiniteKernelListCacheTest/TestKernelListCache_CacheMissOnDirectoryRename" - "TestInfiniteKernelListCacheTest/TestKernelListCache_ListAndDeleteDirectory" - "TestInfiniteKernelListCacheTest/TestKernelListCache_DeleteAndListDirectory" +) +for test_case in "${test_cases[@]}"; do + gcsfuse --kernel-list-cache-ttl-secs=-1 "$TEST_BUCKET_NAME" "$MOUNT_DIR" + GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/kernel_list_cache/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run "$test_case" + sudo umount "$MOUNT_DIR" +done + +test_cases=( + "TestInfiniteKernelListCacheDeleteDirTest/TestKernelListCache_ListAndDeleteDirectory" + "TestInfiniteKernelListCacheDeleteDirTest/TestKernelListCache_DeleteAndListDirectory" ) for test_case in "${test_cases[@]}"; do gcsfuse --kernel-list-cache-ttl-secs=-1 --metadata-cache-ttl-secs=0 "$TEST_BUCKET_NAME" "$MOUNT_DIR" From cec1b20cbed6ecf24b1d71b6dd27cf3f20c774c8 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 6 Jan 2025 13:41:08 +0530 Subject: [PATCH 0107/1298] Make OTel metrics docs and names same as that of OC. (#2857) --- common/otel_metrics.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/common/otel_metrics.go b/common/otel_metrics.go index 7a1c5eb9de..623be51a6a 100644 --- a/common/otel_metrics.go +++ b/common/otel_metrics.go @@ -95,19 +95,19 @@ func (o *otelMetrics) FileCacheReadLatency(ctx context.Context, value float64, a } func NewOTelMetrics() (MetricHandle, error) { - fsOpsCount, err1 := fsOpsMeter.Int64Counter("fs/ops_count", metric.WithDescription("The number of ops processed by the file system.")) - fsOpsLatency, err2 := fsOpsMeter.Float64Histogram("fs/ops_latency", metric.WithDescription("The latency of a file system operation."), metric.WithUnit("us"), + fsOpsCount, err1 := fsOpsMeter.Int64Counter("fs/ops_count", metric.WithDescription("The cumulative number of ops processed by the file system.")) + fsOpsLatency, err2 := fsOpsMeter.Float64Histogram("fs/ops_latency", metric.WithDescription("The cumulative distribution of file system operation latencies"), metric.WithUnit("us"), defaultLatencyDistribution) - fsOpsErrorCount, err3 := fsOpsMeter.Int64Counter("fs/ops_error_count", metric.WithDescription("The number of errors generated by file system operation.")) + fsOpsErrorCount, err3 := fsOpsMeter.Int64Counter("fs/ops_error_count", metric.WithDescription("The cumulative number of errors generated by file system operations")) gcsReadCount, err4 := gcsMeter.Int64Counter("gcs/read_count", metric.WithDescription("Specifies the number of gcs reads made along with type - Sequential/Random")) gcsDownloadBytesCount, err5 := gcsMeter.Int64Counter("gcs/download_bytes_count", metric.WithDescription("The cumulative number of bytes downloaded from GCS along with type - Sequential/Random"), metric.WithUnit("By")) - gcsReadBytesCount, err6 := gcsMeter.Int64Counter("gcs/read_bytes_count", metric.WithDescription("The number of bytes read from GCS objects."), metric.WithUnit("By")) - gcsReaderCount, err7 := gcsMeter.Int64Counter("gcs/reader_count", metric.WithDescription("The number of GCS object readers opened or closed.")) + gcsReadBytesCount, err6 := gcsMeter.Int64Counter("gcs/read_bytes_count", metric.WithDescription("The cumulative number of bytes read from GCS objects."), metric.WithUnit("By")) + gcsReaderCount, err7 := gcsMeter.Int64Counter("gcs/reader_count", metric.WithDescription("The cumulative number of GCS object readers opened or closed.")) gcsRequestCount, err8 := gcsMeter.Int64Counter("gcs/request_count", metric.WithDescription("The cumulative number of GCS requests processed.")) - gcsRequestLatency, err9 := gcsMeter.Float64Histogram("gcs/request_latency", metric.WithDescription("The latency of a GCS request."), metric.WithUnit("ms")) + gcsRequestLatency, err9 := gcsMeter.Float64Histogram("gcs/request_latencies", metric.WithDescription("The cumulative distribution of the GCS request latencies."), metric.WithUnit("ms")) fileCacheReadCount, err10 := fileCacheMeter.Int64Counter("file_cache/read_count", metric.WithDescription("Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false")) @@ -115,7 +115,7 @@ func NewOTelMetrics() (MetricHandle, error) { metric.WithDescription("The cumulative number of bytes read from file cache along with read type - Sequential/Random"), metric.WithUnit("By")) fileCacheReadLatency, err12 := fileCacheMeter.Float64Histogram("file_cache/read_latencies", - metric.WithDescription("Latency of read from file cache along with cache hit - true/false"), + metric.WithDescription("The cumulative distribution of the file cache read latencies along with cache hit - true/false"), metric.WithUnit("us"), defaultLatencyDistribution) From 52632e88150e54936ba9bf6101174ccfe73549bd Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 6 Jan 2025 13:53:41 +0530 Subject: [PATCH 0108/1298] Add Integration Tests for Write Failure Improvements (#2845) * Added Setup * Added e2e tests * added more e2e tests * created two test files. * minor fixes * used stretchr instead of ogletest * Added SetupSuite and TearDownSuite * Update stale_file_handle_synced_file_test.go * Updated Stale_file_handle_local_file_test.go * refactored code * Update tools/integration_tests/stale_handle/setup_test.go Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * Refactored code to to mounting ad unmounting in setup_test.go itself and moved commom parts of test to SetupTest * Added a file that contains common tests of synced object and local inode * lint fix * removed redundant code * Added package to CI Pipeline * Added e2e tests in CD Pipeline --------- Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> --- tools/cd_scripts/e2e_test.sh | 1 + tools/integration_tests/run_e2e_tests.sh | 1 + .../stale_handle/setup_test.go | 70 ++++++++++++ .../stale_file_handle_common_test.go | 71 ++++++++++++ .../stale_file_handle_local_file_test.go | 75 +++++++++++++ .../stale_file_handle_synced_file_test.go | 102 ++++++++++++++++++ .../util/client/gcs_helper.go | 31 +++--- tools/integration_tests/util/setup/setup.go | 7 +- 8 files changed, 340 insertions(+), 18 deletions(-) create mode 100644 tools/integration_tests/stale_handle/setup_test.go create mode 100644 tools/integration_tests/stale_handle/stale_file_handle_common_test.go create mode 100644 tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go create mode 100644 tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 80eb6517e5..ab054f646d 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -160,6 +160,7 @@ TEST_DIR_PARALLEL=( "concurrent_operations" "benchmarking" "mount_timeout" + "stale_handle" ) # These tests never become parallel as they are changing bucket permissions. diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index b06a08ce9d..d49dd4ebd2 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -83,6 +83,7 @@ TEST_DIR_PARALLEL=( "concurrent_operations" "benchmarking" "mount_timeout" + "stale_handle" ) # These tests never become parallel as it is changing bucket permissions. diff --git a/tools/integration_tests/stale_handle/setup_test.go b/tools/integration_tests/stale_handle/setup_test.go new file mode 100644 index 0000000000..b0b46bbe72 --- /dev/null +++ b/tools/integration_tests/stale_handle/setup_test.go @@ -0,0 +1,70 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stale_handle + +import ( + "context" + "log" + "os" + "testing" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +var ( + storageClient *storage.Client + ctx context.Context +) + +//////////////////////////////////////////////////////////////////////// +// TestMain +//////////////////////////////////////////////////////////////////////// + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + + // Create common storage client to be used in test. + ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() + + // If Mounted Directory flag is set, run tests for mounted directory. + setup.RunTestsForMountedDirectoryFlag(m) + // Else run tests for testBucket. + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + + // Define flag set to run the tests. + flagsSet := [][]string{ + {"--metadata-cache-ttl-secs=0", "--precondition-errors=true"}, + } + + if !testing.Short() { + setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc") + } + + successCode := static_mounting.RunTests(flagsSet, m) + + os.Exit(successCode) +} diff --git a/tools/integration_tests/stale_handle/stale_file_handle_common_test.go b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go new file mode 100644 index 0000000000..e36464dd90 --- /dev/null +++ b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go @@ -0,0 +1,71 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stale_handle + +import ( + "os" + "path" + + "cloud.google.com/go/storage" + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type staleFileHandleCommon struct { + f1 *os.File + suite.Suite +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (s *staleFileHandleCommon) TestClobberedFileSyncAndCloseThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + operations.WriteWithoutClose(s.f1, Content, s.T()) + // Replace the underlying object with a new generation. + err := WriteToObject(ctx, storageClient, path.Join(s.T().Name(), FileName1), FileContents, storage.Conditions{}) + assert.NoError(s.T(), err) + + err = s.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(s.T(), err) + err = s.f1.Close() + operations.ValidateStaleNFSFileHandleError(s.T(), err) +} + +func (s *staleFileHandleCommon) TestDeletedFileSyncAndCloseThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + _, err := s.f1.WriteString(Content) + assert.NoError(s.T(), err) + // Delete the file. + operations.RemoveFile(s.f1.Name()) + // Verify unlink operation succeeds. + operations.ValidateNoFileOrDirError(s.T(), s.f1.Name()) + // Attempt to write to file should not give any error. + operations.WriteWithoutClose(s.f1, Content2, s.T()) + + err = s.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(s.T(), err) + err = s.f1.Close() + operations.ValidateStaleNFSFileHandleError(s.T(), err) +} diff --git a/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go new file mode 100644 index 0000000000..f50e3af45b --- /dev/null +++ b/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go @@ -0,0 +1,75 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stale_handle + +import ( + "os" + "path" + "testing" + + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/stretchr/testify/suite" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + + "github.com/stretchr/testify/assert" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type staleFileHandleLocalFile struct { + staleFileHandleCommon +} + +func (s *staleFileHandleLocalFile) SetupTest() { + testDirPath := setup.SetupTestDirectory(s.T().Name()) + // Create a local file. + _, s.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, s.T()) +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (s *staleFileHandleLocalFile) TestUnlinkedDirectoryContainingSyncedAndLocalFilesCloseThrowsStaleFileHandleError() { + explicitDir := path.Join(setup.MntDir(), s.T().Name(), ExplicitDirName) + // Create explicit directory with one synced and one local file. + operations.CreateDirectory(explicitDir, s.T()) + CreateObjectInGCSTestDir(ctx, storageClient, path.Join(s.T().Name(), ExplicitDirName), ExplicitFileName1, "", s.T()) + _, f2 := CreateLocalFileInTestDir(ctx, storageClient, explicitDir, ExplicitLocalFileName1, s.T()) + err := os.RemoveAll(explicitDir) + assert.NoError(s.T(), err) + operations.ValidateNoFileOrDirError(s.T(), explicitDir+"/") + operations.ValidateNoFileOrDirError(s.T(), path.Join(explicitDir, ExplicitFileName1)) + operations.ValidateNoFileOrDirError(s.T(), path.Join(explicitDir, ExplicitLocalFileName1)) + // Validate writing content to unlinked local file does not throw error. + operations.WriteWithoutClose(f2, FileContents, s.T()) + + err = f2.Close() + + operations.ValidateStaleNFSFileHandleError(s.T(), err) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestStaleFileHandleLocalFileTest(t *testing.T) { + suite.Run(t, new(staleFileHandleLocalFile)) +} diff --git a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go new file mode 100644 index 0000000000..f82576bed2 --- /dev/null +++ b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go @@ -0,0 +1,102 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stale_handle + +import ( + "os" + "path" + "syscall" + "testing" + + "cloud.google.com/go/storage" + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +const Content = "foobar" +const Content2 = "foobar2" + +type staleFileHandleSyncedFile struct { + staleFileHandleCommon +} + +func (s *staleFileHandleSyncedFile) SetupTest() { + testDirPath := setup.SetupTestDirectory(s.T().Name()) + // Create an object on bucket + err := CreateObjectOnGCS(ctx, storageClient, path.Join(s.T().Name(), FileName1), GCSFileContent) + assert.NoError(s.T(), err) + s.f1, err = os.OpenFile(path.Join(testDirPath, FileName1), os.O_RDWR|syscall.O_DIRECT, operations.FilePermission_0600) + assert.NoError(s.T(), err) +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (s *staleFileHandleSyncedFile) TestClobberedFileReadThrowsStaleFileHandleError() { + // Replace the underlying object with a new generation. + err := WriteToObject(ctx, storageClient, path.Join(s.T().Name(), FileName1), FileContents, storage.Conditions{}) + assert.NoError(s.T(), err) + + buffer := make([]byte, GCSFileSize) + _, err = s.f1.Read(buffer) + + operations.ValidateStaleNFSFileHandleError(s.T(), err) +} + +func (s *staleFileHandleSyncedFile) TestClobberedFileFirstWriteThrowsStaleFileHandleError() { + // Replace the underlying object with a new generation. + err := WriteToObject(ctx, storageClient, path.Join(s.T().Name(), FileName1), FileContents, storage.Conditions{}) + assert.NoError(s.T(), err) + + _, err = s.f1.WriteString(Content) + + operations.ValidateStaleNFSFileHandleError(s.T(), err) + // Attempt to sync to file should not result in error as we first check if the + // content has been dirtied before clobbered check in Sync flow. + operations.SyncFile(s.f1, s.T()) +} + +func (s *staleFileHandleSyncedFile) TestRenamedFileSyncAndCloseThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + _, err := s.f1.WriteString(Content) + assert.NoError(s.T(), err) + err = operations.RenameFile(s.f1.Name(), path.Join(setup.MntDir(), s.T().Name(), FileName2)) + assert.NoError(s.T(), err) + // Attempt to write to file should not give any error. + _, err = s.f1.WriteString(Content2) + assert.NoError(s.T(), err) + + err = s.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(s.T(), err) + err = s.f1.Close() + operations.ValidateStaleNFSFileHandleError(s.T(), err) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestStaleFileHandleSyncedFileTest(t *testing.T) { + suite.Run(t, new(staleFileHandleSyncedFile)) +} diff --git a/tools/integration_tests/util/client/gcs_helper.go b/tools/integration_tests/util/client/gcs_helper.go index 55fd11206e..9130b60564 100644 --- a/tools/integration_tests/util/client/gcs_helper.go +++ b/tools/integration_tests/util/client/gcs_helper.go @@ -29,21 +29,22 @@ import ( ) const ( - FileName1 = "foo1" - FileName2 = "foo2" - FileName3 = "foo3" - ExplicitDirName = "explicit" - ExplicitFileName1 = "explicitFile1" - ImplicitDirName = "implicit" - ImplicitFileName1 = "implicitFile1" - FileContents = "testString" - SizeOfFileContents = 10 - GCSFileContent = "GCSteststring" - GCSFileSize = 13 - FilePerms = 0644 - SizeTruncate = 5 - NewFileName = "newName" - NewDirName = "newDirName" + FileName1 = "foo1" + FileName2 = "foo2" + FileName3 = "foo3" + ExplicitDirName = "explicit" + ExplicitFileName1 = "explicitFile1" + ExplicitLocalFileName1 = "explicitLocalFile1" + ImplicitDirName = "implicit" + ImplicitFileName1 = "implicitFile1" + FileContents = "testString" + SizeOfFileContents = 10 + GCSFileContent = "GCSteststring" + GCSFileSize = 13 + FilePerms = 0644 + SizeTruncate = 5 + NewFileName = "newName" + NewDirName = "newDirName" ) func CreateImplicitDir(ctx context.Context, storageClient *storage.Client, diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 9d8922e9b2..850fc36f84 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -370,11 +370,12 @@ func CleanUpDir(directoryPath string) { } } -// SetupTestDirectory creates a testDirectory in the mounted directory and cleans up -// any content present in it. +// SetupTestDirectory creates a test directory hierarchy in the mounted directory, +// cleaning up any content present. It takes a testDirName which can include +// slashes to create nested directories (e.g., "a/b/c"). func SetupTestDirectory(testDirName string) string { testDirPath := path.Join(MntDir(), testDirName) - err := os.Mkdir(testDirPath, DirPermission_0755) + err := os.MkdirAll(testDirPath, DirPermission_0755) if err != nil && !strings.Contains(err.Error(), "file exists") { log.Printf("Error while setting up directory %s for testing: %v", testDirPath, err) } From edc5e7bd124bd28e60d373e654e150c8b508fb74 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 6 Jan 2025 21:12:36 +0530 Subject: [PATCH 0109/1298] code changes to separate out flush and sync flow (#2856) --- internal/fs/fs.go | 97 ++- internal/fs/inode/file.go | 59 +- .../fs/inode/file_streaming_writes_test.go | 218 +++++- internal/fs/inode/file_test.go | 626 +++++++++++------- 4 files changed, 735 insertions(+), 265 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index b2c4a7c5b0..3e577ba0bf 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1108,56 +1108,99 @@ func (fs *fileSystem) lookUpOrCreateChildDirInode( return child, nil } -// Synchronize the supplied file inode to GCS, updating the index as +// promoteToGenerationBacked updates the file system maps for the given file inode +// after it has been synced to GCS. +// The inode is removed from the localFileInodes map and added to the +// generationBackedInodes map. +// +// LOCKS_EXCLUDED(fs.mu) +// LOCKS_REQUIRED(f) +func (fs *fileSystem) promoteToGenerationBacked(f *inode.FileInode) { + fs.mu.Lock() + delete(fs.localFileInodes, f.Name()) + if _, ok := fs.generationBackedInodes[f.Name()]; !ok { + fs.generationBackedInodes[f.Name()] = f + } + fs.mu.Unlock() + + // We need not update fileIndex: + // + // We've held the inode lock the whole time, so there's no way that this + // inode could have been booted from the index. Therefore, if it's not in the + // index at the moment, it must not have been in there when we started. That + // is, it must have been clobbered remotely. + // + // In other words, either this inode is still in the index or it has been + // clobbered and *should* be anonymous. +} + +// Flushes the supplied file inode to GCS, updating the index as // appropriate. // // LOCKS_EXCLUDED(fs.mu) // LOCKS_REQUIRED(f) -func (fs *fileSystem) syncFile( +func (fs *fileSystem) flushFile( ctx context.Context, - f *inode.FileInode) (err error) { + f *inode.FileInode) error { // SyncFile can be triggered for unlinked files if the fileHandle is open by // same or another user. This indicates a potential file clobbering scenario: // - The file was deleted (unlinked) while a handle to it was still open. if f.IsLocal() && f.IsUnlinked() { - err = &gcsfuse_errors.FileClobberedError{ + return &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("file %s was unlinked while it was still open, indicating file clobbering", f.Name().LocalName()), } - return } - // Sync the inode. - err = f.Sync(ctx) + // Flush the inode. + err := f.Flush(ctx) if err != nil { err = fmt.Errorf("FileInode.Sync: %w", err) // If the inode was local file inode, treat it as unlinked. fs.mu.Lock() delete(fs.localFileInodes, f.Name()) fs.mu.Unlock() - return + return err } - // Once the inode is synced to GCS, it is no longer an localFileInode. - // Delete the entry from localFileInodes map and add it to generationBackedInodes. - fs.mu.Lock() - delete(fs.localFileInodes, f.Name()) - _, ok := fs.generationBackedInodes[f.Name()] - if !ok { - fs.generationBackedInodes[f.Name()] = f + // Promote the inode to generationBackedInodes in fs maps. + fs.promoteToGenerationBacked(f) + return nil +} + +// Synchronizes the supplied file inode to GCS, updating the index as +// appropriate. +// +// LOCKS_EXCLUDED(fs.mu) +// LOCKS_REQUIRED(f) +func (fs *fileSystem) syncFile( + ctx context.Context, + f *inode.FileInode) error { + // SyncFile can be triggered for unlinked files if the fileHandle is open by + // same or another user. This indicates a potential file clobbering scenario: + // - The file was deleted (unlinked) while a handle to it was still open. + if f.IsLocal() && f.IsUnlinked() { + return &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("file %s was unlinked while it was still open, indicating file clobbering", f.Name().LocalName()), + } } - fs.mu.Unlock() - // We need not update fileIndex: - // - // We've held the inode lock the whole time, so there's no way that this - // inode could have been booted from the index. Therefore, if it's not in the - // index at the moment, it must not have been in there when we started. That - // is, it must have been clobbered remotely. - // - // In other words, either this inode is still in the index or it has been - // clobbered and *should* be anonymous. + // Sync the inode. + gcsSynced, err := f.Sync(ctx) + if err != nil { + err = fmt.Errorf("FileInode.Sync: %w", err) + // If the inode was local file inode, treat it as unlinked. + fs.mu.Lock() + delete(fs.localFileInodes, f.Name()) + fs.mu.Unlock() + return err + } - return + // If gcsSynced is true, it means the inode was fully synced to GCS In this + // case, we need to promote the inode to generationBackedInodes in fs maps. + if gcsSynced { + fs.promoteToGenerationBacked(f) + } + return nil } // Decrement the supplied inode's lookup count, destroying it if the inode says @@ -2546,7 +2589,7 @@ func (fs *fileSystem) FlushFile( defer in.Unlock() // Sync it. - if err := fs.syncFile(ctx, in); err != nil { + if err := fs.flushFile(ctx, in); err != nil { return err } diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 1b415b74ba..7023fe6b93 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -582,7 +582,7 @@ func (f *FileInode) writeUsingTempFile(ctx context.Context, data []byte, offset // LOCKS_REQUIRED(f.mu) func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, offset int64) error { err := f.bwh.Write(data, offset) - if err == bufferedwrites.ErrOutOfOrderWrite || err == bufferedwrites.ErrUploadFailure { + if errors.Is(err, bufferedwrites.ErrOutOfOrderWrite) || errors.Is(err, bufferedwrites.ErrUploadFailure) { // Finalize the object. flushErr := f.flushUsingBufferedWriteHandler() if flushErr != nil { @@ -729,20 +729,42 @@ func (f *FileInode) fetchLatestGcsObject(ctx context.Context) (*gcs.Object, erro return latestGcsObj, err } -// Sync writes out contents to GCS. If this fails due to the generation having been -// clobbered, failure is propagated back to the calling function as an error. +// Sync writes out contents to GCS. If this fails due to the generation +// having been clobbered, failure is propagated back to the calling +// function as an error. // -// After this method succeeds, SourceGeneration will return the new generation -// by which this inode should be known (which may be the same as before). If it +// For buffered writes, this method only waits for any partial buffers to be +// uploaded to GCS. It does not guarantee that the entire contents of the file +// have been persisted. +// +// For non-buffered writes, this method writes the entire contents to GCS. +// If this method succeeds, SourceGeneration will return the new generation by +// which this inode should be known (which may be the same as before). If it // fails, the generation will not change. // // LOCKS_REQUIRED(f.mu) -func (f *FileInode) Sync(ctx context.Context) (err error) { +func (f *FileInode) Sync(ctx context.Context) (gcsSynced bool, err error) { // If we have not been dirtied, there is nothing to do. - if f.content == nil { + if f.content == nil && f.bwh == nil { return } + if f.bwh != nil { + // bwh.Sync does not finalize the upload, so return gcsSynced as false. + return false, f.bwh.Sync() + } + err = f.syncUsingContent(ctx) + if err != nil { + return false, err + } + return true, nil +} + +// syncUsingContent syncs the inode content to GCS. It fetches the latest GCS +// object, syncs the content and updates the inode state. +// +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) syncUsingContent(ctx context.Context) (err error) { latestGcsObj, err := f.fetchLatestGcsObject(ctx) if err != nil { return @@ -772,6 +794,29 @@ func (f *FileInode) Sync(ctx context.Context) (err error) { return } +// Flush writes out contents to GCS. If this fails due to the generation +// having been clobbered, failure is propagated back to the calling +// function as an error. +// +// After this method succeeds, SourceGeneration will return the new generation +// by which this inode should be known (which may be the same as before). If it +// fails, the generation will not change. +// +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) Flush(ctx context.Context) (err error) { + // If we have not been dirtied, there is nothing to do. + if f.content == nil && f.bwh == nil { + return + } + + // Flush using the appropriate method based on whether we're using a + // buffered write handler. + if f.bwh != nil { + return f.flushUsingBufferedWriteHandler() + } + return f.syncUsingContent(ctx) +} + func (f *FileInode) updateInodeStateAfterSync(minObj *gcs.MinObject) { if minObj != nil && !f.localFileCache { f.src = *minObj diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index be0dd92b91..45028c1752 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -66,6 +66,10 @@ func (t *FileStreamingWritesTest) TearDownTest() { t.in.Unlock() } +func (t *FileStreamingWritesTest) SetupSubTest() { + t.SetupTest() +} + func (t *FileStreamingWritesTest) createInode(fileName string, fileType string) { if fileType != emptyGCSFile && fileType != localFile { t.T().Errorf("fileType should be either local or empty") @@ -165,6 +169,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF require.NotNil(t.T(), t.in.bwh) assert.Equal(t.T(), int64(4), t.in.bwh.WriteFileInfo().TotalSize) + // Out of order write. err = t.in.Write(t.ctx, []byte("hello"), tc.offset) require.Nil(t.T(), err) @@ -177,14 +182,13 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF assert.Equal(t.T(), uint64(len(tc.expectedContent)), attrs.Size) assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) // sync file and validate content - err = t.in.Sync(t.ctx) + gcsSynced, err := t.in.Sync(t.ctx) require.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) assert.Nil(t.T(), err) assert.Equal(t.T(), tc.expectedContent, string(contents)) - t.TearDownTest() - t.SetupTest() }) } } @@ -249,3 +253,211 @@ func (t *FileStreamingWritesTest) TestUnlinkEmptySyncedFile() { assert.True(t.T(), t.in.unlinked) } + +func (t *FileStreamingWritesTest) TestWriteToFileAndFlush() { + testCases := []struct { + name string + isLocal bool + }{ + { + name: "local_file", + isLocal: true, + }, + { + name: "synced_empty_file", + isLocal: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + if tc.isLocal { + assert.True(t.T(), t.in.IsLocal()) + } else { + t.createInode(fileName, emptyGCSFile) + assert.False(t.T(), t.in.IsLocal()) + } + // Write some content to temp file. + err := t.in.Write(t.ctx, []byte("tacos"), 0) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.bwh) + t.clock.AdvanceTime(10 * time.Second) + + err = t.in.Flush(t.ctx) + + require.Nil(t.T(), err) + // Ensure bwh cleared. + assert.Nil(t.T(), t.in.bwh) + // Verify that fileInode is no more local + assert.False(t.T(), t.in.IsLocal()) + // Check attributes. + attrs, err := t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(len("tacos")), attrs.Size) + assert.Equal(t.T(), t.clock.Now().UTC(), attrs.Mtime.UTC()) + // Validate Object on GCS. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + m, _, err := t.bucket.StatObject(t.ctx, statReq) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(len("tacos")), m.Size) + // Mtime metadata is not written for buffered writes. + assert.Equal(t.T(), "", m.Metadata["gcsfuse_mtime"]) + contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "tacos", string(contents)) + }) + } +} + +func (t *FileStreamingWritesTest) TestFlushEmptyFile() { + testCases := []struct { + name string + isLocal bool + }{ + { + name: "local_file", + isLocal: true, + }, + { + name: "synced_empty_file", + isLocal: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + if tc.isLocal { + assert.True(t.T(), t.in.IsLocal()) + } else { + t.createInode(fileName, emptyGCSFile) + assert.False(t.T(), t.in.IsLocal()) + } + t.clock.AdvanceTime(10 * time.Second) + + err := t.in.Flush(t.ctx) + + require.Nil(t.T(), err) + // Ensure bwh cleared. + assert.Nil(t.T(), t.in.bwh) + // Verify that fileInode is no more local + assert.False(t.T(), t.in.IsLocal()) + // Check attributes. + attrs, err := t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(0), attrs.Size) + // For synced file, mtime is updated by SetInodeAttributes call. + if tc.isLocal { + assert.Equal(t.T(), t.clock.Now().UTC(), attrs.Mtime.UTC()) + } + // Validate Object on GCS. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + m, _, err := t.bucket.StatObject(t.ctx, statReq) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(0), m.Size) + // Mtime metadata is not written for buffered writes. + assert.Equal(t.T(), "", m.Metadata["gcsfuse_mtime"]) + contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "", string(contents)) + }) + } +} + +func (t *FileStreamingWritesTest) TestFlushClobberedFile() { + testCases := []struct { + name string + isLocal bool + }{ + { + name: "local_file", + isLocal: true, + }, + { + name: "synced_empty_file", + isLocal: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + if tc.isLocal { + assert.True(t.T(), t.in.IsLocal()) + } else { + t.createInode(fileName, emptyGCSFile) + assert.False(t.T(), t.in.IsLocal()) + } + t.clock.AdvanceTime(10 * time.Second) + // Clobber the file. + objWritten, err := storageutil.CreateObject(t.ctx, t.bucket, fileName, []byte("taco")) + require.Nil(t.T(), err) + + err = t.in.Flush(t.ctx) + + require.Error(t.T(), err) + var fileClobberedError *gcsfuse_errors.FileClobberedError + assert.ErrorAs(t.T(), err, &fileClobberedError) + // Validate Object on GCS not updated. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + objGot, _, err := t.bucket.StatObject(t.ctx, statReq) + assert.Nil(t.T(), err) + assert.Equal(t.T(), storageutil.ConvertObjToMinObject(objWritten), objGot) + }) + } +} + +func (t *FileStreamingWritesTest) TestWriteToFileAndSync() { + testCases := []struct { + name string + isLocal bool + }{ + { + name: "local_file", + isLocal: true, + }, + { + name: "synced_empty_file", + isLocal: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + if tc.isLocal { + assert.True(t.T(), t.in.IsLocal()) + } else { + t.createInode(fileName, emptyGCSFile) + assert.False(t.T(), t.in.IsLocal()) + } + // Write some content to temp file. + err := t.in.Write(t.ctx, []byte("tacos"), 0) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.in.bwh) + t.clock.AdvanceTime(10 * time.Second) + + gcsSynced, err := t.in.Sync(t.ctx) + + require.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) + // Ensure bwh not cleared. + assert.NotNil(t.T(), t.in.bwh) + // Validate Object not written on GCS. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + m, _, err := t.bucket.StatObject(t.ctx, statReq) + if tc.isLocal { + require.Error(t.T(), err) + var notFoundErr *gcs.NotFoundError + assert.ErrorAs(t.T(), err, ¬FoundErr) + } else { + assert.NoError(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), uint64(0), m.Size) + } + }) + } +} diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index dccb6db4c2..06c68c9a0e 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -71,6 +71,10 @@ func TestFileTestSuite(t *testing.T) { suite.Run(t, new(FileTest)) } +func (t *FileTest) SetupSubTest() { + t.SetupTest() +} + func (t *FileTest) SetupTest() { // Enabling invariant check for all tests. syncutil.EnableInvariantChecking() @@ -359,267 +363,404 @@ func (t *FileTest) TestTruncate() { } func (t *FileTest) TestWriteThenSync() { - var attrs fuseops.InodeAttributes - var err error + testcases := []struct { + name string + callSync bool + }{ + { + name: "sync", + callSync: true, + }, + { + name: "flush", + callSync: false, + }, + } - assert.Equal(t.T(), "taco", t.initialContents) + for _, tc := range testcases { + t.Run(tc.name, func() { + var attrs fuseops.InodeAttributes + var err error - // Overwite a byte. - t.clock.AdvanceTime(time.Second) - writeTime := t.clock.Now() + assert.Equal(t.T(), "taco", t.initialContents) - err = t.in.Write(t.ctx, []byte("p"), 0) - assert.Nil(t.T(), err) + // Overwrite a byte. + t.clock.AdvanceTime(time.Second) + writeTime := t.clock.Now() - t.clock.AdvanceTime(time.Second) + err = t.in.Write(t.ctx, []byte("p"), 0) + assert.Nil(t.T(), err) - // Sync. - err = t.in.Sync(t.ctx) - assert.Nil(t.T(), err) + t.clock.AdvanceTime(time.Second) - // The generation should have advanced. - assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) + if tc.callSync { + gcsSynced, err := t.in.Sync(t.ctx) + assert.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) + } else { + err = t.in.Flush(t.ctx) + assert.Nil(t.T(), err) + } - // Stat the current object in the bucket. - statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} - m, _, err := t.bucket.StatObject(t.ctx, statReq) + // The generation should have advanced. + assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) - assert.Nil(t.T(), err) - assert.NotNil(t.T(), m) - assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) - assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) - assert.Equal(t.T(), uint64(len("paco")), m.Size) - assert.Equal(t.T(), - writeTime.UTC().Format(time.RFC3339Nano), - m.Metadata["gcsfuse_mtime"]) + // Stat the current object in the bucket. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + m, _, err := t.bucket.StatObject(t.ctx, statReq) - // Read the object's contents. - contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(len("paco")), m.Size) + assert.Equal(t.T(), + writeTime.UTC().Format(time.RFC3339Nano), + m.Metadata["gcsfuse_mtime"]) - assert.Nil(t.T(), err) - assert.Equal(t.T(), "paco", string(contents)) + // Read the object's contents. + contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - // Check attributes. - attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "paco", string(contents)) + + // Check attributes. + attrs, err = t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) - assert.Equal(t.T(), uint64(len("paco")), attrs.Size) - assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) + assert.Equal(t.T(), uint64(len("paco")), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) + }) + } } func (t *FileTest) TestWriteToLocalFileThenSync() { - var attrs fuseops.InodeAttributes - var err error - // Create a local file inode. - t.createInodeWithLocalParam("test", true) - // Create a temp file for the local inode created above. - err = t.in.CreateBufferedOrTempWriter(t.ctx) - assert.Nil(t.T(), err) - // Write some content to temp file. - t.clock.AdvanceTime(time.Second) - writeTime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("tacos"), 0) - assert.Nil(t.T(), err) - t.clock.AdvanceTime(time.Second) + testcases := []struct { + name string + callSync bool + }{ + { + name: "sync", + callSync: true, + }, + { + name: "flush", + callSync: false, + }, + } - // Sync. - err = t.in.Sync(t.ctx) + for _, tc := range testcases { + t.Run(tc.name, func() { + var attrs fuseops.InodeAttributes + var err error + // Create a local file inode. + t.createInodeWithLocalParam("test", true) + // Create a temp file for the local inode created above. + err = t.in.CreateBufferedOrTempWriter(t.ctx) + assert.Nil(t.T(), err) + // Write some content to temp file. + t.clock.AdvanceTime(time.Second) + writeTime := t.clock.Now() + err = t.in.Write(t.ctx, []byte("tacos"), 0) + assert.Nil(t.T(), err) + t.clock.AdvanceTime(time.Second) - assert.Nil(t.T(), err) - // Verify that fileInode is no more local - assert.False(t.T(), t.in.IsLocal()) - // Stat the current object in the bucket. - statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} - m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) - assert.NotNil(t.T(), m) - assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) - assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) - assert.Equal(t.T(), uint64(len("tacos")), m.Size) - assert.Equal(t.T(), - writeTime.UTC().Format(time.RFC3339Nano), - m.Metadata["gcsfuse_mtime"]) - // Read the object's contents. - contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - assert.Nil(t.T(), err) - assert.Equal(t.T(), "tacos", string(contents)) - // Check attributes. - attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) - assert.Equal(t.T(), uint64(len("tacos")), attrs.Size) - assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) + if tc.callSync { + gcsSynced, err := t.in.Sync(t.ctx) + assert.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) + } else { + err = t.in.Flush(t.ctx) + assert.Nil(t.T(), err) + } + + // Verify that fileInode is no more local + assert.False(t.T(), t.in.IsLocal()) + // Stat the current object in the bucket. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + m, _, err := t.bucket.StatObject(t.ctx, statReq) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(len("tacos")), m.Size) + assert.Equal(t.T(), + writeTime.UTC().Format(time.RFC3339Nano), + m.Metadata["gcsfuse_mtime"]) + // Read the object's contents. + contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "tacos", string(contents)) + // Check attributes. + attrs, err = t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(len("tacos")), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) + }) + } } func (t *FileTest) TestSyncEmptyLocalFile() { - var attrs fuseops.InodeAttributes - var err error - // Create a local file inode. - t.createInodeWithLocalParam("test", true) - creationTime := t.clock.Now() - // Create a temp file for the local inode created above. - err = t.in.CreateBufferedOrTempWriter(t.ctx) - assert.Nil(t.T(), err) + testcases := []struct { + name string + callSync bool + }{ + { + name: "sync", + callSync: true, + }, + { + name: "flush", + callSync: false, + }, + } - // Sync. - err = t.in.Sync(t.ctx) + for _, tc := range testcases { + t.Run(tc.name, func() { + var attrs fuseops.InodeAttributes + var err error + // Create a local file inode. + t.createInodeWithLocalParam("test", true) + creationTime := t.clock.Now() + // Create a temp file for the local inode created above. + err = t.in.CreateBufferedOrTempWriter(t.ctx) + assert.Nil(t.T(), err) - assert.Nil(t.T(), err) - // Verify that fileInode is no more local - assert.False(t.T(), t.in.IsLocal()) - // Stat the current object in the bucket. - statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} - m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) - assert.NotNil(t.T(), m) - assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) - assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) - assert.Equal(t.T(), uint64(0), m.Size) - // Validate the mtime. - mtimeInBucket, ok := m.Metadata["gcsfuse_mtime"] - assert.True(t.T(), ok) - mtime, _ := time.Parse(time.RFC3339Nano, mtimeInBucket) - assert.WithinDuration(t.T(), mtime, creationTime, Delta) - // Read the object's contents. - contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - assert.Nil(t.T(), err) - assert.Equal(t.T(), "", string(contents)) - // Check attributes. - attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) - assert.Equal(t.T(), uint64(0), attrs.Size) + if tc.callSync { + gcsSynced, err := t.in.Sync(t.ctx) + assert.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) + } else { + err = t.in.Flush(t.ctx) + assert.Nil(t.T(), err) + } + + // Verify that fileInode is no more local + assert.False(t.T(), t.in.IsLocal()) + // Stat the current object in the bucket. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + m, _, err := t.bucket.StatObject(t.ctx, statReq) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(0), m.Size) + // Validate the mtime. + mtimeInBucket, ok := m.Metadata["gcsfuse_mtime"] + assert.True(t.T(), ok) + mtime, _ := time.Parse(time.RFC3339Nano, mtimeInBucket) + assert.WithinDuration(t.T(), mtime, creationTime, Delta) + // Read the object's contents. + contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "", string(contents)) + // Check attributes. + attrs, err = t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + assert.Equal(t.T(), uint64(0), attrs.Size) + }) + } } func (t *FileTest) TestAppendThenSync() { - var attrs fuseops.InodeAttributes - var err error + testcases := []struct { + name string + callSync bool + }{ + { + name: "sync", + callSync: true, + }, + { + name: "flush", + callSync: false, + }, + } - assert.Equal(t.T(), "taco", t.initialContents) + for _, tc := range testcases { + t.Run(tc.name, func() { + var attrs fuseops.InodeAttributes + var err error - // Append some data. - t.clock.AdvanceTime(time.Second) - writeTime := t.clock.Now() + assert.Equal(t.T(), "taco", t.initialContents) - err = t.in.Write(t.ctx, []byte("burrito"), int64(len("taco"))) - assert.Nil(t.T(), err) + // Append some data. + t.clock.AdvanceTime(time.Second) + writeTime := t.clock.Now() - t.clock.AdvanceTime(time.Second) + err = t.in.Write(t.ctx, []byte("burrito"), int64(len("taco"))) + assert.Nil(t.T(), err) - // Sync. - err = t.in.Sync(t.ctx) - assert.Nil(t.T(), err) + t.clock.AdvanceTime(time.Second) - // The generation should have advanced. - assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) + if tc.callSync { + gcsSynced, err := t.in.Sync(t.ctx) + assert.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) + } else { + err = t.in.Flush(t.ctx) + assert.Nil(t.T(), err) + } - // Stat the current object in the bucket. - statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} - m, _, err := t.bucket.StatObject(t.ctx, statReq) + // The generation should have advanced. + assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) - assert.Nil(t.T(), err) - assert.NotNil(t.T(), m) - assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) - assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) - assert.Equal(t.T(), uint64(len("tacoburrito")), m.Size) - assert.Equal(t.T(), - writeTime.UTC().Format(time.RFC3339Nano), - m.Metadata["gcsfuse_mtime"]) + // Stat the current object in the bucket. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + m, _, err := t.bucket.StatObject(t.ctx, statReq) - // Read the object's contents. - contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(len("tacoburrito")), m.Size) + assert.Equal(t.T(), + writeTime.UTC().Format(time.RFC3339Nano), + m.Metadata["gcsfuse_mtime"]) - assert.Nil(t.T(), err) - assert.Equal(t.T(), "tacoburrito", string(contents)) + // Read the object's contents. + contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - // Check attributes. - attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "tacoburrito", string(contents)) - assert.Equal(t.T(), uint64(len("tacoburrito")), attrs.Size) - assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) + // Check attributes. + attrs, err = t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + + assert.Equal(t.T(), uint64(len("tacoburrito")), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) + }) + } } func (t *FileTest) TestTruncateDownwardThenSync() { - var attrs fuseops.InodeAttributes - var err error - - // Truncate downward. - t.clock.AdvanceTime(time.Second) - truncateTime := t.clock.Now() + testcases := []struct { + name string + callSync bool + }{ + { + name: "sync", + callSync: true, + }, + { + name: "flush", + callSync: false, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func() { + var attrs fuseops.InodeAttributes + var err error - err = t.in.Truncate(t.ctx, 2) - assert.Nil(t.T(), err) + // Truncate downward. + t.clock.AdvanceTime(time.Second) + truncateTime := t.clock.Now() - t.clock.AdvanceTime(time.Second) + err = t.in.Truncate(t.ctx, 2) + assert.Nil(t.T(), err) - // Sync. - err = t.in.Sync(t.ctx) - assert.Nil(t.T(), err) + t.clock.AdvanceTime(time.Second) - // The generation should have advanced. - assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) + if tc.callSync { + gcsSynced, err := t.in.Sync(t.ctx) + assert.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) + } else { + err = t.in.Flush(t.ctx) + assert.Nil(t.T(), err) + } - // Stat the current object in the bucket. - statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} - m, _, err := t.bucket.StatObject(t.ctx, statReq) + // The generation should have advanced. + assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) - assert.Nil(t.T(), err) - assert.NotNil(t.T(), m) - assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) - assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) - assert.Equal(t.T(), uint64(2), m.Size) - assert.Equal(t.T(), - truncateTime.UTC().Format(time.RFC3339Nano), - m.Metadata["gcsfuse_mtime"]) + // Stat the current object in the bucket. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + m, _, err := t.bucket.StatObject(t.ctx, statReq) - // Check attributes. - attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(2), m.Size) + assert.Equal(t.T(), + truncateTime.UTC().Format(time.RFC3339Nano), + m.Metadata["gcsfuse_mtime"]) + + // Check attributes. + attrs, err = t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) - assert.Equal(t.T(), uint64(2), attrs.Size) - assert.Equal(t.T(), attrs.Mtime, truncateTime.UTC()) + assert.Equal(t.T(), uint64(2), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, truncateTime.UTC()) + }) + } } -func (t *FileTest) TestTruncateUpwardThenSync() { - var attrs fuseops.InodeAttributes - var err error +func (t *FileTest) TestTruncateUpwardThenFlush() { + testcases := []struct { + name string + callSync bool + }{ + { + name: "sync", + callSync: true, + }, + { + name: "flush", + callSync: false, + }, + } - assert.Equal(t.T(), 4, len(t.initialContents)) + for _, tc := range testcases { + t.Run(tc.name, func() { + var attrs fuseops.InodeAttributes + var err error - // Truncate upward. - t.clock.AdvanceTime(time.Second) - truncateTime := t.clock.Now() + assert.Equal(t.T(), 4, len(t.initialContents)) - err = t.in.Truncate(t.ctx, 6) - assert.Nil(t.T(), err) + // Truncate upward. + t.clock.AdvanceTime(time.Second) + truncateTime := t.clock.Now() - t.clock.AdvanceTime(time.Second) + err = t.in.Truncate(t.ctx, 6) + assert.Nil(t.T(), err) - // Sync. - err = t.in.Sync(t.ctx) - assert.Nil(t.T(), err) + t.clock.AdvanceTime(time.Second) - // The generation should have advanced. - assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) + if tc.callSync { + gcsSynced, err := t.in.Sync(t.ctx) + assert.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) + } else { + err = t.in.Flush(t.ctx) + assert.Nil(t.T(), err) + } - // Stat the current object in the bucket. - statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} - m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Equal(t.T(), - truncateTime.UTC().Format(time.RFC3339Nano), - m.Metadata["gcsfuse_mtime"]) + // The generation should have advanced. + assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) - assert.Nil(t.T(), err) - assert.NotNil(t.T(), m) - assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) - assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) - assert.Equal(t.T(), uint64(6), m.Size) + // Stat the current object in the bucket. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + m, _, err := t.bucket.StatObject(t.ctx, statReq) + assert.Equal(t.T(), + truncateTime.UTC().Format(time.RFC3339Nano), + m.Metadata["gcsfuse_mtime"]) - // Check attributes. - attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) + assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), uint64(6), m.Size) - assert.Equal(t.T(), uint64(6), attrs.Size) - assert.Equal(t.T(), attrs.Mtime, truncateTime.UTC()) + // Check attributes. + attrs, err = t.in.Attributes(t.ctx) + assert.Nil(t.T(), err) + + assert.Equal(t.T(), uint64(6), attrs.Size) + assert.Equal(t.T(), attrs.Mtime, truncateTime.UTC()) + }) + } } func (t *FileTest) TestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes() { @@ -846,39 +987,64 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { } } -func (t *FileTest) TestSync_Clobbered() { - var err error +func (t *FileTest) TestSyncFlush_Clobbered() { + testcases := []struct { + name string + callSync bool + }{ + { + name: "sync", + callSync: true, + }, + { + name: "flush", + callSync: false, + }, + } - // Truncate downward. - err = t.in.Truncate(t.ctx, 2) - assert.Nil(t.T(), err) + for _, tc := range testcases { + t.Run(tc.name, func() { + var err error - // Clobber the backing object. - newObj, err := storageutil.CreateObject( - t.ctx, - t.bucket, - t.in.Name().GcsObjectName(), - []byte("burrito")) + // Truncate downward. + err = t.in.Truncate(t.ctx, 2) + assert.Nil(t.T(), err) - assert.Nil(t.T(), err) + // Clobber the backing object. + newObj, err := storageutil.CreateObject( + t.ctx, + t.bucket, + t.in.Name().GcsObjectName(), + []byte("burrito")) - // Sync. The call should not succeed, and we expect a FileClobberedError. - err = t.in.Sync(t.ctx) + assert.Nil(t.T(), err) - // Check if the error is a FileClobberedError - var fcErr *gcsfuse_errors.FileClobberedError - assert.True(t.T(), errors.As(err, &fcErr), "expected FileClobberedError but got %v", err) - assert.Equal(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) - assert.Equal(t.T(), t.backingObj.MetaGeneration, t.in.SourceGeneration().Metadata) + if tc.callSync { + var gcsSynced bool + // Sync. The call should not succeed, and we expect a FileClobberedError. + gcsSynced, err = t.in.Sync(t.ctx) + assert.False(t.T(), gcsSynced) + } else { + // Flush. The call should not succeed, and we expect a FileClobberedError. + err = t.in.Flush(t.ctx) + } - // The object in the bucket should not have been changed. - statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} - m, _, err := t.bucket.StatObject(t.ctx, statReq) + // Check if the error is a FileClobberedError + var fcErr *gcsfuse_errors.FileClobberedError + assert.True(t.T(), errors.As(err, &fcErr), "expected FileClobberedError but got %v", err) + assert.Equal(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) + assert.Equal(t.T(), t.backingObj.MetaGeneration, t.in.SourceGeneration().Metadata) - assert.Nil(t.T(), err) - assert.NotNil(t.T(), m) - assert.Equal(t.T(), newObj.Generation, m.Generation) - assert.Equal(t.T(), newObj.Size, m.Size) + // The object in the bucket should not have been changed. + statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} + m, _, err := t.bucket.StatObject(t.ctx, statReq) + + assert.Nil(t.T(), err) + assert.NotNil(t.T(), m) + assert.Equal(t.T(), newObj.Generation, m.Generation) + assert.Equal(t.T(), newObj.Size, m.Size) + }) + } } func (t *FileTest) TestOpenReader_ThrowsFileClobberedError() { @@ -979,8 +1145,9 @@ func (t *FileTest) TestSetMtime_ContentDirty() { assert.Equal(t.T(), attrs.Mtime, mtime) // Sync. - err = t.in.Sync(t.ctx) + gcsSynced, err := t.in.Sync(t.ctx) assert.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) // Now the object in the bucket should have the appropriate mtime. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} @@ -1068,6 +1235,7 @@ func (t *FileTest) TestSetMtimeForUnlinkedFileIsNoOp() { func (t *FileTest) TestTestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() { var err error var attrs fuseops.InodeAttributes + // Create a local file inode. t.createInodeWithLocalParam("test", true) createTime := t.in.mtimeClock.Now() @@ -1099,6 +1267,7 @@ func (t *FileTest) TestTestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() func (t *FileTest) TestSetMtimeForLocalFileWhenStreamingWritesAreEnabled() { var err error var attrs fuseops.InodeAttributes + // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.config = &cfg.Config{Write: *getWriteConfig()} @@ -1196,6 +1365,7 @@ func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateFileForNonLocalFile func (t *FileTest) TestUnlinkLocalFile() { var err error + // Create a local file inode. t.createInodeWithLocalParam("test", true) // Create a temp file for the local inode created above. From 94c5ebd31c3123513ee73dcea434c8d3b66695a9 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 7 Jan 2025 11:40:52 +0530 Subject: [PATCH 0110/1298] Enable OTel metrics by default (#2859) --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/config_validation_test.go | 2 ++ cmd/root_test.go | 35 +++++++++++++++++++++++------------ 4 files changed, 27 insertions(+), 14 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 890194d286..7d918a25f6 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -327,7 +327,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-nonexistent-type-cache", "", false, "Once set, if an inode is not found in GCS, a type cache entry with type NonexistentType will be created. This also means new file/dir created might not be seen. For example, if this flag is set, and metadata-cache-ttl-secs is set, then if we create the same file/node in the meantime using the same mount, since we are not refreshing the cache, it will still return nil.") - flagSet.BoolP("enable-otel", "", false, "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus.") + flagSet.BoolP("enable-otel", "", true, "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus.") if err := flagSet.MarkHidden("enable-otel"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 5c90a9b221..8af226ec22 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -582,7 +582,7 @@ flag-name: "enable-otel" type: "bool" usage: "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus." - default: false + default: true hide-flag: true - config-path: "metrics.prometheus-port" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index e5501e49ff..b0e8c06b4d 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -804,6 +804,7 @@ func TestValidateConfigFile_MetricsConfigSuccessful(t *testing.T) { StackdriverExportInterval: 0, CloudMetricsExportIntervalSecs: 0, PrometheusPort: 0, + EnableOtel: true, }, }, { @@ -811,6 +812,7 @@ func TestValidateConfigFile_MetricsConfigSuccessful(t *testing.T) { configFile: "testdata/valid_config.yaml", expectedConfig: &cfg.MetricsConfig{ CloudMetricsExportIntervalSecs: 10, + EnableOtel: true, }, }, } diff --git a/cmd/root_test.go b/cmd/root_test.go index 40a28412ad..2aabee5333 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -849,7 +849,7 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { name: "default", args: []string{"gcsfuse", "abc", "pqr"}, expected: &cfg.MetricsConfig{ - EnableOtel: false, + EnableOtel: true, }, }, { @@ -874,14 +874,21 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { }, }, { - name: "cloud-metrics-export-interval-secs-positive", - args: []string{"gcsfuse", "--cloud-metrics-export-interval-secs=10", "abc", "pqr"}, - expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 10}, + name: "cloud-metrics-export-interval-secs-positive", + args: []string{"gcsfuse", "--cloud-metrics-export-interval-secs=10", "abc", "pqr"}, + expected: &cfg.MetricsConfig{ + CloudMetricsExportIntervalSecs: 10, + EnableOtel: true, + }, }, { - name: "stackdriver-export-interval-positive", - args: []string{"gcsfuse", "--stackdriver-export-interval=10h", "abc", "pqr"}, - expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 10 * 3600, StackdriverExportInterval: time.Duration(10) * time.Hour}, + name: "stackdriver-export-interval-positive", + args: []string{"gcsfuse", "--stackdriver-export-interval=10h", "abc", "pqr"}, + expected: &cfg.MetricsConfig{ + CloudMetricsExportIntervalSecs: 10 * 3600, + StackdriverExportInterval: time.Duration(10) * time.Hour, + EnableOtel: true, + }, }, } for _, tc := range tests { @@ -913,7 +920,7 @@ func TestArgsParsing_MetricsViewConfig(t *testing.T) { name: "default", cfgFile: "empty.yml", expected: &cfg.MetricsConfig{ - EnableOtel: false, + EnableOtel: true, }, }, { @@ -933,12 +940,16 @@ func TestArgsParsing_MetricsViewConfig(t *testing.T) { { name: "cloud-metrics-export-interval-secs-positive", cfgFile: "metrics_export_interval_positive.yml", - expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 100}, + expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 100, EnableOtel: true}, }, { - name: "stackdriver-export-interval-positive", - cfgFile: "stackdriver_export_interval_positive.yml", - expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 12 * 3600, StackdriverExportInterval: 12 * time.Hour}, + name: "stackdriver-export-interval-positive", + cfgFile: "stackdriver_export_interval_positive.yml", + expected: &cfg.MetricsConfig{ + CloudMetricsExportIntervalSecs: 12 * 3600, + StackdriverExportInterval: 12 * time.Hour, + EnableOtel: true, + }, }, } for _, tc := range tests { From bad8dfe369579f7ecc536efdc8fbeaea3894d194 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 7 Jan 2025 12:57:19 +0530 Subject: [PATCH 0111/1298] [Move Object] Flag for MoveObject API (#2870) * create hidden flag for moveObject api * fix unit test * update description * review comments * add flag in valid config file --- cfg/config.go | 12 ++++++++++++ cfg/params.yaml | 7 +++++++ cmd/root_test.go | 32 ++++++++++++++++++++++++++++++++ cmd/testdata/valid_config.yaml | 1 + 4 files changed, 52 insertions(+) diff --git a/cfg/config.go b/cfg/config.go index 7d918a25f6..4cb6bc016c 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -30,6 +30,8 @@ type Config struct { Debug DebugConfig `yaml:"debug"` + EnableAtomicRenameObject bool `yaml:"enable-atomic-rename-object"` + EnableHns bool `yaml:"enable-hns"` FileCache FileCacheConfig `yaml:"file-cache"` @@ -313,6 +315,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } + flagSet.BoolP("enable-atomic-rename-object", "", false, "Enables support for atomic rename object operation on HNS bucket.") + + if err := flagSet.MarkHidden("enable-atomic-rename-object"); err != nil { + return err + } + flagSet.BoolP("enable-empty-managed-folders", "", false, "This handles the corner case in listing managed folders. There are two corner cases (a) empty managed folder (b) nested managed folder which doesn't contain any descendent as object. This flag always works in conjunction with --implicit-dirs flag. (a) If only ImplicitDirectories is true, all managed folders are listed other than above two mentioned cases. (b) If both ImplicitDirectories and EnableEmptyManagedFolders are true, then all the managed folders are listed including the above-mentioned corner case. (c) If ImplicitDirectories is false then no managed folders are listed irrespective of enable-empty-managed-folders flag.") if err := flagSet.MarkHidden("enable-empty-managed-folders"); err != nil { @@ -636,6 +644,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("enable-atomic-rename-object", flagSet.Lookup("enable-atomic-rename-object")); err != nil { + return err + } + if err := v.BindPFlag("list.enable-empty-managed-folders", flagSet.Lookup("enable-empty-managed-folders")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 8af226ec22..f80dca4677 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -71,6 +71,13 @@ usage: "Print debug messages when a mutex is held too long." default: false +- config-path: "enable-atomic-rename-object" + flag-name: "enable-atomic-rename-object" + type: "bool" + usage: "Enables support for atomic rename object operation on HNS bucket." + default: false + hide-flag: true + - config-path: "enable-hns" flag-name: "enable-hns" type: "bool" diff --git a/cmd/root_test.go b/cmd/root_test.go index 2aabee5333..62b097a0fe 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -839,6 +839,38 @@ func TestArgsParsing_EnableHNSFlags(t *testing.T) { } } +func TestArgsParsing_EnableAtomicRenameObjectFlag(t *testing.T) { + tests := []struct { + name string + args []string + expectedEnableAtomicRenameObject bool + }{ + { + name: "normal", + args: []string{"gcsfuse", "--enable-atomic-rename-object=true", "abc", "pqr"}, + expectedEnableAtomicRenameObject: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var gotEnableAtomicRenameObject bool + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { + gotEnableAtomicRenameObject = cfg.EnableAtomicRenameObject + return nil + }) + require.Nil(t, err) + cmd.SetArgs(convertToPosixArgs(tc.args, cmd)) + + err = cmd.Execute() + + if assert.NoError(t, err) { + assert.Equal(t, tc.expectedEnableAtomicRenameObject, gotEnableAtomicRenameObject) + } + }) + } +} + func TestArgsParsing_MetricsFlags(t *testing.T) { tests := []struct { name string diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index e8414deee8..38e94bb4d7 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -56,6 +56,7 @@ file-system: list: enable-empty-managed-folders: true enable-hns: false +enable-atomic-rename-object: false metadata-cache: deprecated-stat-cache-capacity: 200 deprecated-stat-cache-ttl: 30s From 7a13c4a295cbf740be4e9504f205d7a549232353 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 7 Jan 2025 13:06:39 +0530 Subject: [PATCH 0112/1298] Avoiding nested panic while lookUpLocalFileInode (#2869) * Avoiding nested panic while lookUpInode * Changing the message * Moving defer after fs.Lock() --- internal/fs/fs.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 3e577ba0bf..5de89762bc 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1028,6 +1028,11 @@ func (fs *fileSystem) lookUpOrCreateChildInode( // UNLOCK_FUNCTION(fs.mu) // LOCK_FUNCTION(child) func (fs *fileSystem) lookUpLocalFileInode(parent inode.DirInode, childName string) (child inode.Inode) { + // Trim the suffix assigned to fix conflicting names. + childName = strings.TrimSuffix(childName, inode.ConflictingFileNameSuffix) + fileName := inode.NewFileName(parent.Name(), childName) + + fs.mu.Lock() defer func() { if child != nil { child.IncrementLookupCount() @@ -1035,11 +1040,6 @@ func (fs *fileSystem) lookUpLocalFileInode(parent inode.DirInode, childName stri fs.mu.Unlock() }() - // Trim the suffix assigned to fix conflicting names. - childName = strings.TrimSuffix(childName, inode.ConflictingFileNameSuffix) - fileName := inode.NewFileName(parent.Name(), childName) - - fs.mu.Lock() var maxTriesToLookupInode = 3 for n := 0; n < maxTriesToLookupInode; n++ { child = fs.localFileInodes[fileName] From fd86c8c099c028b37e11d39766a25f2668978ae4 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 7 Jan 2025 16:04:24 +0530 Subject: [PATCH 0113/1298] Updated validation_helper.go to fetch error message from syscall.ESTALE and then match it with actual error. (#2872) --- tools/integration_tests/util/operations/validation_helper.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/integration_tests/util/operations/validation_helper.go b/tools/integration_tests/util/operations/validation_helper.go index f0166af127..62d7836bb5 100644 --- a/tools/integration_tests/util/operations/validation_helper.go +++ b/tools/integration_tests/util/operations/validation_helper.go @@ -19,6 +19,7 @@ import ( "errors" "os" "strings" + "syscall" "testing" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" @@ -46,7 +47,7 @@ func ValidateObjectNotFoundErr(ctx context.Context, t *testing.T, bucket gcs.Buc func ValidateStaleNFSFileHandleError(t *testing.T, err error) { assert.NotEqual(t, nil, err) - assert.Regexp(t, "stale NFS file handle", err.Error()) + assert.Regexp(t, syscall.ESTALE.Error(), err.Error()) } func CheckErrorForReadOnlyFileSystem(t *testing.T, err error) { From 706d805eeb969f891b157330ecee74567540797d Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 7 Jan 2025 16:04:34 +0530 Subject: [PATCH 0114/1298] Set the precondition-errors flag to default true as feature is complete. (#2867) * Set the precondition-errors flag to default true as feature is complete. * Fixed failing e2e tests. * Delete tools/integration_tests/emulator_tests/proxy-od8hc --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/config_validation_test.go | 6 ++--- cmd/root_test.go | 8 +++---- cmd/testdata/valid_config.yaml | 2 +- .../local_file/create_file_test.go | 5 ++-- .../local_file/read_dir_test.go | 5 ++-- .../local_file/remove_dir_test.go | 13 ++++++---- .../local_file/unlinked_file_test.go | 24 +++++++++++-------- 9 files changed, 38 insertions(+), 29 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 4cb6bc016c..11088691d0 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -477,7 +477,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.StringP("only-dir", "", "", "Mount only a specific directory within the bucket. See docs/mounting for more information") - flagSet.BoolP("precondition-errors", "", false, "Throw Stale NFS file handle error in case the object being synced or read from is modified by some other concurrent process. This helps prevent silent data loss or data corruption.") + flagSet.BoolP("precondition-errors", "", true, "Throw Stale NFS file handle error in case the object being synced or read from is modified by some other concurrent process. This helps prevent silent data loss or data corruption.") if err := flagSet.MarkHidden("precondition-errors"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index f80dca4677..a8d0fb85b7 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -209,7 +209,7 @@ from is modified by some other concurrent process. This helps prevent silent data loss or data corruption. hide-flag: true - default: false + default: true - config-path: "file-system.rename-dir-limit" flag-name: "rename-dir-limit" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index b0e8c06b4d..ffc9272f80 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -514,7 +514,7 @@ func TestValidateConfigFile_FileSystemConfigSuccessful(t *testing.T) { KernelListCacheTtlSecs: 0, RenameDirLimit: 0, TempDir: "", - PreconditionErrors: false, + PreconditionErrors: true, Uid: -1, HandleSigterm: true, }, @@ -534,7 +534,7 @@ func TestValidateConfigFile_FileSystemConfigSuccessful(t *testing.T) { KernelListCacheTtlSecs: 0, RenameDirLimit: 0, TempDir: "", - PreconditionErrors: false, + PreconditionErrors: true, Uid: -1, HandleSigterm: true, }, @@ -554,7 +554,7 @@ func TestValidateConfigFile_FileSystemConfigSuccessful(t *testing.T) { KernelListCacheTtlSecs: 300, RenameDirLimit: 10, TempDir: cfg.ResolvedPath(path.Join(hd, "temp")), - PreconditionErrors: true, + PreconditionErrors: false, Uid: 8, HandleSigterm: true, }, diff --git a/cmd/root_test.go b/cmd/root_test.go index 62b097a0fe..227bde255c 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -642,7 +642,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { }{ { name: "normal", - args: []string{"gcsfuse", "--dir-mode=0777", "--disable-parallel-dirops", "--file-mode=0666", "--o", "ro", "--gid=7", "--ignore-interrupts=false", "--kernel-list-cache-ttl-secs=300", "--rename-dir-limit=10", "--temp-dir=~/temp", "--uid=8", "--precondition-errors=true", "abc", "pqr"}, + args: []string{"gcsfuse", "--dir-mode=0777", "--disable-parallel-dirops", "--file-mode=0666", "--o", "ro", "--gid=7", "--ignore-interrupts=false", "--kernel-list-cache-ttl-secs=300", "--rename-dir-limit=10", "--temp-dir=~/temp", "--uid=8", "--precondition-errors=false", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileSystem: cfg.FileSystemConfig{ DirMode: 0777, @@ -654,7 +654,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { KernelListCacheTtlSecs: 300, RenameDirLimit: 10, TempDir: cfg.ResolvedPath(path.Join(hd, "temp")), - PreconditionErrors: true, + PreconditionErrors: false, Uid: 8, HandleSigterm: true, }, @@ -674,7 +674,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { KernelListCacheTtlSecs: 0, RenameDirLimit: 0, TempDir: "", - PreconditionErrors: false, + PreconditionErrors: true, Uid: -1, HandleSigterm: true, }, @@ -694,7 +694,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { KernelListCacheTtlSecs: 0, RenameDirLimit: 0, TempDir: "", - PreconditionErrors: false, + PreconditionErrors: true, Uid: -1, HandleSigterm: true, }, diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 38e94bb4d7..4a483c368e 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -52,7 +52,7 @@ file-system: kernel-list-cache-ttl-secs: 300 rename-dir-limit: 10 temp-dir: ~/temp - precondition-errors: true + precondition-errors: false list: enable-empty-managed-folders: true enable-hns: false diff --git a/tools/integration_tests/local_file/create_file_test.go b/tools/integration_tests/local_file/create_file_test.go index 9a18af5475..fb045a13b8 100644 --- a/tools/integration_tests/local_file/create_file_test.go +++ b/tools/integration_tests/local_file/create_file_test.go @@ -50,8 +50,9 @@ func TestCreateNewFileWhenSameFileExistsOnGCS(t *testing.T) { // Write to local file. operations.WriteWithoutClose(fh, FileContents, t) - // Close the local file. - operations.CloseFileShouldNotThrowError(fh, t) + // Validate closing local file throws error. + err := fh.Close() + operations.ValidateStaleNFSFileHandleError(t, err) // Ensure that the content on GCS is not overwritten. ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, FileName1, GCSFileContent, t) } diff --git a/tools/integration_tests/local_file/read_dir_test.go b/tools/integration_tests/local_file/read_dir_test.go index 0622f43951..079a635f51 100644 --- a/tools/integration_tests/local_file/read_dir_test.go +++ b/tools/integration_tests/local_file/read_dir_test.go @@ -173,8 +173,9 @@ func TestReadDirWithSameNameLocalAndGCSFile(t *testing.T) { t.Fatalf("ReadDir err: %v", err) } - // Close the local file. - operations.CloseFileShouldNotThrowError(fh1, t) + // Validate closing local file throws error. + err = fh1.Close() + operations.ValidateStaleNFSFileHandleError(t, err) } func TestConcurrentReadDirAndCreationOfLocalFiles_DoesNotThrowError(t *testing.T) { diff --git a/tools/integration_tests/local_file/remove_dir_test.go b/tools/integration_tests/local_file/remove_dir_test.go index bba007655e..54c0eaa0a8 100644 --- a/tools/integration_tests/local_file/remove_dir_test.go +++ b/tools/integration_tests/local_file/remove_dir_test.go @@ -41,8 +41,9 @@ func TestRmDirOfDirectoryContainingGCSAndLocalFiles(t *testing.T) { operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, ExplicitDirName)) // Validate writing content to unlinked local file does not throw error. operations.WriteWithoutClose(fh2, FileContents, t) - // Validate flush file does not throw error and does not create object on GCS. - operations.CloseFileShouldNotThrowError(fh2, t) + // Validate flush file throws error and does not create object on GCS. + err := fh2.Close() + operations.ValidateStaleNFSFileHandleError(t, err) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile, t) // Validate synced files are also deleted. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, syncedFile, t) @@ -63,10 +64,12 @@ func TestRmDirOfDirectoryContainingOnlyLocalFiles(t *testing.T) { // Verify rmDir operation succeeds. operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, ExplicitDirName)) - // Close the local files and validate they are not present on GCS. - operations.CloseFileShouldNotThrowError(fh1, t) + // Validate that closing local files throws error and they are not present on GCS. + err := fh1.Close() + operations.ValidateStaleNFSFileHandleError(t, err) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile1, t) - operations.CloseFileShouldNotThrowError(fh2, t) + err = fh2.Close() + operations.ValidateStaleNFSFileHandleError(t, err) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile2, t) // Validate directory is also deleted. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, ExplicitDirName, t) diff --git a/tools/integration_tests/local_file/unlinked_file_test.go b/tools/integration_tests/local_file/unlinked_file_test.go index f9bac017a6..1096d8911b 100644 --- a/tools/integration_tests/local_file/unlinked_file_test.go +++ b/tools/integration_tests/local_file/unlinked_file_test.go @@ -34,8 +34,9 @@ func TestStatOnUnlinkedLocalFile(t *testing.T) { // Stat the local file and validate error. operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) - // Close the file and validate that file is not created on GCS. - operations.CloseFileShouldNotThrowError(fh, t) + // Validate closing local file throws error and does not create file on GCS. + err := fh.Close() + operations.ValidateStaleNFSFileHandleError(t, err) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) } @@ -60,8 +61,9 @@ func TestReadDirContainingUnlinkedLocalFiles(t *testing.T) { FileName1, "", t) CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testDirName, FileName2, "", t) - // Verify unlinked file is not written to GCS. - operations.CloseFileShouldNotThrowError(fh3, t) + // Verify closing unlinked local file throws error and does not write to GCS. + err := fh3.Close() + operations.ValidateStaleNFSFileHandleError(t, err) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName3, t) } @@ -76,8 +78,9 @@ func TestWriteOnUnlinkedLocalFileSucceeds(t *testing.T) { // Write to unlinked local file. operations.WriteWithoutClose(fh, FileContents, t) - // Validate flush file does not throw error. - operations.CloseFileShouldNotThrowError(fh, t) + // Validate flush file throws error. + err := fh.Close() + operations.ValidateStaleNFSFileHandleError(t, err) // Validate unlinked file is not written to GCS. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) } @@ -92,10 +95,11 @@ func TestSyncOnUnlinkedLocalFile(t *testing.T) { // Verify unlink operation succeeds. operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) - // Validate sync operation does not write to GCS after unlink. - operations.SyncFile(fh, t) + // Validate sync and close operations throws error and do not write to GCS after unlink. + err := fh.Sync() + operations.ValidateStaleNFSFileHandleError(t, err) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) - // Close the local file and validate it is not present on GCS. - operations.CloseFileShouldNotThrowError(fh, t) + err = fh.Close() + operations.ValidateStaleNFSFileHandleError(t, err) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) } From 440fce6956931e9ab40768cd404c811ad42bda24 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 7 Jan 2025 21:41:50 +0530 Subject: [PATCH 0115/1298] Disable write back cache when streaming writes are enabled (#2858) * config changes for streaming writes * remove write-back cache config * test fix --- cfg/rationalize.go | 2 ++ cfg/rationalize_test.go | 9 +++++++-- cfg/validate.go | 7 ++++--- cfg/validate_test.go | 8 ++++++++ cmd/config_validation_test.go | 5 +++-- cmd/mount.go | 2 ++ cmd/root_test.go | 17 +++++++++-------- internal/util/util.go | 2 ++ 8 files changed, 37 insertions(+), 15 deletions(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 00b1765979..b5656fc52a 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -73,6 +73,8 @@ func resolveStreamingWriteConfig(w *WriteConfig) { w.CreateEmptyFile = false } + w.BlockSizeMb *= util.MiB + if w.GlobalMaxBlocks == -1 { w.GlobalMaxBlocks = math.MaxInt64 } diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 4b6b6dd686..6147c68db8 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -315,6 +315,7 @@ func TestRationalize_WriteConfig(t *testing.T) { config *Config expectedCreateEmptyFile bool expectedMaxBlocksPerFile int64 + expectedBlockSizeMB int64 }{ { name: "valid_config_streaming_writes_enabled", @@ -329,12 +330,13 @@ func TestRationalize_WriteConfig(t *testing.T) { }, expectedCreateEmptyFile: false, expectedMaxBlocksPerFile: math.MaxInt64, + expectedBlockSizeMB: 10 * 1024 * 1024, }, { name: "valid_config_global_max_blocks_less_than_blocks_per_file", config: &Config{ Write: WriteConfig{ - BlockSizeMb: 10, + BlockSizeMb: 5, CreateEmptyFile: true, ExperimentalEnableStreamingWrites: true, GlobalMaxBlocks: 10, @@ -343,12 +345,13 @@ func TestRationalize_WriteConfig(t *testing.T) { }, expectedCreateEmptyFile: false, expectedMaxBlocksPerFile: 10, + expectedBlockSizeMB: 5 * 1024 * 1024, }, { name: "valid_config_global_max_blocks_more_than_blocks_per_file", config: &Config{ Write: WriteConfig{ - BlockSizeMb: 10, + BlockSizeMb: 64, CreateEmptyFile: true, ExperimentalEnableStreamingWrites: true, GlobalMaxBlocks: 20, @@ -357,6 +360,7 @@ func TestRationalize_WriteConfig(t *testing.T) { }, expectedCreateEmptyFile: false, expectedMaxBlocksPerFile: 10, + expectedBlockSizeMB: 64 * 1024 * 1024, }, } @@ -367,6 +371,7 @@ func TestRationalize_WriteConfig(t *testing.T) { if assert.NoError(t, actualErr) { assert.Equal(t, tc.expectedCreateEmptyFile, tc.config.Write.CreateEmptyFile) assert.Equal(t, tc.expectedMaxBlocksPerFile, tc.config.Write.MaxBlocksPerFile) + assert.Equal(t, tc.expectedBlockSizeMB, tc.config.Write.BlockSizeMb) } }) } diff --git a/cfg/validate.go b/cfg/validate.go index 161a0c55de..59677407d5 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -17,8 +17,9 @@ package cfg import ( "errors" "fmt" - "math" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" ) const ( @@ -155,8 +156,8 @@ func isValidWriteStreamingConfig(wc *WriteConfig) error { return nil } - if wc.BlockSizeMb <= 0 { - return fmt.Errorf("invalid value of write-block-size-mb; can't be less than 1") + if wc.BlockSizeMb <= 0 || wc.BlockSizeMb > util.MaxMiBsInInt64 { + return fmt.Errorf("invalid value of write-block-size-mb; can't be less than 1 or more than %d", util.MaxMiBsInInt64) } if !(wc.MaxBlocksPerFile == -1 || wc.MaxBlocksPerFile >= 2) { return fmt.Errorf("invalid value of write-max-blocks-per-file: %d; should be >=2 or -1 (for infinite)", wc.MaxBlocksPerFile) diff --git a/cfg/validate_test.go b/cfg/validate_test.go index dafbb19127..460e34028c 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -18,6 +18,7 @@ import ( "testing" "time" + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/stretchr/testify/assert" ) @@ -451,6 +452,13 @@ func Test_isValidWriteStreamingConfig_ErrorScenarios(t *testing.T) { GlobalMaxBlocks: -1, MaxBlocksPerFile: -1, }}, + {"very_large_block_size", WriteConfig{ + BlockSizeMb: util.MaxMiBsInInt64 + 1, + CreateEmptyFile: false, + ExperimentalEnableStreamingWrites: true, + GlobalMaxBlocks: -1, + MaxBlocksPerFile: -1, + }}, {"negative_block_size", WriteConfig{ BlockSizeMb: -1, CreateEmptyFile: false, diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index ffc9272f80..6ca8d67dd2 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -190,7 +191,7 @@ func TestValidateConfigFile_WriteConfig(t *testing.T) { expectedConfig: &cfg.Config{ Write: cfg.WriteConfig{ CreateEmptyFile: false, - BlockSizeMb: 64, + BlockSizeMb: 64 * util.MiB, ExperimentalEnableStreamingWrites: false, GlobalMaxBlocks: math.MaxInt64, MaxBlocksPerFile: math.MaxInt64}, @@ -202,7 +203,7 @@ func TestValidateConfigFile_WriteConfig(t *testing.T) { expectedConfig: &cfg.Config{ Write: cfg.WriteConfig{ CreateEmptyFile: false, // changed due to enabled streaming writes. - BlockSizeMb: 10, + BlockSizeMb: 10 * util.MiB, ExperimentalEnableStreamingWrites: true, GlobalMaxBlocks: 20, MaxBlocksPerFile: 2, diff --git a/cmd/mount.go b/cmd/mount.go index 013034cdc7..3a7c6e4bb6 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -168,6 +168,8 @@ func getFuseMountConfig(fsName string, newConfig *cfg.Config) *fuse.MountConfig // access two files under same directory parallely, then the lookups also // happen parallely. EnableParallelDirOps: !(newConfig.FileSystem.DisableParallelDirops), + // We disable write-back cache when streaming writes are enabled. + DisableWritebackCaching: newConfig.Write.ExperimentalEnableStreamingWrites, } mountCfg.ErrorLogger = logger.NewLegacyLogger(logger.LevelError, "fuse: ") diff --git a/cmd/root_test.go b/cmd/root_test.go index 227bde255c..e94b8488af 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -24,6 +24,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -207,7 +208,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { args: []string{"gcsfuse", "--create-empty-file=true", "abc", "pqr"}, expectedCreateEmptyFile: true, expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 64, + expectedWriteBlockSizeMB: 64 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, expectedWriteMaxBlocksPerFile: math.MaxInt64, }, @@ -216,7 +217,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { args: []string{"gcsfuse", "--create-empty-file=false", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 64, + expectedWriteBlockSizeMB: 64 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, expectedWriteMaxBlocksPerFile: math.MaxInt64, }, @@ -225,7 +226,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { args: []string{"gcsfuse", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 64, + expectedWriteBlockSizeMB: 64 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, expectedWriteMaxBlocksPerFile: math.MaxInt64, }, @@ -234,7 +235,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { args: []string{"gcsfuse", "--experimental-enable-streaming-writes", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 64, + expectedWriteBlockSizeMB: 64 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, expectedWriteMaxBlocksPerFile: math.MaxInt64, }, @@ -243,7 +244,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { args: []string{"gcsfuse", "--experimental-enable-streaming-writes=false", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 64, + expectedWriteBlockSizeMB: 64 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, expectedWriteMaxBlocksPerFile: math.MaxInt64, }, @@ -252,7 +253,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { args: []string{"gcsfuse", "--experimental-enable-streaming-writes", "--write-block-size-mb=10", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 10, + expectedWriteBlockSizeMB: 10 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, expectedWriteMaxBlocksPerFile: math.MaxInt64, }, @@ -261,7 +262,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { args: []string{"gcsfuse", "--experimental-enable-streaming-writes", "--write-global-max-blocks=10", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 64, + expectedWriteBlockSizeMB: 64 * util.MiB, expectedWriteGlobalMaxBlocks: 10, expectedWriteMaxBlocksPerFile: math.MaxInt64, }, @@ -270,7 +271,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { args: []string{"gcsfuse", "--experimental-enable-streaming-writes", "--write-max-blocks-per-file=10", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 64, + expectedWriteBlockSizeMB: 64 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, expectedWriteMaxBlocksPerFile: 10, }, diff --git a/internal/util/util.go b/internal/util/util.go index 757f84cbd7..e8a63c4082 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -34,6 +34,8 @@ const ( Parallel = "Parallel" MaxMiBsInUint64 uint64 = math.MaxUint64 >> 20 + MaxMiBsInInt64 int64 = math.MaxInt64 >> 20 + MiB = 1024 * 1024 // HeapSizeToRssConversionFactor is a constant factor // which we multiply to the calculated heap-size From caa42dd6e1d0cb389e714335e584b11ebede23c1 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 7 Jan 2025 21:58:09 +0530 Subject: [PATCH 0116/1298] [Move Object] fs layer changes (#2871) * create hidden flag for moveObject api * review comments * fs level integration with flag * pass flag in composite tests * add more tests * add e2e test * small fix * small fix * test fix * test fix * test fix * review comment * add comment * nits --- internal/fs/fs.go | 40 ++++++++- internal/fs/hns_bucket_test.go | 88 ++++++++++++++++++- .../operations/move_file_test.go | 25 ++++++ .../operations/operations_test.go | 7 +- .../operations/rename_file_test.go | 19 +++- 5 files changed, 169 insertions(+), 10 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 5de89762bc..8545f54901 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -194,6 +194,7 @@ func NewFileSystem(ctx context.Context, serverCfg *ServerConfig) (fuseutil.FileS fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: serverCfg.NewConfig.FileCache.CacheFileForRangeRead, metricHandle: serverCfg.MetricHandle, + enableAtomicRenameObject: serverCfg.NewConfig.EnableAtomicRenameObject, } // Set up root bucket @@ -484,6 +485,8 @@ type fileSystem struct { cacheFileForRangeRead bool metricHandle common.MetricHandle + + enableAtomicRenameObject bool } //////////////////////////////////////////////////////////////////////// @@ -2037,13 +2040,44 @@ func (fs *fileSystem) Rename( } return fs.renameNonHierarchicalDir(ctx, oldParent, op.OldName, newParent, op.NewName) } - return fs.renameFile(ctx, oldParent, op.OldName, child.MinObject, newParent, op.NewName) + if child.Bucket.BucketType() == gcs.Hierarchical && fs.enableAtomicRenameObject { + return fs.renameHierarchicalFile(ctx, oldParent, op.OldName, child.MinObject, newParent, op.NewName) + } + return fs.renameNonHierarchicalFile(ctx, oldParent, op.OldName, child.MinObject, newParent, op.NewName) +} + +// LOCKS_EXCLUDED(fs.mu) +// LOCKS_EXCLUDED(oldParent) +// LOCKS_EXCLUDED(newParent) +func (fs *fileSystem) renameHierarchicalFile(ctx context.Context, oldParent inode.DirInode, oldName string, oldObject *gcs.MinObject, newParent inode.DirInode, newName string) error { + oldParent.Lock() + defer oldParent.Unlock() + + if newParent != oldParent { + newParent.Lock() + defer newParent.Unlock() + } + + newFileName := inode.NewFileName(newParent.Name(), newName) + + if _, err := oldParent.RenameFile(ctx, oldObject, newFileName.GcsObjectName()); err != nil { + return fmt.Errorf("renameFile: while renaming file: %w", err) + } + + if err := fs.invalidateChildFileCacheIfExist(oldParent, oldName); err != nil { + return fmt.Errorf("renameHierarchicalFile: while invalidating cache for delete file: %w", err) + } + + // Insert new file in type cache. + newParent.InsertFileIntoTypeCache(newName) + + return nil } // LOCKS_EXCLUDED(fs.mu) // LOCKS_EXCLUDED(oldParent) // LOCKS_EXCLUDED(newParent) -func (fs *fileSystem) renameFile( +func (fs *fileSystem) renameNonHierarchicalFile( ctx context.Context, oldParent inode.DirInode, oldName string, @@ -2070,7 +2104,7 @@ func (fs *fileSystem) renameFile( &oldObject.MetaGeneration) if err := fs.invalidateChildFileCacheIfExist(oldParent, oldObject.Name); err != nil { - return fmt.Errorf("renameFile: while invalidating cache for delete file: %w", err) + return fmt.Errorf("renameNonHierarchicalFile: while invalidating cache for delete file: %w", err) } oldParent.Unlock() diff --git a/internal/fs/hns_bucket_test.go b/internal/fs/hns_bucket_test.go index ced8f38651..a3cf6c0043 100644 --- a/internal/fs/hns_bucket_test.go +++ b/internal/fs/hns_bucket_test.go @@ -40,6 +40,9 @@ type dirEntry struct { isDir bool } +const file1Content = "abcdef" +const file2Content = "file2" + var expectedFooDirEntries = []dirEntry{ {name: "test", isDir: true}, {name: "test2", isDir: true}, @@ -53,7 +56,8 @@ func TestHNSBucketTests(t *testing.T) { suite.Run(t, new(HNSBucketTests)) } func (t *HNSBucketTests) SetupSuite() { t.serverCfg.ImplicitDirectories = false t.serverCfg.NewConfig = &cfg.Config{ - EnableHns: true, + EnableHns: true, + EnableAtomicRenameObject: true, } t.serverCfg.MetricHandle = common.NewNoopMetrics() bucketType = gcs.Hierarchical @@ -70,8 +74,8 @@ func (t *HNSBucketTests) SetupTest() { err = t.createObjects( map[string]string{ - "foo/file1.txt": "abcdef", - "foo/file2.txt": "xyz", + "foo/file1.txt": file1Content, + "foo/file2.txt": file2Content, "foo/test/file3.txt": "xyz", "foo/implicit_dir/file3.txt": "xxw", "bar/file1.txt": "-1234556789", @@ -379,3 +383,81 @@ func (t *HNSBucketTests) TestCreateLocalFileInSamePathAfterDeletingParentDirecto _, err = os.Stat(filePath) assert.NoError(t.T(), err) } + +func (t *HNSBucketTests) TestRenameFileWithSrcFileDoesNotExist() { + oldFilePath := path.Join(mntDir, "file") + newFilePath := path.Join(mntDir, "file_rename") + + err := os.Rename(oldFilePath, newFilePath) + + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + _, err = os.Stat(newFilePath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) +} + +func (t *HNSBucketTests) TestRenameFileWithDstDestFileExist() { + oldFilePath := path.Join(mntDir, "foo", "file1.txt") + _, err := os.Stat(oldFilePath) + assert.NoError(t.T(), err) + newFilePath := path.Join(mntDir, "foo", "file2.txt") + _, err = os.Stat(newFilePath) + assert.NoError(t.T(), err) + + err = os.Rename(oldFilePath, newFilePath) + + assert.NoError(t.T(), err) + _, err = os.Stat(oldFilePath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + content, err := os.ReadFile(newFilePath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), file1Content, string(content)) +} + +func (t *HNSBucketTests) TestRenameFile() { + testCases := []struct { + name string + oldFilePath string + newFilePath string + wantContent string + }{ + { + name: "DifferentParent", + oldFilePath: path.Join(mntDir, "foo", "file1.txt"), + newFilePath: path.Join(mntDir, "bar", "file3.txt"), + wantContent: file1Content, + }, + { + name: "SameParent", + oldFilePath: path.Join(mntDir, "foo", "file2.txt"), + newFilePath: path.Join(mntDir, "foo", "file3.txt"), + wantContent: file2Content, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + // Ensure file exists before renaming. + _, err := os.Stat(tc.oldFilePath) + require.NoError(t.T(), err) + + // Rename the file. + err = os.Rename(tc.oldFilePath, tc.newFilePath) + assert.NoError(t.T(), err) + + // Verify the old file no longer exists. + _, err = os.Stat(tc.oldFilePath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + // Verify the new file exists and has the correct content. + f, err := os.Stat(tc.newFilePath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), path.Base(tc.newFilePath), f.Name()) + content, err := os.ReadFile(tc.newFilePath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), tc.wantContent, string(content)) + }) + } +} diff --git a/tools/integration_tests/operations/move_file_test.go b/tools/integration_tests/operations/move_file_test.go index a2fa38eddf..f397b8b946 100644 --- a/tools/integration_tests/operations/move_file_test.go +++ b/tools/integration_tests/operations/move_file_test.go @@ -18,10 +18,12 @@ package operations_test import ( "os" "path" + "strings" "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" ) // Create below directory and file. @@ -100,3 +102,26 @@ func TestMoveFileWithinDifferentDirectory(t *testing.T) { checkIfFileMoveOperationSucceeded(filePath, destDirPath, t) } + +// Rename file from Test/move1.txt to Test/move2.txt +func TestMoveFileWithDestFileExist(t *testing.T) { + // Set up the test directory. + testDir := setup.SetupTestDirectory(DirForOperationTests) + // Define source and destination file names. + srcFilePath := path.Join(testDir, "move1.txt") + destFilePath := path.Join(testDir, "move2.txt") + // Create the source and dest file with some content. + operations.CreateFileWithContent(srcFilePath, setup.FilePermission_0600, Content, t) + operations.CreateFileWithContent(destFilePath, setup.FilePermission_0600, "Hello from dest file", t) + + // Move the file. + err := operations.Move(srcFilePath, destFilePath) + + assert.NoError(t, err, "error in file moving") + // Verify the file was renamed and content is preserved. + setup.CompareFileContents(t, destFilePath, Content) + // Verify the old file is removed. + _, err = os.Stat(srcFilePath) + assert.Error(t, err) + assert.True(t, strings.Contains(err.Error(), "no such file or directory")) +} diff --git a/tools/integration_tests/operations/operations_test.go b/tools/integration_tests/operations/operations_test.go index ba7242e1d7..9446f697dd 100644 --- a/tools/integration_tests/operations/operations_test.go +++ b/tools/integration_tests/operations/operations_test.go @@ -103,6 +103,7 @@ func createMountConfigsAndEquivalentFlags() (flags [][]string) { "write": map[string]interface{}{ "create-empty-file": true, }, + "enable-atomic-rename-object": true, } filePath1 := setup.YAMLConfigFile(mountConfig1, "config1.yaml") @@ -167,7 +168,7 @@ func TestMain(m *testing.M) { // Set up flags to run tests on. // Note: GRPC related tests will work only if you have allow-list bucket. // Note: We are not testing specifically for implicit-dirs because they are covered as part of the other flags. - flagsSet := [][]string{} + flagsSet := [][]string{{"--enable-atomic-rename-object=true"}} // Enable experimental-enable-json-read=true case, but for non-presubmit runs only. if !setup.IsPresubmitRun() { @@ -178,13 +179,13 @@ func TestMain(m *testing.M) { // gRPC tests will not run in TPC environment if !testing.Short() && !setup.TestOnTPCEndPoint() { - flagsSet = append(flagsSet, []string{"--client-protocol=grpc", "--implicit-dirs=true"}) + flagsSet = append(flagsSet, []string{"--client-protocol=grpc", "--implicit-dirs=true", "--enable-atomic-rename-object=true"}) } // HNS tests utilize the gRPC protocol, which is not supported by TPC. if !setup.TestOnTPCEndPoint() { if setup.IsHierarchicalBucket(ctx, storageClient) { - flagsSet = [][]string{{"--experimental-enable-json-read=true"}} + flagsSet = [][]string{{"--experimental-enable-json-read=true", "--enable-atomic-rename-object=true"}} } } diff --git a/tools/integration_tests/operations/rename_file_test.go b/tools/integration_tests/operations/rename_file_test.go index 32d09a3595..c105b8efa2 100644 --- a/tools/integration_tests/operations/rename_file_test.go +++ b/tools/integration_tests/operations/rename_file_test.go @@ -17,10 +17,12 @@ package operations_test import ( "path" + "strings" "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" ) func TestRenameFile(t *testing.T) { @@ -38,8 +40,23 @@ func TestRenameFile(t *testing.T) { err = operations.RenameFile(fileName, newFileName) if err != nil { - t.Errorf("Error in file copying: %v", err) + t.Errorf("Error in file renaming: %v", err) } // Check if the data in the file is the same after renaming. setup.CompareFileContents(t, newFileName, string(content)) } + +func TestRenameFileWithSrcFileDoesNoExist(t *testing.T) { + // Set up the test directory. + testDir := setup.SetupTestDirectory(DirForOperationTests) + // Define source and destination file names. + srcFilePath := path.Join(testDir, "move1.txt") // This file does not exist. + destFilePath := path.Join(testDir, "move2.txt") + + // Attempt to rename the non-existent file. + err := operations.RenameFile(srcFilePath, destFilePath) + + // Assert that an error occurred. + assert.Error(t, err) + assert.True(t, strings.Contains(err.Error(), "no such file or directory")) +} From 16b1e7d5b31e8642d0082d48cb8a885e87540d86 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 7 Jan 2025 23:04:38 +0530 Subject: [PATCH 0117/1298] Handle global max blocks semaphore properly (#2860) * fix semaphore acquire/release when block is created without acquiring global block semaphore * review comments * refactoring --- internal/block/block_pool.go | 30 +++-- internal/block/block_pool_test.go | 178 ++++++++++++++++++++++++++---- 2 files changed, 175 insertions(+), 33 deletions(-) diff --git a/internal/block/block_pool.go b/internal/block/block_pool.go index 1a94ff9494..52b81e864b 100644 --- a/internal/block/block_pool.go +++ b/internal/block/block_pool.go @@ -69,13 +69,7 @@ func (bp *BlockPool) Get() (Block, error) { default: // No lock is required here since blockPool is per file and all write // calls to a single file are serialized because of inode.lock(). - if bp.totalBlocks < bp.maxBlocks { - freeSlotsAvailable := bp.globalMaxBlocksSem.TryAcquire(1) - // We are allowed to create one block per file irrespective of free slots. - if bp.totalBlocks > 0 && !freeSlotsAvailable { - continue - } - + if bp.canAllocateBlock() { b, err := createBlock(bp.blockSize) if err != nil { return nil, err @@ -83,12 +77,28 @@ func (bp *BlockPool) Get() (Block, error) { bp.totalBlocks++ return b, nil - } } } } +// canAllocateBlock checks if a new block can be allocated. +func (bp *BlockPool) canAllocateBlock() bool { + // If max blocks limit is reached, then no more blocks can be allocated. + if bp.totalBlocks >= bp.maxBlocks { + return false + } + + // Always allow allocation if this is the first block for the file. + if bp.totalBlocks == 0 { + return true + } + + // Otherwise, check if we can acquire a semaphore. + semAcquired := bp.globalMaxBlocksSem.TryAcquire(1) + return semAcquired +} + // FreeBlocksChannel returns the freeBlocksCh being used by the block pool. func (bp *BlockPool) FreeBlocksChannel() chan Block { return bp.freeBlocksCh @@ -109,7 +119,9 @@ func (bp *BlockPool) ClearFreeBlockChannel() error { return fmt.Errorf("munmap error: %v", err) } bp.totalBlocks-- - bp.globalMaxBlocksSem.Release(1) + if bp.totalBlocks != 0 { + bp.globalMaxBlocksSem.Release(1) + } default: // Return if there are no more blocks on the channel. return nil diff --git a/internal/block/block_pool_test.go b/internal/block/block_pool_test.go index 36470f98d6..80019daeee 100644 --- a/internal/block/block_pool_test.go +++ b/internal/block/block_pool_test.go @@ -132,30 +132,77 @@ func (t *BlockPoolTest) TestBlockSize() { func (t *BlockPoolTest) TestClearFreeBlockChannel() { bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(3)) require.Nil(t.T(), err) - b1, err := bp.Get() + blocks := make([]Block, 4) + for i := 0; i < 4; i++ { + blocks[i] = t.validateGetBlockIsNotBlocked(bp) + } + // Adding 2 blocks to freeBlocksCh + bp.freeBlocksCh <- blocks[0] + bp.freeBlocksCh <- blocks[1] + require.Equal(t.T(), int64(4), bp.totalBlocks) + + err = bp.ClearFreeBlockChannel() + require.Nil(t.T(), err) - require.NotNil(t.T(), b1) - b2, err := bp.Get() + require.Equal(t.T(), int64(2), bp.totalBlocks) + require.Nil(t.T(), blocks[0].(*memoryBlock).buffer) + require.Nil(t.T(), blocks[1].(*memoryBlock).buffer) + require.NotNil(t.T(), blocks[2].(*memoryBlock).buffer) + require.NotNil(t.T(), blocks[3].(*memoryBlock).buffer) + // Check if semaphore is released correctly. + require.True(t.T(), bp.globalMaxBlocksSem.TryAcquire(2)) + require.False(t.T(), bp.globalMaxBlocksSem.TryAcquire(1)) +} + +func (t *BlockPoolTest) TestFirstBlockIsCreatedWithoutAcquiringGlobalSem() { + bp, err := NewBlockPool(1024, 3, semaphore.NewWeighted(0)) require.Nil(t.T(), err) - require.NotNil(t.T(), b2) - b3, err := bp.Get() + b1, err := bp.Get() require.Nil(t.T(), err) - require.NotNil(t.T(), b3) - // Adding 2 blocks to freeBlocksCh + require.NotNil(t.T(), b1) + // Adding block to freeBlocksCh bp.freeBlocksCh <- b1 - bp.freeBlocksCh <- b2 - require.Equal(t.T(), int64(3), bp.totalBlocks) + require.Equal(t.T(), int64(1), bp.totalBlocks) err = bp.ClearFreeBlockChannel() require.Nil(t.T(), err) - require.Equal(t.T(), int64(1), bp.totalBlocks) + require.Equal(t.T(), int64(0), bp.totalBlocks) + require.Nil(t.T(), b1.(*memoryBlock).buffer) +} + +func (t *BlockPoolTest) TestClearFreeBlockChannelWithMultipleBlockPools() { + globalMaxBlocksSem := semaphore.NewWeighted(1) + bp1, err := NewBlockPool(1024, 3, globalMaxBlocksSem) + require.Nil(t.T(), err) + bp2, err := NewBlockPool(1024, 3, globalMaxBlocksSem) + require.Nil(t.T(), err) + // Create 2 blocks in bp1. + b1 := t.validateGetBlockIsNotBlocked(bp1) + b2 := t.validateGetBlockIsNotBlocked(bp1) + require.Equal(t.T(), int64(2), bp1.totalBlocks) + // Create 1 block in bp2. + b3 := t.validateGetBlockIsNotBlocked(bp2) + require.Equal(t.T(), int64(1), bp2.totalBlocks) + // Freeing up bp1. + bp1.freeBlocksCh <- b1 + bp1.freeBlocksCh <- b2 + err = bp1.ClearFreeBlockChannel() + require.Nil(t.T(), err) require.Nil(t.T(), b1.(*memoryBlock).buffer) require.Nil(t.T(), b2.(*memoryBlock).buffer) - require.NotNil(t.T(), b3.(*memoryBlock).buffer) - // Check if semaphore is released correctly. - require.True(t.T(), bp.globalMaxBlocksSem.TryAcquire(2)) - require.False(t.T(), bp.globalMaxBlocksSem.TryAcquire(1)) + + // After bp1 is freed up, 1 more block can be created in bp2. + b4 := t.validateGetBlockIsNotBlocked(bp2) + require.Equal(t.T(), int64(2), bp2.totalBlocks) + + // Freeing up bp2. + bp2.freeBlocksCh <- b3 + bp2.freeBlocksCh <- b4 + err = bp2.ClearFreeBlockChannel() + require.Nil(t.T(), err) + require.Nil(t.T(), b3.(*memoryBlock).buffer) + require.Nil(t.T(), b4.(*memoryBlock).buffer) } func (t *BlockPoolTest) TestGetWhenGlobalMaxBlocksIsZero() { @@ -170,19 +217,15 @@ func (t *BlockPoolTest) TestGetWhenGlobalMaxBlocksIsZero() { t.validateGetBlockIsBlocked(bp) } -func (t *BlockPoolTest) TestGetWhenTotalBlocksEqualToGlobalBlocks() { +func (t *BlockPoolTest) TestGetWhenLimitedByGlobalBlocks() { bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(2)) require.Nil(t.T(), err) - // Create 1st block - b1, err := bp.Get() - require.Nil(t.T(), err) - require.NotNil(t.T(), b1) - // Create 2nd block - b2, err := bp.Get() - require.Nil(t.T(), err) - require.NotNil(t.T(), b2) - require.Equal(t.T(), int64(2), bp.totalBlocks) + // 3 blocks can be created. + for i := 0; i < 3; i++ { + _ = t.validateGetBlockIsNotBlocked(bp) + } + require.Equal(t.T(), int64(3), bp.totalBlocks) t.validateGetBlockIsBlocked(bp) } @@ -196,6 +239,7 @@ func (t *BlockPoolTest) TestGetWhenTotalBlocksEqualToMaxBlocks() { } func (t *BlockPoolTest) validateGetBlockIsBlocked(bp *BlockPool) { + t.T().Helper() done := make(chan bool, 1) go func() { b, err := bp.Get() @@ -211,6 +255,25 @@ func (t *BlockPoolTest) validateGetBlockIsBlocked(bp *BlockPool) { } } +func (t *BlockPoolTest) validateGetBlockIsNotBlocked(bp *BlockPool) Block { + t.T().Helper() + done := make(chan Block, 1) + go func() { + b, err := bp.Get() + require.Nil(t.T(), err) + require.NotNil(t.T(), b) + done <- b + }() + + select { + case block := <-done: + return block + case <-time.After(1 * time.Second): + assert.FailNow(t.T(), "Not able to get/create a block") + return nil + } +} + func (t *BlockPoolTest) TestBlockPool_FreeBlocksChannel() { freeBlocksCh := make(chan Block) bp := &BlockPool{ @@ -222,3 +285,70 @@ func (t *BlockPoolTest) TestBlockPool_FreeBlocksChannel() { assert.NotNil(t.T(), ch) assert.Equal(t.T(), freeBlocksCh, ch) } + +func (t *BlockPoolTest) TestBlockPool_canAllocateBlock() { + tests := []struct { + name string + maxBlocks int64 + totalBlocks int64 + globalSem *semaphore.Weighted + expected bool + }{ + { + name: "max_blocks_reached", + maxBlocks: 10, + totalBlocks: 10, + globalSem: semaphore.NewWeighted(0), + expected: false, + }, + { + name: "first_block", + maxBlocks: 10, + totalBlocks: 0, + globalSem: semaphore.NewWeighted(0), + expected: true, + }, + { + name: "semaphore_acquirable", + maxBlocks: 10, + totalBlocks: 5, + globalSem: semaphore.NewWeighted(1), + expected: true, + }, + { + name: "semaphore_not_acquirable", + maxBlocks: 10, + totalBlocks: 5, + globalSem: semaphore.NewWeighted(0), + expected: false, + }, + { + name: "equal_max_blocks_and_total_blocks_0", + maxBlocks: 0, + totalBlocks: 0, + globalSem: semaphore.NewWeighted(0), + expected: false, + }, + { + name: "total_blocks_more_than_max_blocks", + maxBlocks: 0, + totalBlocks: 1, + globalSem: semaphore.NewWeighted(0), + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func() { + bp := &BlockPool{ + maxBlocks: tt.maxBlocks, + totalBlocks: tt.totalBlocks, + globalMaxBlocksSem: tt.globalSem, + } + + got := bp.canAllocateBlock() + + assert.Equal(t.T(), tt.expected, got) + }) + } +} From 247da658a4c84c5351e76560ea50e860dd90204b Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 8 Jan 2025 09:56:48 +0530 Subject: [PATCH 0118/1298] Reduce number of iterations for flake-detector (#2874) --- .github/workflows/flake-detector.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/flake-detector.yml b/.github/workflows/flake-detector.yml index 2e5bd514a3..110c261194 100644 --- a/.github/workflows/flake-detector.yml +++ b/.github/workflows/flake-detector.yml @@ -32,7 +32,7 @@ jobs: run: go mod download - name: Test all - run: CGO_ENABLED=0 go test -p 1 -timeout 75m -count 20 -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` `go list ./...` + run: CGO_ENABLED=0 go test -p 1 -timeout 75m -count 5 -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` `go list ./...` - name: Cache RaceDetector Test - run: CGO_ENABLED=0 go test -p 1 -timeout 75m -count 20 ./internal/cache/... + run: CGO_ENABLED=0 go test -p 1 -timeout 75m -count 5 ./internal/cache/... From bc3d55aeb65284a978dc2c658243c5ac23bf9d97 Mon Sep 17 00:00:00 2001 From: Chris Carlon Date: Wed, 8 Jan 2025 01:10:03 -0500 Subject: [PATCH 0119/1298] fix: Retry gRPC UNAUTHENTICATED errors like 401s. (#2781) HTTP 401s are retried transparently by GCSFuse, because we may have a delay between when we fetch an OAuth token and when it is validated in GCS. This applies equivalently to the gRPC error space. --- internal/storage/storageutil/custom_retry.go | 13 +++++++++ .../storage/storageutil/custom_retry_test.go | 28 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/internal/storage/storageutil/custom_retry.go b/internal/storage/storageutil/custom_retry.go index f0d58628dc..996f360883 100644 --- a/internal/storage/storageutil/custom_retry.go +++ b/internal/storage/storageutil/custom_retry.go @@ -18,6 +18,8 @@ import ( "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "google.golang.org/api/googleapi" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) func ShouldRetry(err error) (b bool) { @@ -41,5 +43,16 @@ func ShouldRetry(err error) (b bool) { return } } + + // This is the same case as above, but for gRPC UNAUTHENTICATED errors. See + // https://github.com/golang/oauth2/issues/623 + // TODO: Please incorporate the correct fix post resolution of the above issue. + if status, ok := status.FromError(err); ok { + if status.Code() == codes.Unauthenticated { + b = true + logger.Infof("Retrying for UNAUTHENTICATED error: %v", err) + return + } + } return } diff --git a/internal/storage/storageutil/custom_retry_test.go b/internal/storage/storageutil/custom_retry_test.go index 2e0b9111a9..dfbb3973a3 100644 --- a/internal/storage/storageutil/custom_retry_test.go +++ b/internal/storage/storageutil/custom_retry_test.go @@ -23,6 +23,8 @@ import ( "github.com/stretchr/testify/assert" "google.golang.org/api/googleapi" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) func TestShouldRetryReturnsTrueWithGoogleApiError(t *testing.T) { @@ -118,3 +120,29 @@ func TestShouldRetryReturnsTrueForConnectionRefusedAndResetErrors(t *testing.T) }) } } + +func TestShouldRetryReturnsTrueForUnauthenticatedGrpcErrors(t *testing.T) { + testCases := []struct { + name string + err error + expectedResult bool + }{ + { + name: "UNAUTHENTICATED", + err: status.Error(codes.Unauthenticated, "Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project."), + expectedResult: true, + }, + { + name: "PERMISSION_DENIED", + err: status.Error(codes.PermissionDenied, "unauthorized"), + expectedResult: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actualResult := ShouldRetry(tc.err) + assert.Equal(t, tc.expectedResult, actualResult) + }) + } +} From 5a31b76b6a5c0c1afd2432d1a4c0ecd0ecc3e394 Mon Sep 17 00:00:00 2001 From: codechanges Date: Wed, 8 Jan 2025 11:56:01 +0530 Subject: [PATCH 0120/1298] Composite tests for negative entry in stat cache after object/directory deletion (#2868) * Test changes * Fixed tests for HNS * Fixed tests for HNS * Fixed tests for HNS * Fixed tests for HNS * Fixed tests for HNS --- internal/fs/caching_test.go | 104 ++++++++++++++++++++++++ internal/fs/hns_bucket_test.go | 140 +++++++++++++++++++++++++++++++++ 2 files changed, 244 insertions(+) diff --git a/internal/fs/caching_test.go b/internal/fs/caching_test.go index e66c5a7545..598581a419 100644 --- a/internal/fs/caching_test.go +++ b/internal/fs/caching_test.go @@ -727,3 +727,107 @@ func (t *MultiBucketMountCachingTest) TypeOfNameChanges_RemoteModifier() { AssertEq(nil, err) ExpectFalse(fi.IsDir()) } + +// --------------------- Test for delete directory ----------------- +// Create directory +// stat directory +// Delete directory +// Create directory using storageutil +// Stat the directory - It should return not found error from cache although dir present on GCS +// Stat the directory after TTL expiry and it should appear +// ------------------------------------------------------------------ +func (t *MultiBucketMountCachingTest) DirectoryRemovedLocallyAddedRemotely() { + const name = "foo" + var fi os.FileInfo + var err error + bucket1MntDir := getMultiMountBucketDir(bucket1Name) + bucket1 := uncachedBuckets[bucket1Name] + + // Create a directory via the file system. + err = os.Mkdir(path.Join(bucket1MntDir, name), 0700) + AssertEq(nil, err) + + // Stat the directory + fi, err = os.Stat(path.Join(bucket1MntDir, name)) + AssertEq(nil, err) + ExpectTrue(fi.IsDir()) + + // Remove the directory locally. + err = os.RemoveAll(path.Join(bucket1MntDir, name)) + AssertEq(nil, err) + + // Create a directory with the same name via GCS. + _, err = storageutil.CreateObject( + ctx, + bucket1, + name+"/", + []byte("")) + + AssertEq(nil, err) + + // Because we are caching, the directory should still appear to not exist. + _, err = os.Stat(path.Join(bucket1MntDir, name)) + ExpectTrue(os.IsNotExist(err), "err: %v", err) + + // After the TTL elapses, we should see it reappear. + cacheClock.AdvanceTime(ttl + time.Millisecond) + + fi, err = os.Stat(path.Join(bucket1MntDir, name)) + AssertEq(nil, err) + ExpectTrue(fi.IsDir()) +} + +// --------------------- Test for delete object ------------------- +// Create directory +// Create object in directory +// Stat the object +// Delete object +// Create object using storageutil +// Stat the object. It should return not found error although object present. +// Stat the object after TTL expiry and it should appear +// ------------------------------------------------------------------ +func (t *MultiBucketMountCachingTest) ObjectRemovedLocallyAddedRemotely() { + const dirName = "foo" + const objName = "bar" + var fi os.FileInfo + var err error + bucket1MntDir := getMultiMountBucketDir(bucket1Name) + bucket1 := uncachedBuckets[bucket1Name] + + // Create a directory via the file system. + err = os.Mkdir(path.Join(bucket1MntDir, dirName), 0700) + AssertEq(nil, err) + + // Create an object in the directory via the file system. + err = os.WriteFile(path.Join(bucket1MntDir, dirName, objName), []byte("taco"), 0400) + AssertEq(nil, err) + + // Stat the object + fi, err = os.Stat(path.Join(bucket1MntDir, dirName, objName)) + AssertEq(nil, err) + ExpectFalse(fi.IsDir()) + + // Remove the object locally. + err = os.Remove(path.Join(bucket1MntDir, dirName, objName)) + AssertEq(nil, err) + + // Create an object with the same name via GCS. + _, err = storageutil.CreateObject( + ctx, + bucket1, + path.Join(dirName, objName), + []byte("burrito")) + + AssertEq(nil, err) + + // Because we are caching, the object should still appear to not exist. + _, err = os.Stat(path.Join(bucket1MntDir, dirName, objName)) + ExpectTrue(os.IsNotExist(err), "err: %v", err) + + // After the TTL elapses, we should see it reappear. + cacheClock.AdvanceTime(ttl + time.Millisecond) + + fi, err = os.Stat(path.Join(bucket1MntDir, dirName, objName)) + AssertEq(nil, err) + ExpectFalse(fi.IsDir()) +} diff --git a/internal/fs/hns_bucket_test.go b/internal/fs/hns_bucket_test.go index a3cf6c0043..9417daae54 100644 --- a/internal/fs/hns_bucket_test.go +++ b/internal/fs/hns_bucket_test.go @@ -21,10 +21,16 @@ import ( "path" "strings" "testing" + "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/caching" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/jacobsa/timeutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -461,3 +467,137 @@ func (t *HNSBucketTests) TestRenameFile() { }) } } + +// ////////////////////////////////////////////////////////////////////// +// HNS bucket with caching support tests +// ////////////////////////////////////////////////////////////////////// +const ( + cachedHnsBucketName string = "cachedHnsBucket" +) + +var ( + uncachedHNSBucket gcs.Bucket +) + +type HNSCachedBucketMountTest struct { + suite.Suite + fsTest +} + +func TestHNSCachedBucketTests(t *testing.T) { suite.Run(t, new(HNSCachedBucketMountTest)) } + +func (t *HNSCachedBucketMountTest) SetupSuite() { + uncachedHNSBucket = fake.NewFakeBucket(timeutil.RealClock(), cachedHnsBucketName, gcs.Hierarchical) + lruCache := newLruCache(uint64(1000 * cfg.AverageSizeOfPositiveStatCacheEntry)) + statCache := metadata.NewStatCacheBucketView(lruCache, "") + bucket = caching.NewFastStatBucket( + ttl, + statCache, + &cacheClock, + uncachedHNSBucket) + + // Enable directory type caching. + t.serverCfg.DirTypeCacheTTL = ttl + t.serverCfg.ImplicitDirectories = false + t.serverCfg.NewConfig = &cfg.Config{ + EnableHns: true, + FileCache: defaultFileCacheConfig(), + MetadataCache: cfg.MetadataCacheConfig{ + // Setting default values. + StatCacheMaxSizeMb: 32, + TtlSecs: 60, + TypeCacheMaxSizeMb: 4, + }, + } + bucketType = gcs.Hierarchical + // Call through. + t.fsTest.SetUpTestSuite() +} + +func (t *HNSCachedBucketMountTest) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} + +func (t *HNSCachedBucketMountTest) SetupTest() { + err := t.createFolders([]string{"hns/", "hns/cache/"}) + require.NoError(t.T(), err) +} + +func (t *HNSCachedBucketMountTest) TearDownTest() { + t.fsTest.TearDown() +} + +// ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +// --------------------- Test for delete object ------------------- +// Create directory +// Create object in directory +// Stat the object +// Delete object +// Create object using other client +// Stat the object immediately, It should return not found error although object present +// Stat the object after TTL expiry and it should appear +func (t *HNSCachedBucketMountTest) TestLocalFileIsInaccessibleAfterDeleteObjectButPresentRemotely() { + dirPath := path.Join(mntDir, "hns", "cache") + filePath := path.Join(dirPath, "file1.txt") + // Create local file inside it. + ff, err := os.Create(filePath) + require.NoError(t.T(), ff.Close()) + require.NoError(t.T(), err) + _, err = os.Stat(filePath) + require.NoError(t.T(), err) + // Delete object + err = os.Remove(filePath) + assert.NoError(t.T(), err) + // Create an object with the same name via GCS. + _, err = storageutil.CreateObject( + ctx, + uncachedHNSBucket, + path.Join("hns", "cache", "file1.txt"), + []byte("burrito")) + assert.NoError(t.T(), err) + // Stat the object --> It should return not found error although object present + _, err = os.Stat(filePath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + // After the TTL elapses, we should see it reappear. + cacheClock.AdvanceTime(ttl + time.Millisecond) + _, err = os.Stat(filePath) + assert.NoError(t.T(), err) +} + +// --------------------- Test for delete directory ----------------- +// Create directory +// stat directory +// Delete directory +// Create directory using other client +// Stat the directory immeditely, It should return not found error from cache although dir present +// Stat the directory after TTL expiry and it should appear +// ------------------------------------------------------------------ +func (t *HNSCachedBucketMountTest) TestLocalDirectoryIsInaccessibleAfterDeleteDirectoryButPresentRemotely() { + dirPath := path.Join(mntDir, "hns", "cache", "test") + // Create directory - foo/test2 + err := os.Mkdir(dirPath, dirPerms) + require.NoError(t.T(), err) + // stat directory - foo/test2 + _, err = os.Stat(dirPath) + require.NoError(t.T(), err) + // Delete directory - rm -r foo/test2 + err = os.RemoveAll(dirPath) + assert.NoError(t.T(), err) + // Create a directory with the same name via GCS. + _, err = storageutil.CreateObject( + ctx, + uncachedHNSBucket, + path.Join("hns", "cache", "test")+"/", + []byte("")) + assert.NoError(t.T(), err) + // Stat the directory - foo/test2/test.txt --> It should return not found error from cache although dir present + _, err = os.Stat(dirPath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + + // After the TTL elapses, we should see it reappear. + cacheClock.AdvanceTime(ttl + time.Millisecond) + _, err = os.Stat(dirPath) + assert.NoError(t.T(), err) +} From 8f785d9cc01eadc6307e6462b8a0bd3dd4f95f7d Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:36:22 +0530 Subject: [PATCH 0121/1298] remove experimental prefix from streaming writes (#2880) --- cfg/config.go | 14 +- cfg/params.yaml | 4 +- cfg/rationalize.go | 2 +- cfg/rationalize_test.go | 30 ++-- cfg/validate.go | 2 +- cfg/validate_test.go | 140 +++++++++--------- cmd/config_validation_test.go | 20 +-- cmd/mount.go | 2 +- cmd/root_test.go | 12 +- cmd/testdata/valid_config.yaml | 2 +- ...alid_write_config_due_to_0_block_size.yaml | 2 +- ...config_due_to_small_global_max_blocks.yaml | 2 +- ...nfig_due_to_small_max_blocks_per_file.yaml | 2 +- internal/fs/inode/file.go | 6 +- .../fs/inode/file_streaming_writes_test.go | 8 +- internal/fs/inode/file_test.go | 8 +- .../streaming_writes_empty_gcs_object_test.go | 10 +- .../fs/streaming_writes_local_file_test.go | 10 +- 18 files changed, 138 insertions(+), 138 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 11088691d0..01a8179dc7 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -242,7 +242,7 @@ type WriteConfig struct { CreateEmptyFile bool `yaml:"create-empty-file"` - ExperimentalEnableStreamingWrites bool `yaml:"experimental-enable-streaming-writes"` + EnableStreamingWrites bool `yaml:"enable-streaming-writes"` GlobalMaxBlocks int64 `yaml:"global-max-blocks"` @@ -347,15 +347,15 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.BoolP("experimental-enable-json-read", "", false, "By default, GCSFuse uses the GCS XML API to get and read objects. When this flag is specified, GCSFuse uses the GCS JSON API instead.\"") + flagSet.BoolP("enable-streaming-writes", "", false, "Enables streaming uploads during write file operation.") - if err := flagSet.MarkDeprecated("experimental-enable-json-read", "Experimental flag: could be dropped even in a minor release."); err != nil { + if err := flagSet.MarkHidden("enable-streaming-writes"); err != nil { return err } - flagSet.BoolP("experimental-enable-streaming-writes", "", false, "Enables streaming uploads during write file operation.") + flagSet.BoolP("experimental-enable-json-read", "", false, "By default, GCSFuse uses the GCS XML API to get and read objects. When this flag is specified, GCSFuse uses the GCS JSON API instead.\"") - if err := flagSet.MarkHidden("experimental-enable-streaming-writes"); err != nil { + if err := flagSet.MarkDeprecated("experimental-enable-json-read", "Experimental flag: could be dropped even in a minor release."); err != nil { return err } @@ -668,11 +668,11 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } - if err := v.BindPFlag("gcs-connection.experimental-enable-json-read", flagSet.Lookup("experimental-enable-json-read")); err != nil { + if err := v.BindPFlag("write.enable-streaming-writes", flagSet.Lookup("enable-streaming-writes")); err != nil { return err } - if err := v.BindPFlag("write.experimental-enable-streaming-writes", flagSet.Lookup("experimental-enable-streaming-writes")); err != nil { + if err := v.BindPFlag("gcs-connection.experimental-enable-json-read", flagSet.Lookup("experimental-enable-json-read")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index a8d0fb85b7..4bd2bc40ff 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -653,8 +653,8 @@ hold." default: false -- config-path: "write.experimental-enable-streaming-writes" - flag-name: "experimental-enable-streaming-writes" +- config-path: "write.enable-streaming-writes" + flag-name: "enable-streaming-writes" type: "bool" usage: "Enables streaming uploads during write file operation." default: false diff --git a/cfg/rationalize.go b/cfg/rationalize.go index b5656fc52a..2348ba2ab6 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -69,7 +69,7 @@ func resolveStatCacheMaxSizeMB(v isSet, c *MetadataCacheConfig) { } func resolveStreamingWriteConfig(w *WriteConfig) { - if w.ExperimentalEnableStreamingWrites { + if w.EnableStreamingWrites { w.CreateEmptyFile = false } diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 6147c68db8..040fa35e80 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -321,11 +321,11 @@ func TestRationalize_WriteConfig(t *testing.T) { name: "valid_config_streaming_writes_enabled", config: &Config{ Write: WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: true, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: -1, - MaxBlocksPerFile: -1, + BlockSizeMb: 10, + CreateEmptyFile: true, + EnableStreamingWrites: true, + GlobalMaxBlocks: -1, + MaxBlocksPerFile: -1, }, }, expectedCreateEmptyFile: false, @@ -336,11 +336,11 @@ func TestRationalize_WriteConfig(t *testing.T) { name: "valid_config_global_max_blocks_less_than_blocks_per_file", config: &Config{ Write: WriteConfig{ - BlockSizeMb: 5, - CreateEmptyFile: true, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 10, - MaxBlocksPerFile: 20, + BlockSizeMb: 5, + CreateEmptyFile: true, + EnableStreamingWrites: true, + GlobalMaxBlocks: 10, + MaxBlocksPerFile: 20, }, }, expectedCreateEmptyFile: false, @@ -351,11 +351,11 @@ func TestRationalize_WriteConfig(t *testing.T) { name: "valid_config_global_max_blocks_more_than_blocks_per_file", config: &Config{ Write: WriteConfig{ - BlockSizeMb: 64, - CreateEmptyFile: true, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 20, - MaxBlocksPerFile: 10, + BlockSizeMb: 64, + CreateEmptyFile: true, + EnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: 10, }, }, expectedCreateEmptyFile: false, diff --git a/cfg/validate.go b/cfg/validate.go index 59677407d5..3c7fd79d68 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -152,7 +152,7 @@ func isValidMetadataCache(v isSet, c *MetadataCacheConfig) error { } func isValidWriteStreamingConfig(wc *WriteConfig) error { - if !wc.ExperimentalEnableStreamingWrites { + if !wc.EnableStreamingWrites { return nil } diff --git a/cfg/validate_test.go b/cfg/validate_test.go index 460e34028c..f3d7ca6a9d 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -446,67 +446,67 @@ func Test_isValidWriteStreamingConfig_ErrorScenarios(t *testing.T) { writeConfig WriteConfig }{ {"zero_block_size", WriteConfig{ - BlockSizeMb: 0, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: -1, - MaxBlocksPerFile: -1, + BlockSizeMb: 0, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: -1, + MaxBlocksPerFile: -1, }}, {"very_large_block_size", WriteConfig{ - BlockSizeMb: util.MaxMiBsInInt64 + 1, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: -1, - MaxBlocksPerFile: -1, + BlockSizeMb: util.MaxMiBsInInt64 + 1, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: -1, + MaxBlocksPerFile: -1, }}, {"negative_block_size", WriteConfig{ - BlockSizeMb: -1, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: -1, - MaxBlocksPerFile: -1, + BlockSizeMb: -1, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: -1, + MaxBlocksPerFile: -1, }}, {"-2_global_max_blocks", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: -2, - MaxBlocksPerFile: -1, + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: -2, + MaxBlocksPerFile: -1, }}, {"0_global_max_blocks", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 0, - MaxBlocksPerFile: -1, + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 0, + MaxBlocksPerFile: -1, }}, {"1_global_max_blocks", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 1, - MaxBlocksPerFile: -1, + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 1, + MaxBlocksPerFile: -1, }}, {"-2_max_blocks_per_file", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 20, - MaxBlocksPerFile: -2, + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: -2, }}, {"0_max_blocks_per_file", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 20, - MaxBlocksPerFile: 0, + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: 0, }}, {"1_max_blocks_per_file", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 20, - MaxBlocksPerFile: 1, + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: 1, }}, } @@ -523,39 +523,39 @@ func Test_isValidWriteStreamingConfig_SuccessScenarios(t *testing.T) { writeConfig WriteConfig }{ {"streaming_writes_disabled", WriteConfig{ - BlockSizeMb: -1, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: false, - GlobalMaxBlocks: -10, - MaxBlocksPerFile: -10, + BlockSizeMb: -1, + CreateEmptyFile: false, + EnableStreamingWrites: false, + GlobalMaxBlocks: -10, + MaxBlocksPerFile: -10, }}, {"valid_write_config_1", WriteConfig{ - BlockSizeMb: 1, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: -1, - MaxBlocksPerFile: -1, + BlockSizeMb: 1, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: -1, + MaxBlocksPerFile: -1, }}, {"valid_write_config_2", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 20, - MaxBlocksPerFile: -1, + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: -1, }}, {"valid_write_config_3", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 20, - MaxBlocksPerFile: 20, + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: 20, }}, {"valid_write_config_4", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 40, - MaxBlocksPerFile: 20, + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 40, + MaxBlocksPerFile: 20, }}, } diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 6ca8d67dd2..e73ee1a5f6 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -190,11 +190,11 @@ func TestValidateConfigFile_WriteConfig(t *testing.T) { configFile: "testdata/empty_file.yaml", expectedConfig: &cfg.Config{ Write: cfg.WriteConfig{ - CreateEmptyFile: false, - BlockSizeMb: 64 * util.MiB, - ExperimentalEnableStreamingWrites: false, - GlobalMaxBlocks: math.MaxInt64, - MaxBlocksPerFile: math.MaxInt64}, + CreateEmptyFile: false, + BlockSizeMb: 64 * util.MiB, + EnableStreamingWrites: false, + GlobalMaxBlocks: math.MaxInt64, + MaxBlocksPerFile: math.MaxInt64}, }, }, { @@ -202,11 +202,11 @@ func TestValidateConfigFile_WriteConfig(t *testing.T) { configFile: "testdata/valid_config.yaml", expectedConfig: &cfg.Config{ Write: cfg.WriteConfig{ - CreateEmptyFile: false, // changed due to enabled streaming writes. - BlockSizeMb: 10 * util.MiB, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 20, - MaxBlocksPerFile: 2, + CreateEmptyFile: false, // changed due to enabled streaming writes. + BlockSizeMb: 10 * util.MiB, + EnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: 2, }, }, }, diff --git a/cmd/mount.go b/cmd/mount.go index 3a7c6e4bb6..2ba7d36c8f 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -169,7 +169,7 @@ func getFuseMountConfig(fsName string, newConfig *cfg.Config) *fuse.MountConfig // happen parallely. EnableParallelDirOps: !(newConfig.FileSystem.DisableParallelDirops), // We disable write-back cache when streaming writes are enabled. - DisableWritebackCaching: newConfig.Write.ExperimentalEnableStreamingWrites, + DisableWritebackCaching: newConfig.Write.EnableStreamingWrites, } mountCfg.ErrorLogger = logger.NewLegacyLogger(logger.LevelError, "fuse: ") diff --git a/cmd/root_test.go b/cmd/root_test.go index e94b8488af..dec5cd6a73 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -232,7 +232,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { }, { name: "Test enable-streaming-writes flag true.", - args: []string{"gcsfuse", "--experimental-enable-streaming-writes", "abc", "pqr"}, + args: []string{"gcsfuse", "--enable-streaming-writes", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedWriteBlockSizeMB: 64 * util.MiB, @@ -241,7 +241,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { }, { name: "Test enable-streaming-writes flag false.", - args: []string{"gcsfuse", "--experimental-enable-streaming-writes=false", "abc", "pqr"}, + args: []string{"gcsfuse", "--enable-streaming-writes=false", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: false, expectedWriteBlockSizeMB: 64 * util.MiB, @@ -250,7 +250,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { }, { name: "Test positive write-block-size-mb flag.", - args: []string{"gcsfuse", "--experimental-enable-streaming-writes", "--write-block-size-mb=10", "abc", "pqr"}, + args: []string{"gcsfuse", "--enable-streaming-writes", "--write-block-size-mb=10", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedWriteBlockSizeMB: 10 * util.MiB, @@ -259,7 +259,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { }, { name: "Test positive write-global-max-blocks flag.", - args: []string{"gcsfuse", "--experimental-enable-streaming-writes", "--write-global-max-blocks=10", "abc", "pqr"}, + args: []string{"gcsfuse", "--enable-streaming-writes", "--write-global-max-blocks=10", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedWriteBlockSizeMB: 64 * util.MiB, @@ -268,7 +268,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { }, { name: "Test positive write-max-blocks-per-file flag.", - args: []string{"gcsfuse", "--experimental-enable-streaming-writes", "--write-max-blocks-per-file=10", "abc", "pqr"}, + args: []string{"gcsfuse", "--enable-streaming-writes", "--write-max-blocks-per-file=10", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedWriteBlockSizeMB: 64 * util.MiB, @@ -291,7 +291,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { if assert.NoError(t, err) { assert.Equal(t, tc.expectedCreateEmptyFile, wc.CreateEmptyFile) - assert.Equal(t, tc.expectedEnableStreamingWrites, wc.ExperimentalEnableStreamingWrites) + assert.Equal(t, tc.expectedEnableStreamingWrites, wc.EnableStreamingWrites) assert.Equal(t, tc.expectedWriteBlockSizeMB, wc.BlockSizeMb) assert.Equal(t, tc.expectedWriteGlobalMaxBlocks, wc.GlobalMaxBlocks) } diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 4a483c368e..08ab5940fb 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -1,7 +1,7 @@ app-name: hello write: create-empty-file: true - experimental-enable-streaming-writes: true + enable-streaming-writes: true global-max-blocks: 20 block-size-mb: 10 max-blocks-per-file: 2 diff --git a/cmd/testdata/write_config/invalid_write_config_due_to_0_block_size.yaml b/cmd/testdata/write_config/invalid_write_config_due_to_0_block_size.yaml index 0e0bf5418d..15570c2398 100644 --- a/cmd/testdata/write_config/invalid_write_config_due_to_0_block_size.yaml +++ b/cmd/testdata/write_config/invalid_write_config_due_to_0_block_size.yaml @@ -1,4 +1,4 @@ write: create-empty-file: true - experimental-enable-streaming-writes: true + enable-streaming-writes: true block-size-mb: 0 diff --git a/cmd/testdata/write_config/invalid_write_config_due_to_small_global_max_blocks.yaml b/cmd/testdata/write_config/invalid_write_config_due_to_small_global_max_blocks.yaml index 80ae7444d3..f468c5fe12 100644 --- a/cmd/testdata/write_config/invalid_write_config_due_to_small_global_max_blocks.yaml +++ b/cmd/testdata/write_config/invalid_write_config_due_to_small_global_max_blocks.yaml @@ -1,6 +1,6 @@ write: create-empty-file: true - experimental-enable-streaming-writes: true + enable-streaming-writes: true global-max-blocks: 0 block-size-mb: 10 max-blocks-per-file: 2 diff --git a/cmd/testdata/write_config/invalid_write_config_due_to_small_max_blocks_per_file.yaml b/cmd/testdata/write_config/invalid_write_config_due_to_small_max_blocks_per_file.yaml index 54e96462df..e612c3ea8e 100644 --- a/cmd/testdata/write_config/invalid_write_config_due_to_small_max_blocks_per_file.yaml +++ b/cmd/testdata/write_config/invalid_write_config_due_to_small_max_blocks_per_file.yaml @@ -1,6 +1,6 @@ write: create-empty-file: true - experimental-enable-streaming-writes: true + enable-streaming-writes: true global-max-blocks: 20 block-size-mb: 10 max-blocks-per-file: 1 diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 7023fe6b93..639f3b0d8d 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -545,7 +545,7 @@ func (f *FileInode) Write( data []byte, offset int64) error { // For empty GCS files also we will trigger bufferedWrites flow. - if f.src.Size == 0 && f.config.Write.ExperimentalEnableStreamingWrites { + if f.src.Size == 0 && f.config.Write.EnableStreamingWrites { err := f.ensureBufferedWriteHandler(ctx) if err != nil { return err @@ -843,7 +843,7 @@ func (f *FileInode) Truncate( ctx context.Context, size int64) (err error) { // For empty GCS files also, we will trigger bufferedWrites flow. - if f.src.Size == 0 && f.config.Write.ExperimentalEnableStreamingWrites { + if f.src.Size == 0 && f.config.Write.EnableStreamingWrites { err = f.ensureBufferedWriteHandler(ctx) if err != nil { return @@ -878,7 +878,7 @@ func (f *FileInode) CacheEnsureContent(ctx context.Context) (err error) { func (f *FileInode) CreateBufferedOrTempWriter(ctx context.Context) (err error) { // Skip creating empty file when streaming writes are enabled - if f.local && f.config.Write.ExperimentalEnableStreamingWrites { + if f.local && f.config.Write.EnableStreamingWrites { err = f.ensureBufferedWriteHandler(ctx) if err != nil { return diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 45028c1752..946fe9f771 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -120,10 +120,10 @@ func (t *FileStreamingWritesTest) createInode(fileName string, fileType string) // Set buffered write config for created inode. t.in.config = &cfg.Config{Write: cfg.WriteConfig{ - MaxBlocksPerFile: 5, - BlockSizeMb: 1, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 10, + MaxBlocksPerFile: 5, + BlockSizeMb: 1, + EnableStreamingWrites: true, + GlobalMaxBlocks: 10, }} // Create write handler for the local inode created above. diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 06c68c9a0e..0a1c6fe935 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -1453,7 +1453,7 @@ func (t *FileTest) TestReadEmptyGCSFileWhenStreamingWritesAreNotInProgress() { func (t *FileTest) TestWriteToLocalFileWithInvalidConfigWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) - t.in.config = &cfg.Config{Write: cfg.WriteConfig{ExperimentalEnableStreamingWrites: true}} + t.in.config = &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: true}} assert.Nil(t.T(), t.in.bwh) err := t.in.Write(t.ctx, []byte("hi"), 0) @@ -1641,8 +1641,8 @@ func (t *FileTest) TestDeRegisterFileHandle() { func getWriteConfig() *cfg.WriteConfig { return &cfg.WriteConfig{ - MaxBlocksPerFile: 10, - BlockSizeMb: 10, - ExperimentalEnableStreamingWrites: true, + MaxBlocksPerFile: 10, + BlockSizeMb: 10, + EnableStreamingWrites: true, } } diff --git a/internal/fs/streaming_writes_empty_gcs_object_test.go b/internal/fs/streaming_writes_empty_gcs_object_test.go index 82212ae882..3cafa4e988 100644 --- a/internal/fs/streaming_writes_empty_gcs_object_test.go +++ b/internal/fs/streaming_writes_empty_gcs_object_test.go @@ -35,11 +35,11 @@ type StreamingWritesEmptyGCSObjectTest struct { func (t *StreamingWritesEmptyGCSObjectTest) SetupSuite() { t.serverCfg.NewConfig = &cfg.Config{ Write: cfg.WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 20, - MaxBlocksPerFile: 10, + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: 10, }, } t.fsTest.SetUpTestSuite() diff --git a/internal/fs/streaming_writes_local_file_test.go b/internal/fs/streaming_writes_local_file_test.go index c9067ffe1c..5d9dcc4c75 100644 --- a/internal/fs/streaming_writes_local_file_test.go +++ b/internal/fs/streaming_writes_local_file_test.go @@ -39,11 +39,11 @@ type StreamingWritesLocalFileTest struct { func (t *StreamingWritesLocalFileTest) SetupSuite() { t.serverCfg.NewConfig = &cfg.Config{ Write: cfg.WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - ExperimentalEnableStreamingWrites: true, - GlobalMaxBlocks: 20, - MaxBlocksPerFile: 10, + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: 10, }, MetadataCache: cfg.MetadataCacheConfig{TtlSecs: 0}, } From 44d436dd59c82991e8b2eb47c3b56b6feca40dbe Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 8 Jan 2025 17:07:39 +0530 Subject: [PATCH 0122/1298] Revert "Enable OTel metrics by default (#2859)" (#2882) This reverts commit 94c5ebd31c3123513ee73dcea434c8d3b66695a9. --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/config_validation_test.go | 2 -- cmd/root_test.go | 35 ++++++++++++----------------------- 4 files changed, 14 insertions(+), 27 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 01a8179dc7..7788772949 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -335,7 +335,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-nonexistent-type-cache", "", false, "Once set, if an inode is not found in GCS, a type cache entry with type NonexistentType will be created. This also means new file/dir created might not be seen. For example, if this flag is set, and metadata-cache-ttl-secs is set, then if we create the same file/node in the meantime using the same mount, since we are not refreshing the cache, it will still return nil.") - flagSet.BoolP("enable-otel", "", true, "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus.") + flagSet.BoolP("enable-otel", "", false, "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus.") if err := flagSet.MarkHidden("enable-otel"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 4bd2bc40ff..1ab1188450 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -589,7 +589,7 @@ flag-name: "enable-otel" type: "bool" usage: "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus." - default: true + default: false hide-flag: true - config-path: "metrics.prometheus-port" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index e73ee1a5f6..3ab4a542c2 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -805,7 +805,6 @@ func TestValidateConfigFile_MetricsConfigSuccessful(t *testing.T) { StackdriverExportInterval: 0, CloudMetricsExportIntervalSecs: 0, PrometheusPort: 0, - EnableOtel: true, }, }, { @@ -813,7 +812,6 @@ func TestValidateConfigFile_MetricsConfigSuccessful(t *testing.T) { configFile: "testdata/valid_config.yaml", expectedConfig: &cfg.MetricsConfig{ CloudMetricsExportIntervalSecs: 10, - EnableOtel: true, }, }, } diff --git a/cmd/root_test.go b/cmd/root_test.go index dec5cd6a73..cc022faa69 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -882,7 +882,7 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { name: "default", args: []string{"gcsfuse", "abc", "pqr"}, expected: &cfg.MetricsConfig{ - EnableOtel: true, + EnableOtel: false, }, }, { @@ -907,21 +907,14 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { }, }, { - name: "cloud-metrics-export-interval-secs-positive", - args: []string{"gcsfuse", "--cloud-metrics-export-interval-secs=10", "abc", "pqr"}, - expected: &cfg.MetricsConfig{ - CloudMetricsExportIntervalSecs: 10, - EnableOtel: true, - }, + name: "cloud-metrics-export-interval-secs-positive", + args: []string{"gcsfuse", "--cloud-metrics-export-interval-secs=10", "abc", "pqr"}, + expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 10}, }, { - name: "stackdriver-export-interval-positive", - args: []string{"gcsfuse", "--stackdriver-export-interval=10h", "abc", "pqr"}, - expected: &cfg.MetricsConfig{ - CloudMetricsExportIntervalSecs: 10 * 3600, - StackdriverExportInterval: time.Duration(10) * time.Hour, - EnableOtel: true, - }, + name: "stackdriver-export-interval-positive", + args: []string{"gcsfuse", "--stackdriver-export-interval=10h", "abc", "pqr"}, + expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 10 * 3600, StackdriverExportInterval: time.Duration(10) * time.Hour}, }, } for _, tc := range tests { @@ -953,7 +946,7 @@ func TestArgsParsing_MetricsViewConfig(t *testing.T) { name: "default", cfgFile: "empty.yml", expected: &cfg.MetricsConfig{ - EnableOtel: true, + EnableOtel: false, }, }, { @@ -973,16 +966,12 @@ func TestArgsParsing_MetricsViewConfig(t *testing.T) { { name: "cloud-metrics-export-interval-secs-positive", cfgFile: "metrics_export_interval_positive.yml", - expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 100, EnableOtel: true}, + expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 100}, }, { - name: "stackdriver-export-interval-positive", - cfgFile: "stackdriver_export_interval_positive.yml", - expected: &cfg.MetricsConfig{ - CloudMetricsExportIntervalSecs: 12 * 3600, - StackdriverExportInterval: 12 * time.Hour, - EnableOtel: true, - }, + name: "stackdriver-export-interval-positive", + cfgFile: "stackdriver_export_interval_positive.yml", + expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 12 * 3600, StackdriverExportInterval: 12 * time.Hour}, }, } for _, tc := range tests { From ecb22bcd0568decfea8b7c6774f0447eb0cb3de7 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 8 Jan 2025 20:48:37 +0530 Subject: [PATCH 0123/1298] [Move Object] Rename benchmark e2e perf test (#2878) * rename benchmark * rename benchmark * nit * review comment * review comment --- .../benchmarking/benchmark_delete_test.go | 32 +++++--- .../benchmarking/benchmark_rename_test.go | 79 +++++++++++++++++++ .../benchmarking/benchmark_stat_test.go | 22 +++++- .../benchmarking/setup_test.go | 40 ++++++++-- 4 files changed, 151 insertions(+), 22 deletions(-) create mode 100644 tools/integration_tests/benchmarking/benchmark_rename_test.go diff --git a/tools/integration_tests/benchmarking/benchmark_delete_test.go b/tools/integration_tests/benchmarking/benchmark_delete_test.go index 874b62b66c..68e840e843 100644 --- a/tools/integration_tests/benchmarking/benchmark_delete_test.go +++ b/tools/integration_tests/benchmarking/benchmark_delete_test.go @@ -16,13 +16,13 @@ package benchmarking import ( "fmt" + "log" "os" "path" "testing" "time" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/benchmark_setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) @@ -30,20 +30,16 @@ const ( expectedDeleteLatency time.Duration = 675 * time.Millisecond ) -type benchmarkDeleteTest struct{} +type benchmarkDeleteTest struct { + flags []string +} func (s *benchmarkDeleteTest) SetupB(b *testing.B) { - testDirPath = setup.SetupTestDirectory(testDirName) + mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *benchmarkDeleteTest) TeardownB(b *testing.B) {} - -// createFilesToDelete creates the below objects in the bucket. -// benchmarking/a{i}.txt where i is a counter based on the benchtime value. -func createFilesToDelete(b *testing.B) { - for i := 0; i < b.N; i++ { - operations.CreateFileOfSize(5, path.Join(testDirPath, fmt.Sprintf("a%d.txt", i)), b) - } +func (s *benchmarkDeleteTest) TeardownB(b *testing.B) { + setup.UnmountGCSFuse(rootDir) } //////////////////////////////////////////////////////////////////////// @@ -51,7 +47,7 @@ func createFilesToDelete(b *testing.B) { //////////////////////////////////////////////////////////////////////// func (s *benchmarkDeleteTest) Benchmark_Delete(b *testing.B) { - createFilesToDelete(b) + createFiles(b) b.ResetTimer() for i := 0; i < b.N; i++ { if err := os.Remove(path.Join(testDirPath, fmt.Sprintf("a%d.txt", i))); err != nil { @@ -70,5 +66,15 @@ func (s *benchmarkDeleteTest) Benchmark_Delete(b *testing.B) { func Benchmark_Delete(b *testing.B) { ts := &benchmarkDeleteTest{} - benchmark_setup.RunBenchmarks(b, ts) + + flagsSet := [][]string{ + {"--stat-cache-ttl=0"}, {"--client-protocol=grpc", "--stat-cache-ttl=0"}, + } + + // Run tests. + for _, flags := range flagsSet { + ts.flags = flags + log.Printf("Running tests with flags: %s", ts.flags) + benchmark_setup.RunBenchmarks(b, ts) + } } diff --git a/tools/integration_tests/benchmarking/benchmark_rename_test.go b/tools/integration_tests/benchmarking/benchmark_rename_test.go new file mode 100644 index 0000000000..eee265235b --- /dev/null +++ b/tools/integration_tests/benchmarking/benchmark_rename_test.go @@ -0,0 +1,79 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package benchmarking + +import ( + "fmt" + "log" + "os" + "path" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/benchmark_setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +type benchmarkRenameTest struct { + flags []string +} + +const ( + expectedRenameLatency time.Duration = 700 * time.Millisecond +) + +func (s *benchmarkRenameTest) SetupB(b *testing.B) { + mountGCSFuseAndSetupTestDir(s.flags, testDirName) +} + +func (s *benchmarkRenameTest) TeardownB(b *testing.B) { + setup.UnmountGCSFuse(rootDir) +} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +func (s *benchmarkRenameTest) Benchmark_Rename(b *testing.B) { + createFiles(b) + b.ResetTimer() + for i := 0; i < b.N; i++ { + if err := os.Rename(path.Join(testDirPath, fmt.Sprintf("a%d.txt", i)), path.Join(testDirPath, fmt.Sprintf("b%d.txt", i))); err != nil { + b.Errorf("testing error: %v", err) + } + } + averageRenameLatency := time.Duration(int(b.Elapsed()) / b.N) + if averageRenameLatency > expectedRenameLatency { + b.Errorf("RenameFile took more time (%d msec) than expected (%d msec)", averageRenameLatency.Milliseconds(), expectedRenameLatency.Milliseconds()) + } +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func Benchmark_Rename(b *testing.B) { + ts := &benchmarkRenameTest{} + flagsSet := [][]string{ + {"--stat-cache-ttl=0", "--enable-atomic-rename-object=true"}, {"--client-protocol=grpc", "--stat-cache-ttl=0", "--enable-atomic-rename-object=true"}, + } + + // Run tests. + for _, flags := range flagsSet { + ts.flags = flags + log.Printf("Running tests with flags: %s", ts.flags) + benchmark_setup.RunBenchmarks(b, ts) + } +} diff --git a/tools/integration_tests/benchmarking/benchmark_stat_test.go b/tools/integration_tests/benchmarking/benchmark_stat_test.go index e4eb735a1f..2a2ba2c765 100644 --- a/tools/integration_tests/benchmarking/benchmark_stat_test.go +++ b/tools/integration_tests/benchmarking/benchmark_stat_test.go @@ -15,6 +15,7 @@ package benchmarking import ( + "log" "path" "testing" "time" @@ -32,13 +33,17 @@ const ( expectedStatLatency time.Duration = 390 * time.Millisecond ) -type benchmarkStatTest struct{} +type benchmarkStatTest struct { + flags []string +} func (s *benchmarkStatTest) SetupB(b *testing.B) { - testDirPath = setup.SetupTestDirectory(testDirName) + mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *benchmarkStatTest) TeardownB(b *testing.B) {} +func (s *benchmarkStatTest) TeardownB(b *testing.B) { + setup.UnmountGCSFuse(rootDir) +} // createFilesToStat creates the below object in the bucket. // benchmarking/a.txt @@ -70,5 +75,14 @@ func (s *benchmarkStatTest) Benchmark_Stat(b *testing.B) { func Benchmark_Stat(b *testing.B) { ts := &benchmarkStatTest{} - benchmark_setup.RunBenchmarks(b, ts) + flagsSet := [][]string{ + {"--stat-cache-ttl=0"}, {"--client-protocol=grpc", "--stat-cache-ttl=0"}, + } + + // Run tests. + for _, flags := range flagsSet { + ts.flags = flags + log.Printf("Running tests with flags: %s", ts.flags) + benchmark_setup.RunBenchmarks(b, ts) + } } diff --git a/tools/integration_tests/benchmarking/setup_test.go b/tools/integration_tests/benchmarking/setup_test.go index 5265ef70f7..39f4b3991a 100644 --- a/tools/integration_tests/benchmarking/setup_test.go +++ b/tools/integration_tests/benchmarking/setup_test.go @@ -16,6 +16,7 @@ package benchmarking import ( "context" + "fmt" "log" "os" "path" @@ -24,6 +25,7 @@ import ( "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) @@ -35,8 +37,35 @@ var ( storageClient *storage.Client ctx context.Context testDirPath string + mountFunc func([]string) error + // mount directory is where our tests run. + mountDir string + // root directory is the directory to be unmounted. + rootDir string ) +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func mountGCSFuseAndSetupTestDir(flags []string, testDirName string) { + // When tests are running in GKE environment, use the mounted directory provided as test flag. + if setup.MountedDirectory() != "" { + mountDir = setup.MountedDirectory() + } + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) + setup.SetMntDir(mountDir) + testDirPath = setup.SetupTestDirectory(testDirName) +} + +// createFiles creates the below objects in the bucket. +// benchmarking/a{i}.txt where i is a counter based on the benchtime value. +func createFiles(b *testing.B) { + for i := 0; i < b.N; i++ { + operations.CreateFileOfSize(5, path.Join(testDirPath, fmt.Sprintf("a%d.txt", i)), b) + } +} + func TestMain(m *testing.M) { setup.ParseSetUpFlags() setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() @@ -57,11 +86,12 @@ func TestMain(m *testing.M) { // Set up test directory. setup.SetUpTestDirForTestBucketFlag() - flagsSet := [][]string{ - {"--stat-cache-ttl=0"}, - {"--client-protocol=grpc", "--stat-cache-ttl=0"}, - } - successCode := static_mounting.RunTests(flagsSet, m) + // Save mount and root directory variables. + mountDir, rootDir = setup.MntDir(), setup.MntDir() + + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() // Clean up test directory created. setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) From 1471a92c24d016c1f830fbac173d303bd70713fa Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 9 Jan 2025 10:10:34 +0530 Subject: [PATCH 0124/1298] Refactor write failure composite tests (#2854) * refactored code * removed coverage file * updated copyright year --- internal/fs/stale_file_handle_common_test.go | 92 +++++++++++++ .../fs/stale_file_handle_local_file_test.go | 80 ++---------- .../fs/stale_file_handle_synced_file_test.go | 123 +++--------------- 3 files changed, 127 insertions(+), 168 deletions(-) create mode 100644 internal/fs/stale_file_handle_common_test.go diff --git a/internal/fs/stale_file_handle_common_test.go b/internal/fs/stale_file_handle_common_test.go new file mode 100644 index 0000000000..7413a0d2cf --- /dev/null +++ b/internal/fs/stale_file_handle_common_test.go @@ -0,0 +1,92 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs_test + +import ( + "os" + "path" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type staleFileHandleCommon struct { + // fsTest has f1 *osFile and f2 *osFile which we will reuse here. + fsTest + suite.Suite +} + +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// +func (t *staleFileHandleCommon) TestClobberedFileSyncAndCloseThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + n, err := t.f1.Write([]byte("taco")) + assert.NoError(t.T(), err) + assert.Equal(t.T(), 4, n) + // Replace the underlying object with a new generation. + _, err = storageutil.CreateObject( + ctx, + bucket, + "foo", + []byte("foobar")) + assert.NoError(t.T(), err) + + err = t.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = t.f1.Close() + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file. + t.f1 = nil + // Validate that object is not updated with un-synced content. + contents, err := storageutil.ReadObject(ctx, bucket, "foo") + assert.NoError(t.T(), err) + assert.Equal(t.T(), "foobar", string(contents)) +} + +func (t *staleFileHandleCommon) TestDeletedFileSyncAndCloseThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + n, err := t.f1.Write([]byte("foobar")) + assert.NoError(t.T(), err) + assert.Equal(t.T(), 6, n) + // Unlink the file. + err = os.Remove(t.f1.Name()) + // Verify unlink operation succeeds. + assert.NoError(t.T(), err) + operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, "foo")) + // Attempt to write to file should not give any error. + n, err = t.f1.Write([]byte("taco")) + assert.Equal(t.T(), 4, n) + assert.NoError(t.T(), err) + + err = t.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = t.f1.Close() + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file. + t.f1 = nil + // Verify unlinked file is not present on GCS. + operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, "foo") +} diff --git a/internal/fs/stale_file_handle_local_file_test.go b/internal/fs/stale_file_handle_local_file_test.go index f4df02a68d..e197afff39 100644 --- a/internal/fs/stale_file_handle_local_file_test.go +++ b/internal/fs/stale_file_handle_local_file_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,11 +17,9 @@ package fs_test import ( "os" "path" - "path/filepath" "testing" "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -31,17 +29,15 @@ import ( // Boilerplate // ////////////////////////////////////////////////////////////////////// -type StaleFileHandleLocalFile struct { - // fsTest has f1 *osFile and f2 *osFile which we will reuse here. - fsTest - suite.Suite +type staleFileHandleLocalFile struct { + staleFileHandleCommon } func TestStaleFileHandleLocalFile(t *testing.T) { - suite.Run(t, new(StaleFileHandleLocalFile)) + suite.Run(t, new(staleFileHandleLocalFile)) } -func (t *StaleFileHandleLocalFile) SetupSuite() { +func (t *staleFileHandleLocalFile) SetupSuite() { t.serverCfg.NewConfig = &cfg.Config{ FileSystem: cfg.FileSystemConfig{ PreconditionErrors: true, @@ -53,22 +49,16 @@ func (t *StaleFileHandleLocalFile) SetupSuite() { t.fsTest.SetUpTestSuite() } -func (t *StaleFileHandleLocalFile) TearDownSuite() { +func (t *staleFileHandleLocalFile) TearDownSuite() { t.fsTest.TearDownTestSuite() } -func (t *StaleFileHandleLocalFile) SetupTest() { +func (t *staleFileHandleLocalFile) SetupTest() { // Create a local file. _, t.f1 = operations.CreateLocalFile(ctx, t.T(), mntDir, bucket, "foo") } -func (t *StaleFileHandleLocalFile) TearDownTest() { - filePath := filepath.Join(mntDir, "foo") - if _, err := os.Stat(filePath); err == nil { - err = os.Remove(filePath) - assert.Equal(t.T(), nil, err) - } - +func (t *staleFileHandleLocalFile) TearDownTest() { // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. t.fsTest.TearDown() } @@ -76,50 +66,8 @@ func (t *StaleFileHandleLocalFile) TearDownTest() { // ////////////////////////////////////////////////////////////////////// // Tests // ////////////////////////////////////////////////////////////////////// -func (t *StaleFileHandleLocalFile) TestLocalInodeClobberedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { - // Dirty the local file by giving it some contents. - n, err := t.f1.Write([]byte("taco")) - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), 4, n) - // Replace the underlying object with a new generation. - _, err = storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("foobar")) - assert.Equal(t.T(), nil, err) - - err = t.f1.Sync() - - operations.ValidateStaleNFSFileHandleError(t.T(), err) - err = operations.CloseLocalFile(t.T(), &t.f1) - operations.ValidateStaleNFSFileHandleError(t.T(), err) - // Validate that local file content is not synced to GCS. - contents, err := storageutil.ReadObject(ctx, bucket, "foo") - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), "foobar", string(contents)) -} - -func (t *StaleFileHandleLocalFile) TestUnlinkedLocalInode_SyncAndClose_ThrowsStaleFileHandleError() { - // Unlink the local file. - err := os.Remove(t.f1.Name()) - // Verify unlink operation succeeds. - assert.Equal(t.T(), nil, err) - operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, FileName)) - // Write to unlinked local file. - _, err = t.f1.WriteString(FileContents) - assert.Equal(t.T(), nil, err) - - err = t.f1.Sync() - - operations.ValidateStaleNFSFileHandleError(t.T(), err) - err = operations.CloseLocalFile(t.T(), &t.f1) - operations.ValidateStaleNFSFileHandleError(t.T(), err) - // Verify unlinked file is not present on GCS. - operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, FileName) -} -func (t *StaleFileHandleLocalFile) TestUnlinkedDirectoryContainingSyncedAndLocalFiles_Close_ThrowsStaleFileHandleError() { +func (t *staleFileHandleLocalFile) TestUnlinkedDirectoryContainingSyncedAndLocalFilesCloseThrowsStaleFileHandleError() { // Create explicit directory with one synced and one local file. assert.Equal(t.T(), nil, @@ -129,19 +77,19 @@ func (t *StaleFileHandleLocalFile) TestUnlinkedDirectoryContainingSyncedAndLocal "explicit/": "", "explicit/foo": "", })) - _, t.f1 = operations.CreateLocalFile(ctx, t.T(), mntDir, bucket, "explicit/"+explicitLocalFileName) + _, t.f2 = operations.CreateLocalFile(ctx, t.T(), mntDir, bucket, "explicit/"+explicitLocalFileName) // Attempt to remove explicit directory. err := os.RemoveAll(path.Join(mntDir, "explicit")) // Verify rmDir operation succeeds. - assert.Equal(t.T(), nil, err) + assert.NoError(t.T(), err) operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, "explicit/"+explicitLocalFileName)) operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, "explicit/foo")) operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, "explicit")) // Validate writing content to unlinked local file does not throw error. - _, err = t.f1.WriteString(FileContents) - assert.Equal(t.T(), nil, err) + _, err = t.f2.WriteString(FileContents) + assert.NoError(t.T(), err) - err = operations.CloseLocalFile(t.T(), &t.f1) + err = operations.CloseLocalFile(t.T(), &t.f2) operations.ValidateStaleNFSFileHandleError(t.T(), err) // Validate both local and synced files are deleted. diff --git a/internal/fs/stale_file_handle_synced_file_test.go b/internal/fs/stale_file_handle_synced_file_test.go index 93616a0bef..84be0b292a 100644 --- a/internal/fs/stale_file_handle_synced_file_test.go +++ b/internal/fs/stale_file_handle_synced_file_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -31,17 +31,15 @@ import ( // Boilerplate // ////////////////////////////////////////////////////////////////////// -type StaleFileHandleSyncedFile struct { - // fsTest has f1 *osFile and f2 *osFile which we will reuse here. - fsTest - suite.Suite +type staleFileHandleSyncedFile struct { + staleFileHandleCommon } func TestStaleFileHandleSyncedFile(t *testing.T) { - suite.Run(t, new(StaleFileHandleSyncedFile)) + suite.Run(t, new(staleFileHandleSyncedFile)) } -func (t *StaleFileHandleSyncedFile) SetupSuite() { +func (t *staleFileHandleSyncedFile) SetupSuite() { t.serverCfg.NewConfig = &cfg.Config{ FileSystem: cfg.FileSystemConfig{ PreconditionErrors: true, @@ -53,29 +51,23 @@ func (t *StaleFileHandleSyncedFile) SetupSuite() { t.fsTest.SetUpTestSuite() } -func (t *StaleFileHandleSyncedFile) TearDownSuite() { +func (t *staleFileHandleSyncedFile) TearDownSuite() { t.fsTest.TearDownTestSuite() } -func (t *StaleFileHandleSyncedFile) SetupTest() { +func (t *staleFileHandleSyncedFile) SetupTest() { // Create an object on bucket. _, err := storageutil.CreateObject( ctx, bucket, "foo", []byte("bar")) - assert.Equal(t.T(), nil, err) + assert.NoError(t.T(), err) // Open file handle to read or write. t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDWR|syscall.O_DIRECT, filePerms) - assert.Equal(t.T(), nil, err) + assert.NoError(t.T(), err) } -func (t *StaleFileHandleSyncedFile) TearDownTest() { - err := storageutil.DeleteObject( - ctx, - bucket, - "foo") - assert.Equal(t.T(), nil, err) - +func (t *staleFileHandleSyncedFile) TearDownTest() { // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. t.fsTest.TearDown() } @@ -83,14 +75,14 @@ func (t *StaleFileHandleSyncedFile) TearDownTest() { // ////////////////////////////////////////////////////////////////////// // Tests // ////////////////////////////////////////////////////////////////////// -func (t *StaleFileHandleSyncedFile) TestSyncedObjectClobberedRemotely_Read_ThrowsStaleFileHandleError() { +func (t *staleFileHandleSyncedFile) TestClobberedFileReadThrowsStaleFileHandleError() { // Replace the underlying object with a new generation. _, err := storageutil.CreateObject( ctx, bucket, "foo", []byte("foobar")) - assert.Equal(t.T(), nil, err) + assert.NoError(t.T(), err) buffer := make([]byte, 6) _, err = t.f1.Read(buffer) @@ -98,18 +90,18 @@ func (t *StaleFileHandleSyncedFile) TestSyncedObjectClobberedRemotely_Read_Throw operations.ValidateStaleNFSFileHandleError(t.T(), err) // Validate that object is updated with new content. contents, err := storageutil.ReadObject(ctx, bucket, "foo") - assert.Equal(t.T(), nil, err) + assert.NoError(t.T(), err) assert.Equal(t.T(), "foobar", string(contents)) } -func (t *StaleFileHandleSyncedFile) TestSyncedObjectClobberedRemotely_FirstWrite_ThrowsStaleFileHandleError() { +func (t *staleFileHandleSyncedFile) TestClobberedFileFirstWriteThrowsStaleFileHandleError() { // Replace the underlying object with a new generation. _, err := storageutil.CreateObject( ctx, bucket, "foo", []byte("foobar")) - assert.Equal(t.T(), nil, err) + assert.NoError(t.T(), err) _, err = t.f1.Write([]byte("taco")) @@ -117,97 +109,24 @@ func (t *StaleFileHandleSyncedFile) TestSyncedObjectClobberedRemotely_FirstWrite // Attempt to sync to file should not result in error as we first check if the // content has been dirtied before clobbered check in Sync flow. err = t.f1.Sync() - assert.Equal(t.T(), nil, err) + assert.NoError(t.T(), err) // Validate that object is not updated with new content as write failed. contents, err := storageutil.ReadObject(ctx, bucket, "foo") - assert.Equal(t.T(), nil, err) + assert.NoError(t.T(), err) assert.Equal(t.T(), "foobar", string(contents)) } -func (t *StaleFileHandleSyncedFile) TestSyncedObjectClobberedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { - // Dirty the file by giving it some contents. - n, err := t.f1.Write([]byte("taco")) - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), 4, n) - // Replace the underlying object with a new generation. - _, err = storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("foobar")) - assert.Equal(t.T(), nil, err) - - err = t.f1.Sync() - - operations.ValidateStaleNFSFileHandleError(t.T(), err) - err = t.f1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) - // Make f1 nil, so that another attempt is not taken in TearDown to close the - // file. - t.f1 = nil - // Validate that object is not updated with un-synced content. - contents, err := storageutil.ReadObject(ctx, bucket, "foo") - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), "foobar", string(contents)) -} - -func (t *StaleFileHandleSyncedFile) TestSyncedObjectDeletedRemotely_SyncAndClose_ThrowsStaleFileHandleError() { - // Dirty the file by giving it some contents. - n, err := t.f1.Write([]byte("foobar")) - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), 6, n) - // Delete the object remotely. - err = storageutil.DeleteObject(ctx, bucket, "foo") - assert.Equal(t.T(), nil, err) - // Attempt to write to file should not give any error. - n, err = t.f1.Write([]byte("taco")) - assert.Equal(t.T(), 4, n) - assert.Equal(t.T(), nil, err) - - err = t.f1.Sync() - - operations.ValidateStaleNFSFileHandleError(t.T(), err) - err = t.f1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) - // Make f1 nil, so that another attempt is not taken in TearDown to close the - // file. - t.f1 = nil -} - -func (t *StaleFileHandleSyncedFile) TestSyncedObjectDeletedLocally_SyncAndClose_ThrowsStaleFileHandleError() { - // Dirty the file by giving it some contents. - n, err := t.f1.Write([]byte("foobar")) - assert.Equal(t.T(), nil, err) - assert.Equal(t.T(), 6, n) - // Delete the object locally. - err = os.Remove(t.f1.Name()) - assert.Equal(t.T(), nil, err) - // Attempt to write to file should not give any error. - n, err = t.f1.Write([]byte("taco")) - assert.Equal(t.T(), 4, n) - assert.Equal(t.T(), nil, err) - - err = t.f1.Sync() - - operations.ValidateStaleNFSFileHandleError(t.T(), err) - err = t.f1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) - // Make f1 nil, so that another attempt is not taken in TearDown to close the - // file. - t.f1 = nil -} - -func (t *StaleFileHandleSyncedFile) TestRenamedSyncedObject_Sync_ThrowsStaleFileHandleError() { +func (t *staleFileHandleSyncedFile) TestRenamedFileSyncThrowsStaleFileHandleError() { // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("foobar")) - assert.Equal(t.T(), nil, err) + assert.NoError(t.T(), err) assert.Equal(t.T(), 6, n) // Rename the object. err = os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) - assert.Equal(t.T(), nil, err) + assert.NoError(t.T(), err) // Attempt to write to file should not give any error. n, err = t.f1.Write([]byte("taco")) - assert.Equal(t.T(), nil, err) + assert.NoError(t.T(), err) assert.Equal(t.T(), 4, n) err = t.f1.Sync() From 3c7d198df52a791685de81ca19fdc6e0631c4092 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 9 Jan 2025 12:39:03 +0530 Subject: [PATCH 0125/1298] create global max blocks semaphore in file system layer (#2883) --- internal/fs/fs.go | 9 +++++- internal/fs/handle/dir_handle_test.go | 4 ++- internal/fs/inode/dir_test.go | 4 ++- internal/fs/inode/file.go | 32 +++++++++++-------- .../fs/inode/file_streaming_writes_test.go | 5 ++- internal/fs/inode/file_test.go | 4 ++- 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 8545f54901..383fe506fa 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -29,6 +29,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" + "golang.org/x/sync/semaphore" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" @@ -195,6 +196,7 @@ func NewFileSystem(ctx context.Context, serverCfg *ServerConfig) (fuseutil.FileS cacheFileForRangeRead: serverCfg.NewConfig.FileCache.CacheFileForRangeRead, metricHandle: serverCfg.MetricHandle, enableAtomicRenameObject: serverCfg.NewConfig.EnableAtomicRenameObject, + globalMaxWriteBlocksSem: semaphore.NewWeighted(serverCfg.NewConfig.Write.GlobalMaxBlocks), } // Set up root bucket @@ -487,6 +489,10 @@ type fileSystem struct { metricHandle common.MetricHandle enableAtomicRenameObject bool + + // Limits the max number of blocks that can be created across file system when + // streaming writes are enabled. + globalMaxWriteBlocksSem *semaphore.Weighted } //////////////////////////////////////////////////////////////////////// @@ -807,7 +813,8 @@ func (fs *fileSystem) mintInode(ic inode.Core) (in inode.Inode) { fs.contentCache, fs.mtimeClock, ic.Local, - fs.newConfig) + fs.newConfig, + fs.globalMaxWriteBlocksSem) } // Place it in our map of IDs to inodes. diff --git a/internal/fs/handle/dir_handle_test.go b/internal/fs/handle/dir_handle_test.go index e9d0b46bec..e775c99c62 100644 --- a/internal/fs/handle/dir_handle_test.go +++ b/internal/fs/handle/dir_handle_test.go @@ -31,6 +31,7 @@ import ( "github.com/jacobsa/fuse/fuseutil" . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" + "golang.org/x/sync/semaphore" ) func TestDirHandle(t *testing.T) { RunTests(t) } @@ -105,7 +106,8 @@ func (t *DirHandleTest) createLocalFileInode(name string, id fuseops.InodeID) (i contentcache.New("", &t.clock), &t.clock, true, // localFile - &cfg.Config{Write: cfg.WriteConfig{GlobalMaxBlocks: math.MaxInt64}}) + &cfg.Config{}, + semaphore.NewWeighted(math.MaxInt64)) return } diff --git a/internal/fs/inode/dir_test.go b/internal/fs/inode/dir_test.go index 6f966ed144..1d0f98df45 100644 --- a/internal/fs/inode/dir_test.go +++ b/internal/fs/inode/dir_test.go @@ -31,6 +31,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "golang.org/x/net/context" + "golang.org/x/sync/semaphore" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" "github.com/jacobsa/fuse/fuseops" @@ -206,7 +207,8 @@ func (t *DirTest) createLocalFileInode(parent Name, name string, id fuseops.Inod contentcache.New("", &t.clock), &t.clock, true, //localFile - &cfg.Config{Write: cfg.WriteConfig{GlobalMaxBlocks: math.MaxInt64}}) + &cfg.Config{}, + semaphore.NewWeighted(math.MaxInt64)) return } diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 639f3b0d8d..6340ab2989 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -105,6 +105,10 @@ type FileInode struct { // and set bwh to nil after all fileHandlers are closed. // writeHandleCount tracks the count of open fileHandles in write mode. writeHandleCount int32 + + // Limits the max number of blocks that can be created across file system when + // streaming writes are enabled. + globalMaxWriteBlocksSem *semaphore.Weighted } var _ Inode = &FileInode{} @@ -127,24 +131,26 @@ func NewFileInode( contentCache *contentcache.ContentCache, mtimeClock timeutil.Clock, localFile bool, - cfg *cfg.Config) (f *FileInode) { + cfg *cfg.Config, + globalMaxBlocksSem *semaphore.Weighted) (f *FileInode) { // Set up the basic struct. var minObj gcs.MinObject if m != nil { minObj = *m } f = &FileInode{ - bucket: bucket, - mtimeClock: mtimeClock, - id: id, - name: name, - attrs: attrs, - localFileCache: localFileCache, - contentCache: contentCache, - src: minObj, - local: localFile, - unlinked: false, - config: cfg, + bucket: bucket, + mtimeClock: mtimeClock, + id: id, + name: name, + attrs: attrs, + localFileCache: localFileCache, + contentCache: contentCache, + src: minObj, + local: localFile, + unlinked: false, + config: cfg, + globalMaxWriteBlocksSem: globalMaxBlocksSem, } f.lc.Init(id) @@ -916,7 +922,7 @@ func (f *FileInode) ensureBufferedWriteHandler(ctx context.Context) error { Bucket: f.bucket, BlockSize: f.config.Write.BlockSizeMb, MaxBlocksPerFile: f.config.Write.MaxBlocksPerFile, - GlobalMaxBlocksSem: semaphore.NewWeighted(f.config.Write.GlobalMaxBlocks), + GlobalMaxBlocksSem: f.globalMaxWriteBlocksSem, ChunkTransferTimeoutSecs: f.config.GcsRetries.ChunkTransferTimeoutSecs, }) if err != nil { diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 946fe9f771..229fb4b97c 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -16,6 +16,7 @@ package inode import ( "context" + "math" "testing" "time" @@ -33,6 +34,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" + "golang.org/x/sync/semaphore" ) const localFile = "local" @@ -116,7 +118,8 @@ func (t *FileStreamingWritesTest) createInode(fileName string, fileType string) contentcache.New("", &t.clock), &t.clock, isLocal, - &cfg.Config{}) + &cfg.Config{}, + semaphore.NewWeighted(math.MaxInt64)) // Set buffered write config for created inode. t.in.config = &cfg.Config{Write: cfg.WriteConfig{ diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 0a1c6fe935..3314ea2b8f 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -39,6 +39,7 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "golang.org/x/net/context" + "golang.org/x/sync/semaphore" ) //////////////////////////////////////////////////////////////////////// @@ -148,7 +149,8 @@ func (t *FileTest) createInodeWithLocalParam(fileName string, local bool) { contentcache.New("", &t.clock), &t.clock, local, - &cfg.Config{Write: cfg.WriteConfig{GlobalMaxBlocks: math.MaxInt64}}) + &cfg.Config{}, + semaphore.NewWeighted(math.MaxInt64)) t.in.Lock() } From 5bc2b8f713742d7936a8637e27d14111217faeaa Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 9 Jan 2025 13:02:42 +0530 Subject: [PATCH 0126/1298] rename test (#2884) --- internal/block/block_pool_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/block/block_pool_test.go b/internal/block/block_pool_test.go index 80019daeee..6c85eabbeb 100644 --- a/internal/block/block_pool_test.go +++ b/internal/block/block_pool_test.go @@ -274,7 +274,7 @@ func (t *BlockPoolTest) validateGetBlockIsNotBlocked(bp *BlockPool) Block { } } -func (t *BlockPoolTest) TestBlockPool_FreeBlocksChannel() { +func (t *BlockPoolTest) TestFreeBlocksChannel() { freeBlocksCh := make(chan Block) bp := &BlockPool{ freeBlocksCh: freeBlocksCh, @@ -286,7 +286,7 @@ func (t *BlockPoolTest) TestBlockPool_FreeBlocksChannel() { assert.Equal(t.T(), freeBlocksCh, ch) } -func (t *BlockPoolTest) TestBlockPool_canAllocateBlock() { +func (t *BlockPoolTest) TestCanAllocateBlock() { tests := []struct { name string maxBlocks int64 From 17f539fce88b66572594d5724ff88e159d0a83ce Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 9 Jan 2025 19:17:58 +0530 Subject: [PATCH 0127/1298] remove lock comment (#2886) --- internal/fs/fs.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 383fe506fa..b8121c6dba 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1379,8 +1379,6 @@ func (fs *fileSystem) symlinkInodeOrDie( // invalidateChildFileCacheIfExist invalidates the file in read cache. This is used to // invalidate the file in read cache after deletion of original file. -// -// LOCKS_REQUIRED(fs.mu) func (fs *fileSystem) invalidateChildFileCacheIfExist(parentInode inode.DirInode, objectGCSName string) (err error) { if fs.fileCacheHandler != nil { if bucketOwnedDirInode, ok := parentInode.(inode.BucketOwnedDirInode); ok { @@ -2053,7 +2051,6 @@ func (fs *fileSystem) Rename( return fs.renameNonHierarchicalFile(ctx, oldParent, op.OldName, child.MinObject, newParent, op.NewName) } -// LOCKS_EXCLUDED(fs.mu) // LOCKS_EXCLUDED(oldParent) // LOCKS_EXCLUDED(newParent) func (fs *fileSystem) renameHierarchicalFile(ctx context.Context, oldParent inode.DirInode, oldName string, oldObject *gcs.MinObject, newParent inode.DirInode, newName string) error { @@ -2081,7 +2078,6 @@ func (fs *fileSystem) renameHierarchicalFile(ctx context.Context, oldParent inod return nil } -// LOCKS_EXCLUDED(fs.mu) // LOCKS_EXCLUDED(oldParent) // LOCKS_EXCLUDED(newParent) func (fs *fileSystem) renameNonHierarchicalFile( From 8d25c75748aff4296de35116c511cb0b80aea9e9 Mon Sep 17 00:00:00 2001 From: Ankita Luthra Date: Thu, 9 Jan 2025 19:40:43 +0530 Subject: [PATCH 0128/1298] Negative cache TTL Fix (#2837) * rename ttl to primaryCacheTTL to clearly distinguish from negative cache * adds new field for negative cache ttl * adds documentation for stat cache TTLs * use new ttl for negative cache expiration * renames ttl to primaryCacheTTL in unit tests * refactors unit test for negative cache ttl * passes negative cache ttl from bucket manager * adds Negative stat ttl config in Bucket Config * passes negative stat cache ttl in setup bucket * adds config for negative metadata cache ttl * supports -1 in negative cache ttl * fix for -1 value support in negative cache * adds validation for error values in negative cache ttl * adds validation and defaultvalue for negative cache ttl * adds defaultvalue for negative cache ttl as 5 * adds negative cache ttl in params.yml * minor comment fix * hns test fix * fast stat cache test fix * fix TestKernelListCache_DeleteAndListDirectory test --- cfg/config.go | 12 +++++++ cfg/constants.go | 3 +- cfg/params.yaml | 10 ++++++ cfg/rationalize.go | 7 +++- cfg/rationalize_test.go | 17 ++++++---- cfg/validate.go | 10 ++++++ cmd/config_validation_test.go | 2 ++ cmd/mount.go | 1 + cmd/root_test.go | 4 ++- internal/fs/caching_test.go | 9 +++-- internal/fs/hns_bucket_test.go | 3 +- internal/gcsx/bucket_manager.go | 13 ++++--- internal/storage/caching/fast_stat_bucket.go | 32 ++++++++++------- .../storage/caching/fast_stat_bucket_test.go | 34 ++++++++++--------- internal/storage/caching/integration_test.go | 10 +++--- ...inite_kernel_list_cache_delete_dir_test.go | 2 +- 16 files changed, 118 insertions(+), 51 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 7788772949..fabc4d3141 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -198,6 +198,8 @@ type MetadataCacheConfig struct { ExperimentalMetadataPrefetchOnMount string `yaml:"experimental-metadata-prefetch-on-mount"` + NegativeTtlSecs int64 `yaml:"negative-ttl-secs"` + StatCacheMaxSizeMb int64 `yaml:"stat-cache-max-size-mb"` TtlSecs int64 `yaml:"ttl-secs"` @@ -471,6 +473,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.DurationP("max-retry-sleep", "", 30000000000*time.Nanosecond, "The maximum duration allowed to sleep in a retry loop with exponential backoff for failed requests to GCS backend. Once the backoff duration exceeds this limit, the retry continues with this specified maximum value.") + flagSet.IntP("metadata-cache-negative-ttl-secs", "", 5, "The negative-ttl-secs value in seconds to be used for expiring negative entries in metadata-cache. It can be set to -1 for no-ttl, 0 for no cache and > 0 for ttl-controlled negative entries in metadata-cache. Any value set below -1 will throw an error.") + + if err := flagSet.MarkHidden("metadata-cache-negative-ttl-secs"); err != nil { + return err + } + flagSet.IntP("metadata-cache-ttl-secs", "", 60, "The ttl value in seconds to be used for expiring items in metadata-cache. It can be set to -1 for no-ttl, 0 for no cache and > 0 for ttl-controlled metadata-cache. Any value set below -1 will throw an error.") flagSet.StringSliceP("o", "", []string{}, "Additional system-specific mount options. Multiple options can be passed as comma separated. For readonly, use --o ro") @@ -816,6 +824,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("metadata-cache.negative-ttl-secs", flagSet.Lookup("metadata-cache-negative-ttl-secs")); err != nil { + return err + } + if err := v.BindPFlag("metadata-cache.ttl-secs", flagSet.Lookup("metadata-cache-ttl-secs")); err != nil { return err } diff --git a/cfg/constants.go b/cfg/constants.go index 843d76d16b..d48ea9f8b9 100644 --- a/cfg/constants.go +++ b/cfg/constants.go @@ -75,7 +75,8 @@ const ( AverageSizeOfNegativeStatCacheEntry uint64 = 240 // MetadataCacheTTLConfigKey is the Viper configuration key for the metadata //cache's time-to-live (TTL) in seconds. - MetadataCacheTTLConfigKey = "metadata-cache.ttl-secs" + MetadataCacheTTLConfigKey = "metadata-cache.ttl-secs" + MetadataNegativeCacheTTLConfigKey = "metadata-cache.negative-ttl-secs" // StatCacheMaxSizeConfigKey is the Viper configuration key for the maximum //size of the metadata stat cache in megabytes. StatCacheMaxSizeConfigKey = "metadata-cache.stat-cache-max-size-mb" diff --git a/cfg/params.yaml b/cfg/params.yaml index 1ab1188450..e43245e9dc 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -556,6 +556,16 @@ deprecated: true deprecation-warning: "Experimental flag: could be removed even in a minor release." +- config-path: "metadata-cache.negative-ttl-secs" + flag-name: "metadata-cache-negative-ttl-secs" + type: "int" + usage: >- + The negative-ttl-secs value in seconds to be used for expiring negative entries in metadata-cache. It + can be set to -1 for no-ttl, 0 for no cache and > 0 for ttl-controlled + negative entries in metadata-cache. Any value set below -1 will throw an error. + default: "5" + hide-flag: true + - config-path: "metadata-cache.stat-cache-max-size-mb" flag-name: "stat-cache-max-size-mb" type: "int" diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 2348ba2ab6..fbb46e844d 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -41,7 +41,12 @@ func decodeURL(u string) (string, error) { // on the user flags/configs. func resolveMetadataCacheTTL(v isSet, c *MetadataCacheConfig) { // If metadata-cache:ttl-secs has been set, then it overrides both - // stat-cache-ttl and type-cache-tll. + // stat-cache-ttl, type-cache-tll and negative cache ttl. + if v.IsSet(MetadataNegativeCacheTTLConfigKey) { + if c.NegativeTtlSecs == -1 { + c.NegativeTtlSecs = maxSupportedTTLInSeconds + } + } if v.IsSet(MetadataCacheTTLConfigKey) { if c.TtlSecs == -1 { c.TtlSecs = maxSupportedTTLInSeconds diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 040fa35e80..7de1831ab5 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -226,11 +226,12 @@ func (f flagSet) IsSet(key string) bool { func TestRationalizeMetadataCache(t *testing.T) { testCases := []struct { - name string - flags flagSet - config *Config - expectedTTLSecs int64 - expectedStatCacheSize int64 + name string + flags flagSet + config *Config + expectedTTLSecs int64 + expectedNegativeTTLSecs int64 + expectedStatCacheSize int64 }{ { name: "new_ttl_flag_set", @@ -291,11 +292,13 @@ func TestRationalizeMetadataCache(t *testing.T) { config: &Config{ MetadataCache: MetadataCacheConfig{ TtlSecs: -1, + NegativeTtlSecs: -1, StatCacheMaxSizeMb: -1, }, }, - expectedTTLSecs: math.MaxInt64 / int64(time.Second), // Max supported ttl in seconds. - expectedStatCacheSize: math.MaxUint64 >> 20, // Max supported cache size in MiB. + expectedTTLSecs: math.MaxInt64 / int64(time.Second), // Max supported ttl in seconds. + expectedNegativeTTLSecs: math.MaxInt64 / int64(time.Second), // Max supported ttl in seconds. + expectedStatCacheSize: math.MaxUint64 >> 20, // Max supported cache size in MiB. }, } diff --git a/cfg/validate.go b/cfg/validate.go index 3c7fd79d68..9b29c54307 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -128,6 +128,16 @@ func isValidMetadataCache(v isSet, c *MetadataCacheConfig) error { } } + // Validate negative-ttl-secs. + if v.IsSet(MetadataNegativeCacheTTLConfigKey) { + if c.NegativeTtlSecs < -1 { + return fmt.Errorf("the value of negative-ttl-secs for metadata-cache can't be less than -1") + } + if c.NegativeTtlSecs > maxSupportedTTLInSeconds { + return fmt.Errorf("the value of negative-ttl-secs in metadata-cache is too high to be supported. Max is 9223372036") + } + } + // Validate type-cache-max-size-mb. if c.TypeCacheMaxSizeMb < -1 { return fmt.Errorf("the value of type-cache-max-size-mb for metadata-cache can't be less than -1") diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 3ab4a542c2..f30ffc6026 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -661,6 +661,7 @@ func TestValidateConfigFile_MetadataCacheConfigSuccessful(t *testing.T) { ExperimentalMetadataPrefetchOnMount: "disabled", StatCacheMaxSizeMb: 32, TtlSecs: 60, + NegativeTtlSecs: 5, TypeCacheMaxSizeMb: 4, }, }, @@ -677,6 +678,7 @@ func TestValidateConfigFile_MetadataCacheConfigSuccessful(t *testing.T) { ExperimentalMetadataPrefetchOnMount: "sync", StatCacheMaxSizeMb: 40, TtlSecs: 100, + NegativeTtlSecs: 5, TypeCacheMaxSizeMb: 10, }, }, diff --git a/cmd/mount.go b/cmd/mount.go index 2ba7d36c8f..aee2431cc6 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -93,6 +93,7 @@ be interacting with the file system.`) OpRateLimitHz: newConfig.GcsConnection.LimitOpsPerSec, StatCacheMaxSizeMB: uint64(newConfig.MetadataCache.StatCacheMaxSizeMb), StatCacheTTL: time.Duration(newConfig.MetadataCache.TtlSecs) * time.Second, + NegativeStatCacheTTL: time.Duration(newConfig.MetadataCache.NegativeTtlSecs) * time.Second, EnableMonitoring: cfg.IsMetricsEnabled(&newConfig.Metrics), AppendThreshold: 1 << 21, // 2 MiB, a total guess. ChunkTransferTimeoutSecs: newConfig.GcsRetries.ChunkTransferTimeoutSecs, diff --git a/cmd/root_test.go b/cmd/root_test.go index cc022faa69..8d7ed87a52 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1001,7 +1001,7 @@ func TestArgsParsing_MetadataCacheFlags(t *testing.T) { }{ { name: "normal", - args: []string{"gcsfuse", "--stat-cache-capacity=2000", "--stat-cache-ttl=2m", "--type-cache-ttl=1m20s", "--enable-nonexistent-type-cache", "--experimental-metadata-prefetch-on-mount=async", "--stat-cache-max-size-mb=15", "--metadata-cache-ttl-secs=25", "--type-cache-max-size-mb=30", "abc", "pqr"}, + args: []string{"gcsfuse", "--stat-cache-capacity=2000", "--stat-cache-ttl=2m", "--type-cache-ttl=1m20s", "--enable-nonexistent-type-cache", "--experimental-metadata-prefetch-on-mount=async", "--stat-cache-max-size-mb=15", "--metadata-cache-ttl-secs=25", "--metadata-cache-negative-ttl-secs=20", "--type-cache-max-size-mb=30", "abc", "pqr"}, expectedConfig: &cfg.Config{ MetadataCache: cfg.MetadataCacheConfig{ DeprecatedStatCacheCapacity: 2000, @@ -1011,6 +1011,7 @@ func TestArgsParsing_MetadataCacheFlags(t *testing.T) { ExperimentalMetadataPrefetchOnMount: "async", StatCacheMaxSizeMb: 15, TtlSecs: 25, + NegativeTtlSecs: 20, TypeCacheMaxSizeMb: 30, }, }, @@ -1027,6 +1028,7 @@ func TestArgsParsing_MetadataCacheFlags(t *testing.T) { ExperimentalMetadataPrefetchOnMount: "disabled", StatCacheMaxSizeMb: 32, TtlSecs: 60, + NegativeTtlSecs: 5, TypeCacheMaxSizeMb: 4, }, }, diff --git a/internal/fs/caching_test.go b/internal/fs/caching_test.go index 598581a419..f1a0dce177 100644 --- a/internal/fs/caching_test.go +++ b/internal/fs/caching_test.go @@ -40,6 +40,7 @@ import ( //////////////////////////////////////////////////////////////////////// const ttl = 10 * time.Minute +const negativeCacheTTL = 1 * time.Minute var ( uncachedBucket gcs.Bucket @@ -63,7 +64,9 @@ func (t *cachingTestCommon) SetUpTestSuite() { ttl, statCache, &cacheClock, - uncachedBucket) + uncachedBucket, + negativeCacheTTL, + ) // Enable directory type caching. t.serverCfg.DirTypeCacheTTL = ttl @@ -469,7 +472,9 @@ func (t *MultiBucketMountCachingTest) SetUpTestSuite() { ttl, statCache, &cacheClock, - uncachedBuckets[bucketName]) + uncachedBuckets[bucketName], + negativeCacheTTL, + ) } // Enable directory type caching. diff --git a/internal/fs/hns_bucket_test.go b/internal/fs/hns_bucket_test.go index 9417daae54..e596549a26 100644 --- a/internal/fs/hns_bucket_test.go +++ b/internal/fs/hns_bucket_test.go @@ -494,7 +494,8 @@ func (t *HNSCachedBucketMountTest) SetupSuite() { ttl, statCache, &cacheClock, - uncachedHNSBucket) + uncachedHNSBucket, + negativeCacheTTL) // Enable directory type caching. t.serverCfg.DirTypeCacheTTL = ttl diff --git a/internal/gcsx/bucket_manager.go b/internal/gcsx/bucket_manager.go index f817b874a7..ba2171d392 100644 --- a/internal/gcsx/bucket_manager.go +++ b/internal/gcsx/bucket_manager.go @@ -40,8 +40,11 @@ type BucketConfig struct { EgressBandwidthLimitBytesPerSecond float64 OpRateLimitHz float64 StatCacheMaxSizeMB uint64 - StatCacheTTL time.Duration - EnableMonitoring bool + // Config for TTL of entries for existing file in stat cache + StatCacheTTL time.Duration + // Config for TTL of entries for non-existing file in stat cache + NegativeStatCacheTTL time.Duration + EnableMonitoring bool // Files backed by on object of length at least AppendThreshold that have // only been appended to (i.e. none of the object's contents have been @@ -195,7 +198,8 @@ func (bm *bucketManager) SetUpBucket( return } - // Enable cached StatObject results, if appropriate. + // Enable cached StatObject results based on stat cache config. + // Disabling stat cache with below config also disables negative stat cache. if bm.config.StatCacheTTL != 0 && bm.sharedStatCache != nil { var statCache metadata.StatCache if isMultibucketMount { @@ -208,7 +212,8 @@ func (bm *bucketManager) SetUpBucket( bm.config.StatCacheTTL, statCache, timeutil.RealClock(), - b) + b, + bm.config.NegativeStatCacheTTL) } // Enable content type awareness diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index bfe86b5a50..2e985d8479 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -34,15 +34,18 @@ import ( // bucket. Records are invalidated when modifications are made through this // bucket, and after the supplied TTL. func NewFastStatBucket( - ttl time.Duration, + primaryCacheTTL time.Duration, cache metadata.StatCache, clock timeutil.Clock, - wrapped gcs.Bucket) (b gcs.Bucket) { + wrapped gcs.Bucket, + negativeCacheTTL time.Duration, +) (b gcs.Bucket) { fsb := &fastStatBucket{ - cache: cache, - clock: clock, - wrapped: wrapped, - ttl: ttl, + cache: cache, + clock: clock, + wrapped: wrapped, + primaryCacheTTL: primaryCacheTTL, + negativeCacheTTL: negativeCacheTTL, } b = fsb @@ -66,7 +69,10 @@ type fastStatBucket struct { // Constant data ///////////////////////// - ttl time.Duration + // TTL for entries for existing files and folders in the cache. + primaryCacheTTL time.Duration + // TTL for entries for non-existing files and folders in the cache. + negativeCacheTTL time.Duration } //////////////////////////////////////////////////////////////////////// @@ -78,7 +84,7 @@ func (b *fastStatBucket) insertMultiple(objs []*gcs.Object) { b.mu.Lock() defer b.mu.Unlock() - expiration := b.clock.Now().Add(b.ttl) + expiration := b.clock.Now().Add(b.primaryCacheTTL) for _, o := range objs { m := storageutil.ConvertObjToMinObject(o) b.cache.Insert(m, expiration) @@ -90,7 +96,7 @@ func (b *fastStatBucket) insertMultipleMinObjects(minObjs []*gcs.MinObject) { b.mu.Lock() defer b.mu.Unlock() - expiration := b.clock.Now().Add(b.ttl) + expiration := b.clock.Now().Add(b.primaryCacheTTL) for _, o := range minObjs { b.cache.Insert(o, expiration) } @@ -111,7 +117,7 @@ func (b *fastStatBucket) insertHierarchicalListing(listing *gcs.Listing) { b.mu.Lock() defer b.mu.Unlock() - expiration := b.clock.Now().Add(b.ttl) + expiration := b.clock.Now().Add(b.primaryCacheTTL) for _, o := range listing.MinObjects { if !strings.HasSuffix(o.Name, "/") { @@ -147,7 +153,7 @@ func (b *fastStatBucket) insertFolder(f *gcs.Folder) { b.mu.Lock() defer b.mu.Unlock() - b.cache.InsertFolder(f, b.clock.Now().Add(b.ttl)) + b.cache.InsertFolder(f, b.clock.Now().Add(b.primaryCacheTTL)) } // LOCKS_EXCLUDED(b.mu) @@ -155,7 +161,7 @@ func (b *fastStatBucket) addNegativeEntry(name string) { b.mu.Lock() defer b.mu.Unlock() - expiration := b.clock.Now().Add(b.ttl) + expiration := b.clock.Now().Add(b.negativeCacheTTL) b.cache.AddNegativeEntry(name, expiration) } @@ -164,7 +170,7 @@ func (b *fastStatBucket) addNegativeEntryForFolder(name string) { b.mu.Lock() defer b.mu.Unlock() - expiration := b.clock.Now().Add(b.ttl) + expiration := b.clock.Now().Add(b.negativeCacheTTL) b.cache.AddNegativeEntryForFolder(name, expiration) } diff --git a/internal/storage/caching/fast_stat_bucket_test.go b/internal/storage/caching/fast_stat_bucket_test.go index 5ab06fa6e2..84a6e78936 100644 --- a/internal/storage/caching/fast_stat_bucket_test.go +++ b/internal/storage/caching/fast_stat_bucket_test.go @@ -38,7 +38,8 @@ func TestFastStatBucket(t *testing.T) { RunTests(t) } // Boilerplate //////////////////////////////////////////////////////////////////////// -const ttl = time.Second +const primaryCacheTTL = time.Second +const negativeCacheTTL = time.Second * 5 type fastStatBucketTest struct { cache mock_gcscaching.MockStatCache @@ -57,10 +58,11 @@ func (t *fastStatBucketTest) SetUp(ti *TestInfo) { t.wrapped = storage.NewMockBucket(ti.MockController, "wrapped") t.bucket = caching.NewFastStatBucket( - ttl, + primaryCacheTTL, t.cache, &t.clock, - t.wrapped) + t.wrapped, + negativeCacheTTL) } //////////////////////////////////////////////////////////////////////// @@ -128,7 +130,7 @@ func (t *CreateObjectTest) WrappedSucceeds() { WillOnce(Return(obj, nil)) // Insert - ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))) + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))) // Call o, err := t.bucket.CreateObject(context.TODO(), &gcs.CreateObjectRequest{}) @@ -267,7 +269,7 @@ func (t *FinalizeUploadTest) WrappedSucceeds() { ExpectCall(t.wrapped, "FinalizeUpload")(Any(), Any()). WillOnce(Return(&gcs.MinObject{}, nil)) // Insert - ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))) + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))) // Call o, err := t.bucket.FinalizeUpload(context.TODO(), writer) @@ -343,7 +345,7 @@ func (t *CopyObjectTest) WrappedSucceeds() { WillOnce(Return(obj, nil)) // Insert - ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))) + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))) // Call o, err := t.bucket.CopyObject(context.TODO(), &gcs.CopyObjectRequest{}) @@ -421,7 +423,7 @@ func (t *ComposeObjectsTest) WrappedSucceeds() { WillOnce(Return(obj, nil)) // Insert - ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))) + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))) // Call o, err := t.bucket.ComposeObjects(context.TODO(), &gcs.ComposeObjectsRequest{}) @@ -519,7 +521,7 @@ func (t *StatObjectTest) IgnoresCacheEntryWhenForceFetchFromGcsIsTrue() { WillOnce(Return(minObjFromGcs, extObjAttrFromGcs, nil)) // Insert - ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))) + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))) m, e, err := t.bucket.StatObject(context.TODO(), req) AssertEq(nil, err) @@ -551,7 +553,7 @@ func (t *StatObjectTest) TestStatObject_ForceFetchFromGcsTrueAndReturnExtendedOb WillOnce(Return(minObjFromGcs, &gcs.ExtendedObjectAttributes{}, nil)) // Insert - ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))) + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))) m, e, err := t.bucket.StatObject(context.TODO(), req) AssertEq(nil, err) @@ -631,7 +633,7 @@ func (t *StatObjectTest) WrappedSaysNotFound() { // AddNegativeEntry ExpectCall(t.cache, "AddNegativeEntry")( name, - timeutil.TimeEq(t.clock.Now().Add(ttl))) + timeutil.TimeEq(t.clock.Now().Add(negativeCacheTTL))) // Call req := &gcs.StatObjectRequest{ @@ -659,7 +661,7 @@ func (t *StatObjectTest) WrappedSucceeds() { WillOnce(Return(minObj, nil, nil)) // Insert - ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))) + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))) // Call req := &gcs.StatObjectRequest{ @@ -741,7 +743,7 @@ func (t *ListObjectsTest) NonEmptyListing() { WillOnce(Return(expected, nil)) // Insert - ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))).Times(2) + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))).Times(2) // Call listing, err := t.bucket.ListObjects(context.TODO(), &gcs.ListObjectsRequest{}) @@ -767,9 +769,9 @@ func (t *ListObjectsTest) NonEmptyListingForHNS() { WillOnce(Return(expected, nil)) // insert - ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))).Times(2) + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))).Times(2) - ExpectCall(t.cache, "InsertFolder")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))).Times(1) + ExpectCall(t.cache, "InsertFolder")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))).Times(1) // call listing, err := t.bucket.ListObjects(context.TODO(), &gcs.ListObjectsRequest{}) @@ -843,7 +845,7 @@ func (t *UpdateObjectTest) WrappedSucceeds() { WillOnce(Return(obj, nil)) // Insert - ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))) + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))) // Call o, err := t.bucket.UpdateObject(context.TODO(), &gcs.UpdateObjectRequest{}) @@ -1098,7 +1100,7 @@ func (t *MoveObjectTest) MoveObjectSucceeds() { ExpectCall(t.wrapped, "MoveObject")(Any(), Any()).WillOnce(Return(obj, nil)) // Insert in cache - ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(ttl))) + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))) // Call o, err := t.bucket.MoveObject(context.TODO(), &gcs.MoveObjectRequest{}) diff --git a/internal/storage/caching/integration_test.go b/internal/storage/caching/integration_test.go index c2a070e29e..c450699a37 100644 --- a/internal/storage/caching/integration_test.go +++ b/internal/storage/caching/integration_test.go @@ -62,10 +62,12 @@ func (t *IntegrationTest) SetUp(ti *TestInfo) { t.wrapped = fake.NewFakeBucket(&t.clock, bucketName, gcs.NonHierarchical) t.bucket = caching.NewFastStatBucket( - ttl, + primaryCacheTTL, cache, &t.clock, - t.wrapped) + t.wrapped, + negativeCacheTTL, + ) } func (t *IntegrationTest) stat(name string) (o *gcs.Object, err error) { @@ -183,7 +185,7 @@ func (t *IntegrationTest) PositiveCacheExpiration() { AssertEq(nil, err) // Advance time. - t.clock.AdvanceTime(ttl + time.Millisecond) + t.clock.AdvanceTime(primaryCacheTTL + time.Millisecond) // StatObject should no longer see it. _, err = t.stat(name) @@ -286,7 +288,7 @@ func (t *IntegrationTest) NegativeCacheExpiration() { AssertEq(nil, err) // Advance time. - t.clock.AdvanceTime(ttl + time.Millisecond) + t.clock.AdvanceTime(negativeCacheTTL + time.Millisecond) // Now StatObject should see it. o, err := t.stat(name) diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go index c53d3c192c..6ba291a2dd 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go @@ -122,7 +122,7 @@ func TestInfiniteKernelListCacheDeleteDirTest(t *testing.T) { // already became stale due to delete operation. // TODO: Replace metadata-cache-ttl-secs with something better flagsSet := [][]string{ - {"--kernel-list-cache-ttl-secs=-1", "--metadata-cache-ttl-secs=0"}, + {"--kernel-list-cache-ttl-secs=-1", "--metadata-cache-ttl-secs=0", "--metadata-cache-negative-ttl-secs=0"}, } // Run tests. From 3a70525064a6faecd7cd92fa783bf50614162c70 Mon Sep 17 00:00:00 2001 From: Ankita Luthra Date: Fri, 10 Jan 2025 12:13:20 +0530 Subject: [PATCH 0129/1298] Negative cache integration tests (#2887) * add negative cache test with 0 ttl * removes unwanted TODO * adds integration test for negative cache finite ttl * adds integration test for negative cache infinite ttl * updates year in file header * updates year in file header * fixed disabled NACK test * fixed NACK tests * adds in CI pipeline * adds neg_stat_cache in CD pipeline * header check fix --- tools/cd_scripts/e2e_test.sh | 1 + ...inite_kernel_list_cache_delete_dir_test.go | 1 - .../disabled_negative_stat_cache_test.go | 91 ++++++++++++++ .../finite_int_negative_stat_cache_test.go | 102 ++++++++++++++++ .../infinite_negative_stat_cache_test.go | 90 ++++++++++++++ .../negative_stat_cache/setup_test.go | 113 ++++++++++++++++++ tools/integration_tests/run_e2e_tests.sh | 1 + 7 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go create mode 100644 tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go create mode 100644 tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go create mode 100644 tools/integration_tests/negative_stat_cache/setup_test.go diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index ab054f646d..328a20d7cb 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -161,6 +161,7 @@ TEST_DIR_PARALLEL=( "benchmarking" "mount_timeout" "stale_handle" + "negative_stat_cache" ) # These tests never become parallel as they are changing bucket permissions. diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go index 6ba291a2dd..3268fb9759 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go @@ -120,7 +120,6 @@ func TestInfiniteKernelListCacheDeleteDirTest(t *testing.T) { // Note: metadata cache is disabled to avoid cache consistency issue between // gcsfuse cache and kernel cache. As gcsfuse cache might hold the entry which // already became stale due to delete operation. - // TODO: Replace metadata-cache-ttl-secs with something better flagsSet := [][]string{ {"--kernel-list-cache-ttl-secs=-1", "--metadata-cache-ttl-secs=0", "--metadata-cache-negative-ttl-secs=0"}, } diff --git a/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go new file mode 100644 index 0000000000..dc9acf5faa --- /dev/null +++ b/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go @@ -0,0 +1,91 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package negative_stat_cache + +import ( + "log" + "os" + "path" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/assert" +) + +type disabledNegativeStatCacheTest struct { + flags []string +} + +func (s *disabledNegativeStatCacheTest) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(s.flags, testDirName) +} + +func (s *disabledNegativeStatCacheTest) Teardown(t *testing.T) { + setup.UnmountGCSFuse(rootDir) +} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +func (s *disabledNegativeStatCacheTest) TestNegativeStatCacheDisabled(t *testing.T) { + targetDir := path.Join(testDirPath, "explicit_dir") + // Create test directory + operations.CreateDirectory(targetDir, t) + targetFile := path.Join(targetDir, "file1.txt") + + // Error should be returned as file does not exist + _, err := os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) + + assert.NotNil(t, err) + // Assert the underlying error is File Not Exist + assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") + + // Adding the object with same name + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, "explicit_dir/file1.txt", "some-content", t) + + // File should be returned, as call will be served from GCS and gcsfuse should not return from cache + f, err := os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) + + //Assert File is found + assert.NoError(t, err) + assert.Contains(t, f.Name(), "explicit_dir/file1.txt") + assert.Nil(t, f.Close()) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestDisabledNegativeStatCacheTest(t *testing.T) { + ts := &disabledNegativeStatCacheTest{} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + // Define flag set to run the tests. + flagsSet := []string{"--metadata-cache-negative-ttl-secs=0"} + + // Run tests. + ts.flags = flagsSet + log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) +} diff --git a/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go new file mode 100644 index 0000000000..5e130e5191 --- /dev/null +++ b/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go @@ -0,0 +1,102 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package negative_stat_cache + +import ( + "log" + "os" + "path" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/assert" +) + +type finiteNegativeStatCacheTest struct { + flags []string +} + +func (s *finiteNegativeStatCacheTest) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(s.flags, testDirName) +} + +func (s *finiteNegativeStatCacheTest) Teardown(t *testing.T) { + setup.UnmountGCSFuse(rootDir) +} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +func (s *finiteNegativeStatCacheTest) TestFiniteNegativeStatCache(t *testing.T) { + targetDir := path.Join(testDirPath, "explicit_dir") + // Create test directory + operations.CreateDirectory(targetDir, t) + targetFile := path.Join(targetDir, "file1.txt") + + // Error should be returned as file does not exist + _, err := os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) + + assert.NotNil(t, err) + // Assert the underlying error is File Not Exist + assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") + + // Adding the object with same name + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file1.txt"), "some-content", t) + + // Error should be returned again, as call will not be served from GCS due to finite gcsfuse stat cache + _, err = os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) + + assert.NotNil(t, err) + // Assert the underlying error is File Not Exist + assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") + + //Wait for Cache to expire + time.Sleep(3 * time.Second) + + // File should be returned, as call will be served from GCS and gcsfuse should not return from cache + f, err := os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) + + //Assert File is found + assert.NoError(t, err) + assert.Contains(t, f.Name(), "explicit_dir/file1.txt") + assert.Nil(t, f.Close()) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestFiniteNegativeStatCacheTest(t *testing.T) { + ts := &finiteNegativeStatCacheTest{} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + // Define flag set to run the tests. + flagsSet := []string{"--metadata-cache-negative-ttl-secs=2"} + + // Run tests. + ts.flags = flagsSet + log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) +} diff --git a/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go new file mode 100644 index 0000000000..4072e48b2f --- /dev/null +++ b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go @@ -0,0 +1,90 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package negative_stat_cache + +import ( + "log" + "os" + "path" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/assert" +) + +type infiniteNegativeStatCacheTest struct { + flags []string +} + +func (s *infiniteNegativeStatCacheTest) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(s.flags, testDirName) +} + +func (s *infiniteNegativeStatCacheTest) Teardown(t *testing.T) { + setup.UnmountGCSFuse(rootDir) +} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +func (s *infiniteNegativeStatCacheTest) TestInfiniteNegativeStatCache(t *testing.T) { + targetDir := path.Join(testDirPath, "explicit_dir") + // Create test directory + operations.CreateDirectory(targetDir, t) + targetFile := path.Join(targetDir, "file1.txt") + + // Error should be returned as file does not exist + _, err := os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) + + assert.NotNil(t, err) + // Assert the underlying error is File Not Exist + assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") + + // Adding the object with same name + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, "explicit_dir/file1.txt", "some-content", t) + + // Error should be returned again, as call will not be served from GCS due to infinite gcsfuse stat cache + _, err = os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) + + assert.NotNil(t, err) + // Assert the underlying error is File Not Exist + assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestInfiniteNegativeStatCacheTest(t *testing.T) { + ts := &infiniteNegativeStatCacheTest{} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + // Define flag set to run the tests. + flagsSet := []string{"--metadata-cache-negative-ttl-secs=-1"} + + // Run tests. + ts.flags = flagsSet + log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) +} diff --git a/tools/integration_tests/negative_stat_cache/setup_test.go b/tools/integration_tests/negative_stat_cache/setup_test.go new file mode 100644 index 0000000000..3ee184121f --- /dev/null +++ b/tools/integration_tests/negative_stat_cache/setup_test.go @@ -0,0 +1,113 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package negative_stat_cache + +import ( + "context" + "log" + "os" + "path" + "testing" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +const ( + testDirName = "NegativeStatCacheTest" + onlyDirMounted = "OnlyDirMountNegativeStatCache" +) + +var ( + testDirPath string + mountFunc func([]string) error + // mount directory is where our tests run. + mountDir string + // root directory is the directory to be unmounted. + rootDir string + storageClient *storage.Client + ctx context.Context +) + +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func mountGCSFuseAndSetupTestDir(flags []string, testDirName string) { + // When tests are running in GKE environment, use the mounted directory provided as test flag. + if setup.MountedDirectory() != "" { + mountDir = setup.MountedDirectory() + } + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) + setup.SetMntDir(mountDir) + testDirPath = setup.SetupTestDirectory(testDirName) +} + +//////////////////////////////////////////////////////////////////////// +// TestMain +//////////////////////////////////////////////////////////////////////// + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + + // Create common storage client to be used in test. + ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() + + // If Mounted Directory flag is set, run tests for mounted directory. + setup.RunTestsForMountedDirectoryFlag(m) + // Else run tests for testBucket. + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + + // Save mount and root directory variables. + mountDir, rootDir = setup.MntDir(), setup.MntDir() + + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() + + if successCode == 0 { + log.Println("Running dynamic mounting tests...") + // Save mount directory variable to have path of bucket to run tests. + mountDir = path.Join(setup.MntDir(), setup.TestBucket()) + mountFunc = dynamic_mounting.MountGcsfuseWithDynamicMounting + successCode = m.Run() + } + + if successCode == 0 { + log.Println("Running only dir mounting tests...") + setup.SetOnlyDirMounted(onlyDirMounted + "/") + mountDir = rootDir + mountFunc = only_dir_mounting.MountGcsfuseWithOnlyDir + successCode = m.Run() + setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), setup.OnlyDirMounted(), testDirName)) + } + + // Clean up test directory created. + setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) + os.Exit(successCode) +} diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index d49dd4ebd2..6907101cdf 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -84,6 +84,7 @@ TEST_DIR_PARALLEL=( "benchmarking" "mount_timeout" "stale_handle" + "negative_stat_cache" ) # These tests never become parallel as it is changing bucket permissions. From 9c18a82cee701a0807983d09ab44dcc8300fa7cb Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Fri, 10 Jan 2025 14:09:00 +0530 Subject: [PATCH 0130/1298] Upgrading direct go dependencies (#2888) --- go.mod | 70 ++- go.sum | 1406 ++++---------------------------------------------------- 2 files changed, 116 insertions(+), 1360 deletions(-) diff --git a/go.mod b/go.mod index 426450c3c2..574c2a77c0 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,15 @@ go 1.23.4 require ( cloud.google.com/go/compute/metadata v0.6.0 - cloud.google.com/go/iam v1.3.0 - cloud.google.com/go/secretmanager v1.14.2 - cloud.google.com/go/storage v1.49.0 + cloud.google.com/go/iam v1.3.1 + cloud.google.com/go/secretmanager v1.14.3 + cloud.google.com/go/storage v1.50.0 contrib.go.opencensus.io/exporter/ocagent v0.7.0 contrib.go.opencensus.io/exporter/prometheus v0.4.2 contrib.go.opencensus.io/exporter/stackdriver v0.13.14 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0 - github.com/fsouza/fake-gcs-server v1.50.2 + github.com/fsouza/fake-gcs-server v1.52.1 github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.1 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec @@ -41,81 +41,79 @@ require ( go.opentelemetry.io/otel/sdk v1.33.0 go.opentelemetry.io/otel/sdk/metric v1.33.0 go.opentelemetry.io/otel/trace v1.33.0 - golang.org/x/net v0.33.0 - golang.org/x/oauth2 v0.24.0 + golang.org/x/net v0.34.0 + golang.org/x/oauth2 v0.25.0 golang.org/x/sync v0.10.0 - golang.org/x/sys v0.28.0 + golang.org/x/sys v0.29.0 golang.org/x/text v0.21.0 - golang.org/x/time v0.8.0 - google.golang.org/api v0.214.0 + golang.org/x/time v0.9.0 + google.golang.org/api v0.216.0 google.golang.org/grpc v1.69.2 - google.golang.org/protobuf v1.36.1 + google.golang.org/protobuf v1.36.2 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 ) require ( cel.dev/expr v0.19.1 // indirect - cloud.google.com/go v0.117.0 // indirect - cloud.google.com/go/auth v0.13.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect - cloud.google.com/go/longrunning v0.6.3 // indirect - cloud.google.com/go/monitoring v1.22.0 // indirect + cloud.google.com/go v0.118.0 // indirect + cloud.google.com/go/auth v0.14.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect + cloud.google.com/go/longrunning v0.6.4 // indirect + cloud.google.com/go/monitoring v1.22.1 // indirect cloud.google.com/go/pubsub v1.45.3 // indirect - cloud.google.com/go/trace v1.11.2 // indirect + cloud.google.com/go/trace v1.11.3 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect - github.com/aws/aws-sdk-go v1.44.217 // indirect + github.com/aws/aws-sdk-go v1.55.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20241213214725-57cfbe6fad57 // indirect + github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/envoyproxy/go-control-plane v0.13.1 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.3 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-kit/log v0.2.1 // indirect - github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/renameio/v2 v2.0.0 // indirect - github.com/google/s2a-go v0.1.8 // indirect + github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.17.11 // indirect - github.com/magiconair/properties v1.8.7 // indirect + github.com/magiconair/properties v1.8.9 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/xattr v0.4.10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/prometheus/prometheus v0.35.0 // indirect - github.com/prometheus/statsd_exporter v0.22.7 // indirect - github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/prometheus/prometheus v0.301.0 // indirect + github.com/prometheus/statsd_exporter v0.28.0 // indirect + github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/afero v1.12.0 // indirect + github.com/spf13/cast v1.7.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 // indirect - google.golang.org/genproto v0.0.0-20241219192143-6b3ec007d9bb // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect + google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 59f29e681a..43e309353a 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -bazil.org/fuse v0.0.0-20200407214033-5883e5a4b512/go.mod h1:FbcW6z/2VytnFDhZfumh8Ss8zxHE6qpMP5sHTRe0EaM= cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -17,67 +15,49 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.117.0 h1:Z5TNFfQxj7WG2FgOGX1ekC5RiXrYgms6QscOm32M/4s= -cloud.google.com/go v0.117.0/go.mod h1:ZbwhVTb1DBGt2Iwb3tNO6SEK4q+cplHZmLWH+DelYYc= -cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= -cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= -cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= -cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go v0.118.0 h1:tvZe1mgqRxpiVa3XlIGMiPcEUbP1gNXELgD4y/IXmeQ= +cloud.google.com/go v0.118.0/go.mod h1:zIt2pkedt/mo+DQjcT4/L3NDxzHPR29j5HcclNH+9PM= +cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM= +cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A= +cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= +cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= -cloud.google.com/go/iam v1.3.0 h1:4Wo2qTaGKFtajbLpF6I4mywg900u3TLlHDb6mriLDPU= -cloud.google.com/go/iam v1.3.0/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= -cloud.google.com/go/kms v1.20.2 h1:NGTHOxAyhDVUGVU5KngeyGScrg2D39X76Aphe6NC7S0= -cloud.google.com/go/kms v1.20.2/go.mod h1:LywpNiVCvzYNJWS9JUcGJSVTNSwPwi0vBAotzDqn2nc= -cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= -cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= -cloud.google.com/go/longrunning v0.6.3 h1:A2q2vuyXysRcwzqDpMMLSI6mb6o39miS52UEG/Rd2ng= -cloud.google.com/go/longrunning v0.6.3/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= -cloud.google.com/go/monitoring v1.22.0 h1:mQ0040B7dpuRq1+4YiQD43M2vW9HgoVxY98xhqGT+YI= -cloud.google.com/go/monitoring v1.22.0/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/iam v1.3.1 h1:KFf8SaT71yYq+sQtRISn90Gyhyf4X8RGgeAVC8XGf3E= +cloud.google.com/go/iam v1.3.1/go.mod h1:3wMtuyT4NcbnYNPLMBzYRFiEfjKfJlLVLrisE7bwm34= +cloud.google.com/go/kms v1.20.4 h1:CJ0hMpOg1ANN9tx/a/GPJ+Uxudy8k6f3fvGFuTHiE5A= +cloud.google.com/go/kms v1.20.4/go.mod h1:gPLsp1r4FblUgBYPOcvI/bUPpdMg2Jm1ZVKU4tQUfcc= +cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= +cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= +cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg= +cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs= +cloud.google.com/go/monitoring v1.22.1 h1:KQbnAC4IAH+5x3iWuPZT5iN9VXqKMzzOgqcYB6fqPDE= +cloud.google.com/go/monitoring v1.22.1/go.mod h1:AuZZXAoN0WWWfsSvET1Cpc4/1D8LXq8KRDU87fMS6XY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/pubsub v1.45.3 h1:prYj8EEAAAwkp6WNoGTE4ahe0DgHoyJd5Pbop931zow= cloud.google.com/go/pubsub v1.45.3/go.mod h1:cGyloK/hXC4at7smAtxFnXprKEFTqmMXNNd9w+bd94Q= -cloud.google.com/go/secretmanager v1.14.2 h1:2XscWCfy//l/qF96YE18/oUaNJynAx749Jg3u0CjQr8= -cloud.google.com/go/secretmanager v1.14.2/go.mod h1:Q18wAPMM6RXLC/zVpWTlqq2IBSbbm7pKBlM3lCKsmjw= +cloud.google.com/go/secretmanager v1.14.3 h1:XVGHbcXEsbrgi4XHzgK5np81l1eO7O72WOXHhXUemrM= +cloud.google.com/go/secretmanager v1.14.3/go.mod h1:Pwzcfn69Ni9Lrk1/XBzo1H9+MCJwJ6CDCoeoQUsMN+c= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= -cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= -cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= -cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= +cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= +cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= +cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE= +cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8= contrib.go.opencensus.io/exporter/ocagent v0.7.0 h1:BEfdCTXfMV30tLZD8c9n64V/tIZX5+9sXiuFLnrr1k8= contrib.go.opencensus.io/exporter/ocagent v0.7.0/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= @@ -85,33 +65,8 @@ contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9 contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4= contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= -github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v63.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.25/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI= @@ -122,443 +77,85 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0/go.mod h1:l2fIqmwB+FKSfvn3bAD/0i+AXAxhIZjTK2svT/mgUXs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 h1:GYUJLfvd++4DMuMhCFLgLXvFwofIxh/qOwoGuS/LTew= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= -github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.20/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= -github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= -github.com/Microsoft/hcsshim v0.9.2/go.mod h1:7pLA8lDk46WKDWlVsENo92gC0XFa8rbKfyFRBqxEbCc= -github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= -github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= -github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.43.11/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.44.217 h1:FcWC56MRl+k756aH3qeMQTylSdeJ58WN0iFz3fkyRz0= -github.com/aws/aws-sdk-go v1.44.217/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= -github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= -github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= -github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= -github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= -github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= -github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20241213214725-57cfbe6fad57 h1:put7Je9ZyxbHtwr7IqGrW4LLVUupJQ2gbsDshKISSgU= -github.com/cncf/xds/go v0.0.0-20241213214725-57cfbe6fad57/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= -github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= -github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= -github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= -github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= -github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= -github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/cgroups v1.0.3/go.mod h1:/ofk34relqNjSGyqPrmEULrO4Sc8LJhvJmWbUCUKqj8= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= -github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= -github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= -github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= -github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= -github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= -github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= -github.com/containerd/containerd v1.6.1/go.mod h1:1nJz5xCZPusx6jJU8Frfct988y0NpumIq9ODB0kLtoE= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= -github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/continuity v0.2.2/go.mod h1:pWygW9u7LtS1o4N/Tn0FoCFDIXZ7rxcMX7HX1Dmibvk= -github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= -github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= -github.com/containerd/go-cni v1.1.0/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= -github.com/containerd/go-cni v1.1.3/go.mod h1:Rflh2EJ/++BA2/vY5ao3K6WJRR/bZKsX123aPk+kUtA= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= -github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= -github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= -github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= -github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= -github.com/containerd/imgcrypt v1.1.3/go.mod h1:/TPA1GIDXMzbj01yd8pIbQiLdQxed5ue1wb8bP7PQu4= -github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= -github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= -github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= -github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= -github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= -github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= -github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= -github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v1.0.1/go.mod h1:AKuhXbN5EzmD4yTNtfSsX3tPcmtrBI6QcRV0NiNt15Y= -github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containernetworking/plugins v1.0.1/go.mod h1:QHCfGpaTwYTbbH+nZXKVTxNBDZcxSOplJT5ico8/FLE= -github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= -github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/containers/ocicrypt v1.1.2/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.6.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= +github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= -github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= -github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= -github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= -github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dgryski/go-sip13 v0.0.0-20200911182023-62edffca9245/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/digitalocean/godo v1.78.0/go.mod h1:GBmu8MkjZmNARE7IXRPmkbbnocNN8+uBm0xbEVw2LCs= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v20.10.14+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= -github.com/envoyproxy/go-control-plane v0.13.1 h1:vPfJZCkob6yTMEgS+0TwfTUfbHjfy/6vOJ8hUWX/uXE= -github.com/envoyproxy/go-control-plane v0.13.1/go.mod h1:X45hY0mufo6Fd0KW3rqsGvQMw58jvjymeCzBU3mWyHw= +github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= +github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= +github.com/envoyproxy/go-control-plane/envoy v1.32.3 h1:hVEaommgvzTjTd4xCaFd+kEQ2iYBtGxP6luyLrx6uOk= +github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= +github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fsouza/fake-gcs-server v1.50.2 h1:ulrS1pavCOCbMZfN5ZPgBRMFWclON9xDsuLBniXtQoE= -github.com/fsouza/fake-gcs-server v1.50.2/go.mod h1:VU6Zgei4647KuT4XER8WHv5Hcj2NIySndyG8gfvwckA= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= -github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsouza/fake-gcs-server v1.52.1 h1:Hx3G2ZpyBzHGmW7cHURWWoTm6jM3M5fcWMIAHBYlJyc= +github.com/fsouza/fake-gcs-server v1.52.1/go.mod h1:Paxf25VmSNMN52L+2/cVulF5fkLUA0YJIYjTGJiwf3c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= -github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.0/go.mod h1:YkVgnZu1ZjjL7xTxrfm/LLZBfkhTqSR1ydtm6jTKKwI= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/analysis v0.21.2/go.mod h1:HZwRk4RRisyG8vx2Oe6aqeSQcoxRp47Xkp3+K6q+LdY= -github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/errors v0.20.2/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M= -github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= -github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/loads v0.21.1/go.mod h1:/DtAMXXneXFjbQMGEtbamCZb+4x7eGwkvZCvBmwUG+g= -github.com/go-openapi/runtime v0.23.1/go.mod h1:AKurw9fNre+h3ELZfk6ILsfvPN+bvvlaU/M9q/r9hpk= -github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= -github.com/go-openapi/strfmt v0.21.0/go.mod h1:ZRQ409bWMj+SOgXofQAGTIo2Ebu72Gs+WaRADcS5iNg= -github.com/go-openapi/strfmt v0.21.1/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= -github.com/go-openapi/strfmt v0.21.2/go.mod h1:I/XVKeLc5+MM5oPNN7P6urMOpuLXEcNrCX/rPGuWb0k= -github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/validate v0.21.0/go.mod h1:rjnrwK57VJ7A8xqfpAOEKRH8yQSGUriMu5/zuPSQ1hg= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-resty/resty/v2 v2.1.1-0.20191201195748-d7b97669fe48/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4= -github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= -github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= -github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= -github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= -github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= +github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= -github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -572,8 +169,6 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -589,16 +184,11 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -606,25 +196,16 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -634,120 +215,34 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20220318212150-b2ab0324ddda/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= -github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= -github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= +github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= -github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= -github.com/gophercloud/gophercloud v0.24.0/go.mod h1:Q8fZtyi5zZxPS/j9aj3sSxtvj41AdQMDwyo1myduD5c= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/regexp v0.0.0-20220304095617-2e8d9baf4ac2/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2 h1:gDLXvp5S9izjldquuoAhDzccbskOL6tDC5jMSyx3zxE= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.15.2/go.mod h1:7pdNwVWBBHGiCxa9lAszqCJMbfTISJ7oMftp8+UGV08= -github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= -github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= -github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.12.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= -github.com/hetznercloud/hcloud-go v1.33.1/go.mod h1:XX/TQub3ge0yWR2yHWmnDVIrB+MQbda1pHxkUmDlUME= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20210905161508-09a460cdf81d/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/intel/goresctrl v0.2.0/go.mod h1:+CZdzouYFn5EsxgqAQTEzMfwKwuc0fVdMrT9FCCAVRQ= -github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= -github.com/j-keck/arping v1.0.2/go.mod h1:aJbELhR92bSk7tp79AWM/ftfc90EfEi2bQJrbBFOsPw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= github.com/jacobsa/fuse v0.0.0-20240607092844-7285af0d05b0 h1:IWVMQZZvWN+9FeRwWnZAINYNrsr3yyCWI2BcddQBDvk= @@ -764,223 +259,61 @@ github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3 h1:+gHfvQxomE6fI4 github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3/go.mod h1:mPvulh9VKXvo+yOlrD4VYOOYuLdZJ36wa/5QIrtXvWs= github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6 h1:XKHJmHcgU9glxk3eLPiRZT5VFSHJitVTnMj/EgIoXC4= github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6/go.mod h1:JEWKD6V8xETMW+DEv+IQVz++f8Cn8O/X0HPeDY3qNis= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= -github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/kolo/xmlrpc v0.0.0-20201022064351-38db28db192b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM= +github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= +github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/linode/linodego v1.4.0/go.mod h1:PVsRxSlOiJyvG4/scTszpmZDTdgS+to3X6eS8pRrWI8= -github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= -github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= -github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= -github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= -github.com/miekg/dns v1.1.48/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.78 h1:LqW2zy52fxnI4gg8C2oZviTaKHcBV36scS+RzJnxUFs= -github.com/minio/minio-go/v7 v7.0.78/go.mod h1:84gmIilaX4zcvAWWzJ5Z1WI5axN+hAbM5w25xf8xvC0= -github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/minio/minio-go/v7 v7.0.83 h1:W4Kokksvlz3OKf3OqIlzDNKd4MERlC2oN8YptwJ0+GA= +github.com/minio/minio-go/v7 v7.0.83/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= -github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU= -github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg= -github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/sys/symlink v0.2.0/go.mod h1:7uZVF2dqJjG/NsClqul95CqKOBRQyYSNnJ6BMgR/gFs= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= -github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= -github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= -github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= -github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= -github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= -github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA= github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= @@ -988,17 +321,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/prometheus/alertmanager v0.24.0/go.mod h1:r6fy/D7FRuZh5YbnX6J3MBY0eI4Pb5yPYS7/bPSXXqI= -github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= @@ -1006,323 +330,124 @@ github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrb github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.29.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.34.0/go.mod h1:gB3sOl7P0TvJabZpLY5uQMpUqRCPPCyRLCZYc7JZTNE= github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= -github.com/prometheus/common/assets v0.1.0/go.mod h1:D17UVUE12bHbim7HzwUvtqm6gwBEaDQ0F+hIGbFbccI= -github.com/prometheus/common/sigv4 v0.1.0/go.mod h1:2Jkxxk9yYvCkE5G1sQT7GuEXm57JrvHu9k5YwTjsNtI= -github.com/prometheus/exporter-toolkit v0.7.1/go.mod h1:ZUBIj498ePooX9t/2xtDjeQYwvRpiPP2lh5u4iblj2g= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/prometheus/prometheus v0.35.0 h1:N93oX6BrJ2iP3UuE2Uz4Lt+5BkUpaFer3L9CbADzesc= -github.com/prometheus/prometheus v0.35.0/go.mod h1:7HaLx5kEPKJ0GDgbODG0fZgXbQ8K/XjZNJXQmbmgQlY= -github.com/prometheus/statsd_exporter v0.22.7 h1:7Pji/i2GuhK6Lu7DHrtTkFmNBCudCPT1pX2CziuyQR0= +github.com/prometheus/prometheus v0.301.0 h1:0z8dgegmILivNomCd79RKvVkIols8vBGPKmcIBc7OyY= +github.com/prometheus/prometheus v0.301.0/go.mod h1:BJLjWCKNfRfjp7Q48DrAjARnCi7GhfUVvUFEAWTssZM= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/prometheus/statsd_exporter v0.28.0 h1:S3ZLyLm/hOKHYZFOF0h4zYmd0EeKyPF9R1pFBYXUgYY= +github.com/prometheus/statsd_exporter v0.28.0/go.mod h1:Lq41vNkMLfiPANmI+uHb5/rpFFUTxPXiiNpmsAYLvDI= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= -github.com/rs/cors v1.8.2/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= -github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= +github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= -github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= -github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= -github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= +github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= +github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= -github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= -github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= -github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= -github.com/tv42/httpunix v0.0.0-20191220191345-2ba4b9c3382c/go.mod h1:hzIxponao9Kjc7aWznkXaL4U4TWaDSs8zcsY4Ka08nM= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netlink v1.1.1-0.20210330154013-f5de75959ad5/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v1.1.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= go.einride.tech/aip v0.68.0 h1:4seM66oLzTpz50u4K1zlJyOXQ3tCzcJN7I22tKkjipw= go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= -go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= -go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= -go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= -go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= -go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= -go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= -go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= -go.mongodb.org/mongo-driver v1.7.3/go.mod h1:NqaYOwnXWr5Pm7AOpO5QFxKJ503nbMse/R79oO62zWg= -go.mongodb.org/mongo-driver v1.7.5/go.mod h1:VXEWRZ6URJIkUq2SCAyapmhH0ZLRBP+FT4xhp5Zvxng= -go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/detectors/gcp v1.33.0 h1:FVPoXEoILwgbZUu4X7YSgsESsAmGRgoYcnXkzgQPhP4= go.opentelemetry.io/contrib/detectors/gcp v1.33.0/go.mod h1:ZHrLmr4ikK2AwRj9QL+c9s2SOlgoSRyMpNVzUj2fZqI= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.28.0/go.mod h1:vEhqr0m4eTc+DWxfsXoXue2GBgV2uUwVznkGIHW/e5w= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.31.0/go.mod h1:PFmBsWbldL1kiWZk9+0LBZz2brhByaGsvp6pRICMlPE= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= -go.opentelemetry.io/otel v1.3.0/go.mod h1:PWIKzi6JCp7sM0k9yZ43VX+T345uNbAkDKwHVjb2PTs= -go.opentelemetry.io/otel v1.6.0/go.mod h1:bfJD2DZVw0LBxghOTlgnlI0CV3hLDu9XF/QKOUXMTQQ= -go.opentelemetry.io/otel v1.6.1/go.mod h1:blzUabWHkX6LJewxvadmzafgh/wnvBSDBdOuwkAtrWQ= go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= -go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.3.0/go.mod h1:VpP4/RMn8bv8gNo9uK7/IMY4mtWLELsS+JIP0inH0h4= -go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.6.1/go.mod h1:NEu79Xo32iVb+0gVNV8PMd7GoWqnyDXRlj04yFjqz40= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.3.0/go.mod h1:hO1KLR7jcKaDDKDkvI9dP/FIhpmna5lkqPUQdEjFAM8= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.1/go.mod h1:YJ/JbY5ag/tSQFXzH3mtDmHqzF3aFn3DI/aB1n7pt4w= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.3.0/go.mod h1:keUU7UfnwWTWpJ+FWnyqmogPa82nuU5VUANFq49hlMY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.6.1/go.mod h1:UJJXJj0rltNIemDMwkOJyggsvyMG9QHfJeFH0HS5JjM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.3.0/go.mod h1:QNX1aly8ehqqX1LEa6YniTU7VY9I6R3X/oPxhGdTceE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.6.1/go.mod h1:DAKwdo06hFLc0U88O10x4xnb5sc7dDRDqRuiN+io8JE= go.opentelemetry.io/otel/exporters/prometheus v0.55.0 h1:sSPw658Lk2NWAv74lkD3B/RSDb+xRFx46GjkrL3VUZo= go.opentelemetry.io/otel/exporters/prometheus v0.55.0/go.mod h1:nC00vyCmQixoeaxF6KNyP42II/RHa9UdruK02qBmHvI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0 h1:W5AWUn/IVe8RFb5pZx1Uh9Laf/4+Qmm4kJL5zPuvR+0= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0/go.mod h1:mzKxJywMNBdEX8TSJais3NnsVZUaJ+bAy6UxPTng2vk= -go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= -go.opentelemetry.io/otel/metric v0.28.0/go.mod h1:TrzsfQAmQaB1PDcdhBauLMk7nyyg9hm+GoQq/ekE9Iw= go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= -go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= -go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= -go.opentelemetry.io/otel/sdk v1.3.0/go.mod h1:rIo4suHNhQwBIPg9axF8V9CA72Wz2mKF1teNrup8yzs= -go.opentelemetry.io/otel/sdk v1.6.1/go.mod h1:IVYrddmFZ+eJqu2k38qD3WezFR2pymCzm8tdxyh3R4E= go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= -go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= -go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU= go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= -go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= -go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk= -go.opentelemetry.io/otel/trace v1.6.0/go.mod h1:qs7BrU5cZ8dXQHBGxHMOxwME/27YH2qEp4/+tZLLwJE= -go.opentelemetry.io/otel/trace v1.6.1/go.mod h1:RkFRM1m0puWIq10oxImnGEduNBzxiN7TXluRBtE+5j0= go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.11.0/go.mod h1:QpEjXPrNQzrFDZgoTo49dgHR9RYRSrg3NAKnUGl9YpQ= -go.opentelemetry.io/proto/otlp v0.12.1/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/automaxprocs v1.5.1/go.mod h1:BF4eumQw0P9GtnuxxovUd06vwm1o18oMzFtK66vU6XU= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -1333,8 +458,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0 h1:Mi0bCswbz+9cXmwFAdxoo5GPFMKONUpua6iUdtQS7lk= -golang.org/x/exp v0.0.0-20240530194437-404ba88c7ed0/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -1347,8 +472,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -1357,39 +480,21 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1400,130 +505,57 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= -golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1531,135 +563,54 @@ golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210903071746-97244b99971b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= -golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -1679,38 +630,16 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20200916195026-c9a70fc28ce3/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -1728,41 +657,19 @@ google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.214.0 h1:h2Gkq07OYi6kusGOaT/9rnNljuXmqPnaig7WGPmKbwA= -google.golang.org/api v0.214.0/go.mod h1:bYPpLG8AyeMWwDU6NXoB00xC0DFkikVvd5MfwoxjLqE= +google.golang.org/api v0.216.0 h1:xnEHy+xWFrtYInWPy8OdGFsyIfWJjtVnO39g7pz2BFY= +google.golang.org/api v0.216.0/go.mod h1:K9wzQMvWi47Z9IU7OgdOofvZuw75Ge3PPITImZR/UyI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -1771,7 +678,6 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1780,7 +686,6 @@ google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -1791,59 +696,16 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20241219192143-6b3ec007d9bb h1:JGs+s1Q6osip3cDY197L1HmkuPn8wPp9Hfy9jl+Uz+U= -google.golang.org/genproto v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:o8GgNarfULyZPNaIY8RDfXM7AZcmcKC/tbMWp/ZOFDw= -google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb h1:B7GIB7sr443wZ/EAEl7VZjmh1V6qzkt5V+RYcUYtS1U= -google.golang.org/genproto/googleapis/api v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:E5//3O5ZIG2l71Xnt+P/CYUY8Bxs8E7WMoZ9tlcMbAY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb h1:3oy2tynMOP1QbTC0MsNNAV+Se8M2Bd0A5+x1QHyw+pI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241219192143-6b3ec007d9bb/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422 h1:6GUHKGv2huWOHKmDXLMNE94q3fBDlEHI+oTRIZSebK0= +google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422/go.mod h1:1NPAxoesyw/SgLPqaUp9u1f9PWCLAk/jVmhx7gJZStg= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= +google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1852,27 +714,9 @@ google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKa google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1885,38 +729,21 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= +google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/telebot.v3 v3.0.0/go.mod h1:7rExV8/0mDDNu9epSrDm/8j22KLaActH1Tbee6YjzWg= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -1927,14 +754,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -1942,69 +763,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= -k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/api v0.22.5/go.mod h1:mEhXyLaSD1qTOf40rRiKXkc+2iCem09rWLlFwhCEiAs= -k8s.io/api v0.23.5/go.mod h1:Na4XuKng8PXJ2JsploYYrivXrINeTaycCGcYgF91Xm8= -k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apimachinery v0.22.5/go.mod h1:xziclGKwuuJ2RM5/rSFQSYAj0zdbci3DH8kj+WvyN0U= -k8s.io/apimachinery v0.23.5/go.mod h1:BEuFMMBaIbcOqVIJqNZJXGFTP4W6AycEpb5+m/97hrM= -k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= -k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= -k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/apiserver v0.22.5/go.mod h1:s2WbtgZAkTKt679sYtSudEQrTGWUSQAPe6MupLnlmaQ= -k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= -k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= -k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/client-go v0.22.5/go.mod h1:cs6yf/61q2T1SdQL5Rdcjg9J1ElXSwbjSrW2vFImM4Y= -k8s.io/client-go v0.23.5/go.mod h1:flkeinTO1CirYgzMPRWxUCnV0G4Fbu2vLhYCObnt/r4= -k8s.io/code-generator v0.19.7/go.mod h1:lwEq3YnLYb/7uVXLorOJfxg+cUu2oihFhHZ0n9NIla0= -k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= -k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/component-base v0.22.5/go.mod h1:VK3I+TjuF9eaa+Ln67dKxhGar5ynVbwnGrUiNF4MqCI= -k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= -k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= -k8s.io/cri-api v0.23.1/go.mod h1:REJE3PSU0h/LOV1APBrupxrEJqnoxZC8KWzkBUHwrK4= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201113003025-83324d819ded/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/klog/v2 v2.40.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211109043538-20434351676c/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= -k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20211116205334-6203023598ed/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= -sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= From 70735eafeeaf51f6cb6ddaa5dc141fe4148c6200 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 10 Jan 2025 15:29:30 +0530 Subject: [PATCH 0131/1298] turn on the flag (#2889) --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/root_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index fabc4d3141..5758ef8b23 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -317,7 +317,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.BoolP("enable-atomic-rename-object", "", false, "Enables support for atomic rename object operation on HNS bucket.") + flagSet.BoolP("enable-atomic-rename-object", "", true, "Enables support for atomic rename object operation on HNS bucket.") if err := flagSet.MarkHidden("enable-atomic-rename-object"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index e43245e9dc..5bb8ec2b35 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -75,7 +75,7 @@ flag-name: "enable-atomic-rename-object" type: "bool" usage: "Enables support for atomic rename object operation on HNS bucket." - default: false + default: true hide-flag: true - config-path: "enable-hns" diff --git a/cmd/root_test.go b/cmd/root_test.go index 8d7ed87a52..d5a200fe33 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -848,8 +848,8 @@ func TestArgsParsing_EnableAtomicRenameObjectFlag(t *testing.T) { }{ { name: "normal", - args: []string{"gcsfuse", "--enable-atomic-rename-object=true", "abc", "pqr"}, - expectedEnableAtomicRenameObject: true, + args: []string{"gcsfuse", "--enable-atomic-rename-object=false", "abc", "pqr"}, + expectedEnableAtomicRenameObject: false, }, } From 6015cdbd122e8352137f27d36cea17b52076a3a1 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 10 Jan 2025 16:38:39 +0530 Subject: [PATCH 0132/1298] Update wrapped error while returning FileClobberedError after calling clobbered method (#2885) * Update wrapped error in FileClobberedError * handled nil error explicitly * executing checking err for not nil before clobbered check * refactored code according to go style guide * minor fix * fixed formatting --- internal/fs/inode/file.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 6340ab2989..1083caccc8 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -726,13 +726,15 @@ func (f *FileInode) fetchLatestGcsObject(ctx context.Context) (*gcs.Object, erro // default sets the projection to full, which fetches all the object // properties. latestGcsObj, isClobbered, err := f.clobbered(ctx, true, true) + if err != nil { + return nil, err + } if isClobbered { - err = &gcsfuse_errors.FileClobberedError{ - Err: err, + return nil, &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("file was clobbered"), } } - - return latestGcsObj, err + return latestGcsObj, nil } // Sync writes out contents to GCS. If this fails due to the generation From 869049382f88c6f4e8e295247bc661c827fba6e7 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Fri, 10 Jan 2025 11:57:52 +0000 Subject: [PATCH 0133/1298] Added new integration test cases for streaming writes flow (#2842) * Added new testcases * Added truncate and different config tests * added comment * refactoring as per PR comments * refactoring as per PR comments * handling test failures * Update tools/integration_tests/streaming_writes/truncate_file_test.go Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * added subtest method --------- Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> --- tools/cd_scripts/e2e_test.sh | 1 + .../local_file/create_file_test.go | 9 ++ .../local_file/unlinked_file_test.go | 46 +++++++ .../local_file/write_file_test.go | 60 +++++++++ tools/integration_tests/run_e2e_tests.sh | 1 + .../streaming_writes/buffer_size_test.go | 73 +++++++++++ .../default_mount_local_file_test.go | 44 +++++++ .../streaming_writes/default_mount_test.go | 38 ++++++ .../streaming_writes/streaming_writes_test.go | 69 +++++++++++ .../streaming_writes/truncate_file_test.go | 117 ++++++++++++++++++ 10 files changed, 458 insertions(+) create mode 100644 tools/integration_tests/streaming_writes/buffer_size_test.go create mode 100644 tools/integration_tests/streaming_writes/default_mount_local_file_test.go create mode 100644 tools/integration_tests/streaming_writes/default_mount_test.go create mode 100644 tools/integration_tests/streaming_writes/streaming_writes_test.go create mode 100644 tools/integration_tests/streaming_writes/truncate_file_test.go diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 328a20d7cb..66026b9103 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -162,6 +162,7 @@ TEST_DIR_PARALLEL=( "mount_timeout" "stale_handle" "negative_stat_cache" + "streaming_writes" ) # These tests never become parallel as they are changing bucket permissions. diff --git a/tools/integration_tests/local_file/create_file_test.go b/tools/integration_tests/local_file/create_file_test.go index fb045a13b8..8754063d3f 100644 --- a/tools/integration_tests/local_file/create_file_test.go +++ b/tools/integration_tests/local_file/create_file_test.go @@ -56,3 +56,12 @@ func TestCreateNewFileWhenSameFileExistsOnGCS(t *testing.T) { // Ensure that the content on GCS is not overwritten. ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, FileName1, GCSFileContent, t) } + +func TestEmptyFileCreation(t *testing.T) { + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, "", t) +} diff --git a/tools/integration_tests/local_file/unlinked_file_test.go b/tools/integration_tests/local_file/unlinked_file_test.go index 1096d8911b..260fc21419 100644 --- a/tools/integration_tests/local_file/unlinked_file_test.go +++ b/tools/integration_tests/local_file/unlinked_file_test.go @@ -103,3 +103,49 @@ func TestSyncOnUnlinkedLocalFile(t *testing.T) { operations.ValidateStaleNFSFileHandleError(t, err) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) } + +func TestFileWithSameNameCanBeCreatedWhenDeletedBeforeSync(t *testing.T) { + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + // Write some content. + operations.WriteWithoutClose(fh, FileContents, t) + // Remove and close the file. + operations.RemoveFile(filePath) + // Currently flush calls returns error if unlinked. Ignoring that error here. + _ = fh.Close() + // Validate that file is not created on GCS + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + // Verify unlink operation succeeds. + operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) + + // Create a local file. + _, fh = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + + newContents := "newContents" + operations.WriteWithoutClose(fh, newContents, t) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, newContents, t) +} + +func TestFileWithSameNameCanBeCreatedAfterDelete(t *testing.T) { + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + // Write some content. + operations.WriteWithoutClose(fh, FileContents, t) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, + FileName1, FileContents, t) + // Remove the file. + operations.RemoveFile(filePath) + // Validate that file id deleted from GCS + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + // Verify unlink operation succeeds. + operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) + + // Create a local file. + _, fh = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + + newContents := "newContents" + operations.WriteWithoutClose(fh, newContents, t) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, newContents, t) +} diff --git a/tools/integration_tests/local_file/write_file_test.go b/tools/integration_tests/local_file/write_file_test.go index a98aaaa5b2..5cdecb0b3a 100644 --- a/tools/integration_tests/local_file/write_file_test.go +++ b/tools/integration_tests/local_file/write_file_test.go @@ -54,3 +54,63 @@ func TestRandomWritesToLocalFile(t *testing.T) { CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, "stsstring3", t) } + +func TestOutOfOrderWritesToNewFile(t *testing.T) { + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + + // Write some contents to file sequentially. + for i := 0; i < 2; i++ { + operations.WriteWithoutClose(fh, FileContents, t) + } + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + + // Write at previous offset. + operations.WriteAt("hello", 0, fh, t) + + expectedString := "hellotringtestString" + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, + FileName1, expectedString, t) +} + +func TestMultipleOutOfOrderWritesToNewFile(t *testing.T) { + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + + // Write some contents to file sequentially. + for i := 0; i < 2; i++ { + operations.WriteWithoutClose(fh, FileContents, t) + } + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + + // Write at previous offset. + operations.WriteAt("hello", 15, fh, t) + + // Write at new offset. + operations.WriteAt("hey", 30, fh, t) + + emptyBytes := [10]byte{} + expectedString := "testStringtestShello" + string(emptyBytes[:]) + "hey" + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, + FileName1, expectedString, t) +} + +func TestWritesToNewFileStartingAtNonZeroOffset(t *testing.T) { + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + // Write at future offset. + operations.WriteAt("hello", 15, fh, t) + // Write at zero offset now. + operations.WriteAt("hey", 0, fh, t) + + emptyBytes := [12]byte{} + expectedString := "hey" + string(emptyBytes[:]) + "hello" + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, + FileName1, expectedString, t) +} diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 6907101cdf..e87e97ffbd 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -85,6 +85,7 @@ TEST_DIR_PARALLEL=( "mount_timeout" "stale_handle" "negative_stat_cache" + "streaming_writes" ) # These tests never become parallel as it is changing bucket permissions. diff --git a/tools/integration_tests/streaming_writes/buffer_size_test.go b/tools/integration_tests/streaming_writes/buffer_size_test.go new file mode 100644 index 0000000000..209eb8ee33 --- /dev/null +++ b/tools/integration_tests/streaming_writes/buffer_size_test.go @@ -0,0 +1,73 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes + +import ( + "testing" + + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +func TestWritesWithDifferentConfig(t *testing.T) { + testCases := []struct { + name string + flags []string + fileSize int64 + }{ + { + name: "BlockSizeGreaterThanFileSize", + flags: []string{"--enable-streaming-writes=true", "--write-block-size-mb=5", "--write-max-blocks-per-file=2"}, + fileSize: 2 * 1024 * 1024, + }, + { + name: "BlockSizeLessThanFileSize", + flags: []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=20"}, + fileSize: 5 * 1024 * 1024, + }, + { + // BlockSize*num_blocks < fileSize + name: "NumberOfBlocksLessThanFileSize", + flags: []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"}, + fileSize: 10 * 1024 * 1024, + }, + { + name: "BlockSizeEqualToFileSize", + flags: []string{"--enable-streaming-writes=true", "--write-block-size-mb=5", "--write-max-blocks-per-file=2"}, + fileSize: 5 * 1024 * 1024, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + setup.MountGCSFuseWithGivenMountFunc(tc.flags, mountFunc) + defer setup.UnmountGCSFuse(rootDir) + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + data, err := operations.GenerateRandomData(tc.fileSize) + if err != nil { + t.Fatalf("Error in generating data: %v", err) + } + + // Write data to file. + operations.WriteAt(string(data[:]), 0, fh, t) + + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, string(data[:]), t) + }) + } +} diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go new file mode 100644 index 0000000000..cb19583b12 --- /dev/null +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -0,0 +1,44 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes + +import ( + "testing" + + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/suite" +) + +type defaultMountLocalFile struct { + defaultMountCommonTest +} + +func (t *defaultMountLocalFile) SetupTest() { + t.fileName = FileName1 + setup.GenerateRandomString(5) + // Create a local file. + _, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) +} + +func (t *defaultMountLocalFile) SetupSubTest() { + t.fileName = FileName1 + setup.GenerateRandomString(5) + // Create a local file. + _, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) +} + +// Executes all tests that run with single streamingWrites configuration for localFiles. +func TestDefaultMountLocalFileTest(t *testing.T) { + suite.Run(t, new(defaultMountLocalFile)) +} diff --git a/tools/integration_tests/streaming_writes/default_mount_test.go b/tools/integration_tests/streaming_writes/default_mount_test.go new file mode 100644 index 0000000000..e473d8f64b --- /dev/null +++ b/tools/integration_tests/streaming_writes/default_mount_test.go @@ -0,0 +1,38 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes + +import ( + "os" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/suite" +) + +type defaultMountCommonTest struct { + f1 *os.File + fileName string + suite.Suite +} + +func (t *defaultMountCommonTest) SetupSuite() { + flags := []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"} + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) + testDirPath = setup.SetupTestDirectory(testDirName) +} + +func (t *defaultMountCommonTest) TearDownSuite() { + setup.UnmountGCSFuse(rootDir) +} diff --git a/tools/integration_tests/streaming_writes/streaming_writes_test.go b/tools/integration_tests/streaming_writes/streaming_writes_test.go new file mode 100644 index 0000000000..6b73b47bfb --- /dev/null +++ b/tools/integration_tests/streaming_writes/streaming_writes_test.go @@ -0,0 +1,69 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes + +import ( + "context" + "log" + "os" + "testing" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +const ( + testDirName = "StreamingWritesTest" +) + +var ( + testDirPath string + mountFunc func([]string) error + // root directory is the directory to be unmounted. + rootDir string + storageClient *storage.Client + ctx context.Context +) + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + if setup.MountedDirectory() != "" { + // Once streaming writes are enabled by default, we can run all defaultMount tests here. + log.Printf("These tests will not run with mounted directory..") + return + } + + // Create storage client before running tests. + ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() + + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + rootDir = setup.MntDir() + + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() + os.Exit(successCode) +} diff --git a/tools/integration_tests/streaming_writes/truncate_file_test.go b/tools/integration_tests/streaming_writes/truncate_file_test.go new file mode 100644 index 0000000000..c3fdb5f4b5 --- /dev/null +++ b/tools/integration_tests/streaming_writes/truncate_file_test.go @@ -0,0 +1,117 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes + +import ( + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func (t *defaultMountCommonTest) TestTruncate() { + truncateSize := 2 * 1024 * 1024 + + err := t.f1.Truncate(int64(truncateSize)) + + assert.NoError(t.T(), err) + data := make([]byte, truncateSize) + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, string(data[:]), t.T()) +} + +func (t *defaultMountCommonTest) TestWriteAfterTruncate() { + truncateSize := 10 + + testCases := []struct { + name string + offset int64 + fileSize int64 + }{ + { + name: "ZeroOffset", + offset: 0, + fileSize: 10, + }, + { + name: "RandomOffset", + offset: 5, + fileSize: 10, + }, + { + name: "Append", + offset: 10, + fileSize: 12, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + data := make([]byte, tc.fileSize) + // Perform truncate. + err := t.f1.Truncate(int64(truncateSize)) + require.NoError(t.T(), err) + + // Triggers writes after truncate. + newData := []byte("hi") + _, err = t.f1.WriteAt(newData, tc.offset) + + require.NoError(t.T(), err) + data[tc.offset] = newData[0] + data[tc.offset+1] = newData[1] + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, string(data[:]), t.T()) + }) + } + +} + +func (t *defaultMountCommonTest) TestWriteAndTruncate() { + truncateSize := 20 + operations.WriteWithoutClose(t.f1, FileContents, t.T()) + + err := t.f1.Truncate(int64(truncateSize)) + + require.NoError(t.T(), err) + data := make([]byte, 10) + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents+string(data[:]), t.T()) +} + +func (t *defaultMountCommonTest) TestWriteTruncateWrite() { + truncateSize := 30 + + // Write + operations.WriteWithoutClose(t.f1, FileContents, t.T()) + // Perform truncate + err := t.f1.Truncate(int64(truncateSize)) + require.NoError(t.T(), err) + // Write + operations.WriteWithoutClose(t.f1, FileContents, t.T()) + + data := make([]byte, 10) + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents+FileContents+string(data[:]), t.T()) +} + +func (t *defaultMountCommonTest) TestTruncateToLowerSizeAfterWrite() { + // Write + operations.WriteWithoutClose(t.f1, FileContents+FileContents, t.T()) + // Perform truncate + err := t.f1.Truncate(int64(5)) + + // Truncating to lower size after writes are not allowed. + require.Error(t.T(), err) +} From 0a7d3dbb9d73d0bbf116767153a0d9732441ba6b Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 10 Jan 2025 18:43:35 +0530 Subject: [PATCH 0134/1298] Write Failure Improvements - Do not deviate from local file system when file is unlinked from the same mount (#2890) * fixed write failure deviating behavior from native fs * set file handle to nil * Update internal/fs/fs.go Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * Update internal/fs/fs.go Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * fixed formatting * Added check to return nil if file is unlinked --------- Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> --- internal/fs/fs.go | 23 ++++------- internal/fs/stale_file_handle_common_test.go | 9 ++--- .../fs/stale_file_handle_local_file_test.go | 38 ------------------- .../fs/stale_file_handle_synced_file_test.go | 26 +++++++++++++ .../local_file/remove_dir_test.go | 13 +++---- .../local_file/unlinked_file_test.go | 24 +++++------- .../stale_file_handle_common_test.go | 9 ++--- .../stale_file_handle_local_file_test.go | 29 -------------- .../stale_file_handle_synced_file_test.go | 19 ++++++++++ 9 files changed, 74 insertions(+), 116 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index b8121c6dba..355082fa82 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -28,7 +28,6 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "golang.org/x/sync/semaphore" "github.com/googlecloudplatform/gcsfuse/v2/cfg" @@ -1152,13 +1151,10 @@ func (fs *fileSystem) promoteToGenerationBacked(f *inode.FileInode) { func (fs *fileSystem) flushFile( ctx context.Context, f *inode.FileInode) error { - // SyncFile can be triggered for unlinked files if the fileHandle is open by - // same or another user. This indicates a potential file clobbering scenario: - // - The file was deleted (unlinked) while a handle to it was still open. - if f.IsLocal() && f.IsUnlinked() { - return &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("file %s was unlinked while it was still open, indicating file clobbering", f.Name().LocalName()), - } + // FlushFile mirrors the behavior of native filesystems by not returning an error + // when file to be synced has been unlinked from the same mount. + if f.IsUnlinked() { + return nil } // Flush the inode. @@ -1185,13 +1181,10 @@ func (fs *fileSystem) flushFile( func (fs *fileSystem) syncFile( ctx context.Context, f *inode.FileInode) error { - // SyncFile can be triggered for unlinked files if the fileHandle is open by - // same or another user. This indicates a potential file clobbering scenario: - // - The file was deleted (unlinked) while a handle to it was still open. - if f.IsLocal() && f.IsUnlinked() { - return &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("file %s was unlinked while it was still open, indicating file clobbering", f.Name().LocalName()), - } + // SyncFile mirrors the behavior of native filesystems by not returning an error + // when file to be synced has been unlinked from the same mount. + if f.IsUnlinked() { + return nil } // Sync the inode. diff --git a/internal/fs/stale_file_handle_common_test.go b/internal/fs/stale_file_handle_common_test.go index 7413a0d2cf..25f82d5b3d 100644 --- a/internal/fs/stale_file_handle_common_test.go +++ b/internal/fs/stale_file_handle_common_test.go @@ -64,7 +64,7 @@ func (t *staleFileHandleCommon) TestClobberedFileSyncAndCloseThrowsStaleFileHand assert.Equal(t.T(), "foobar", string(contents)) } -func (t *staleFileHandleCommon) TestDeletedFileSyncAndCloseThrowsStaleFileHandleError() { +func (t *staleFileHandleCommon) TestFileDeletedLocallySyncAndCloseDoNotThrowError() { // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("foobar")) assert.NoError(t.T(), err) @@ -79,14 +79,11 @@ func (t *staleFileHandleCommon) TestDeletedFileSyncAndCloseThrowsStaleFileHandle assert.Equal(t.T(), 4, n) assert.NoError(t.T(), err) - err = t.f1.Sync() + operations.SyncFile(t.f1, t.T()) + operations.CloseFile(t.f1) - operations.ValidateStaleNFSFileHandleError(t.T(), err) - err = t.f1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) // Make f1 nil, so that another attempt is not taken in TearDown to close the // file. t.f1 = nil - // Verify unlinked file is not present on GCS. operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, "foo") } diff --git a/internal/fs/stale_file_handle_local_file_test.go b/internal/fs/stale_file_handle_local_file_test.go index e197afff39..106a2105d5 100644 --- a/internal/fs/stale_file_handle_local_file_test.go +++ b/internal/fs/stale_file_handle_local_file_test.go @@ -15,13 +15,10 @@ package fs_test import ( - "os" - "path" "testing" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -62,38 +59,3 @@ func (t *staleFileHandleLocalFile) TearDownTest() { // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. t.fsTest.TearDown() } - -// ////////////////////////////////////////////////////////////////////// -// Tests -// ////////////////////////////////////////////////////////////////////// - -func (t *staleFileHandleLocalFile) TestUnlinkedDirectoryContainingSyncedAndLocalFilesCloseThrowsStaleFileHandleError() { - // Create explicit directory with one synced and one local file. - assert.Equal(t.T(), - nil, - t.createObjects( - map[string]string{ - // File - "explicit/": "", - "explicit/foo": "", - })) - _, t.f2 = operations.CreateLocalFile(ctx, t.T(), mntDir, bucket, "explicit/"+explicitLocalFileName) - // Attempt to remove explicit directory. - err := os.RemoveAll(path.Join(mntDir, "explicit")) - // Verify rmDir operation succeeds. - assert.NoError(t.T(), err) - operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, "explicit/"+explicitLocalFileName)) - operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, "explicit/foo")) - operations.ValidateNoFileOrDirError(t.T(), path.Join(mntDir, "explicit")) - // Validate writing content to unlinked local file does not throw error. - _, err = t.f2.WriteString(FileContents) - assert.NoError(t.T(), err) - - err = operations.CloseLocalFile(t.T(), &t.f2) - - operations.ValidateStaleNFSFileHandleError(t.T(), err) - // Validate both local and synced files are deleted. - operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, "explicit/"+explicitLocalFileName) - operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, "explicit/foo") - operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, "explicit/") -} diff --git a/internal/fs/stale_file_handle_synced_file_test.go b/internal/fs/stale_file_handle_synced_file_test.go index 84be0b292a..2a71303d8b 100644 --- a/internal/fs/stale_file_handle_synced_file_test.go +++ b/internal/fs/stale_file_handle_synced_file_test.go @@ -138,3 +138,29 @@ func (t *staleFileHandleSyncedFile) TestRenamedFileSyncThrowsStaleFileHandleErro // file. t.f1 = nil } + +func (t *staleFileHandleSyncedFile) TestFileDeletedRemotelySyncAndCloseThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + n, err := t.f1.Write([]byte("foobar")) + assert.NoError(t.T(), err) + assert.Equal(t.T(), 6, n) + // Unlink the file. + err = storageutil.DeleteObject(ctx, bucket, "foo") + assert.NoError(t.T(), err) + // Verify unlink operation succeeds. + assert.NoError(t.T(), err) + operations.ValidateObjectNotFoundErr(ctx, t.T(), bucket, "foo") + // Attempt to write to file should not give any error. + n, err = t.f1.Write([]byte("taco")) + assert.Equal(t.T(), 4, n) + assert.NoError(t.T(), err) + + err = t.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = t.f1.Close() + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file. + t.f1 = nil +} diff --git a/tools/integration_tests/local_file/remove_dir_test.go b/tools/integration_tests/local_file/remove_dir_test.go index 54c0eaa0a8..bba007655e 100644 --- a/tools/integration_tests/local_file/remove_dir_test.go +++ b/tools/integration_tests/local_file/remove_dir_test.go @@ -41,9 +41,8 @@ func TestRmDirOfDirectoryContainingGCSAndLocalFiles(t *testing.T) { operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, ExplicitDirName)) // Validate writing content to unlinked local file does not throw error. operations.WriteWithoutClose(fh2, FileContents, t) - // Validate flush file throws error and does not create object on GCS. - err := fh2.Close() - operations.ValidateStaleNFSFileHandleError(t, err) + // Validate flush file does not throw error and does not create object on GCS. + operations.CloseFileShouldNotThrowError(fh2, t) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile, t) // Validate synced files are also deleted. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, syncedFile, t) @@ -64,12 +63,10 @@ func TestRmDirOfDirectoryContainingOnlyLocalFiles(t *testing.T) { // Verify rmDir operation succeeds. operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, ExplicitDirName)) - // Validate that closing local files throws error and they are not present on GCS. - err := fh1.Close() - operations.ValidateStaleNFSFileHandleError(t, err) + // Close the local files and validate they are not present on GCS. + operations.CloseFileShouldNotThrowError(fh1, t) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile1, t) - err = fh2.Close() - operations.ValidateStaleNFSFileHandleError(t, err) + operations.CloseFileShouldNotThrowError(fh2, t) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile2, t) // Validate directory is also deleted. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, ExplicitDirName, t) diff --git a/tools/integration_tests/local_file/unlinked_file_test.go b/tools/integration_tests/local_file/unlinked_file_test.go index 260fc21419..ec7b2dcc49 100644 --- a/tools/integration_tests/local_file/unlinked_file_test.go +++ b/tools/integration_tests/local_file/unlinked_file_test.go @@ -34,9 +34,8 @@ func TestStatOnUnlinkedLocalFile(t *testing.T) { // Stat the local file and validate error. operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) - // Validate closing local file throws error and does not create file on GCS. - err := fh.Close() - operations.ValidateStaleNFSFileHandleError(t, err) + // Close the file and validate that file is not created on GCS. + operations.CloseFileShouldNotThrowError(fh, t) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) } @@ -61,9 +60,8 @@ func TestReadDirContainingUnlinkedLocalFiles(t *testing.T) { FileName1, "", t) CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testDirName, FileName2, "", t) - // Verify closing unlinked local file throws error and does not write to GCS. - err := fh3.Close() - operations.ValidateStaleNFSFileHandleError(t, err) + // Verify unlinked file is not written to GCS. + operations.CloseFileShouldNotThrowError(fh3, t) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName3, t) } @@ -78,9 +76,8 @@ func TestWriteOnUnlinkedLocalFileSucceeds(t *testing.T) { // Write to unlinked local file. operations.WriteWithoutClose(fh, FileContents, t) - // Validate flush file throws error. - err := fh.Close() - operations.ValidateStaleNFSFileHandleError(t, err) + // Validate flush file does not throw error. + operations.CloseFileShouldNotThrowError(fh, t) // Validate unlinked file is not written to GCS. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) } @@ -95,12 +92,11 @@ func TestSyncOnUnlinkedLocalFile(t *testing.T) { // Verify unlink operation succeeds. operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) - // Validate sync and close operations throws error and do not write to GCS after unlink. - err := fh.Sync() - operations.ValidateStaleNFSFileHandleError(t, err) + // Validate sync operation does not write to GCS after unlink. + operations.SyncFile(fh, t) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) - err = fh.Close() - operations.ValidateStaleNFSFileHandleError(t, err) + // Close the local file and validate it is not present on GCS. + operations.CloseFileShouldNotThrowError(fh, t) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) } diff --git a/tools/integration_tests/stale_handle/stale_file_handle_common_test.go b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go index e36464dd90..e05a159bf9 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_common_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go @@ -52,7 +52,7 @@ func (s *staleFileHandleCommon) TestClobberedFileSyncAndCloseThrowsStaleFileHand operations.ValidateStaleNFSFileHandleError(s.T(), err) } -func (s *staleFileHandleCommon) TestDeletedFileSyncAndCloseThrowsStaleFileHandleError() { +func (s *staleFileHandleCommon) TestFileDeletedLocallySyncAndCloseDoNotThrowError() { // Dirty the file by giving it some contents. _, err := s.f1.WriteString(Content) assert.NoError(s.T(), err) @@ -63,9 +63,6 @@ func (s *staleFileHandleCommon) TestDeletedFileSyncAndCloseThrowsStaleFileHandle // Attempt to write to file should not give any error. operations.WriteWithoutClose(s.f1, Content2, s.T()) - err = s.f1.Sync() - - operations.ValidateStaleNFSFileHandleError(s.T(), err) - err = s.f1.Close() - operations.ValidateStaleNFSFileHandleError(s.T(), err) + operations.SyncFile(s.f1, s.T()) + operations.CloseFile(s.f1) } diff --git a/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go index f50e3af45b..65da6a242f 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go @@ -15,18 +15,12 @@ package stale_handle import ( - "os" - "path" "testing" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/stretchr/testify/suite" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - - "github.com/stretchr/testify/assert" ) // ////////////////////////////////////////////////////////////////////// @@ -43,29 +37,6 @@ func (s *staleFileHandleLocalFile) SetupTest() { _, s.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, s.T()) } -//////////////////////////////////////////////////////////////////////// -// Tests -//////////////////////////////////////////////////////////////////////// - -func (s *staleFileHandleLocalFile) TestUnlinkedDirectoryContainingSyncedAndLocalFilesCloseThrowsStaleFileHandleError() { - explicitDir := path.Join(setup.MntDir(), s.T().Name(), ExplicitDirName) - // Create explicit directory with one synced and one local file. - operations.CreateDirectory(explicitDir, s.T()) - CreateObjectInGCSTestDir(ctx, storageClient, path.Join(s.T().Name(), ExplicitDirName), ExplicitFileName1, "", s.T()) - _, f2 := CreateLocalFileInTestDir(ctx, storageClient, explicitDir, ExplicitLocalFileName1, s.T()) - err := os.RemoveAll(explicitDir) - assert.NoError(s.T(), err) - operations.ValidateNoFileOrDirError(s.T(), explicitDir+"/") - operations.ValidateNoFileOrDirError(s.T(), path.Join(explicitDir, ExplicitFileName1)) - operations.ValidateNoFileOrDirError(s.T(), path.Join(explicitDir, ExplicitLocalFileName1)) - // Validate writing content to unlinked local file does not throw error. - operations.WriteWithoutClose(f2, FileContents, s.T()) - - err = f2.Close() - - operations.ValidateStaleNFSFileHandleError(s.T(), err) -} - //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go index f82576bed2..f856d68c19 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go @@ -93,6 +93,25 @@ func (s *staleFileHandleSyncedFile) TestRenamedFileSyncAndCloseThrowsStaleFileHa operations.ValidateStaleNFSFileHandleError(s.T(), err) } +func (s *staleFileHandleSyncedFile) TestFileDeletedRemotelySyncAndCloseThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + _, err := s.f1.WriteString(Content) + assert.NoError(s.T(), err) + // Delete the file remotely. + err = DeleteObjectOnGCS(ctx, storageClient, path.Join(s.T().Name(), FileName1)) + assert.NoError(s.T(), err) + // Verify unlink operation succeeds. + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, s.T().Name(), FileName1, s.T()) + // Attempt to write to file should not give any error. + operations.WriteWithoutClose(s.f1, Content2, s.T()) + + err = s.f1.Sync() + + operations.ValidateStaleNFSFileHandleError(s.T(), err) + err = s.f1.Close() + operations.ValidateStaleNFSFileHandleError(s.T(), err) +} + //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// From e2ff2d07ff8971b980189e85cee1164433f40fcb Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Fri, 10 Jan 2025 18:44:25 +0530 Subject: [PATCH 0135/1298] Downgrade go-sdk from v1.50.0 to v.149.0 (#2891) --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 574c2a77c0..665c1fcd24 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,13 @@ require ( cloud.google.com/go/compute/metadata v0.6.0 cloud.google.com/go/iam v1.3.1 cloud.google.com/go/secretmanager v1.14.3 - cloud.google.com/go/storage v1.50.0 + cloud.google.com/go/storage v1.49.0 contrib.go.opencensus.io/exporter/ocagent v0.7.0 contrib.go.opencensus.io/exporter/prometheus v0.4.2 contrib.go.opencensus.io/exporter/stackdriver v0.13.14 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0 - github.com/fsouza/fake-gcs-server v1.52.1 + github.com/fsouza/fake-gcs-server v1.52.0 github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.1 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec diff --git a/go.sum b/go.sum index 43e309353a..a36a1f3c8f 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE= @@ -129,6 +131,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsouza/fake-gcs-server v1.52.0 h1:ps23VAKR0pKu+QsdMUo25mtDXp6KpKxksIRkbHNBPug= +github.com/fsouza/fake-gcs-server v1.52.0/go.mod h1:tmfEOHhUOdkk243WkWUPC1MuHEaGdpJrslDijp5nZRs= github.com/fsouza/fake-gcs-server v1.52.1 h1:Hx3G2ZpyBzHGmW7cHURWWoTm6jM3M5fcWMIAHBYlJyc= github.com/fsouza/fake-gcs-server v1.52.1/go.mod h1:Paxf25VmSNMN52L+2/cVulF5fkLUA0YJIYjTGJiwf3c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= From 73773f87ea6d3eb7137d0425009b8c8be8bc3cf1 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:54:43 +0530 Subject: [PATCH 0136/1298] Revert "turn on the flag (#2889)" (#2892) This reverts commit 70735eafeeaf51f6cb6ddaa5dc141fe4148c6200. --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/root_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 5758ef8b23..fabc4d3141 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -317,7 +317,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.BoolP("enable-atomic-rename-object", "", true, "Enables support for atomic rename object operation on HNS bucket.") + flagSet.BoolP("enable-atomic-rename-object", "", false, "Enables support for atomic rename object operation on HNS bucket.") if err := flagSet.MarkHidden("enable-atomic-rename-object"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 5bb8ec2b35..e43245e9dc 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -75,7 +75,7 @@ flag-name: "enable-atomic-rename-object" type: "bool" usage: "Enables support for atomic rename object operation on HNS bucket." - default: true + default: false hide-flag: true - config-path: "enable-hns" diff --git a/cmd/root_test.go b/cmd/root_test.go index d5a200fe33..8d7ed87a52 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -848,8 +848,8 @@ func TestArgsParsing_EnableAtomicRenameObjectFlag(t *testing.T) { }{ { name: "normal", - args: []string{"gcsfuse", "--enable-atomic-rename-object=false", "abc", "pqr"}, - expectedEnableAtomicRenameObject: false, + args: []string{"gcsfuse", "--enable-atomic-rename-object=true", "abc", "pqr"}, + expectedEnableAtomicRenameObject: true, }, } From 5322cffd66a29c6d87338c8bed45beb9b94c1e8c Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 13 Jan 2025 10:00:42 +0530 Subject: [PATCH 0137/1298] Semantic doc for unsupported object with suffix `\n` (#2893) --- docs/semantics.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/semantics.md b/docs/semantics.md index 9b31d24268..cb627a8d55 100644 --- a/docs/semantics.md +++ b/docs/semantics.md @@ -385,13 +385,13 @@ Instead, when a conflicting pair of foo and ```foo/``` objects both exist, it ap ### Unsupported object names -Objects in GCS with double slashes '//' as a name or -prefix are not supported in GCSfuse. Accessing a directory with such -named files will cause an 'input/output error', as the Linux -filesystem does not support files or directories named with a '/'. -The most common example of this is an object called, for example -'A//C.txt' where 'A' indicates a directory and 'C.txt' indicates a -file, and is missing directory 'B/' between 'A/' and 'C.txt'. +- Objects in GCS with `double slashes '//'` as a name or prefix are not supported in GCSfuse. Accessing a directory with such named files will cause an 'input/output error', as the Linux filesystem does not support files or directories named with a '/'. The most common example of this is an object called, for example 'A//C.txt' where 'A' indicates a directory and 'C.txt' indicates a file, and is missing directory 'B/' between 'A/' and 'C.txt'. + + +- Objects in GCS with suffix `/\n` like, `gs://gcs-bkt/a/\n`: +Mounting bucket with such objects leads to crash `sync: unlock of unlocked mutex` or `Panic: Inode 'a/' cannot have child file`. +`\n` in GCSFuse is used to resolve the name conflicts, in case there is a file and directory exists with the same name. Ref: [name-conflict](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/semantics.md#name-conflicts) section. + ## Memory-mapped files From e7012c45ef0f121baf698e46c8a8f742874bce5a Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 13 Jan 2025 12:06:20 +0530 Subject: [PATCH 0138/1298] Close github issues if Cx doesn't response for 2 weeks (#2896) --- .github/workflows/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index a9cdccc5a7..f639ee4495 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -14,9 +14,9 @@ jobs: with: only-labels: "pending customer action" days-before-issue-stale: -1 - days-before-issue-close: 30 + days-before-issue-close: 14 stale-issue-label: "pending customer action" - close-issue-message: "Closing this issue as we haven't received any response in 30 days. Please reopen if you are still experiencing this issue." + close-issue-message: "Closing this issue as we haven't received any response in 14 days. Please reopen if you are still experiencing this issue." days-before-pr-stale: -1 days-before-pr-close: -1 repo-token: ${{ secrets.GITHUB_TOKEN }} From 6f2e04bb151d740d36a09aa8134cd5d2b80a9f6e Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 15 Jan 2025 09:26:42 +0530 Subject: [PATCH 0139/1298] Skipping KLC test for kernel version b/w v6.9.x and v6.12.x (#2901) --- internal/fs/kernel_list_cache_inifinite_ttl_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/fs/kernel_list_cache_inifinite_ttl_test.go b/internal/fs/kernel_list_cache_inifinite_ttl_test.go index e690ab5df2..f1eb23e844 100644 --- a/internal/fs/kernel_list_cache_inifinite_ttl_test.go +++ b/internal/fs/kernel_list_cache_inifinite_ttl_test.go @@ -36,7 +36,7 @@ func SkipTestForUnsupportedKernelVersion(t *testing.T) { // TODO: b/384648943 make this part of fsTest.SetUpTestSuite() after post fs // tests are fully migrated to stretchr/testify. t.Helper() - UnsupportedKernelVersions := []string{`^6\.9\.\d+`, `^6\.\d{2}\.\d+`} + UnsupportedKernelVersions := []string{`^6\.9\.\d+`, `^6\.10\.\d+`, `^6\.11\.\d+`, `^6\.12\.\d+`} kernelVersion := operations.KernelVersion(t) for i := 0; i < len(UnsupportedKernelVersions); i++ { From 07ac6ac5279b5e4ccbcdcf41bf2f5ac58ce7db5f Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Fri, 17 Jan 2025 17:13:29 +0530 Subject: [PATCH 0140/1298] Skipping Kernel List Cache E2E Test on the Unsupported Kernel Version (#2904) * Making klc skip regex more generic * Refactoring and skipping e2e tests too * formatting * fixing formatting issue * review comments --- common/util.go | 62 ++++++++++++++ common/util_test.go | 82 +++++++++++++++++++ .../kernel_list_cache_inifinite_ttl_test.go | 15 +--- .../finite_kernel_list_cache_test.go | 2 + ...inite_kernel_list_cache_delete_dir_test.go | 2 + .../util/operations/operations.go | 14 ---- .../util/operations/validation_helper.go | 10 +++ 7 files changed, 162 insertions(+), 25 deletions(-) create mode 100644 common/util.go create mode 100644 common/util_test.go diff --git a/common/util.go b/common/util.go new file mode 100644 index 0000000000..9f23f95ffd --- /dev/null +++ b/common/util.go @@ -0,0 +1,62 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "os/exec" + "regexp" + "strings" +) + +// GetKernelVersion returns the kernel version. +func GetKernelVersion() (string, error) { + cmd := exec.Command("uname", "-r") + out, err := cmd.Output() + if err != nil { + return "", err + } + kernelVersion := strings.TrimSpace(string(out)) + return kernelVersion, nil +} + +// kernelVersion is just a wrapper over GetKernelVersion. This +// allows us to mock it in the unit test of ShouldSkipKernelListCacheTest. +var kernelVersionToTest = func() (string, error) { + return GetKernelVersion() +} + +// IsKLCacheEvictionUnSupported returns true if Kernel List Cache Eviction is not supported +// for the current linux version. +// In case of any non-nil error it returns false. +func IsKLCacheEvictionUnSupported() (bool, error) { + UnsupportedKernelVersions := []string{`^6\.9\.\d+`, `^6\.10\.\d+`, `^6\.11\.\d+`, `^6\.12\.\d+`} + + kernelVersion, err := kernelVersionToTest() + if err != nil { + return false, err + } + + for i := 0; i < len(UnsupportedKernelVersions); i++ { + matched, err := regexp.MatchString(UnsupportedKernelVersions[i], kernelVersion) + if err != nil { + return false, err + } + if matched { + return true, nil + } + } + + return false, nil +} diff --git a/common/util_test.go b/common/util_test.go new file mode 100644 index 0000000000..31fc2c35b7 --- /dev/null +++ b/common/util_test.go @@ -0,0 +1,82 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package common + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestIsKLCacheEvictionUnSupported(t *testing.T) { + testCases := []struct { + name string + mockKernelVersion string + expectedSkip bool + }{ + { + name: "Cloudtop_Supported", + mockKernelVersion: "4.19.0-17-amd64", + expectedSkip: false, + }, + { + name: "Cloudtop_Unsupported", + mockKernelVersion: "6.10.11-1rodete2-amd64", + expectedSkip: true, + }, + { + name: "GCP_Supported", + mockKernelVersion: "6.8.0-1020-gcp", + expectedSkip: false, + }, + { + name: "GCP_Unsupported_6.9.x", + mockKernelVersion: "6.9.0-1020-gcp", + expectedSkip: true, + }, + { + name: "GCP_Unsupported_6.10.x", + mockKernelVersion: "6.10.0-1020-gcp", + expectedSkip: true, + }, + { + name: "GCP_Unsupported_6.11.x", + mockKernelVersion: "6.11.0-1020-gcp", + expectedSkip: true, + }, + { + name: "GCP_Unsupported_6.12.x", + mockKernelVersion: "6.12.0-1020-gcp", + expectedSkip: true, + }, + { + name: "Amd64_Unsupported_6.10.x", + mockKernelVersion: "6.10.0-1-amd64", + expectedSkip: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + originalKernelVersion := kernelVersionToTest + kernelVersionToTest = func() (string, error) { return tc.mockKernelVersion, nil } + defer func() { kernelVersionToTest = originalKernelVersion }() + + skip, err := IsKLCacheEvictionUnSupported() + require.NoError(t, err) + assert.Equal(t, tc.expectedSkip, skip) + }) + } +} diff --git a/internal/fs/kernel_list_cache_inifinite_ttl_test.go b/internal/fs/kernel_list_cache_inifinite_ttl_test.go index f1eb23e844..007f16cf91 100644 --- a/internal/fs/kernel_list_cache_inifinite_ttl_test.go +++ b/internal/fs/kernel_list_cache_inifinite_ttl_test.go @@ -20,13 +20,11 @@ import ( "errors" "os" "path" - "regexp" "testing" "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -36,15 +34,10 @@ func SkipTestForUnsupportedKernelVersion(t *testing.T) { // TODO: b/384648943 make this part of fsTest.SetUpTestSuite() after post fs // tests are fully migrated to stretchr/testify. t.Helper() - UnsupportedKernelVersions := []string{`^6\.9\.\d+`, `^6\.10\.\d+`, `^6\.11\.\d+`, `^6\.12\.\d+`} - - kernelVersion := operations.KernelVersion(t) - for i := 0; i < len(UnsupportedKernelVersions); i++ { - matched, err := regexp.MatchString(UnsupportedKernelVersions[i], kernelVersion) - assert.NoError(t, err) - if matched { - t.SkipNow() - } + unsupported, err := common.IsKLCacheEvictionUnSupported() + assert.NoError(t, err) + if unsupported { + t.SkipNow() } } diff --git a/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go index 65e223dcfe..9f10f8e27b 100644 --- a/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go @@ -50,6 +50,8 @@ func (s *finiteKernelListCacheTest) Teardown(t *testing.T) { //////////////////////////////////////////////////////////////////////// func (s *finiteKernelListCacheTest) TestKernelListCache_CacheHitWithinLimit_CacheMissAfterLimit(t *testing.T) { + operations.SkipKLCTestForUnsupportedKernelVersion(t) + targetDir := path.Join(testDirPath, "explicit_dir") operations.CreateDirectory(targetDir, t) // Create test data diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go index 3268fb9759..8905676c0b 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go @@ -108,6 +108,8 @@ func (s *infiniteKernelListCacheDeleteDirTest) TestKernelListCache_DeleteAndList //////////////////////////////////////////////////////////////////////// func TestInfiniteKernelListCacheDeleteDirTest(t *testing.T) { + operations.SkipKLCTestForUnsupportedKernelVersion(t) + ts := &infiniteKernelListCacheDeleteDirTest{} // Run tests for mounted directory if the flag is set. diff --git a/tools/integration_tests/util/operations/operations.go b/tools/integration_tests/util/operations/operations.go index 980b97a5e0..cbd09eb083 100644 --- a/tools/integration_tests/util/operations/operations.go +++ b/tools/integration_tests/util/operations/operations.go @@ -20,11 +20,7 @@ import ( "fmt" "math/rand" "os/exec" - "strings" - "testing" "time" - - "github.com/stretchr/testify/assert" ) // GenerateRandomData generates random data that can be used to write to a file. @@ -87,13 +83,3 @@ func ExecuteGcloudCommandf(format string, args ...any) ([]byte, error) { func ExecuteGcloudCommand(command string) ([]byte, error) { return executeToolCommand("gcloud", command) } - -func KernelVersion(t *testing.T) string { - t.Helper() - - cmd := exec.Command("uname", "-r") - out, err := cmd.Output() - assert.NoError(t, err) - kernelVersion := strings.TrimSpace(string(out)) - return kernelVersion -} diff --git a/tools/integration_tests/util/operations/validation_helper.go b/tools/integration_tests/util/operations/validation_helper.go index 62d7836bb5..e01c0edac9 100644 --- a/tools/integration_tests/util/operations/validation_helper.go +++ b/tools/integration_tests/util/operations/validation_helper.go @@ -22,6 +22,7 @@ import ( "syscall" "testing" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/stretchr/testify/assert" @@ -60,3 +61,12 @@ func CheckErrorForReadOnlyFileSystem(t *testing.T, err error) { } t.Errorf("Incorrect error for readonly file system: %v", err.Error()) } + +func SkipKLCTestForUnsupportedKernelVersion(t *testing.T) { + t.Helper() + unsupported, err := common.IsKLCacheEvictionUnSupported() + assert.NoError(t, err) + if unsupported { + t.SkipNow() + } +} From 5c6f665bd15da35a9633d9e1d22b64f17bf53255 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 17 Jan 2025 18:32:44 +0530 Subject: [PATCH 0141/1298] Add stale_handle integration tests to run_tests_mounted_directory.sh (#2903) * Add stale_handle integration tests to run_tests_mounted_directory.sh * Update run_tests_mounted_directory.sh --- .../integration_tests/run_tests_mounted_directory.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index 1598dcc6b9..6cf5a7a162 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -594,3 +594,14 @@ for test_case in "${test_cases[@]}"; do GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/kernel_list_cache/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run "$test_case" sudo umount "$MOUNT_DIR" done + +# Test package: stale_handle +# Run tests with static mounting. (flags: --metadata-cache-ttl-secs=0 --precondition-errors=true) +gcsfuse --metadata-cache-ttl-secs=0 --precondition-errors=true $TEST_BUCKET_NAME $MOUNT_DIR +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/stale_handle/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +sudo umount $MOUNT_DIR + +# Run test with persistent mounting. (flags: --metadata-cache-ttl-secs=0 --precondition-errors=true) +mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o metadata_cache_ttl_secs=0,precondition_errors=true +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/stale_handle/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +sudo umount $MOUNT_DIR From 06737af12edcac677cb632f42c16e4c4be6e3b02 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 20 Jan 2025 12:26:16 +0530 Subject: [PATCH 0142/1298] Refactor list_large_dir tests to use storage client to delete directories instead of gcloud. (#2905) * Refactored list_large_dir tests * refactored tests to use stretchr framework * refactored test * updated list_large_dir tests to use go sdk to delete files. * Update list_dir_with_twelve_thousand_files_test.go * minor fix * fixed formatting * minor fix --- ...ist_dir_with_twelve_thousand_files_test.go | 262 ++++++++---------- .../list_large_dir/list_large_dir_test.go | 41 ++- .../list_large_dir/testdata/delete_objects.sh | 22 -- .../testdata/upload_files_to_bucket.sh | 4 +- .../util/client/storage_client.go | 44 ++- 5 files changed, 186 insertions(+), 187 deletions(-) delete mode 100755 tools/integration_tests/list_large_dir/testdata/delete_objects.sh diff --git a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go index 1a3fe1625e..cf73653cde 100644 --- a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go +++ b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +//     http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -package list_large_dir_test +package list_large_dir import ( + "fmt" "math" "os" "path" @@ -23,140 +24,115 @@ import ( "testing" "time" + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) // ////////////////////////////////////////////////////////////////////// -// Helpers +// Boilerplate // ////////////////////////////////////////////////////////////////////// -func validateDirectoryWithTwelveThousandFiles(objs []os.DirEntry, t *testing.T) { - // number of objs - 12000 - if len(objs) != NumberOfFilesInDirectoryWithTwelveThousandFiles { - t.Errorf("Listed incorrect number of files from directory: %v, expected 12000", len(objs)) - } - // Checking if all the object is File type. - for i := 0; i < len(objs); i++ { - if objs[i].IsDir() { - t.Errorf("Listed object is incorrect.") - } - } - - for i := 0; i < len(objs); i++ { - checkIfObjNameIsCorrect(objs[i].Name(), PrefixFileInDirectoryWithTwelveThousandFiles, NumberOfFilesInDirectoryWithTwelveThousandFiles, t) - } +type listLargeDir struct { + suite.Suite } -func validateDirectoryWithTwelveThousandFilesHundredExplicitDirAndHundredImplicitDir(objs []os.DirEntry, t *testing.T) { - var numberOfFiles = 0 - var numberOfDirs = 0 +func (t *listLargeDir) TearDownSuite() { + err := DeleteAllObjectsWithPrefix(ctx, storageClient, t.T().Name()) + assert.NoError(t.T(), err) +} - // Checking if correct objects present in bucket. - for i := 0; i < len(objs); i++ { - if !objs[i].IsDir() { - numberOfFiles++ +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// - // Checking if Prefix1 to Prefix12000 present in the bucket - checkIfObjNameIsCorrect(objs[i].Name(), PrefixFileInDirectoryWithTwelveThousandFiles, NumberOfFilesInDirectoryWithTwelveThousandFiles, t) - } +// validateDirectory checks if the directory listing matches expectations. +func validateDirectory(t *testing.T, objs []os.DirEntry, expectExplicitDirs, expectImplicitDirs bool) { + t.Helper() - if objs[i].IsDir() { - numberOfDirs++ + var ( + numberOfFiles int + numberOfExplicitDirs int + numberOfImplicitDirs int + ) - if strings.Contains(objs[i].Name(), PrefixExplicitDirInLargeDirListTest) { - // Checking if explicitDir1 to explicitDir100 present in the bucket. - checkIfObjNameIsCorrect(objs[i].Name(), PrefixExplicitDirInLargeDirListTest, NumberOfExplicitDirsInDirectoryWithTwelveThousandFiles, t) - } else { - // Checking if implicitDir1 to implicitDir100 present in the bucket. - checkIfObjNameIsCorrect(objs[i].Name(), PrefixImplicitDirInLargeDirListTest, NumberOfImplicitDirsInDirectoryWithTwelveThousandFiles, t) - } + for _, obj := range objs { + if !obj.IsDir() { + numberOfFiles++ + checkIfObjNameIsCorrect(t, obj.Name(), prefixFileInDirectoryWithTwelveThousandFiles, numberOfFilesInDirectoryWithTwelveThousandFiles) + } else if strings.Contains(obj.Name(), prefixExplicitDirInLargeDirListTest) { + numberOfExplicitDirs++ + checkIfObjNameIsCorrect(t, obj.Name(), prefixExplicitDirInLargeDirListTest, numberOfExplicitDirsInDirectoryWithTwelveThousandFiles) + } else if strings.Contains(obj.Name(), prefixImplicitDirInLargeDirListTest) { + numberOfImplicitDirs++ + checkIfObjNameIsCorrect(t, obj.Name(), prefixImplicitDirInLargeDirListTest, numberOfImplicitDirsInDirectoryWithTwelveThousandFiles) } } - // number of dirs = 200(Number of implicit + Number of explicit directories) - if numberOfDirs != NumberOfImplicitDirsInDirectoryWithTwelveThousandFiles+NumberOfExplicitDirsInDirectoryWithTwelveThousandFiles { - t.Errorf("Listed incorrect number of directories from directory: %v, expected 200", numberOfDirs) - } - // number of files = 12000 - if numberOfFiles != NumberOfFilesInDirectoryWithTwelveThousandFiles { - t.Errorf("Listed incorrect number of files from directory: %v, expected 12000", numberOfFiles) + if numberOfFiles != numberOfFilesInDirectoryWithTwelveThousandFiles { + t.Errorf("Incorrect number of files: got %d, want %d", numberOfFiles, numberOfFilesInDirectoryWithTwelveThousandFiles) } -} - -func validateDirectoryWithTwelveThousandFilesAndHundredExplicitDirectory(objs []os.DirEntry, t *testing.T) { - var numberOfFiles = 0 - var numberOfDirs = 0 - - // Checking if correct objects present in bucket. - for i := 0; i < len(objs); i++ { - if !objs[i].IsDir() { - numberOfFiles++ - // Checking if Prefix1 to Prefix12000 present in the bucket - checkIfObjNameIsCorrect(objs[i].Name(), PrefixFileInDirectoryWithTwelveThousandFiles, NumberOfFilesInDirectoryWithTwelveThousandFiles, t) - } - if objs[i].IsDir() { - numberOfDirs++ - // Checking if Prefix1 to Prefix100 present in the bucket - checkIfObjNameIsCorrect(objs[i].Name(), PrefixExplicitDirInLargeDirListTest, NumberOfExplicitDirsInDirectoryWithTwelveThousandFiles, t) - } + if expectExplicitDirs && numberOfExplicitDirs != numberOfExplicitDirsInDirectoryWithTwelveThousandFiles { + t.Errorf("Incorrect number of explicit directories: got %d, want %d", numberOfExplicitDirs, numberOfExplicitDirsInDirectoryWithTwelveThousandFiles) } - // number of explicit dirs = 100 - if numberOfDirs != NumberOfExplicitDirsInDirectoryWithTwelveThousandFiles { - t.Errorf("Listed incorrect number of directories from directory: %v, expected 100", numberOfDirs) - } - // number of files = 12000 - if numberOfFiles != NumberOfFilesInDirectoryWithTwelveThousandFiles { - t.Errorf("Listed incorrect number of files from directory: %v, expected 12000", numberOfFiles) + if expectImplicitDirs && numberOfImplicitDirs != numberOfImplicitDirsInDirectoryWithTwelveThousandFiles { + t.Errorf("Incorrect number of implicit directories: got %d, want %d", numberOfImplicitDirs, numberOfImplicitDirsInDirectoryWithTwelveThousandFiles) } } -func checkIfObjNameIsCorrect(objName string, prefix string, maxNumber int, t *testing.T) { - // Extracting object number. - objNumberStr := strings.ReplaceAll(objName, prefix, "") +// checkIfObjNameIsCorrect validates the object name against a prefix and expected range. +func checkIfObjNameIsCorrect(t *testing.T, objName string, prefix string, maxNumber int) { + t.Helper() + + objNumberStr := strings.TrimPrefix(objName, prefix) objNumber, err := strconv.Atoi(objNumberStr) if err != nil { - t.Errorf("Error in extracting file number: %v", err) + t.Errorf("Error extracting object number from %q: %v", objName, err) } if objNumber < 1 || objNumber > maxNumber { - t.Errorf("Correct object does not exist.") + t.Errorf("Invalid object number in %q: %d (should be between 1 and %d)", objName, objNumber, maxNumber) } } -func createTwelveThousandFilesAndUploadOnTestBucket(t *testing.T) { - // Creating twelve thousand files in DirectoryWithTwelveThousandFiles directory to upload them on a bucket for testing. - localDirPath := path.Join(os.Getenv("HOME"), DirectoryWithTwelveThousandFiles) - operations.CreateDirectoryWithNFiles(NumberOfFilesInDirectoryWithTwelveThousandFiles, localDirPath, PrefixFileInDirectoryWithTwelveThousandFiles, t) +// createFilesAndUpload generates files and uploads them to the specified directory. +func createFilesAndUpload(t *testing.T, dirPath string) { + t.Helper() - // Uploading twelve thousand files to directoryWithTwelveThousandFiles in testBucket. - dirPath := path.Join(setup.TestBucket(), DirectoryForListLargeFileTests, DirectoryWithTwelveThousandFiles) - setup.RunScriptForTestData("testdata/upload_files_to_bucket.sh", dirPath, DirectoryWithTwelveThousandFiles, PrefixFileInDirectoryWithTwelveThousandFiles) + localDirPath := path.Join(os.Getenv("HOME"), directoryWithTwelveThousandFiles) + operations.CreateDirectoryWithNFiles(numberOfFilesInDirectoryWithTwelveThousandFiles, localDirPath, prefixFileInDirectoryWithTwelveThousandFiles, t) + + setup.RunScriptForTestData("testdata/upload_files_to_bucket.sh", dirPath, localDirPath, prefixFileInDirectoryWithTwelveThousandFiles) } -// Create a hundred explicit directories. -func createHundredExplicitDir(dirPath string, t *testing.T) { - // Create hundred explicit directories. - for i := 1; i <= NumberOfExplicitDirsInDirectoryWithTwelveThousandFiles; i++ { - subDirPath := path.Join(dirPath, PrefixExplicitDirInLargeDirListTest+strconv.Itoa(i)) +// createExplicitDirs creates empty explicit directories in the specified directory. +func createExplicitDirs(t *testing.T, dirPath string) { + t.Helper() + + for i := 1; i <= numberOfExplicitDirsInDirectoryWithTwelveThousandFiles; i++ { + subDirPath := path.Join(dirPath, fmt.Sprintf("%s%d", prefixExplicitDirInLargeDirListTest, i)) operations.CreateDirectoryWithNFiles(0, subDirPath, "", t) } } -func listDirectoryTime(dirPath string, validateDirectory func([]os.DirEntry, *testing.T), t *testing.T) (time.Duration, time.Duration) { - // List Directory first time +// listDirTime measures the time taken to list a directory with and without cache. +func listDirTime(t *testing.T, dirPath string, expectExplicitDirs bool, expectImplicitDirs bool) (time.Duration, time.Duration) { + t.Helper() + startTime := time.Now() objs, err := os.ReadDir(dirPath) if err != nil { t.Fatalf("Error in listing directory: %v", err) } endTime := time.Now() - validateDirectory(objs, t) + + validateDirectory(t, objs, expectExplicitDirs, expectImplicitDirs) firstListTime := endTime.Sub(startTime) - // Listing the directory a second time should retrieve the response from the kernel cache. minSecondListTime := time.Duration(math.MaxInt64) for i := 0; i < 5; i++ { startTime = time.Now() @@ -165,10 +141,8 @@ func listDirectoryTime(dirPath string, validateDirectory func([]os.DirEntry, *te t.Fatalf("Error in listing directory: %v", err) } endTime = time.Now() - validateDirectory(objs, t) + validateDirectory(t, objs, expectExplicitDirs, expectImplicitDirs) secondListTime := endTime.Sub(startTime) - - // Update the minimum listing time for the second listing if secondListTime < minSecondListTime { minSecondListTime = secondListTime } @@ -176,66 +150,66 @@ func listDirectoryTime(dirPath string, validateDirectory func([]os.DirEntry, *te return firstListTime, minSecondListTime } -//////////////////////////////////////////////////////////////////////// -// Tests -//////////////////////////////////////////////////////////////////////// +// prepareTestDirectory sets up a test directory with files and required explicit and implicit directories. +func prepareTestDirectory(t *testing.T, withExplicitDirs bool, withImplicitDirs bool) string { + t.Helper() -// Test with a bucket with twelve thousand files. -func TestListDirectoryWithTwelveThousandFiles(t *testing.T) { - createTwelveThousandFilesAndUploadOnTestBucket(t) - testDirPath := path.Join(setup.MntDir(), DirectoryForListLargeFileTests) - testDirPathOnBucket := path.Join(setup.TestBucket(), DirectoryForListLargeFileTests) - dirPath := path.Join(testDirPath, DirectoryWithTwelveThousandFiles) + testDirPathOnBucket := path.Join(setup.TestBucket(), t.Name()) + testDirPath := path.Join(setup.MntDir(), t.Name()) - firstListTime, secondListTime := listDirectoryTime(dirPath, validateDirectoryWithTwelveThousandFiles, t) + err := os.MkdirAll(testDirPath, 0755) + if err != nil { + t.Fatalf("Failed to create directory: %v", err) + } - // Fetching data from the kernel for the second list will be faster. - assert.Less(t, secondListTime, firstListTime) - // The second directory listing should be 2 times better performant since it - // will be retrieved from the kernel cache. - assert.Less(t, 2*secondListTime, firstListTime) + createFilesAndUpload(t, testDirPathOnBucket) - // Clear the data after testing. - setup.RunScriptForTestData("testdata/delete_objects.sh", testDirPathOnBucket) + if withExplicitDirs { + createExplicitDirs(t, testDirPath) + } + + if withImplicitDirs { + setup.RunScriptForTestData("testdata/create_implicit_dir.sh", testDirPathOnBucket, prefixImplicitDirInLargeDirListTest, strconv.Itoa(numberOfImplicitDirsInDirectoryWithTwelveThousandFiles)) + } + + return testDirPath } -// Test with a bucket with twelve thousand files and hundred explicit directories. -func TestListDirectoryWithTwelveThousandFilesAndHundredExplicitDir(t *testing.T) { - createTwelveThousandFilesAndUploadOnTestBucket(t) - testDirPath := path.Join(setup.MntDir(), DirectoryForListLargeFileTests) - testDirPathOnBucket := path.Join(setup.TestBucket(), DirectoryForListLargeFileTests) - dirPath := path.Join(testDirPath, DirectoryWithTwelveThousandFiles) - createHundredExplicitDir(dirPath, t) +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// + +func (t *listLargeDir) TestListDirectoryWithTwelveThousandFiles() { + dirPath := prepareTestDirectory(t.T(), false, false) + + firstListTime, secondListTime := listDirTime(t.T(), dirPath, false, false) + + assert.Less(t.T(), secondListTime, firstListTime) + assert.Less(t.T(), 2*secondListTime, firstListTime) +} + +func (t *listLargeDir) TestListDirectoryWithTwelveThousandFilesAndHundredExplicitDir() { + dirPath := prepareTestDirectory(t.T(), true, false) - firstListTime, secondListTime := listDirectoryTime(dirPath, validateDirectoryWithTwelveThousandFilesAndHundredExplicitDirectory, t) + firstListTime, secondListTime := listDirTime(t.T(), dirPath, true, false) - // Fetching data from the kernel for the second list will be faster. - assert.Less(t, secondListTime, firstListTime) - // The second directory listing should be 2 times better performant since it - // will be retrieved from the kernel cache. - assert.Less(t, 2*secondListTime, firstListTime) + assert.Less(t.T(), secondListTime, firstListTime) + assert.Less(t.T(), 2*secondListTime, firstListTime) +} + +func (t *listLargeDir) TestListDirectoryWithTwelveThousandFilesAndHundredExplicitDirAndHundredImplicitDir() { + dirPath := prepareTestDirectory(t.T(), true, true) + + firstListTime, secondListTime := listDirTime(t.T(), dirPath, true, true) - // Clear the bucket after testing. - setup.RunScriptForTestData("testdata/delete_objects.sh", testDirPathOnBucket) + assert.Less(t.T(), secondListTime, firstListTime) + assert.Less(t.T(), 2*secondListTime, firstListTime) } -// Test with a bucket with twelve thousand files, hundred explicit directories, and hundred implicit directories. -func TestListDirectoryWithTwelveThousandFilesAndHundredExplicitDirAndHundredImplicitDir(t *testing.T) { - createTwelveThousandFilesAndUploadOnTestBucket(t) - testDirPath := path.Join(setup.MntDir(), DirectoryForListLargeFileTests) - testDirPathOnBucket := path.Join(setup.TestBucket(), DirectoryForListLargeFileTests) - dirPath := path.Join(testDirPath, DirectoryWithTwelveThousandFiles) - createHundredExplicitDir(dirPath, t) - subDirPath := path.Join(testDirPathOnBucket, DirectoryWithTwelveThousandFiles) - setup.RunScriptForTestData("testdata/create_implicit_dir.sh", subDirPath, PrefixImplicitDirInLargeDirListTest, strconv.Itoa(NumberOfImplicitDirsInDirectoryWithTwelveThousandFiles)) - - firstListTime, secondListTime := listDirectoryTime(dirPath, validateDirectoryWithTwelveThousandFilesHundredExplicitDirAndHundredImplicitDir, t) - - // Fetching data from the kernel for the second list will be faster. - assert.Less(t, secondListTime, firstListTime) - // The second directory listing should be 2 times better performant since it - // will be retrieved from the kernel cache. - assert.Less(t, 2*secondListTime, firstListTime) - // Clear the bucket after testing. - setup.RunScriptForTestData("testdata/delete_objects.sh", testDirPathOnBucket) +//////////////////////////////////////////////////////////////////////// +// Test Suite Function +//////////////////////////////////////////////////////////////////////// + +func TestListLargeDir(t *testing.T) { + suite.Run(t, new(listLargeDir)) } diff --git a/tools/integration_tests/list_large_dir/list_large_dir_test.go b/tools/integration_tests/list_large_dir/list_large_dir_test.go index 5d81f76389..7e49d325bc 100644 --- a/tools/integration_tests/list_large_dir/list_large_dir_test.go +++ b/tools/integration_tests/list_large_dir/list_large_dir_test.go @@ -13,40 +13,51 @@ // limitations under the License. // Provide test for listing large directory -package list_large_dir_test +package list_large_dir import ( + "context" "log" "os" "testing" + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -const DirectoryForListLargeFileTests = "directoryForListLargeFileTests" -const PrefixFileInDirectoryWithTwelveThousandFiles = "fileInDirectoryWithTwelveThousandFiles" -const PrefixExplicitDirInLargeDirListTest = "explicitDirInLargeDirListTest" -const PrefixImplicitDirInLargeDirListTest = "implicitDirInLargeDirListTest" -const NumberOfFilesInDirectoryWithTwelveThousandFiles = 12000 -const NumberOfImplicitDirsInDirectoryWithTwelveThousandFiles = 100 -const NumberOfExplicitDirsInDirectoryWithTwelveThousandFiles = 100 +const prefixFileInDirectoryWithTwelveThousandFiles = "fileInDirectoryWithTwelveThousandFiles" +const prefixExplicitDirInLargeDirListTest = "explicitDirInLargeDirListTest" +const prefixImplicitDirInLargeDirListTest = "implicitDirInLargeDirListTest" +const numberOfFilesInDirectoryWithTwelveThousandFiles = 12000 +const numberOfImplicitDirsInDirectoryWithTwelveThousandFiles = 100 +const numberOfExplicitDirsInDirectoryWithTwelveThousandFiles = 100 -var DirectoryWithTwelveThousandFiles = "directoryWithTwelveThousandFiles" + setup.GenerateRandomString(5) +var ( + directoryWithTwelveThousandFiles = "directoryWithTwelveThousandFiles" + setup.GenerateRandomString(5) + storageClient *storage.Client + ctx context.Context +) func TestMain(m *testing.M) { - setup.ParseSetUpFlags() + setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + + // Create common storage client to be used in test. + ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() flags := [][]string{{"--implicit-dirs", "--stat-cache-ttl=0", "--kernel-list-cache-ttl-secs=-1"}} if !testing.Short() { flags = append(flags, []string{"--client-protocol=grpc", "--implicit-dirs=true", "--stat-cache-ttl=0", "--kernel-list-cache-ttl-secs=-1"}) } - if setup.TestBucket() == "" && setup.MountedDirectory() != "" { - log.Print("Please pass the name of bucket mounted at mountedDirectory to --testBucket flag.") - os.Exit(1) - } - // Run tests for mountedDirectory only if --mountedDirectory flag is set. setup.RunTestsForMountedDirectoryFlag(m) diff --git a/tools/integration_tests/list_large_dir/testdata/delete_objects.sh b/tools/integration_tests/list_large_dir/testdata/delete_objects.sh deleted file mode 100755 index 6c8d3bd18d..0000000000 --- a/tools/integration_tests/list_large_dir/testdata/delete_objects.sh +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -# Here $1 refers to the testBucket argument -gcloud storage rm -r --log-http --verbosity=debug gs://$1/** - -# If bucket is empty it will throw an CommandException. -if [ $? -eq 1 ]; then - echo "Bucket is already empty." - exit 0 -fi diff --git a/tools/integration_tests/list_large_dir/testdata/upload_files_to_bucket.sh b/tools/integration_tests/list_large_dir/testdata/upload_files_to_bucket.sh index 7bf9597393..71a2158c97 100755 --- a/tools/integration_tests/list_large_dir/testdata/upload_files_to_bucket.sh +++ b/tools/integration_tests/list_large_dir/testdata/upload_files_to_bucket.sh @@ -19,7 +19,7 @@ TEST_BUCKET=$1 DIR_WITH_TWELVE_THOUSAND_FILES=$2 FILES=$3 -cd ~/$DIR_WITH_TWELVE_THOUSAND_FILES +cd $DIR_WITH_TWELVE_THOUSAND_FILES gcloud storage mv $FILES* gs://$TEST_BUCKET/ cd ../ -rm -r ~/$DIR_WITH_TWELVE_THOUSAND_FILES +rm -r $DIR_WITH_TWELVE_THOUSAND_FILES diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 3b4ae9426e..a1a8013425 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -16,11 +16,14 @@ package client import ( "context" + "errors" "fmt" "io" "log" "os" "reflect" + "runtime" + "sync" "testing" "time" @@ -208,6 +211,9 @@ func DeleteObjectOnGCS(ctx context.Context, client *storage.Client, objectName s return nil } +// DeleteAllObjectsWithPrefix deletes all objects with the specified prefix in a GCS bucket. +// It concurrently iterates through objects with the given prefix and deletes them using multiple goroutines, +// leveraging the number of CPU cores for optimal performance. func DeleteAllObjectsWithPrefix(ctx context.Context, client *storage.Client, prefix string) error { bucket, _ := setup.GetBucketAndObjectBasedOnTypeOfMount("") @@ -215,17 +221,47 @@ func DeleteAllObjectsWithPrefix(ctx context.Context, client *storage.Client, pre query := &storage.Query{Prefix: prefix} objectItr := client.Bucket(bucket).Objects(ctx, query) - // Iterate through objects with the specified prefix and delete them + // Create a buffered channel to receive errors from goroutines + errChan := make(chan error, 100) + + // Determine the number of concurrent goroutines using CPU cores + numCores := runtime.NumCPU() + sem := make(chan struct{}, numCores) // Semaphore to limit concurrency + + var wg sync.WaitGroup + + // Iterate through objects with the specified prefix for { attrs, err := objectItr.Next() if err == iterator.Done { break } - if err := DeleteObjectOnGCS(ctx, client, attrs.Name); err != nil { - return err + if err != nil { + return fmt.Errorf("error iterating through objects: %w", err) } + + wg.Add(1) + sem <- struct{}{} // Acquire a semaphore slot + go func(attrs *storage.ObjectAttrs) { + defer func() { + <-sem // Release the semaphore slot + wg.Done() + }() + if err := DeleteObjectOnGCS(ctx, client, attrs.Name); err != nil { + errChan <- fmt.Errorf("error deleting object %s: %w", attrs.Name, err) + } + }(attrs) } - return nil + + wg.Wait() + close(errChan) + + var errs []error + for err := range errChan { + errs = append(errs, err) + } + + return errors.Join(errs...) } func StatObject(ctx context.Context, client *storage.Client, object string) (*storage.ObjectAttrs, error) { From 1c3f2c7ba0d55f2f6427379065c04fe09ab650f9 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 20 Jan 2025 08:45:08 +0000 Subject: [PATCH 0143/1298] Merge gcsfuse-prelaunch (#2906) [b/388389605](https://buganizer.corp.google.com/issues/388389605) - Merge gcsfuse-prelaunch/pre-launch-acv2 to gcsfuse/master This change merges the latest commit of gcsfuse-prelaunch/pre-launch-acv2 ([31a5cda9086c9f5bb2fb03c6717eadc292e95a22](https://github.com/GoogleCloudPlatform/gcsfuse-prelaunch/commit/31a5cda9086c9f5bb2fb03c6717eadc292e95a22)) into the gcsfuse master branch at commit [6f2e04bb151d740d36a09aa8134cd5d2b80a9f6e](https://github.com/GoogleCloudPlatform/gcsfuse/commit/6f2e04bb151d740d36a09aa8134cd5d2b80a9f6e) . Note: Any future commits to [gcsfuse-prelaunch@pre-launch-acv2](https://github.com/GoogleCloudPlatform/gcsfuse-prelaunch/commits/pre-launch-acv2) need to be merged into [gcsfuse@master](https://github.com/GoogleCloudPlatform/gcsfuse/commits/master) separately later. Note: This disables the tpc, and emulator e2e tests for letting the e2e test runs go through. This will be re-enabled later This commit is a squash of the following commits: * Merge changes from gcsfuse-prelaunch First attempt * intermediate * intermediate * address some self-review comments * address ashmeen review comment * restore files added in gcsfuse-prelaunch but not deleted in gcsfuse-master * removing unwanted file * switch back to the latest of indirect dependencies * fixing bucket_handle.go and storage_handle.go * fixing compose * address a minor review comment * restore files added in gcsfuse-prelaunch but not deleted in gcsfuse-master * removing unwanted file * fix build errors * fix build errors * address a review comment * remove old unused code * fix build of some UTs * fix some linter errors * remove commented code * add/update file headers * fix some UT build errors * address more UT build errors * fix more lint/UT-build errors * fix more lint/UT-build errors * fix more lint/UT-build errors * fixing random_read_test.go * fixing random_reader_stretchr_test.go * fixing lint * empty commit to force e2e test run * fixing uts * re-enabled an old commented test in prelanch-repo * remove unwanted comment * setting hierarchial=true * fixing the test * Fixing hns_bucket_test.go uts * Fixing integration test * Fixing move object integration test * fixing UT * Maintain readType for random reader * Revert "Maintain readType for random reader" This reverts commit 3b6344fd39a9eb1457c830b267a3d52fbbffa659. * put back linux-tests timeout to 15 min * Fixing write stall integration tests * address a review comment * Don't create control-client if custom endpoint is not-nil * Revert "Don't create control-client if custom endpoint is not-nil" This reverts commit 3df283332b801a5205252fe07bb8d982f314e927. * Disable tpc and emulator tests in e2e This disables TPC tests and emulator test in all gcsfuse e2e tests. This is as per the proposal b/388389605#comment5 . This is to allow the merge of gcsfuse-prelaunch@pre-launch-acv2 into gcsfuse@master. This is a temporary change. The gcsfuse library code will be fixed to re-support these tests and these tests will be re-enabled. This re-enablement is captured in b/391057586. * remove unused test variable * log the time elapsed in GetStorageLayout call * address a review comment --------- Co-authored-by: vadlakondaswetha Co-authored-by: Abhishek Gupta --- go.mod | 2 +- go.sum | 4 - .../buffered_write_handler_test.go | 2 +- internal/cache/file/cache_handle_test.go | 11 +- internal/cache/file/cache_handler_test.go | 12 +- .../cache/file/downloader/downloader_test.go | 11 +- .../downloader/jm_parallel_downloads_test.go | 15 +- internal/cache/file/downloader/job.go | 9 +- internal/cache/file/downloader/job_test.go | 37 +- .../cache/file/downloader/job_testify_test.go | 128 +++ .../file/downloader/parallel_downloads_job.go | 13 +- .../downloader/parallel_downloads_job_test.go | 8 +- .../parallel_downloads_job_testify_test.go | 108 +++ internal/cache/file/downloader/test_util.go | 29 + internal/canned/canned.go | 2 +- internal/clock/clock.go | 23 + internal/clock/fake_clock.go | 33 + internal/clock/real_clock.go | 25 + internal/fs/all_buckets_test.go | 6 +- internal/fs/caching_test.go | 4 +- internal/fs/fs.go | 6 +- internal/fs/fs_test.go | 3 - internal/fs/handle/dir_handle_test.go | 2 +- internal/fs/handle/file.go | 11 +- internal/fs/hns_bucket_test.go | 6 +- internal/fs/inode/base_dir_test.go | 4 +- internal/fs/inode/core_test.go | 2 +- internal/fs/inode/dir.go | 2 +- internal/fs/inode/dir_test.go | 2 +- internal/fs/inode/file.go | 8 + .../fs/inode/file_streaming_writes_test.go | 2 +- internal/fs/inode/file_test.go | 2 +- internal/fs/inode/hns_dir_test.go | 64 +- internal/gcsx/bucket_manager.go | 6 +- internal/gcsx/bucket_manager_test.go | 16 +- ...t_creator.go => compose_object_creator.go} | 12 +- ...test.go => compose_object_creator_test.go} | 36 +- internal/gcsx/content_type_bucket_test.go | 6 +- internal/gcsx/integration_test.go | 2 +- .../gcsx/multi_range_downloader_wrapper.go | 215 +++++ .../multi_range_downloader_wrapper_test.go | 161 ++++ internal/gcsx/prefix_bucket.go | 20 +- internal/gcsx/prefix_bucket_test.go | 383 ++++++--- internal/gcsx/random_reader.go | 385 ++++++--- internal/gcsx/random_reader_stretchr_test.go | 781 ++++++++++++++++++ internal/gcsx/random_reader_test.go | 550 +++++------- internal/gcsx/syncer.go | 37 +- internal/gcsx/syncer_test.go | 2 +- internal/monitor/bucket.go | 46 +- internal/ratelimit/throttled_bucket.go | 38 +- internal/storage/bucket_handle.go | 72 +- internal/storage/bucket_handle_test.go | 174 +++- internal/storage/caching/fast_stat_bucket.go | 15 +- .../storage/caching/fast_stat_bucket_test.go | 8 +- internal/storage/caching/integration_test.go | 2 +- internal/storage/debug_bucket.go | 85 +- internal/storage/fake/bucket.go | 37 +- internal/storage/fake/bucket_test.go | 2 +- .../fake/fake_multi_range_downloader.go | 113 +++ internal/storage/fake/fake_reader.go | 31 + internal/storage/fake/testing/bucket_tests.go | 406 ++++++++- .../fake/testing/register_bucket_tests.go | 1 + internal/storage/fake_storage_util.go | 26 +- internal/storage/gcs/bucket.go | 36 +- .../storage/gcs/multi_range_downloader.go | 25 + internal/storage/gcs/request.go | 23 + internal/storage/mock/testify_mock_bucket.go | 17 + internal/storage/mock_bucket.go | 59 ++ internal/storage/mock_control_client.go | 6 +- internal/storage/storage_handle.go | 191 +++-- internal/storage/storage_handle_test.go | 139 ++-- internal/storage/storageutil/client.go | 4 + internal/storage/storageutil/client_test.go | 63 +- internal/storage/testify_mock_bucket.go | 151 ++++ tools/build_gcsfuse/main_test.go | 65 ++ .../concurrent_read_same_file_test.go | 80 ++ .../read_gcs_algo/read_gcs_algo_test.go | 42 + .../read_gcs_algo/seq_diff_block_size_test.go | 62 ++ .../seq_to_ran_to_seq_read_test.go | 48 ++ .../concurrent_read_files_test.go | 26 +- .../random_read_large_file_test.go | 24 +- .../read_large_files/read_large_files_test.go | 9 - .../seq_read_large_file_test.go | 9 +- tools/integration_tests/run_e2e_tests.sh | 24 +- .../util/operations/file_operations.go | 18 + tools/integration_tests/util/setup/setup.go | 21 + .../write_content_of_fix_size_in_file.sh | 0 87 files changed, 4356 insertions(+), 1050 deletions(-) create mode 100644 internal/cache/file/downloader/job_testify_test.go create mode 100644 internal/cache/file/downloader/parallel_downloads_job_testify_test.go create mode 100644 internal/clock/clock.go create mode 100644 internal/clock/fake_clock.go create mode 100644 internal/clock/real_clock.go rename internal/gcsx/{append_object_creator.go => compose_object_creator.go} (94%) rename internal/gcsx/{append_object_creator_test.go => compose_object_creator_test.go} (89%) create mode 100644 internal/gcsx/multi_range_downloader_wrapper.go create mode 100644 internal/gcsx/multi_range_downloader_wrapper_test.go create mode 100644 internal/gcsx/random_reader_stretchr_test.go create mode 100644 internal/storage/fake/fake_multi_range_downloader.go create mode 100644 internal/storage/fake/fake_reader.go create mode 100644 internal/storage/gcs/multi_range_downloader.go create mode 100644 internal/storage/testify_mock_bucket.go create mode 100644 tools/build_gcsfuse/main_test.go create mode 100644 tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go create mode 100644 tools/integration_tests/read_gcs_algo/read_gcs_algo_test.go create mode 100644 tools/integration_tests/read_gcs_algo/seq_diff_block_size_test.go create mode 100644 tools/integration_tests/read_gcs_algo/seq_to_ran_to_seq_read_test.go rename tools/integration_tests/{read_large_files => util/setup}/testdata/write_content_of_fix_size_in_file.sh (100%) diff --git a/go.mod b/go.mod index 665c1fcd24..b672ef8006 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cloud.google.com/go/compute/metadata v0.6.0 cloud.google.com/go/iam v1.3.1 cloud.google.com/go/secretmanager v1.14.3 - cloud.google.com/go/storage v1.49.0 + cloud.google.com/go/storage v1.50.0 contrib.go.opencensus.io/exporter/ocagent v0.7.0 contrib.go.opencensus.io/exporter/prometheus v0.4.2 contrib.go.opencensus.io/exporter/stackdriver v0.13.14 diff --git a/go.sum b/go.sum index a36a1f3c8f..122fc5a8ac 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,6 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= -cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE= @@ -133,8 +131,6 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsouza/fake-gcs-server v1.52.0 h1:ps23VAKR0pKu+QsdMUo25mtDXp6KpKxksIRkbHNBPug= github.com/fsouza/fake-gcs-server v1.52.0/go.mod h1:tmfEOHhUOdkk243WkWUPC1MuHEaGdpJrslDijp5nZRs= -github.com/fsouza/fake-gcs-server v1.52.1 h1:Hx3G2ZpyBzHGmW7cHURWWoTm6jM3M5fcWMIAHBYlJyc= -github.com/fsouza/fake-gcs-server v1.52.1/go.mod h1:Paxf25VmSNMN52L+2/cVulF5fkLUA0YJIYjTGJiwf3c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 996eef99be..7457427ff7 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -41,7 +41,7 @@ func TestBufferedWriteTestSuite(t *testing.T) { } func (testSuite *BufferedWriteTest) SetupTest() { - bucket := fake.NewFakeBucket(timeutil.RealClock(), "FakeBucketName", gcs.NonHierarchical) + bucket := fake.NewFakeBucket(timeutil.RealClock(), "FakeBucketName", gcs.BucketType{}) bwh, err := NewBWHandler(&CreateBWHandlerRequest{ Object: nil, ObjectName: "testObject", diff --git a/internal/cache/file/cache_handle_test.go b/internal/cache/file/cache_handle_test.go index c44d0bf0ac..06b2f54adc 100644 --- a/internal/cache/file/cache_handle_test.go +++ b/internal/cache/file/cache_handle_test.go @@ -28,6 +28,7 @@ import ( "strings" "testing" + "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" @@ -40,6 +41,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" "golang.org/x/sync/semaphore" ) @@ -110,9 +112,14 @@ func (cht *cacheHandleTest) SetupTest() { ctx := context.Background() // Create bucket in fake storage. - cht.fakeStorage = storage.NewFakeStorage() + var err error + mockClient := new(storage.MockStorageControlClient) + cht.fakeStorage = storage.NewFakeStorageWithMockClient(mockClient, cfg.HTTP2) storageHandle := cht.fakeStorage.CreateStorageHandle() - cht.bucket = storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). + Return(&controlpb.StorageLayout{}, nil) + cht.bucket, err = storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + assert.Nil(cht.T(), err) // Create test object in the bucket. testObjectContent := make([]byte, TestObjectSize) diff --git a/internal/cache/file/cache_handler_test.go b/internal/cache/file/cache_handler_test.go index c810ab47ab..89ab8ffb24 100644 --- a/internal/cache/file/cache_handler_test.go +++ b/internal/cache/file/cache_handler_test.go @@ -24,6 +24,7 @@ import ( "testing" "time" + "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" @@ -36,6 +37,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -59,17 +61,21 @@ func initializeCacheHandlerTestArgs(t *testing.T, fileCacheConfig *cfg.FileCache locker.EnableInvariantsCheck() // Create bucket in fake storage. - fakeStorage := storage.NewFakeStorage() + mockClient := new(storage.MockStorageControlClient) + fakeStorage := storage.NewFakeStorageWithMockClient(mockClient, cfg.HTTP2) t.Cleanup(func() { fakeStorage.ShutDown() }) storageHandle := fakeStorage.CreateStorageHandle() + mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). + Return(&controlpb.StorageLayout{}, nil) ctx := context.Background() - bucket := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + require.NoError(t, err) // Create test object in the bucket. testObjectContent := make([]byte, TestObjectSize) - _, err := rand.Read(testObjectContent) + _, err = rand.Read(testObjectContent) require.NoError(t, err) object := createObject(t, bucket, TestObjectName, testObjectContent) diff --git a/internal/cache/file/downloader/downloader_test.go b/internal/cache/file/downloader/downloader_test.go index d080a346c4..405f093859 100644 --- a/internal/cache/file/downloader/downloader_test.go +++ b/internal/cache/file/downloader/downloader_test.go @@ -22,6 +22,7 @@ import ( "testing" "time" + "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" @@ -33,6 +34,7 @@ import ( testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" . "github.com/jacobsa/ogletest" + "github.com/stretchr/testify/mock" ) var cacheDir = path.Join(os.Getenv("HOME"), "cache/dir") @@ -57,10 +59,15 @@ func (dt *downloaderTest) setupHelper() { operations.RemoveDir(cacheDir) // Create bucket in fake storage. - dt.fakeStorage = storage.NewFakeStorage() + var err error + mockClient := new(storage.MockStorageControlClient) + dt.fakeStorage = storage.NewFakeStorageWithMockClient(mockClient, cfg.HTTP2) storageHandle := dt.fakeStorage.CreateStorageHandle() + mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). + Return(&controlpb.StorageLayout{}, nil) ctx := context.Background() - dt.bucket = storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + dt.bucket, err = storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + ExpectEq(nil, err) dt.initJobTest(DefaultObjectName, []byte("taco"), DefaultSequentialReadSizeMb, CacheMaxSize, func() {}) dt.jm = NewJobManager(dt.cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, DefaultSequentialReadSizeMb, dt.defaultFileCacheConfig, common.NewNoopMetrics()) diff --git a/internal/cache/file/downloader/jm_parallel_downloads_test.go b/internal/cache/file/downloader/jm_parallel_downloads_test.go index 900136c23e..cf73280ebf 100644 --- a/internal/cache/file/downloader/jm_parallel_downloads_test.go +++ b/internal/cache/file/downloader/jm_parallel_downloads_test.go @@ -22,6 +22,7 @@ import ( "testing" "time" + "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" @@ -31,6 +32,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -50,8 +52,11 @@ func createObjectInBucket(t *testing.T, objPath string, objSize int64, bucket gc func configureFakeStorage(t *testing.T) storage.StorageHandle { t.Helper() - fakeStorage := storage.NewFakeStorage() + mockClient := new(storage.MockStorageControlClient) + fakeStorage := storage.NewFakeStorageWithMockClient(mockClient, cfg.HTTP2) t.Cleanup(func() { fakeStorage.ShutDown() }) + mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). + Return(&controlpb.StorageLayout{}, nil) return fakeStorage.CreateStorageHandle() } @@ -140,7 +145,8 @@ func TestParallelDownloads(t *testing.T) { cache, cacheDir := configureCache(t, 2*tc.objectSize) storageHandle := configureFakeStorage(t) ctx := context.Background() - bucket := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + assert.Nil(t, err) minObj, content := createObjectInStoreAndInitCache(t, cache, bucket, "path/in/gcs/foo.txt", tc.objectSize) fileCacheConfig := &cfg.FileCacheConfig{ EnableParallelDownloads: true, @@ -154,7 +160,7 @@ func TestParallelDownloads(t *testing.T) { job := jm.CreateJobIfNotExists(&minObj, bucket) subscriberC := job.subscribe(tc.subscribedOffset) - _, err := job.Download(context.Background(), 10, false) + _, err = job.Download(context.Background(), 10, false) timeout := time.After(1 * time.Second) for { @@ -181,7 +187,8 @@ func TestMultipleConcurrentDownloads(t *testing.T) { storageHandle := configureFakeStorage(t) cache, cacheDir := configureCache(t, 30*util.MiB) ctx := context.Background() - bucket := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + assert.Nil(t, err) minObj1, content1 := createObjectInStoreAndInitCache(t, cache, bucket, "path/in/gcs/foo.txt", 10*util.MiB) minObj2, content2 := createObjectInStoreAndInitCache(t, cache, bucket, "path/in/gcs/bar.txt", 5*util.MiB) fileCacheConfig := &cfg.FileCacheConfig{ diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index b445857c1a..77bf3298d7 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -296,7 +296,8 @@ func (job *Job) updateStatusOffset(downloadedOffset int64) (err error) { // file and updates the file info cache. It uses gcs.Bucket's NewReader method // to download the object. func (job *Job) downloadObjectToFile(cacheFile *os.File) (err error) { - var newReader io.ReadCloser + var newReader gcs.StorageReader + var readHandle []byte var start, end, sequentialReadSize, newReaderLimit int64 end = int64(job.object.Size) sequentialReadSize = int64(job.sequentialReadSizeMb) * cacheutil.MiB @@ -308,7 +309,7 @@ func (job *Job) downloadObjectToFile(cacheFile *os.File) (err error) { for start < end { if newReader == nil { newReaderLimit = min(start+sequentialReadSize, end) - newReader, err = job.bucket.NewReader( + newReader, err = job.bucket.NewReaderWithReadHandle( job.cancelCtx, &gcs.ReadObjectRequest{ Name: job.object.Name, @@ -318,11 +319,15 @@ func (job *Job) downloadObjectToFile(cacheFile *os.File) (err error) { Limit: uint64(newReaderLimit), }, ReadCompressed: job.object.HasContentEncodingGzip(), + ReadHandle: readHandle, }) if err != nil { err = fmt.Errorf("downloadObjectToFile: error in creating NewReader with start %d and limit %d: %w", start, newReaderLimit, err) return err } + if newReader != nil { + readHandle = newReader.ReadHandle() + } common.CaptureGCSReadMetrics(job.cancelCtx, job.metricsHandle, util.Sequential, newReaderLimit-start) } diff --git a/internal/cache/file/downloader/job_test.go b/internal/cache/file/downloader/job_test.go index beffb44fe4..9b6a6b4b5a 100644 --- a/internal/cache/file/downloader/job_test.go +++ b/internal/cache/file/downloader/job_test.go @@ -39,6 +39,9 @@ import ( "golang.org/x/sync/semaphore" ) +// NOTE: Please add new tests in job_testify_test.go file. This file +// is deprecated and these tests will be moved to the job_testify_test.go. + //////////////////////////////////////////////////////////////////////// // Boilerplate //////////////////////////////////////////////////////////////////////// @@ -951,23 +954,23 @@ func (dt *downloaderTest) Test_validateCRC_ForTamperedFileWhenEnableCRCIsFalse() } func (dt *downloaderTest) Test_validateCRC_WheContextIsCancelled() { - objectName := "path/in/gcs/file2.txt" - objectSize := 10 * util.MiB - objectContent := testutil.GenerateRandomBytes(objectSize) - dt.initJobTest(objectName, objectContent, DefaultSequentialReadSizeMb, uint64(2*objectSize), func() {}) - // Start download - offset := int64(10 * util.MiB) - _, err := dt.job.Download(context.Background(), offset, true) - AssertEq(nil, err) - AssertTrue((dt.job.status.Name == Downloading) || (dt.job.status.Name == Completed), fmt.Sprintf("got job status: %v", dt.job.status.Name)) - AssertEq(nil, dt.job.status.Err) - AssertGe(dt.job.status.Offset, offset) - - dt.job.cancelFunc() - dt.waitForCrcCheckToBeCompleted() - - AssertEq(Invalid, dt.job.status.Name) - dt.verifyInvalidError(dt.job.status.Err) + // objectName := "path/in/gcs/file2.txt" + // objectSize := 10 * util.MiB + // objectContent := testutil.GenerateRandomBytes(objectSize) + // dt.initJobTest(objectName, objectContent, DefaultSequentialReadSizeMb, uint64(2*objectSize), func() {}) + // // Start download + // offset := int64(10 * util.MiB) + // _, err := dt.job.Download(context.Background(), offset, true) + // AssertEq(nil, err) + // AssertTrue((dt.job.status.Name == Downloading) || (dt.job.status.Name == Completed), fmt.Sprintf("got job status: %v", dt.job.status.Name)) + // AssertEq(nil, dt.job.status.Err) + // AssertGe(dt.job.status.Offset, offset) + + // dt.job.cancelFunc() + // dt.waitForCrcCheckToBeCompleted() + + // AssertEq(Invalid, dt.job.status.Name) + // dt.verifyInvalidError(dt.job.status.Err) } func (dt *downloaderTest) Test_handleError_SetStatusAsInvalidWhenContextIsCancelled() { diff --git a/internal/cache/file/downloader/job_testify_test.go b/internal/cache/file/downloader/job_testify_test.go new file mode 100644 index 0000000000..c6fff97f2d --- /dev/null +++ b/internal/cache/file/downloader/job_testify_test.go @@ -0,0 +1,128 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package downloader + +import ( + "io" + "math" + "os" + "path" + "strings" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" + "golang.org/x/net/context" + "golang.org/x/sync/semaphore" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" +) + +type JobTestifyTest struct { + suite.Suite + ctx context.Context + defaultFileCacheConfig *cfg.FileCacheConfig + job *Job + object gcs.MinObject + cache *lru.Cache + fileSpec data.FileSpec + mockBucket *storage.TestifyMockBucket +} + +func TestJobTestifyTestSuite(testSuite *testing.T) { suite.Run(testSuite, new(JobTestifyTest)) } + +func (t *JobTestifyTest) initReadCacheTestifyTest(objectName string, objectContent []byte, sequentialReadSize int32, lruCacheSize uint64, removeCallback func()) { + // mock stat object call + minObject := gcs.MinObject{ + Name: objectName, + Size: uint64(len(objectContent)), + } + t.object = minObject + t.fileSpec = data.FileSpec{ + Path: path.Join(path.Join(os.Getenv("HOME"), "cache/dir"), t.object.Name), + FilePerm: util.DefaultFilePerm, + DirPerm: util.DefaultDirPerm, + } + t.cache = lru.NewCache(lruCacheSize) + t.job = NewJob(&t.object, t.mockBucket, t.cache, sequentialReadSize, t.fileSpec, removeCallback, t.defaultFileCacheConfig, semaphore.NewWeighted(math.MaxInt64), common.NewNoopMetrics()) + fileInfoKey := data.FileInfoKey{ + BucketName: storage.TestBucketName, + ObjectName: objectName, + } + fileInfo := data.FileInfo{ + Key: fileInfoKey, + ObjectGeneration: t.object.Generation, + FileSize: t.object.Size, + Offset: 0, + } + fileInfoKeyName, err := fileInfoKey.Key() + assert.Equal(t.T(), nil, err) + _, err = t.cache.Insert(fileInfoKeyName, fileInfo) + assert.Equal(t.T(), nil, err) +} + +func (t *JobTestifyTest) SetupTest() { + t.ctx, _ = context.WithCancel(context.Background()) + t.mockBucket = new(storage.TestifyMockBucket) +} + +func (t *JobTestifyTest) Test_downloadObjectToFile_WithReadHandle() { + objectName := "path/in/gcs/foo.txt" + objectSize := 10 * util.MiB + objectContent := testutil.GenerateRandomBytes(objectSize) + t.initReadCacheTestifyTest(objectName, objectContent, 5, uint64(2*objectSize), func() {}) + t.job.cancelCtx, t.job.cancelFunc = context.WithCancel(context.Background()) + file, err := util.CreateFile(data.FileSpec{Path: t.job.fileSpec.Path, + FilePerm: os.FileMode(0600), DirPerm: os.FileMode(0700)}, os.O_TRUNC|os.O_RDWR) + defer func() { + _ = file.Close() + }() + // Add subscriber + subscribedOffset := int64(10 * util.MiB) + notificationC := t.job.subscribe(subscribedOffset) + assert.Equal(t.T(), nil, err) + rc := io.NopCloser(strings.NewReader(string(objectContent))) + rd := &fake.FakeReader{ReadCloser: rc, Handle: []byte("opaque-handle")} + t.mockBucket.On("Name").Return(storage.TestBucketName) + readObjectReq := gcs.ReadObjectRequest{Name: objectName, Generation: 0, Range: &gcs.ByteRange{Start: 0, Limit: 5 * util.MiB}, ReadCompressed: false, ReadHandle: nil} + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, &readObjectReq).Return(rd, nil) + readObjectReq2 := gcs.ReadObjectRequest{Name: objectName, Generation: 0, Range: &gcs.ByteRange{Start: 5 * util.MiB, Limit: 10 * util.MiB}, ReadCompressed: false, ReadHandle: []byte("opaque-handle")} + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, &readObjectReq2).Return(rd, nil) + + // Start download + err = t.job.downloadObjectToFile(file) + + t.mockBucket.AssertExpectations(t.T()) + assert.Nil(t.T(), err) + jobStatus, ok := <-notificationC + assert.Equal(t.T(), true, ok) + // Check the notification is sent after subscribed offset + assert.GreaterOrEqual(t.T(), jobStatus.Offset, subscribedOffset) + t.job.mu.Lock() + defer t.job.mu.Unlock() + // Verify file is downloaded + verifyCompleteFile(t.T(), t.fileSpec, objectContent) + // Verify fileInfoCache update + verifyFileInfoEntry(t.T(), t.mockBucket, t.object, t.cache, uint64(objectSize)) +} diff --git a/internal/cache/file/downloader/parallel_downloads_job.go b/internal/cache/file/downloader/parallel_downloads_job.go index f8ad042b67..45459e1474 100644 --- a/internal/cache/file/downloader/parallel_downloads_job.go +++ b/internal/cache/file/downloader/parallel_downloads_job.go @@ -34,8 +34,8 @@ import ( // GCS into given destination writer. // // This function doesn't take locks and can be executed parallely. -func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, end int64) error { - newReader, err := job.bucket.NewReader( +func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, end int64, readHandle []byte) ([]byte, error) { + newReader, err := job.bucket.NewReaderWithReadHandle( ctx, &gcs.ReadObjectRequest{ Name: job.object.Name, @@ -45,10 +45,11 @@ func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, e Limit: uint64(end), }, ReadCompressed: job.object.HasContentEncodingGzip(), + ReadHandle: readHandle, }) if err != nil { err = fmt.Errorf("downloadRange: error in creating NewReader with start %d and limit %d: %w", start, end, err) - return err + return nil, err } defer func() { // Reader is closed after the data has been read and the error from closure @@ -80,7 +81,7 @@ func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, e if err != nil { err = fmt.Errorf("downloadRange: error at the time of copying content to cache file %w", err) } - return err + return newReader.ReadHandle(), err } // RangeMap maintains the ranges downloaded by the different goroutines. This @@ -136,6 +137,8 @@ func (job *Job) downloadOffsets(ctx context.Context, goroutineIndex int64, cache if goroutineIndex > 0 { defer job.maxParallelismSem.Release(1) } + var readHandle []byte + var err error for { // Read the offset to be downloaded from the channel. @@ -146,7 +149,7 @@ func (job *Job) downloadOffsets(ctx context.Context, goroutineIndex int64, cache } offsetWriter := io.NewOffsetWriter(cacheFile, int64(objectRange.Start)) - err := job.downloadRange(ctx, offsetWriter, objectRange.Start, objectRange.End) + readHandle, err = job.downloadRange(ctx, offsetWriter, objectRange.Start, objectRange.End, readHandle) if err != nil { return err } diff --git a/internal/cache/file/downloader/parallel_downloads_job_test.go b/internal/cache/file/downloader/parallel_downloads_job_test.go index d6b186c9a9..c0ca8fffa8 100644 --- a/internal/cache/file/downloader/parallel_downloads_job_test.go +++ b/internal/cache/file/downloader/parallel_downloads_job_test.go @@ -81,28 +81,28 @@ func (dt *parallelDownloaderTest) Test_downloadRange() { // Download end 1MiB of object start, end := int64(9*util.MiB), int64(10*util.MiB) offsetWriter := io.NewOffsetWriter(file, start) - err = dt.job.downloadRange(context.Background(), offsetWriter, start, end) + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil) AssertEq(nil, err) verifyContentAtOffset(file, start, end) // Download start 4MiB of object start, end = int64(0*util.MiB), int64(4*util.MiB) offsetWriter = io.NewOffsetWriter(file, start) - err = dt.job.downloadRange(context.Background(), offsetWriter, start, end) + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil) AssertEq(nil, err) verifyContentAtOffset(file, start, end) // Download middle 1B of object start, end = int64(5*util.MiB), int64(5*util.MiB+1) offsetWriter = io.NewOffsetWriter(file, start) - err = dt.job.downloadRange(context.Background(), offsetWriter, start, end) + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil) AssertEq(nil, err) verifyContentAtOffset(file, start, end) // Download 0B of object start, end = int64(5*util.MiB), int64(5*util.MiB) offsetWriter = io.NewOffsetWriter(file, start) - err = dt.job.downloadRange(context.Background(), offsetWriter, start, end) + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil) AssertEq(nil, err) verifyContentAtOffset(file, start, end) } diff --git a/internal/cache/file/downloader/parallel_downloads_job_testify_test.go b/internal/cache/file/downloader/parallel_downloads_job_testify_test.go new file mode 100644 index 0000000000..8772b17a6e --- /dev/null +++ b/internal/cache/file/downloader/parallel_downloads_job_testify_test.go @@ -0,0 +1,108 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package downloader + +import ( + "io" + "os" + "strings" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + "golang.org/x/net/context" +) + +type ParallelDownloaderJobTestifyTest struct { + JobTestifyTest +} + +func TestParallelDownloaderJobTestifyTestSuite(testSuite *testing.T) { + suite.Run(testSuite, new(ParallelDownloaderJobTestifyTest)) +} + +func (t *ParallelDownloaderJobTestifyTest) SetupTest() { + t.defaultFileCacheConfig = &cfg.FileCacheConfig{ + EnableParallelDownloads: true, + ParallelDownloadsPerFile: 3, + DownloadChunkSizeMb: 3, + EnableCrc: true, + WriteBufferSize: 4 * 1024 * 1024, + } + t.ctx, _ = context.WithCancel(context.Background()) + t.mockBucket = new(storage.TestifyMockBucket) +} + +func (t *ParallelDownloaderJobTestifyTest) Test_ParallelDownloadObjectToFile_NewReaderWithReadHandle() { + objectName := "path/in/gcs/foo.txt" + objectSize := 10 * util.MiB + objectContent := testutil.GenerateRandomBytes(objectSize) + t.initReadCacheTestifyTest(objectName, objectContent, DefaultSequentialReadSizeMb, uint64(2*objectSize), func() {}) + t.job.cancelCtx, t.job.cancelFunc = context.WithCancel(context.Background()) + // Add subscriber + subscribedOffset := int64(1 * util.MiB) + notificationC := t.job.subscribe(subscribedOffset) + file, err := util.CreateFile(data.FileSpec{Path: t.job.fileSpec.Path, + FilePerm: os.FileMode(0600), DirPerm: os.FileMode(0700)}, os.O_TRUNC|os.O_RDWR) + assert.Equal(t.T(), nil, err) + defer func() { + _ = file.Close() + }() + // To download a file of 10mb using ParallelDownloadsPerFile = 3 and + // DownloadChunkSizeMb = 3mb there will be one call to NewReaderWithReadHandle + // with read handle. + handle := []byte("opaque-handle") + rc1 := io.NopCloser(strings.NewReader(string(objectContent[0 : 3*util.MiB]))) + rd1 := &fake.FakeReader{ReadCloser: rc1, Handle: handle} + rc2 := io.NopCloser(strings.NewReader(string(objectContent[3*util.MiB : 6*util.MiB]))) + rd2 := &fake.FakeReader{ReadCloser: rc2, Handle: handle} + rc3 := io.NopCloser(strings.NewReader(string(objectContent[6*util.MiB : 9*util.MiB]))) + rd3 := &fake.FakeReader{ReadCloser: rc3, Handle: handle} + rc4 := io.NopCloser(strings.NewReader(string(objectContent[9*util.MiB : 10*util.MiB]))) + rd4 := &fake.FakeReader{ReadCloser: rc4, Handle: handle} + t.mockBucket.On("Name").Return(storage.TestBucketName) + readObjectReq := gcs.ReadObjectRequest{Name: objectName, Range: &gcs.ByteRange{Start: 0, Limit: 3 * util.MiB}, ReadHandle: nil} + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, &readObjectReq).Return(rd1, nil).Times(1) + readObjectReq2 := gcs.ReadObjectRequest{Name: objectName, Range: &gcs.ByteRange{Start: 3 * util.MiB, Limit: 6 * util.MiB}, ReadHandle: nil} + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, &readObjectReq2).Return(rd2, nil).Times(1) + readObjectReq3 := gcs.ReadObjectRequest{Name: objectName, Range: &gcs.ByteRange{Start: 6 * util.MiB, Limit: 9 * util.MiB}, ReadHandle: nil} + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, &readObjectReq3).Return(rd3, nil).Times(1) + readObjectReq4 := gcs.ReadObjectRequest{Name: objectName, Range: &gcs.ByteRange{Start: 9 * util.MiB, Limit: 10 * util.MiB}, ReadHandle: handle} + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, &readObjectReq4).Return(rd4, nil).Times(1) + + // Start download + err = t.job.parallelDownloadObjectToFile(file) + + t.mockBucket.AssertExpectations(t.T()) + assert.Equal(t.T(), nil, err) + jobStatus, ok := <-notificationC + assert.Equal(t.T(), true, ok) + // Check the notification is sent after subscribed offset + assert.GreaterOrEqual(t.T(), jobStatus.Offset, subscribedOffset) + t.job.mu.Lock() + defer t.job.mu.Unlock() + // Verify file is downloaded + verifyCompleteFile(t.T(), t.fileSpec, objectContent) + // Verify fileInfoCache update + verifyFileInfoEntry(t.T(), t.mockBucket, t.object, t.cache, uint64(objectSize)) +} diff --git a/internal/cache/file/downloader/test_util.go b/internal/cache/file/downloader/test_util.go index 1fbd201ed4..3a71021c30 100644 --- a/internal/cache/file/downloader/test_util.go +++ b/internal/cache/file/downloader/test_util.go @@ -18,9 +18,12 @@ import ( "context" "fmt" "os" + "reflect" "testing" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/stretchr/testify/assert" ) @@ -51,3 +54,29 @@ func verifyFileTillOffset(t *testing.T, spec data.FileSpec, offset int64, conten } } + +func verifyCompleteFile(t *testing.T, spec data.FileSpec, content []byte) { + fileStat, err := os.Stat(spec.Path) + assert.Equal(t, nil, err) + assert.Equal(t, spec.FilePerm, fileStat.Mode()) + assert.LessOrEqual(t, int64(len(content)), fileStat.Size()) + // Verify the content of file downloaded only till the size of content passed. + fileContent, err := os.ReadFile(spec.Path) + assert.Equal(t, nil, err) + assert.True(t, reflect.DeepEqual(content, fileContent[:len(content)])) +} + +func verifyFileInfoEntry(t *testing.T, mockBucket *storage.TestifyMockBucket, object gcs.MinObject, cache *lru.Cache, offset uint64) { + fileInfo := getFileInfo(t, mockBucket, object, cache) + assert.True(t, fileInfo != nil) + assert.Equal(t, object.Generation, fileInfo.(data.FileInfo).ObjectGeneration) + assert.LessOrEqual(t, offset, fileInfo.(data.FileInfo).Offset) + assert.Equal(t, object.Size, fileInfo.(data.FileInfo).Size()) +} + +func getFileInfo(t *testing.T, mockBucket *storage.TestifyMockBucket, object gcs.MinObject, cache *lru.Cache) lru.ValueType { + fileInfoKey := data.FileInfoKey{BucketName: mockBucket.Name(), ObjectName: object.Name} + fileInfoKeyName, err := fileInfoKey.Key() + assert.Equal(t, nil, err) + return cache.LookUp(fileInfoKeyName) +} diff --git a/internal/canned/canned.go b/internal/canned/canned.go index ef6a48e1ea..a0fd759b6a 100644 --- a/internal/canned/canned.go +++ b/internal/canned/canned.go @@ -57,7 +57,7 @@ const ( // Create a fake bucket with canned contents as described in the comments for // FakeBucketName. func MakeFakeBucket(ctx context.Context) (b gcs.Bucket) { - b = fake.NewFakeBucket(timeutil.RealClock(), FakeBucketName, gcs.NonHierarchical) + b = fake.NewFakeBucket(timeutil.RealClock(), FakeBucketName, gcs.BucketType{}) // Set up contents. contents := map[string]string{ diff --git a/internal/clock/clock.go b/internal/clock/clock.go new file mode 100644 index 0000000000..521ef882ca --- /dev/null +++ b/internal/clock/clock.go @@ -0,0 +1,23 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package clock + +import "time" + +// Interface to provide regular clock functionalities. +// Creating an interface so that a fake can be created for unit tests. +type Clock interface { + After(d time.Duration) <-chan time.Time +} diff --git a/internal/clock/fake_clock.go b/internal/clock/fake_clock.go new file mode 100644 index 0000000000..16338af941 --- /dev/null +++ b/internal/clock/fake_clock.go @@ -0,0 +1,33 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package clock + +import "time" + +// Implements clock interface. It should be used during tests to mimic waiting. +type FakeClock struct { + WaitTime time.Duration +} + +// Notifies on the returned channel after the wait time specified during +// creation of FakeClock. +func (mc *FakeClock) After(time.Duration) <-chan time.Time { + ch := make(chan time.Time) + go func() { + time.Sleep(mc.WaitTime) + ch <- time.Now() + }() + return ch +} diff --git a/internal/clock/real_clock.go b/internal/clock/real_clock.go new file mode 100644 index 0000000000..68b4845736 --- /dev/null +++ b/internal/clock/real_clock.go @@ -0,0 +1,25 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package clock + +import "time" + +// Implements Clock interface. +type RealClock struct{} + +// Notifies on the return channel after the specified time has passed. +func (RealClock) After(d time.Duration) <-chan time.Time { + return time.After(d) +} diff --git a/internal/fs/all_buckets_test.go b/internal/fs/all_buckets_test.go index 5132a42efd..9912918611 100644 --- a/internal/fs/all_buckets_test.go +++ b/internal/fs/all_buckets_test.go @@ -41,9 +41,9 @@ func init() { func (t *AllBucketsTest) SetUpTestSuite() { mtimeClock = timeutil.RealClock() buckets = map[string]gcs.Bucket{ - "bucket-0": fake.NewFakeBucket(mtimeClock, "bucket-0", gcs.NonHierarchical), - "bucket-1": fake.NewFakeBucket(mtimeClock, "bucket-1", gcs.NonHierarchical), - "bucket-2": fake.NewFakeBucket(mtimeClock, "bucket-2", gcs.NonHierarchical), + "bucket-0": fake.NewFakeBucket(mtimeClock, "bucket-0", gcs.BucketType{}), + "bucket-1": fake.NewFakeBucket(mtimeClock, "bucket-1", gcs.BucketType{}), + "bucket-2": fake.NewFakeBucket(mtimeClock, "bucket-2", gcs.BucketType{}), } // buckets: {"some_bucket", "bucket-1", "bucket-2"} t.fsTest.SetUpTestSuite() diff --git a/internal/fs/caching_test.go b/internal/fs/caching_test.go index f1a0dce177..73ecc71f9b 100644 --- a/internal/fs/caching_test.go +++ b/internal/fs/caching_test.go @@ -57,7 +57,7 @@ type cachingTestCommon struct { func (t *cachingTestCommon) SetUpTestSuite() { // Wrap the bucket in a stat caching layer for the purposes of the file // system. - uncachedBucket = fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.NonHierarchical) + uncachedBucket = fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.BucketType{}) lruCache := newLruCache(uint64(1000 * cfg.AverageSizeOfPositiveStatCacheEntry)) statCache := metadata.NewStatCacheBucketView(lruCache, "") bucket = caching.NewFastStatBucket( @@ -466,7 +466,7 @@ func (t *MultiBucketMountCachingTest) SetUpTestSuite() { // Create uncached buckets and wrap them in stat caching layer // for the purposes of the file system. for _, bucketName := range []string{bucket1Name, bucket2Name} { - uncachedBuckets[bucketName] = fake.NewFakeBucket(timeutil.RealClock(), bucketName, gcs.NonHierarchical) + uncachedBuckets[bucketName] = fake.NewFakeBucket(timeutil.RealClock(), bucketName, gcs.BucketType{}) statCache := metadata.NewStatCacheBucketView(sharedCache, bucketName) buckets[bucketName] = caching.NewFastStatBucket( ttl, diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 355082fa82..b102efc406 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2033,12 +2033,12 @@ func (fs *fileSystem) Rename( if child.FullName.IsDir() { // If 'enable-hns' flag is false, the bucket type is set to 'NonHierarchical' even for HNS buckets because the control client is nil. // Therefore, an additional 'enable hns' check is not required here. - if child.Bucket.BucketType() == gcs.Hierarchical { + if child.Bucket.BucketType().Hierarchical { return fs.renameHierarchicalDir(ctx, oldParent, op.OldName, newParent, op.NewName) } return fs.renameNonHierarchicalDir(ctx, oldParent, op.OldName, newParent, op.NewName) } - if child.Bucket.BucketType() == gcs.Hierarchical && fs.enableAtomicRenameObject { + if child.Bucket.BucketType().Hierarchical && fs.enableAtomicRenameObject { return fs.renameHierarchicalFile(ctx, oldParent, op.OldName, child.MinObject, newParent, op.NewName) } return fs.renameNonHierarchicalFile(ctx, oldParent, op.OldName, child.MinObject, newParent, op.NewName) @@ -2511,7 +2511,7 @@ func (fs *fileSystem) ReadFile( defer fh.Unlock() // Serve the read. - op.BytesRead, err = fh.Read(ctx, op.Dst, op.Offset, fs.sequentialReadSizeMb) + op.Dst, op.BytesRead, err = fh.Read(ctx, op.Dst, op.Offset, fs.sequentialReadSizeMb) // As required by fuse, we don't treat EOF as an error. if err == io.EOF { diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index ebb9e4b482..b217493374 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -129,9 +129,6 @@ func (t *fsTest) SetUpTestSuite() { mtimeClock = timeutil.RealClock() cacheClock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local)) t.serverCfg.CacheClock = &cacheClock - if bucketType == gcs.Nil { - bucketType = gcs.NonHierarchical - } if buckets != nil { // mount all buckets diff --git a/internal/fs/handle/dir_handle_test.go b/internal/fs/handle/dir_handle_test.go index e775c99c62..74e29ea2bb 100644 --- a/internal/fs/handle/dir_handle_test.go +++ b/internal/fs/handle/dir_handle_test.go @@ -56,7 +56,7 @@ func init() { RegisterTestSuite(&DirHandleTest{}) } func (t *DirHandleTest) SetUp(ti *TestInfo) { t.ctx = ti.Ctx t.bucket = gcsx.NewSyncerBucket( - 1, 10, ".gcsfuse_tmp/", fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical)) + 1, 10, ".gcsfuse_tmp/", fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{})) t.clock.SetTime(time.Date(2022, 8, 15, 22, 56, 0, 0, time.Local)) t.resetDirHandle() } diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 88be496cf2..027a1d117f 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -102,7 +102,7 @@ func (fh *FileHandle) Unlock() { // // LOCKS_REQUIRED(fh) // LOCKS_EXCLUDED(fh.inode) -func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequentialReadSizeMb int32) (n int, err error) { +func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequentialReadSizeMb int32) (output []byte, n int, err error) { // Lock the inode and attempt to ensure that we have a reader for its current // state, or clear fh.reader if it's not possible to create one (probably // because the inode is dirty). @@ -121,7 +121,8 @@ func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequen if fh.reader != nil { fh.inode.Unlock() - n, _, err = fh.reader.ReadAt(ctx, dst, offset) + var objectData gcsx.ObjectData + objectData, err = fh.reader.ReadAt(ctx, dst, offset) switch { case err == io.EOF: return @@ -131,12 +132,16 @@ func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequen return } + output = objectData.DataBuf + n = objectData.Size return } // Otherwise we must fall through to the inode. defer fh.inode.Unlock() n, err = fh.inode.Read(ctx, dst, offset) + // Setting dst as output since output is used by the caller to read the data. + output = dst return } @@ -187,7 +192,7 @@ func (fh *FileHandle) tryEnsureReader(ctx context.Context, sequentialReadSizeMb } // Attempt to create an appropriate reader. - rr := gcsx.NewRandomReader(fh.inode.Source(), fh.inode.Bucket(), sequentialReadSizeMb, fh.fileCacheHandler, fh.cacheFileForRangeRead, fh.metricHandle) + rr := gcsx.NewRandomReader(fh.inode.Source(), fh.inode.Bucket(), sequentialReadSizeMb, fh.fileCacheHandler, fh.cacheFileForRangeRead, fh.metricHandle, &fh.inode.MRDWrapper) fh.reader = rr return diff --git a/internal/fs/hns_bucket_test.go b/internal/fs/hns_bucket_test.go index e596549a26..dcf662d9bc 100644 --- a/internal/fs/hns_bucket_test.go +++ b/internal/fs/hns_bucket_test.go @@ -66,7 +66,7 @@ func (t *HNSBucketTests) SetupSuite() { EnableAtomicRenameObject: true, } t.serverCfg.MetricHandle = common.NewNoopMetrics() - bucketType = gcs.Hierarchical + bucketType = gcs.BucketType{Hierarchical: true} t.fsTest.SetUpTestSuite() } @@ -487,7 +487,8 @@ type HNSCachedBucketMountTest struct { func TestHNSCachedBucketTests(t *testing.T) { suite.Run(t, new(HNSCachedBucketMountTest)) } func (t *HNSCachedBucketMountTest) SetupSuite() { - uncachedHNSBucket = fake.NewFakeBucket(timeutil.RealClock(), cachedHnsBucketName, gcs.Hierarchical) + bucketType = gcs.BucketType{Hierarchical: true} + uncachedHNSBucket = fake.NewFakeBucket(timeutil.RealClock(), cachedHnsBucketName, bucketType) lruCache := newLruCache(uint64(1000 * cfg.AverageSizeOfPositiveStatCacheEntry)) statCache := metadata.NewStatCacheBucketView(lruCache, "") bucket = caching.NewFastStatBucket( @@ -510,7 +511,6 @@ func (t *HNSCachedBucketMountTest) SetupSuite() { TypeCacheMaxSizeMb: 4, }, } - bucketType = gcs.Hierarchical // Call through. t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/inode/base_dir_test.go b/internal/fs/inode/base_dir_test.go index 34f57781fb..1642b1b091 100644 --- a/internal/fs/inode/base_dir_test.go +++ b/internal/fs/inode/base_dir_test.go @@ -64,13 +64,13 @@ func (t *BaseDirTest) SetUp(ti *TestInfo) { 1, // Append threshold ChunkTransferTimeoutSecs, ".gcsfuse_tmp/", - fake.NewFakeBucket(&t.clock, "bucketA", gcs.NonHierarchical), + fake.NewFakeBucket(&t.clock, "bucketA", gcs.BucketType{}), ) t.bm.buckets["bucketB"] = gcsx.NewSyncerBucket( 1, // Append threshold ChunkTransferTimeoutSecs, ".gcsfuse_tmp/", - fake.NewFakeBucket(&t.clock, "bucketB", gcs.NonHierarchical), + fake.NewFakeBucket(&t.clock, "bucketB", gcs.BucketType{}), ) // Create the inode. No implicit dirs by default. diff --git a/internal/fs/inode/core_test.go b/internal/fs/inode/core_test.go index f761c353b1..5ab0a3a63b 100644 --- a/internal/fs/inode/core_test.go +++ b/internal/fs/inode/core_test.go @@ -49,7 +49,7 @@ func init() { RegisterTestSuite(&CoreTest{}) } func (t *CoreTest) SetUp(ti *TestInfo) { t.ctx = ti.Ctx t.bucket = gcsx.NewSyncerBucket( - 1, 10, ".gcsfuse_tmp/", fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical)) + 1, 10, ".gcsfuse_tmp/", fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{})) t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)) } diff --git a/internal/fs/inode/dir.go b/internal/fs/inode/dir.go index 6d4641ee59..fff3ab3b09 100644 --- a/internal/fs/inode/dir.go +++ b/internal/fs/inode/dir.go @@ -1078,7 +1078,7 @@ func (d *dirInode) InvalidateKernelListCache() { } func (d *dirInode) isBucketHierarchical() bool { - if d.isHNSEnabled && d.bucket.BucketType() == gcs.Hierarchical { + if d.isHNSEnabled && d.bucket.BucketType().Hierarchical { return true } return false diff --git a/internal/fs/inode/dir_test.go b/internal/fs/inode/dir_test.go index 1d0f98df45..014bb56d87 100644 --- a/internal/fs/inode/dir_test.go +++ b/internal/fs/inode/dir_test.go @@ -69,7 +69,7 @@ func init() { RegisterTestSuite(&DirTest{}) } func (t *DirTest) SetUp(ti *TestInfo) { t.ctx = ti.Ctx t.clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local)) - bucket := fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical) + bucket := fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{}) t.bucket = gcsx.NewSyncerBucket( 1, // Append threshold ChunkTransferTimeoutSecs, diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 1083caccc8..0ca2f80013 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -94,6 +94,13 @@ type FileInode struct { // Represents if local file has been unlinked. unlinked bool + // Wrapper object for multi range downloader. Needed as we will create the MRD in + // random reader and we can't pass fileInode object to random reader as it + // creates a cyclic dependency. + // Todo: Investigate if cyclic dependency can be removed by removing some unused + // code. + MRDWrapper gcsx.MultiRangeDownloaderWrapper + bwh *bufferedwrites.BufferedWriteHandler config *cfg.Config @@ -151,6 +158,7 @@ func NewFileInode( unlinked: false, config: cfg, globalMaxWriteBlocksSem: globalMaxBlocksSem, + MRDWrapper: gcsx.NewMultiRangeDownloaderWrapper(bucket, &minObj), } f.lc.Init(id) diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 229fb4b97c..28062a92a6 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -58,7 +58,7 @@ func (t *FileStreamingWritesTest) SetupTest() { syncutil.EnableInvariantChecking() t.ctx = context.Background() t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)) - t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical) + t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{}) // Create the inode. t.createInode(fileName, localFile) diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 3314ea2b8f..fad6dd33cd 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -81,7 +81,7 @@ func (t *FileTest) SetupTest() { syncutil.EnableInvariantChecking() t.ctx = context.Background() t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)) - t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical) + t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{}) // Set up the backing object. var err error diff --git a/internal/fs/inode/hns_dir_test.go b/internal/fs/inode/hns_dir_test.go index c613b8df5e..b21eff945f 100644 --- a/internal/fs/inode/hns_dir_test.go +++ b/internal/fs/inode/hns_dir_test.go @@ -37,7 +37,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" ) -type HNSDirTest struct { +type hnsDirTest struct { suite.Suite ctx context.Context bucket gcsx.SyncerBucket @@ -47,11 +47,26 @@ type HNSDirTest struct { fixedTime timeutil.SimulatedClock } -func TestHNSDirSuite(testSuite *testing.T) { suite.Run(testSuite, new(HNSDirTest)) } +type HNSDirTest struct { + hnsDirTest +} -func (t *HNSDirTest) SetupTest() { +type NonHNSDirTest struct { + hnsDirTest +} + +func TestHNSDirSuiteWithHierarchicalBucket(testSuite *testing.T) { + suite.Run(testSuite, &HNSDirTest{}) +} + +func TestHNSDirSuiteWithNonHierarchicalBucket(testSuite *testing.T) { + suite.Run(testSuite, &NonHNSDirTest{}) +} + +func (t *hnsDirTest) setupTestSuite(hierarchical bool) { t.ctx = context.Background() t.mockBucket = new(storagemock.TestifyMockBucket) + t.mockBucket.On("BucketType").Return(gcs.BucketType{Hierarchical: hierarchical}) t.bucket = gcsx.NewSyncerBucket( 1, ChunkTransferTimeoutSecs, @@ -60,11 +75,19 @@ func (t *HNSDirTest) SetupTest() { t.resetDirInode(false, false, true) } -func (t *HNSDirTest) resetDirInode(implicitDirs, enableNonexistentTypeCache, enableManagedFoldersListing bool) { +func (t *HNSDirTest) SetupTest() { + t.setupTestSuite(true) +} + +func (t *NonHNSDirTest) SetupTest() { + t.setupTestSuite(false) +} + +func (t *hnsDirTest) resetDirInode(implicitDirs, enableNonexistentTypeCache, enableManagedFoldersListing bool) { t.resetDirInodeWithTypeCacheConfigs(implicitDirs, enableNonexistentTypeCache, enableManagedFoldersListing, 4, typeCacheTTL) } -func (t *HNSDirTest) resetDirInodeWithTypeCacheConfigs(implicitDirs, enableNonexistentTypeCache, enableManagedFoldersListing bool, typeCacheMaxSizeMB int64, typeCacheTTL time.Duration) { +func (t *hnsDirTest) resetDirInodeWithTypeCacheConfigs(implicitDirs, enableNonexistentTypeCache, enableManagedFoldersListing bool, typeCacheMaxSizeMB int64, typeCacheTTL time.Duration) { t.fixedTime.SetTime(time.Date(2024, 7, 22, 2, 15, 0, 0, time.Local)) t.in = NewDirInode( @@ -95,7 +118,7 @@ func (t *HNSDirTest) resetDirInodeWithTypeCacheConfigs(implicitDirs, enableNonex t.in.Lock() } -func (t *HNSDirTest) createDirInode(dirInodeName string) DirInode { +func (t *hnsDirTest) createDirInode(dirInodeName string) DirInode { return NewDirInode( 5, NewDirName(NewRootName(""), dirInodeName), @@ -120,6 +143,10 @@ func (t *HNSDirTest) TearDownTest() { t.in.Unlock() } +func (t *NonHNSDirTest) TearDownTest() { + t.in.Unlock() +} + func (t *HNSDirTest) TestShouldFindExplicitHNSFolder() { const name = "qux" dirName := path.Join(dirInodeName, name) + "/" @@ -162,7 +189,6 @@ func (t *HNSDirTest) TestLookUpChildWithConflictMarkerName() { object := gcs.MinObject{Name: dirName} t.mockBucket.On("GetFolder", mock.Anything, dirName).Return(folder, nil) t.mockBucket.On("StatObject", mock.Anything, &statObjectRequest).Return(&object, &gcs.ExtendedObjectAttributes{}, nil) - t.mockBucket.On("BucketType").Return(gcs.Hierarchical) c, err := t.in.LookUpChild(t.ctx, name+"\n") @@ -179,7 +205,6 @@ func (t *HNSDirTest) TestLookUpChildShouldCheckOnlyForExplicitHNSDirectory() { Name: dirName, } t.mockBucket.On("GetFolder", mock.Anything, mock.Anything).Return(folder, nil) - t.mockBucket.On("BucketType").Return(gcs.Hierarchical) t.typeCache.Insert(t.fixedTime.Now().Add(time.Minute), name, metadata.ExplicitDirType) // Look up with the proper name. @@ -202,7 +227,6 @@ func (t *HNSDirTest) TestLookUpChildShouldCheckForHNSDirectoryWhenTypeNotPresent t.mockBucket.On("GetFolder", mock.Anything, mock.Anything).Return(folder, nil) notFoundErr := &gcs.NotFoundError{Err: errors.New("storage: object doesn't exist")} t.mockBucket.On("StatObject", mock.Anything, mock.Anything).Return(nil, nil, notFoundErr) - t.mockBucket.On("BucketType").Return(gcs.Hierarchical) assert.Equal(t.T(), metadata.UnknownType, t.typeCache.Get(t.fixedTime.Now(), name)) // Look up with the proper name. result, err := t.in.LookUpChild(t.ctx, name) @@ -258,7 +282,6 @@ func (t *HNSDirTest) TestLookUpChildShouldCheckForHNSDirectoryWhenTypeIsSymlinkT CacheControl: "some-value", } t.mockBucket.On("StatObject", mock.Anything, mock.Anything).Return(minObject, attrs, nil) - t.mockBucket.On("BucketType").Return(gcs.Hierarchical) t.typeCache.Insert(t.fixedTime.Now().Add(time.Minute), name, metadata.SymlinkType) // Look up with the proper name. result, err := t.in.LookUpChild(t.ctx, name) @@ -373,7 +396,7 @@ func (t *HNSDirTest) TestRenameFileWithNonExistentSourceFile() { assert.Nil(t.T(), f) } -func (t *HNSDirTest) TestDeleteChildDir_WhenImplicitDirFlagTrueOnNonHNSBucket() { +func (t *NonHNSDirTest) TestDeleteChildDir_WhenImplicitDirFlagTrueOnNonHNSBucket() { const folderName = "folder" dirName := path.Join(dirInodeName, folderName) + "/" dirIn := t.createDirInode(dirName) @@ -385,14 +408,13 @@ func (t *HNSDirTest) TestDeleteChildDir_WhenImplicitDirFlagTrueOnNonHNSBucket() assert.NoError(t.T(), err) // Ensure no error occurred } -func (t *HNSDirTest) TestDeleteChildDir_WhenImplicitDirFlagFalseAndNonHNSBucket_DeleteObjectGiveSuccess() { +func (t *NonHNSDirTest) TestDeleteChildDir_WhenImplicitDirFlagFalseAndNonHNSBucket_DeleteObjectGiveSuccess() { const name = "dir" dirName := path.Join(dirInodeName, name) + "/" deleteObjectReq := gcs.DeleteObjectRequest{ Name: dirName, Generation: 0, } - t.mockBucket.On("BucketType").Return(gcs.NonHierarchical) t.mockBucket.On("DeleteObject", t.ctx, &deleteObjectReq).Return(nil) dirIn := t.createDirInode(dirName) @@ -403,15 +425,13 @@ func (t *HNSDirTest) TestDeleteChildDir_WhenImplicitDirFlagFalseAndNonHNSBucket_ assert.Equal(t.T(), metadata.Type(0), t.typeCache.Get(t.fixedTime.Now(), dirName)) assert.False(t.T(), dirIn.IsUnlinked()) } - -func (t *HNSDirTest) TestDeleteChildDir_WithImplicitDirFlagFalseAndNonHNSBucket_DeleteObjectThrowAnError() { +func (t *NonHNSDirTest) TestDeleteChildDir_WithImplicitDirFlagFalseAndNonHNSBucket_DeleteObjectThrowAnError() { const name = "folder" dirName := path.Join(dirInodeName, name) + "/" deleteObjectReq := gcs.DeleteObjectRequest{ Name: dirName, Generation: 0, } - t.mockBucket.On("BucketType").Return(gcs.NonHierarchical) t.mockBucket.On("DeleteObject", t.ctx, &deleteObjectReq).Return(fmt.Errorf("mock error")) dirIn := t.createDirInode(dirName) @@ -430,7 +450,6 @@ func (t *HNSDirTest) TestDeleteChildDir_WithImplicitDirFlagFalseAndBucketTypeIsH Name: dirName, Generation: 0, } - t.mockBucket.On("BucketType").Return(gcs.Hierarchical) t.mockBucket.On("DeleteObject", t.ctx, &deleteObjectReq).Return(nil) t.mockBucket.On("DeleteFolder", t.ctx, dirName).Return(fmt.Errorf("mock error")) dirIn := t.createDirInode(dirName) @@ -450,7 +469,6 @@ func (t *HNSDirTest) TestDeleteChildDir_WithImplicitDirFlagFalseAndBucketTypeIsH Name: dirName, Generation: 0, } - t.mockBucket.On("BucketType").Return(gcs.Hierarchical) t.mockBucket.On("DeleteObject", t.ctx, &deleteObjectReq).Return(fmt.Errorf("mock error")) t.mockBucket.On("DeleteFolder", t.ctx, dirName).Return(nil) dirIn := t.createDirInode(dirName) @@ -471,7 +489,6 @@ func (t *HNSDirTest) TestDeleteChildDir_WithImplicitDirFlagFalseAndBucketTypeIsH Name: dirName, Generation: 0, } - t.mockBucket.On("BucketType").Return(gcs.Hierarchical) t.mockBucket.On("DeleteObject", t.ctx, &deleteObjectReq).Return(fmt.Errorf("mock error")) t.mockBucket.On("DeleteFolder", t.ctx, dirName).Return(fmt.Errorf("mock delete folder error")) dirIn := t.createDirInode(dirName) @@ -489,7 +506,6 @@ func (t *HNSDirTest) TestDeleteChildDir_WithImplicitDirFlagFalseAndBucketTypeIsH func (t *HNSDirTest) TestCreateChildDirWhenBucketTypeIsHNSWithFailure() { const name = "folder" dirName := path.Join(dirInodeName, name) + "/" - t.mockBucket.On("BucketType").Return(gcs.Hierarchical) t.mockBucket.On("CreateFolder", t.ctx, dirName).Return(nil, fmt.Errorf("mock error")) result, err := t.in.CreateChildDir(t.ctx, name) @@ -504,7 +520,6 @@ func (t *HNSDirTest) TestCreateChildDirWhenBucketTypeIsHNSWithSuccess() { const name = "folder" dirName := path.Join(dirInodeName, name) + "/" folder := gcs.Folder{Name: dirName} - t.mockBucket.On("BucketType").Return(gcs.Hierarchical) t.mockBucket.On("CreateFolder", t.ctx, dirName).Return(&folder, nil) result, err := t.in.CreateChildDir(t.ctx, name) @@ -517,12 +532,11 @@ func (t *HNSDirTest) TestCreateChildDirWhenBucketTypeIsHNSWithSuccess() { assert.Equal(t.T(), metadata.ExplicitDirType, t.typeCache.Get(t.fixedTime.Now(), name)) } -func (t *HNSDirTest) TestCreateChildDirWhenBucketTypeIsNonHNSWithFailure() { +func (t *NonHNSDirTest) TestCreateChildDirWhenBucketTypeIsNonHNSWithFailure() { const name = "folder" var preCond int64 dirName := path.Join(dirInodeName, name) + "/" createObjectReq := gcs.CreateObjectRequest{Name: dirName, Contents: strings.NewReader(""), GenerationPrecondition: &preCond} - t.mockBucket.On("BucketType").Return(gcs.NonHierarchical) t.mockBucket.On("CreateObject", t.ctx, &createObjectReq).Return(nil, fmt.Errorf("mock error")) result, err := t.in.CreateChildDir(t.ctx, name) @@ -533,13 +547,12 @@ func (t *HNSDirTest) TestCreateChildDirWhenBucketTypeIsNonHNSWithFailure() { assert.Equal(t.T(), metadata.Type(0), t.typeCache.Get(t.fixedTime.Now(), dirName)) } -func (t *HNSDirTest) TestCreateChildDirWhenBucketTypeIsNonHNSWithSuccess() { +func (t *NonHNSDirTest) TestCreateChildDirWhenBucketTypeIsNonHNSWithSuccess() { const name = "folder" dirName := path.Join(dirInodeName, name) + "/" var preCond int64 createObjectReq := gcs.CreateObjectRequest{Name: dirName, Contents: strings.NewReader(""), GenerationPrecondition: &preCond} object := gcs.Object{Name: dirName} - t.mockBucket.On("BucketType").Return(gcs.NonHierarchical) t.mockBucket.On("CreateObject", t.ctx, &createObjectReq).Return(&object, nil) result, err := t.in.CreateChildDir(t.ctx, name) @@ -582,7 +595,6 @@ func (t *HNSDirTest) TestReadEntriesInHierarchicalBucket() { MaxResults: 5000, ProjectionVal: gcs.NoAcl, } - t.mockBucket.On("BucketType").Return(gcs.Hierarchical) t.mockBucket.On("ListObjects", t.ctx, &listObjectReq).Return(&listing, nil) entries, _, err := t.in.ReadEntries(t.ctx, tok) diff --git a/internal/gcsx/bucket_manager.go b/internal/gcsx/bucket_manager.go index ba2171d392..9e36d98d03 100644 --- a/internal/gcsx/bucket_manager.go +++ b/internal/gcsx/bucket_manager.go @@ -167,7 +167,11 @@ func (bm *bucketManager) SetUpBucket( if name == canned.FakeBucketName { b = canned.MakeFakeBucket(ctx) } else { - b = bm.storageHandle.BucketHandle(ctx, name, bm.config.BillingProject) + b, err = bm.storageHandle.BucketHandle(ctx, name, bm.config.BillingProject) + if err != nil { + err = fmt.Errorf("BucketHandle: %w", err) + return + } } // Enable monitoring. diff --git a/internal/gcsx/bucket_manager_test.go b/internal/gcsx/bucket_manager_test.go index 066b171d8e..5a0241342f 100644 --- a/internal/gcsx/bucket_manager_test.go +++ b/internal/gcsx/bucket_manager_test.go @@ -19,10 +19,13 @@ import ( "testing" "time" + "cloud.google.com/go/storage/control/apiv2/controlpb" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" . "github.com/jacobsa/ogletest" + "github.com/stretchr/testify/mock" ) func TestBucketManager(t *testing.T) { RunTests(t) } @@ -38,6 +41,7 @@ type BucketManagerTest struct { bucket gcs.Bucket storageHandle storage.StorageHandle fakeStorage storage.FakeStorage + mockClient *storage.MockStorageControlClient } var _ SetUpInterface = &BucketManagerTest{} @@ -46,12 +50,20 @@ var _ TearDownInterface = &BucketManagerTest{} func init() { RegisterTestSuite(&BucketManagerTest{}) } func (t *BucketManagerTest) SetUp(_ *TestInfo) { - t.fakeStorage = storage.NewFakeStorage() + var err error + t.mockClient = new(storage.MockStorageControlClient) + t.fakeStorage = storage.NewFakeStorageWithMockClient(t.mockClient, cfg.HTTP2) t.storageHandle = t.fakeStorage.CreateStorageHandle() + t.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). + Return(&controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + LocationType: "zone", + }, nil) ctx := context.Background() - t.bucket = t.storageHandle.BucketHandle(ctx, TestBucketName, "") + t.bucket, err = t.storageHandle.BucketHandle(ctx, TestBucketName, "") AssertNe(nil, t.bucket) + AssertEq(nil, err) } func (t *BucketManagerTest) TearDown() { diff --git a/internal/gcsx/append_object_creator.go b/internal/gcsx/compose_object_creator.go similarity index 94% rename from internal/gcsx/append_object_creator.go rename to internal/gcsx/compose_object_creator.go index ad2782d296..bbc6758f6c 100644 --- a/internal/gcsx/append_object_creator.go +++ b/internal/gcsx/compose_object_creator.go @@ -34,10 +34,10 @@ import ( // // Create guarantees to return *gcs.PreconditionError when the source object // has been clobbered. -func newAppendObjectCreator( +func newComposeObjectCreator( prefix string, bucket gcs.Bucket) (oc objectCreator) { - oc = &appendObjectCreator{ + oc = &composeObjectCreator{ prefix: prefix, bucket: bucket, } @@ -49,12 +49,12 @@ func newAppendObjectCreator( // Implementation //////////////////////////////////////////////////////////////////////// -type appendObjectCreator struct { +type composeObjectCreator struct { prefix string bucket gcs.Bucket } -func (oc *appendObjectCreator) chooseName() (name string, err error) { +func (oc *composeObjectCreator) chooseName() (name string, err error) { // Generate a good 64-bit random number. var buf [8]byte _, err = io.ReadFull(rand.Reader, buf[:]) @@ -79,9 +79,9 @@ func (oc *appendObjectCreator) chooseName() (name string, err error) { } // ObjectName param is present here for consistency between fullObjectCreator -// and appendObjectCreator. ObjectName is not used in append flow since +// and composeObjectCreator. ObjectName is not used in append flow since // srcObject.Name gives the objectName. -func (oc *appendObjectCreator) Create( +func (oc *composeObjectCreator) Create( ctx context.Context, objectName string, srcObject *gcs.Object, diff --git a/internal/gcsx/append_object_creator_test.go b/internal/gcsx/compose_object_creator_test.go similarity index 89% rename from internal/gcsx/append_object_creator_test.go rename to internal/gcsx/compose_object_creator_test.go index 0e60868ae0..1200c52bb6 100644 --- a/internal/gcsx/append_object_creator_test.go +++ b/internal/gcsx/compose_object_creator_test.go @@ -30,7 +30,7 @@ import ( "golang.org/x/net/context" ) -func TestAppendObjectCreator(t *testing.T) { RunTests(t) } +func TestComposeObjectCreator(t *testing.T) { RunTests(t) } //////////////////////////////////////////////////////////////////////// // Helpers @@ -63,7 +63,7 @@ func deleteReqName(expected string) (m Matcher) { const prefix = ".gcsfuse_tmp/" -type AppendObjectCreatorTest struct { +type ComposeObjectCreatorTest struct { ctx context.Context bucket storage.MockBucket creator objectCreator @@ -73,21 +73,21 @@ type AppendObjectCreatorTest struct { mtime time.Time } -var _ SetUpInterface = &AppendObjectCreatorTest{} +var _ SetUpInterface = &ComposeObjectCreatorTest{} -func init() { RegisterTestSuite(&AppendObjectCreatorTest{}) } +func init() { RegisterTestSuite(&ComposeObjectCreatorTest{}) } -func (t *AppendObjectCreatorTest) SetUp(ti *TestInfo) { +func (t *ComposeObjectCreatorTest) SetUp(ti *TestInfo) { t.ctx = ti.Ctx // Create the bucket. t.bucket = storage.NewMockBucket(ti.MockController, "bucket") // Create the creator. - t.creator = newAppendObjectCreator(prefix, t.bucket) + t.creator = newComposeObjectCreator(prefix, t.bucket) } -func (t *AppendObjectCreatorTest) call() (o *gcs.Object, err error) { +func (t *ComposeObjectCreatorTest) call() (o *gcs.Object, err error) { o, err = t.creator.Create( t.ctx, t.srcObject.Name, @@ -103,7 +103,7 @@ func (t *AppendObjectCreatorTest) call() (o *gcs.Object, err error) { // Tests //////////////////////////////////////////////////////////////////////// -func (t *AppendObjectCreatorTest) CallsCreateObject() { +func (t *ComposeObjectCreatorTest) CallsCreateObject() { t.srcContents = "taco" // CreateObject @@ -124,7 +124,7 @@ func (t *AppendObjectCreatorTest) CallsCreateObject() { ExpectEq(t.srcContents, string(b)) } -func (t *AppendObjectCreatorTest) CreateObjectFails() { +func (t *ComposeObjectCreatorTest) CreateObjectFails() { var err error // CreateObject @@ -138,7 +138,7 @@ func (t *AppendObjectCreatorTest) CreateObjectFails() { ExpectThat(err, Error(HasSubstr("taco"))) } -func (t *AppendObjectCreatorTest) CreateObjectReturnsPreconditionError() { +func (t *ComposeObjectCreatorTest) CreateObjectReturnsPreconditionError() { var err error // CreateObject @@ -154,7 +154,7 @@ func (t *AppendObjectCreatorTest) CreateObjectReturnsPreconditionError() { ExpectThat(err, Error(HasSubstr("taco"))) } -func (t *AppendObjectCreatorTest) CallsComposeObjects() { +func (t *ComposeObjectCreatorTest) CallsComposeObjects() { t.srcObject.Name = "foo" t.srcObject.Generation = 17 t.srcObject.MetaGeneration = 23 @@ -206,7 +206,7 @@ func (t *AppendObjectCreatorTest) CallsComposeObjects() { ExpectEq(tmpObject.Generation, src.Generation) } -func (t *AppendObjectCreatorTest) CallsComposeObjectsWithObjectProperties() { +func (t *ComposeObjectCreatorTest) CallsComposeObjectsWithObjectProperties() { t.srcObject.Name = "foo" t.srcObject.Generation = 17 t.srcObject.MetaGeneration = 23 @@ -274,7 +274,7 @@ func (t *AppendObjectCreatorTest) CallsComposeObjectsWithObjectProperties() { ExpectEq(tmpObject.Generation, src.Generation) } -func (t *AppendObjectCreatorTest) ComposeObjectsFails() { +func (t *ComposeObjectCreatorTest) ComposeObjectsFails() { // CreateObject tmpObject := &gcs.Object{ Name: "bar", @@ -298,7 +298,7 @@ func (t *AppendObjectCreatorTest) ComposeObjectsFails() { ExpectThat(err, Error(HasSubstr("taco"))) } -func (t *AppendObjectCreatorTest) ComposeObjectsReturnsPreconditionError() { +func (t *ComposeObjectCreatorTest) ComposeObjectsReturnsPreconditionError() { // CreateObject tmpObject := &gcs.Object{ Name: "bar", @@ -324,7 +324,7 @@ func (t *AppendObjectCreatorTest) ComposeObjectsReturnsPreconditionError() { ExpectThat(err, Error(HasSubstr("taco"))) } -func (t *AppendObjectCreatorTest) ComposeObjectsReturnsNotFoundError() { +func (t *ComposeObjectCreatorTest) ComposeObjectsReturnsNotFoundError() { // CreateObject tmpObject := &gcs.Object{ Name: "bar", @@ -350,7 +350,7 @@ func (t *AppendObjectCreatorTest) ComposeObjectsReturnsNotFoundError() { ExpectThat(err, Error(HasSubstr("taco"))) } -func (t *AppendObjectCreatorTest) CallsDeleteObject() { +func (t *ComposeObjectCreatorTest) CallsDeleteObject() { // CreateObject tmpObject := &gcs.Object{ Name: "bar", @@ -372,7 +372,7 @@ func (t *AppendObjectCreatorTest) CallsDeleteObject() { t.call() } -func (t *AppendObjectCreatorTest) DeleteObjectFails() { +func (t *ComposeObjectCreatorTest) DeleteObjectFails() { // CreateObject tmpObject := &gcs.Object{ Name: "bar", @@ -397,7 +397,7 @@ func (t *AppendObjectCreatorTest) DeleteObjectFails() { ExpectThat(err, Error(HasSubstr("taco"))) } -func (t *AppendObjectCreatorTest) DeleteObjectSucceeds() { +func (t *ComposeObjectCreatorTest) DeleteObjectSucceeds() { // CreateObject tmpObject := &gcs.Object{ Name: "bar", diff --git a/internal/gcsx/content_type_bucket_test.go b/internal/gcsx/content_type_bucket_test.go index c8a845beb5..7ac918fe23 100644 --- a/internal/gcsx/content_type_bucket_test.go +++ b/internal/gcsx/content_type_bucket_test.go @@ -83,7 +83,7 @@ func TestContentTypeBucket_CreateObject(t *testing.T) { for i, tc := range contentTypeBucketTestCases { // Set up a bucket. bucket := gcsx.NewContentTypeBucket( - fake.NewFakeBucket(timeutil.RealClock(), "", gcs.NonHierarchical)) + fake.NewFakeBucket(timeutil.RealClock(), "", gcs.BucketType{})) // Create the object. req := &gcs.CreateObjectRequest{ @@ -108,7 +108,7 @@ func TestContentTypeBucket_CreateObjectChunkWriter(t *testing.T) { for i, tc := range contentTypeBucketTestCases { // Set up a bucket. bucket := gcsx.NewContentTypeBucket( - fake.NewFakeBucket(timeutil.RealClock(), "", gcs.NonHierarchical)) + fake.NewFakeBucket(timeutil.RealClock(), "", gcs.BucketType{})) // Create the object. req := &gcs.CreateObjectRequest{ @@ -136,7 +136,7 @@ func TestContentTypeBucket_ComposeObjects(t *testing.T) { for i, tc := range contentTypeBucketTestCases { // Set up a bucket. bucket := gcsx.NewContentTypeBucket( - fake.NewFakeBucket(timeutil.RealClock(), "", gcs.NonHierarchical)) + fake.NewFakeBucket(timeutil.RealClock(), "", gcs.BucketType{})) // Create a source object. const srcName = "some_src" diff --git a/internal/gcsx/integration_test.go b/internal/gcsx/integration_test.go index c0a19a2ba9..0df91e1937 100644 --- a/internal/gcsx/integration_test.go +++ b/internal/gcsx/integration_test.go @@ -79,7 +79,7 @@ func init() { RegisterTestSuite(&IntegrationTest{}) } func (t *IntegrationTest) SetUp(ti *TestInfo) { t.ctx = ti.Ctx - t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical) + t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{}) // Set up a fixed, non-zero time. t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)) diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go new file mode 100644 index 0000000000..13187d27e3 --- /dev/null +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -0,0 +1,215 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "bytes" + "fmt" + "io" + "sync" + "time" + + "github.com/google/uuid" + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "golang.org/x/net/context" +) + +// Timeout value which determines when the MultiRangeDownloader will be closed after +// it's refcount reaches 0. +const multiRangeDownloaderTimeout = 60 * time.Second + +func NewMultiRangeDownloaderWrapper(bucket gcs.Bucket, object *gcs.MinObject) MultiRangeDownloaderWrapper { + return NewMultiRangeDownloaderWrapperWithClock(bucket, object, clock.RealClock{}) +} + +func NewMultiRangeDownloaderWrapperWithClock(bucket gcs.Bucket, object *gcs.MinObject, clock clock.Clock) MultiRangeDownloaderWrapper { + return MultiRangeDownloaderWrapper{ + clock: clock, + bucket: bucket, + object: object, + } +} + +type readResult struct { + bytesRead int + err error +} + +type MultiRangeDownloaderWrapper struct { + // Holds the object implementing MultiRangeDownloader interface. + Wrapped gcs.MultiRangeDownloader + + // Bucket and object details for MultiRangeDownloader. + object *gcs.MinObject + bucket gcs.Bucket + + // Refcount is used to determine when to close the MultiRangeDownloader. + refCount int + // Mutex is used to synchronize access over refCount. + mu sync.Mutex + // Holds the cancel function, which can be called to cancel the cleanup function. + cancelCleanup context.CancelFunc + // Used for waiting for timeout (helps us in mocking the functionality). + clock clock.Clock +} + +// Returns current refcount. +func (mrdWrapper *MultiRangeDownloaderWrapper) GetRefCount() int { + mrdWrapper.mu.Lock() + defer mrdWrapper.mu.Unlock() + return mrdWrapper.refCount +} + +// Increment the refcount and cancel any running cleanup function. +// This method should be called exactly once per user of this wrapper. +// It has to be called before using the MultiRangeDownloader. +func (mrdWrapper *MultiRangeDownloaderWrapper) IncrementRefCount() { + mrdWrapper.mu.Lock() + defer mrdWrapper.mu.Unlock() + + mrdWrapper.refCount++ + if mrdWrapper.cancelCleanup != nil { + mrdWrapper.cancelCleanup() + mrdWrapper.cancelCleanup = nil + } +} + +// Decrement the refcount. In case refcount reaches 0, cleanup the MRD. +// Returns error on invalid usage. +// This method should be called exactly once per user of this wrapper +// when MultiRangeDownloader is no longer needed & can be cleaned up. +func (mrdWrapper *MultiRangeDownloaderWrapper) DecrementRefCount() (err error) { + mrdWrapper.mu.Lock() + defer mrdWrapper.mu.Unlock() + + if mrdWrapper.refCount <= 0 { + err = fmt.Errorf("MultiRangeDownloaderWrapper DecrementRefCount: Refcount cannot be negative") + return + } + + mrdWrapper.refCount-- + if mrdWrapper.refCount == 0 { + mrdWrapper.cleanupMultiRangeDownloader() + } + return +} + +// Spawns a cancellable go routine to close the MRD after the timeout. +// Always call after taking MultiRangeDownloaderWrapper's mutex lock. +func (mrdWrapper *MultiRangeDownloaderWrapper) cleanupMultiRangeDownloader() { + closeMRD := func(ctx context.Context) { + select { + case <-mrdWrapper.clock.After(multiRangeDownloaderTimeout): + mrdWrapper.mu.Lock() + defer mrdWrapper.mu.Unlock() + + if mrdWrapper.refCount == 0 && mrdWrapper.Wrapped != nil { + mrdWrapper.Wrapped.Close() + mrdWrapper.Wrapped = nil + mrdWrapper.cancelCleanup = nil + } + case <-ctx.Done(): + return + } + } + + ctx, cancel := context.WithCancel(context.Background()) + mrdWrapper.cancelCleanup = cancel + go closeMRD(ctx) +} + +// Ensures that MultiRangeDownloader exists, creating it if it does not exist. +func (mrdWrapper *MultiRangeDownloaderWrapper) ensureMultiRangeDownloader() (err error) { + if mrdWrapper.Wrapped == nil { + mrdWrapper.Wrapped, err = mrdWrapper.bucket.NewMultiRangeDownloader(context.Background(), &gcs.MultiRangeDownloaderRequest{ + Name: mrdWrapper.object.Name, + Generation: mrdWrapper.object.Generation, + ReadCompressed: mrdWrapper.object.HasContentEncodingGzip(), + }) + } + return +} + +// Reads the data using MultiRangeDownloader. +func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []byte, + startOffset int64, endOffset int64, timeout time.Duration, metricHandle common.MetricHandle) (bytesRead int, err error) { + // Bidi Api with 0 as read_limit means no limit whereas we do not want to read anything with empty buffer. + // Hence, handling it separately. + if len(buf) == 0 { + return 0, nil + } + + err = mrdWrapper.ensureMultiRangeDownloader() + if err != nil { + err = fmt.Errorf("MultiRangeDownloaderWrapper::Read: Error in creating MultiRangeDownloader: %v", err) + return + } + + // We will only read what is requested by the client. Hence, capping end to the requested value. + if endOffset > startOffset+int64(len(buf)) { + endOffset = startOffset + int64(len(buf)) + } + + buffer := bytes.NewBuffer(buf) + buffer.Reset() + done := make(chan readResult, 1) + + mu := sync.Mutex{} + defer func() { + mu.Lock() + close(done) + done = nil + mu.Unlock() + }() + + requestId := uuid.New() + logger.Tracef("%.13v <- MultiRangeDownloader::Add (%s, [%d, %d))", requestId, mrdWrapper.object.Name, startOffset, endOffset) + start := time.Now() + mrdWrapper.Wrapped.Add(buffer, startOffset, endOffset-startOffset, func(offsetAddCallback int64, limit int64, e error) { + defer func() { + mu.Lock() + if done != nil { + done <- readResult{bytesRead: int(limit), err: e} + } + mu.Unlock() + }() + + if e != nil && e != io.EOF { + e = fmt.Errorf("MultiRangeDownloaderWrapper::Read: Error in Add Call: %w", e) + } + }) + + select { + case <-time.After(timeout): + err = fmt.Errorf("MultiRangeDownloaderWrapper::Read: Timeout") + case <-ctx.Done(): + err = fmt.Errorf("MultiRangeDownloaderWrapper::Read: Context Cancelled: %w", ctx.Err()) + case res := <-done: + bytesRead = res.bytesRead + err = res.err + } + duration := time.Since(start) + monitor.CaptureMultiRangeDownloaderMetrics(ctx, metricHandle, "MultiRangeDownloader::Add", start) + errDesc := "OK" + if err != nil { + errDesc = err.Error() + } + logger.Tracef("%.13v -> MultiRangeDownloader::Add (%s, [%d, %d)) (%v): %v", requestId, mrdWrapper.object.Name, startOffset, endOffset, duration, errDesc) + return +} diff --git a/internal/gcsx/multi_range_downloader_wrapper_test.go b/internal/gcsx/multi_range_downloader_wrapper_test.go new file mode 100644 index 0000000000..7102008a0f --- /dev/null +++ b/internal/gcsx/multi_range_downloader_wrapper_test.go @@ -0,0 +1,161 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "context" + "sync" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" +) + +type mrdWrapperTest struct { + suite.Suite + object *gcs.MinObject + objectData []byte + mockBucket *storage.TestifyMockBucket + mrdWrapper MultiRangeDownloaderWrapper + mrdTimeout time.Duration +} + +func TestMRDWrapperTestSuite(t *testing.T) { + suite.Run(t, new(mrdWrapperTest)) +} + +func (t *mrdWrapperTest) SetupTest() { + t.object = &gcs.MinObject{ + Name: "foo", + Size: 100, + Generation: 1234, + } + t.objectData = testutil.GenerateRandomBytes(int(t.object.Size)) + // Create the bucket. + t.mockBucket = new(storage.TestifyMockBucket) + t.mrdTimeout = time.Millisecond + t.mrdWrapper = NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{WaitTime: t.mrdTimeout}) + t.mrdWrapper.Wrapped = fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond) + t.mrdWrapper.refCount = 0 +} + +func (t *mrdWrapperTest) Test_IncrementRefCount_ParallelUpdates() { + const finalRefCount int = 1 + wg := sync.WaitGroup{} + for i := 0; i < finalRefCount; i++ { + wg.Add(1) + go func() { + t.mrdWrapper.IncrementRefCount() + wg.Done() + }() + } + wg.Wait() + + assert.Equal(t.T(), finalRefCount, t.mrdWrapper.refCount) +} + +func (t *mrdWrapperTest) Test_IncrementRefCount_CancelCleanup() { + const finalRefCount int = 1 + t.mrdWrapper.IncrementRefCount() + err := t.mrdWrapper.DecrementRefCount() + + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.mrdWrapper.cancelCleanup) + assert.NotNil(t.T(), t.mrdWrapper.Wrapped) + + t.mrdWrapper.IncrementRefCount() + + assert.Equal(t.T(), finalRefCount, t.mrdWrapper.refCount) + assert.Nil(t.T(), t.mrdWrapper.cancelCleanup) + assert.NotNil(t.T(), t.mrdWrapper.Wrapped) +} + +func (t *mrdWrapperTest) Test_DecrementRefCount_ParallelUpdates() { + const finalRefCount int = 0 + maxRefCount := 10 + wg := sync.WaitGroup{} + // Incrementing refcount in parallel. + for i := 0; i < maxRefCount; i++ { + wg.Add(1) + go func() { + t.mrdWrapper.IncrementRefCount() + wg.Done() + }() + } + wg.Wait() + // Decrementing refcount in parallel. + for i := 0; i < maxRefCount; i++ { + wg.Add(1) + go func() { + err := t.mrdWrapper.DecrementRefCount() + assert.Nil(t.T(), err) + wg.Done() + }() + } + wg.Wait() + + assert.Equal(t.T(), finalRefCount, t.mrdWrapper.GetRefCount()) + assert.NotNil(t.T(), t.mrdWrapper.Wrapped) + assert.NotNil(t.T(), t.mrdWrapper.cancelCleanup) + // Waiting for the cleanup to be done. + time.Sleep(t.mrdTimeout + time.Millisecond) + assert.Nil(t.T(), t.mrdWrapper.Wrapped) +} + +func (t *mrdWrapperTest) Test_DecrementRefCount_InvalidUse() { + errMsg := "MultiRangeDownloaderWrapper DecrementRefCount: Refcount cannot be negative" + assert.ErrorContains(t.T(), t.mrdWrapper.DecrementRefCount(), errMsg) +} + +func (t *mrdWrapperTest) Test_Read() { + testCases := []struct { + name string + start int + end int + }{ + { + name: "ReadFull", + start: 0, + end: int(t.object.Size), + }, + { + name: "ReadChunk", + start: 10, + end: 10 + int(t.object.Size)/2, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + buf := make([]byte, tc.end-tc.start) + t.mrdWrapper.Wrapped = nil + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond)) + + bytesRead, err := t.mrdWrapper.Read(context.Background(), buf, int64(tc.start), int64(tc.end), time.Millisecond, common.NewNoopMetrics()) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), tc.end-tc.start, bytesRead) + assert.Equal(t.T(), t.objectData[tc.start:tc.end], buf[:bytesRead]) + }) + } +} diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index 44d2ed4533..0b3c8d6165 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -70,12 +70,19 @@ func (b *prefixBucket) BucketType() gcs.BucketType { func (b *prefixBucket) NewReader( ctx context.Context, req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { + rc, err = b.NewReaderWithReadHandle(ctx, req) + return +} + +func (b *prefixBucket) NewReaderWithReadHandle( + ctx context.Context, + req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { // Modify the request and call through. mReq := new(gcs.ReadObjectRequest) *mReq = *req mReq.Name = b.wrappedName(req.Name) - rc, err = b.wrapped.NewReader(ctx, mReq) + rd, err = b.wrapped.NewReaderWithReadHandle(ctx, mReq) return } @@ -294,3 +301,14 @@ func (b *prefixBucket) RenameFolder(ctx context.Context, folderName string, dest return f, err } + +func (b *prefixBucket) NewMultiRangeDownloader( + ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (mrd gcs.MultiRangeDownloader, err error) { + // Modify the request and call through. + mReq := new(gcs.MultiRangeDownloaderRequest) + *mReq = *req + mReq.Name = b.wrappedName(req.Name) + + mrd, err = b.wrapped.NewMultiRangeDownloader(ctx, mReq) + return +} diff --git a/internal/gcsx/prefix_bucket_test.go b/internal/gcsx/prefix_bucket_test.go index 09dccf8842..cc3d158451 100644 --- a/internal/gcsx/prefix_bucket_test.go +++ b/internal/gcsx/prefix_bucket_test.go @@ -15,6 +15,7 @@ package gcsx_test import ( + "bytes" "errors" "io" "strings" @@ -28,48 +29,47 @@ import ( "golang.org/x/net/context" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - . "github.com/jacobsa/oglematchers" - . "github.com/jacobsa/ogletest" + "github.com/stretchr/testify/suite" + "github.com/jacobsa/timeutil" ) -func TestPrefixBucket(t *testing.T) { RunTests(t) } - //////////////////////////////////////////////////////////////////////// // Boilerplate //////////////////////////////////////////////////////////////////////// type PrefixBucketTest struct { + suite.Suite ctx context.Context prefix string wrapped gcs.Bucket bucket gcs.Bucket } -var _ SetUpInterface = &PrefixBucketTest{} - -func init() { RegisterTestSuite(&PrefixBucketTest{}) } +func TestPrefixBucket(t *testing.T) { + suite.Run(t, new(PrefixBucketTest)) +} -func (t *PrefixBucketTest) SetUp(ti *TestInfo) { +func (t *PrefixBucketTest) SetupTest() { var err error - t.ctx = ti.Ctx + t.ctx = context.Background() t.prefix = "foo_" - t.wrapped = fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.NonHierarchical) + t.wrapped = fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.BucketType{}) t.bucket, err = gcsx.NewPrefixBucket(t.prefix, t.wrapped) - AssertEq(nil, err) + assert.NoError(t.T(), err) } //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// -func (t *PrefixBucketTest) Name() { - ExpectEq(t.wrapped.Name(), t.bucket.Name()) +func (t *PrefixBucketTest) Test_Name() { + assert.Equal(t.T(), t.wrapped.Name(), t.bucket.Name()) } -func (t *PrefixBucketTest) NewReader() { +func (t *PrefixBucketTest) Test_NewReader() { var err error suffix := "taco" name := t.prefix + suffix @@ -77,7 +77,7 @@ func (t *PrefixBucketTest) NewReader() { // Create an object through the back door. _, err = storageutil.CreateObject(t.ctx, t.wrapped, name, []byte(contents)) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // Read it through the prefix bucket. rc, err := t.bucket.NewReader( @@ -86,15 +86,205 @@ func (t *PrefixBucketTest) NewReader() { Name: suffix, }) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) + defer rc.Close() + + actual, err := io.ReadAll(rc) + assert.NoError(t.T(), err) + assert.Equal(t.T(), contents, string(actual)) +} + +func (t *PrefixBucketTest) Test_NewReaderWithReadHandle() { + var err error + suffix := "taco" + name := t.prefix + suffix + contents := "foobar" + // Create an object through the back door. + _, err = storageutil.CreateObject(t.ctx, t.wrapped, name, []byte(contents)) + assert.Equal(t.T(), nil, err) + + // Read it through the prefix bucket with read handle. + rc, err := t.bucket.NewReaderWithReadHandle( + t.ctx, + &gcs.ReadObjectRequest{ + Name: suffix, + ReadHandle: []byte("new-handle"), + }) + + assert.Equal(t.T(), nil, err) defer rc.Close() + actual, err := io.ReadAll(rc) + assert.NoError(t.T(), nil, err) + assert.Equal(t.T(), contents, string(actual)) + assert.Equal(t.T(), string(rc.ReadHandle()), "opaque-handle") +} + +func (t *PrefixBucketTest) Test_NewReaderWithNilReadHandle() { + var err error + suffix := "taco" + name := t.prefix + suffix + contents := "foobar" + // Create an object through the back door. + _, err = storageutil.CreateObject(t.ctx, t.wrapped, name, []byte(contents)) + assert.Equal(t.T(), nil, err) + // Read it through the prefix bucket with out read handle. + rc, err := t.bucket.NewReaderWithReadHandle( + t.ctx, + &gcs.ReadObjectRequest{ + Name: suffix, + }) + + assert.NoError(t.T(), err) + defer rc.Close() actual, err := io.ReadAll(rc) - AssertEq(nil, err) - ExpectEq(contents, string(actual)) + assert.NoError(t.T(), err) + assert.Equal(t.T(), contents, string(actual)) + assert.Equal(t.T(), string(rc.ReadHandle()), "opaque-handle") +} + +func (t *PrefixBucketTest) Test_NewMultiRangeReader_WithFullContentRead() { + var err error + suffix := "taco" + name := t.prefix + suffix + contents := "foobar" + // Create an object through the back door. + _, err = storageutil.CreateObject(t.ctx, t.wrapped, name, []byte(contents)) + assert.Equal(t.T(), nil, err) + + // Read it through the prefix bucket. + mrd, err := t.bucket.NewMultiRangeDownloader( + t.ctx, + &gcs.MultiRangeDownloaderRequest{ + Name: suffix, + }) + + assert.NoError(t.T(), err) + assert.NotNil(t.T(), mrd) + defer func() { + assert.NoError(t.T(), mrd.Close()) + }() + + size := int64(len(contents)) + var outputString string + outputWriter := bytes.NewBufferString(outputString) + mrd.Add(outputWriter, 0, size, func(int64, int64, error) {}) + mrd.Wait() + + assert.Equal(t.T(), contents, outputWriter.String()) +} + +func (t *PrefixBucketTest) Test_NewMultiRangeReader_WithoutWait() { + var err error + suffix := "taco" + name := t.prefix + suffix + contents := "foobar" + // Create an object through the back door. + _, err = storageutil.CreateObject(t.ctx, t.wrapped, name, []byte(contents)) + assert.Equal(t.T(), nil, err) + + // Read it through the prefix bucket with out read handle. + mrd, err := t.bucket.NewMultiRangeDownloader( + t.ctx, + &gcs.MultiRangeDownloaderRequest{ + Name: suffix, + }) + + var outputString string + outputWriter := bytes.NewBufferString(outputString) + + assert.NoError(t.T(), err) + assert.NotNil(t.T(), mrd) + defer func() { + assert.NoError(t.T(), mrd.Close()) + assert.Equal(t.T(), contents, outputWriter.String()) + }() + + size := int64(len(contents)) + mrd.Add(outputWriter, 0, size, func(offset, length int64, err error) {}) +} + +func (t *PrefixBucketTest) Test_NewMultiRangeReader_WithMultipleReads() { + var err error + suffix := "taco" + name := t.prefix + suffix + contents := "foobar" + // Create an object through the back door. + _, err = storageutil.CreateObject(t.ctx, t.wrapped, name, []byte(contents)) + assert.Equal(t.T(), nil, err) + + // Read it through the prefix bucket with out read handle. + mrd, err := t.bucket.NewMultiRangeDownloader( + t.ctx, + &gcs.MultiRangeDownloaderRequest{ + Name: suffix, + }) + + assert.NoError(t.T(), err) + assert.NotNil(t.T(), mrd) + defer func() { + assert.NoError(t.T(), mrd.Close()) + }() + + size := int64(len(contents)) + halfSize := size / 2 + var outputString1 string + outputWriter1 := bytes.NewBufferString(outputString1) + mrd.Add(outputWriter1, 0, halfSize, func(offset, length int64, err error) {}) + + var outputString2 string + outputWriter2 := bytes.NewBufferString(outputString2) + mrd.Add(outputWriter2, halfSize, halfSize, func(offset, length int64, err error) {}) + + mrd.Wait() + + assert.Equal(t.T(), "foo", outputWriter1.String()) + assert.Equal(t.T(), "bar", outputWriter2.String()) +} + +func (t *PrefixBucketTest) Test_NewMultiRangeReader_WithOutOfBoundsReadError() { + var err error + suffix := "taco" + name := t.prefix + suffix + contents := "foobar" + // Create an object through the back door. + _, err = storageutil.CreateObject(t.ctx, t.wrapped, name, []byte(contents)) + assert.Equal(t.T(), nil, err) + + // Read it through the prefix bucket with out read handle. + mrd, err := t.bucket.NewMultiRangeDownloader( + t.ctx, + &gcs.MultiRangeDownloaderRequest{ + Name: suffix, + }) + + assert.NoError(t.T(), err) + assert.NotNil(t.T(), mrd) + defer func() { + assert.Error(t.T(), mrd.Close()) + }() + + size := int64(len(contents)) + var outputString string + outputWriter := bytes.NewBufferString(outputString) + mrd.Add(outputWriter, size+1, 1, func(offset, length int64, err error) {}) +} + +func (t *PrefixBucketTest) Test_NewMultiRangeReader_WithNonexistentObjectError() { + var err error + + // Read it through the prefix bucket with out read handle. + mrd, err := t.bucket.NewMultiRangeDownloader( + t.ctx, + &gcs.MultiRangeDownloaderRequest{ + Name: "taco", + }) + + assert.Error(t.T(), err) + assert.Nil(t.T(), mrd) } -func (t *PrefixBucketTest) CreateObject() { +func (t *PrefixBucketTest) Test_CreateObject() { var err error suffix := "taco" contents := "foobar" @@ -108,14 +298,14 @@ func (t *PrefixBucketTest) CreateObject() { Contents: strings.NewReader(contents), }) - AssertEq(nil, err) - ExpectEq(suffix, o.Name) - ExpectEq("en-GB", o.ContentLanguage) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), suffix, o.Name) + assert.Equal(t.T(), "en-GB", o.ContentLanguage) // Read it through the back door. actual, err := storageutil.ReadObject(t.ctx, t.wrapped, t.prefix+suffix) - AssertEq(nil, err) - ExpectEq(contents, string(actual)) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), contents, string(actual)) } func (t *PrefixBucketTest) CreateObjectChunkWriterAndFinalizeUpload() { @@ -132,21 +322,22 @@ func (t *PrefixBucketTest) CreateObjectChunkWriterAndFinalizeUpload() { Contents: nil, }, 1024, nil) - AssertEq(nil, err) + assert.NoError(t.T(), err) _, err = w.Write(content) - AssertEq(nil, err) + assert.NoError(t.T(), err) o, err := t.bucket.FinalizeUpload(t.ctx, w) - AssertEq(nil, err) - ExpectEq(suffix, o.Name) - ExpectEq("gzip", o.ContentEncoding) + assert.NoError(t.T(), err) + assert.Equal(t.T(), suffix, o.Name) + //assert.Equal(t.T(), "en-GB", o.ContentLanguage) + assert.Equal(t.T(), "gzip", o.ContentEncoding) // Read it through the back door. actual, err := storageutil.ReadObject(t.ctx, t.wrapped, t.prefix+suffix) - AssertEq(nil, err) - ExpectEq(string(content), string(actual)) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), string(content), string(actual)) } -func (t *PrefixBucketTest) CopyObject() { +func (t *PrefixBucketTest) Test_CopyObject() { var err error suffix := "taco" name := t.prefix + suffix @@ -154,7 +345,7 @@ func (t *PrefixBucketTest) CopyObject() { // Create an object through the back door. _, err = storageutil.CreateObject(t.ctx, t.wrapped, name, []byte(contents)) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // Copy it to a new name. newSuffix := "burrito" @@ -165,16 +356,16 @@ func (t *PrefixBucketTest) CopyObject() { DstName: newSuffix, }) - AssertEq(nil, err) - ExpectEq(newSuffix, o.Name) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), newSuffix, o.Name) // Read it through the back door. actual, err := storageutil.ReadObject(t.ctx, t.wrapped, t.prefix+newSuffix) - AssertEq(nil, err) - ExpectEq(contents, string(actual)) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), contents, string(actual)) } -func (t *PrefixBucketTest) ComposeObjects() { +func (t *PrefixBucketTest) Test_ComposeObjects() { var err error suffix0 := "taco" @@ -192,7 +383,7 @@ func (t *PrefixBucketTest) ComposeObjects() { t.prefix + suffix1: []byte(contents1), }) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // Compose them. newSuffix := "enchilada" @@ -206,16 +397,16 @@ func (t *PrefixBucketTest) ComposeObjects() { }, }) - AssertEq(nil, err) - ExpectEq(newSuffix, o.Name) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), newSuffix, o.Name) // Read it through the back door. actual, err := storageutil.ReadObject(t.ctx, t.wrapped, t.prefix+newSuffix) - AssertEq(nil, err) - ExpectEq(contents0+contents1, string(actual)) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), contents0+contents1, string(actual)) } -func (t *PrefixBucketTest) StatObject() { +func (t *PrefixBucketTest) Test_StatObject() { var err error suffix := "taco" name := t.prefix + suffix @@ -223,7 +414,7 @@ func (t *PrefixBucketTest) StatObject() { // Create an object through the back door. _, err = storageutil.CreateObject(t.ctx, t.wrapped, name, []byte(contents)) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // Stat it. m, _, err := t.bucket.StatObject( @@ -232,13 +423,13 @@ func (t *PrefixBucketTest) StatObject() { Name: suffix, }) - AssertEq(nil, err) - AssertNe(nil, m) - ExpectEq(suffix, m.Name) - ExpectEq(len(contents), m.Size) + assert.Equal(t.T(), nil, err) + assert.NotEqual(t.T(), nil, m) + assert.Equal(t.T(), suffix, m.Name) + assert.Equal(t.T(), uint64(len(contents)), m.Size) } -func (t *PrefixBucketTest) ListObjects_NoOptions() { +func (t *PrefixBucketTest) Test_ListObjects_NoOptions() { var err error // Create a few objects. @@ -252,24 +443,24 @@ func (t *PrefixBucketTest) ListObjects_NoOptions() { "some_other": []byte(""), }) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // List. l, err := t.bucket.ListObjects( t.ctx, &gcs.ListObjectsRequest{}) - AssertEq(nil, err) - AssertEq("", l.ContinuationToken) - AssertThat(l.CollapsedRuns, ElementsAre()) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "", l.ContinuationToken) + assert.Empty(t.T(), l.CollapsedRuns) - AssertEq(3, len(l.MinObjects)) - ExpectEq("burrito", l.MinObjects[0].Name) - ExpectEq("enchilada", l.MinObjects[1].Name) - ExpectEq("taco", l.MinObjects[2].Name) + assert.Equal(t.T(), 3, len(l.MinObjects)) + assert.Equal(t.T(), "burrito", l.MinObjects[0].Name) + assert.Equal(t.T(), "enchilada", l.MinObjects[1].Name) + assert.Equal(t.T(), "taco", l.MinObjects[2].Name) } -func (t *PrefixBucketTest) ListObjects_Prefix() { +func (t *PrefixBucketTest) Test_ListObjects_Prefix() { var err error // Create a few objects. @@ -284,7 +475,7 @@ func (t *PrefixBucketTest) ListObjects_Prefix() { "some_other": []byte(""), }) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // List, with a prefix. l, err := t.bucket.ListObjects( @@ -293,16 +484,16 @@ func (t *PrefixBucketTest) ListObjects_Prefix() { Prefix: "burrito", }) - AssertEq(nil, err) - AssertEq("", l.ContinuationToken) - AssertThat(l.CollapsedRuns, ElementsAre()) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "", l.ContinuationToken) + assert.Empty(t.T(), l.CollapsedRuns) - AssertEq(2, len(l.MinObjects)) - ExpectEq("burrito0", l.MinObjects[0].Name) - ExpectEq("burrito1", l.MinObjects[1].Name) + assert.Equal(t.T(), 2, len(l.MinObjects)) + assert.Equal(t.T(), "burrito0", l.MinObjects[0].Name) + assert.Equal(t.T(), "burrito1", l.MinObjects[1].Name) } -func (t *PrefixBucketTest) ListObjects_Delimeter() { +func (t *PrefixBucketTest) Test_ListObjects_Delimeter() { var err error // Create a few objects. @@ -317,27 +508,27 @@ func (t *PrefixBucketTest) ListObjects_Delimeter() { "some_other": []byte(""), }) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // List, with a delimiter. Make things extra interesting by using a delimiter // that is contained within the bucket prefix. - AssertNe(-1, strings.IndexByte(t.prefix, '_')) + assert.NotEqual(t.T(), -1, strings.IndexByte(t.prefix, '_')) l, err := t.bucket.ListObjects( t.ctx, &gcs.ListObjectsRequest{ Delimiter: "_", }) - AssertEq(nil, err) - AssertEq("", l.ContinuationToken) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "", l.ContinuationToken) - ExpectThat(l.CollapsedRuns, ElementsAre("burrito_", "enchilada_")) + assert.ElementsMatch(t.T(), l.CollapsedRuns, []string{"burrito_", "enchilada_"}) - AssertEq(1, len(l.MinObjects)) - ExpectEq("burrito", l.MinObjects[0].Name) + assert.Equal(t.T(), 1, len(l.MinObjects)) + assert.Equal(t.T(), "burrito", l.MinObjects[0].Name) } -func (t *PrefixBucketTest) ListObjects_PrefixAndDelimeter() { +func (t *PrefixBucketTest) Test_ListObjects_PrefixAndDelimeter() { var err error // Create a few objects. @@ -352,11 +543,11 @@ func (t *PrefixBucketTest) ListObjects_PrefixAndDelimeter() { "some_other": []byte(""), }) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // List, with a delimiter and a prefix. Make things extra interesting by // using a delimiter that is contained within the bucket prefix. - AssertNe(-1, strings.IndexByte(t.prefix, '_')) + assert.NotEqual(t.T(), -1, strings.IndexByte(t.prefix, '_')) l, err := t.bucket.ListObjects( t.ctx, &gcs.ListObjectsRequest{ @@ -364,16 +555,16 @@ func (t *PrefixBucketTest) ListObjects_PrefixAndDelimeter() { Prefix: "burrito", }) - AssertEq(nil, err) - AssertEq("", l.ContinuationToken) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), "", l.ContinuationToken) - ExpectThat(l.CollapsedRuns, ElementsAre("burrito_")) + assert.ElementsMatch(t.T(), l.CollapsedRuns, []string{"burrito_"}) - AssertEq(1, len(l.MinObjects)) - ExpectEq("burrito", l.MinObjects[0].Name) + assert.Equal(t.T(), 1, len(l.MinObjects)) + assert.Equal(t.T(), "burrito", l.MinObjects[0].Name) } -func (t *PrefixBucketTest) UpdateObject() { +func (t *PrefixBucketTest) Test_UpdateObject() { var err error suffix := "taco" name := t.prefix + suffix @@ -381,7 +572,7 @@ func (t *PrefixBucketTest) UpdateObject() { // Create an object through the back door. _, err = storageutil.CreateObject(t.ctx, t.wrapped, name, []byte(contents)) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // Update it. newContentLanguage := "en-GB" @@ -392,12 +583,12 @@ func (t *PrefixBucketTest) UpdateObject() { ContentLanguage: &newContentLanguage, }) - AssertEq(nil, err) - ExpectEq(suffix, o.Name) - ExpectEq(newContentLanguage, o.ContentLanguage) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), suffix, o.Name) + assert.Equal(t.T(), newContentLanguage, o.ContentLanguage) } -func (t *PrefixBucketTest) DeleteObject() { +func (t *PrefixBucketTest) Test_DeleteObject() { var err error suffix := "taco" name := t.prefix + suffix @@ -405,7 +596,7 @@ func (t *PrefixBucketTest) DeleteObject() { // Create an object through the back door. _, err = storageutil.CreateObject(t.ctx, t.wrapped, name, []byte(contents)) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // Delete it. err = t.bucket.DeleteObject( @@ -414,7 +605,7 @@ func (t *PrefixBucketTest) DeleteObject() { Name: suffix, }) - AssertEq(nil, err) + assert.Equal(t.T(), nil, err) // It should be gone. _, _, err = t.wrapped.StatObject( @@ -424,12 +615,12 @@ func (t *PrefixBucketTest) DeleteObject() { }) var notFoundErr *gcs.NotFoundError - ExpectTrue(errors.As(err, ¬FoundErr)) + assert.True(t.T(), errors.As(err, ¬FoundErr)) } func TestGetFolder_Prefix(t *testing.T) { prefix := "foo_" - wrapped := fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.NonHierarchical) + wrapped := fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.BucketType{}) bucket, err := gcsx.NewPrefixBucket(prefix, wrapped) require.Nil(t, err) folderName := "taco" @@ -448,7 +639,7 @@ func TestGetFolder_Prefix(t *testing.T) { func TestDeleteFolder(t *testing.T) { prefix := "foo_" - wrapped := fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.NonHierarchical) + wrapped := fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.BucketType{}) bucket, err := gcsx.NewPrefixBucket(prefix, wrapped) require.Nil(t, err) folderName := "taco" @@ -477,7 +668,7 @@ func TestRenameFolder(t *testing.T) { old_suffix := "test" name := prefix + old_suffix new_suffix := "new_test" - wrapped := fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.NonHierarchical) + wrapped := fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.BucketType{}) bucket, err := gcsx.NewPrefixBucket(prefix, wrapped) require.Nil(t, err) ctx := context.Background() @@ -501,7 +692,7 @@ func TestCreateFolder(t *testing.T) { prefix := "foo_" var err error suffix := "test" - wrapped := fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.NonHierarchical) + wrapped := fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.BucketType{}) bucket, err := gcsx.NewPrefixBucket(prefix, wrapped) require.NoError(t, err) ctx := context.Background() @@ -520,7 +711,7 @@ func TestMoveObject(t *testing.T) { var err error prefix := "foo_" suffix := "test" - wrapped := fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.Hierarchical) + wrapped := fake.NewFakeBucket(timeutil.RealClock(), "some_bucket", gcs.BucketType{Hierarchical: true}) bucket, err := gcsx.NewPrefixBucket(prefix, wrapped) require.NoError(t, err) ctx := context.Background() diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 3a58d474d8..76e4ee1bee 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "io" + "math" "strconv" "strings" "time" @@ -56,6 +57,9 @@ const minSeeksForRandom = 2 // "readOp" is the value used in read context to store pointer to the read operation. const ReadOp = "readOp" +// TODO(b/385826024): Revert timeout to an appropriate value +const TimeoutForMultiRangeRead = time.Hour + // RandomReader is an object that knows how to read ranges within a particular // generation of a particular GCS object. Optimised for (large) sequential reads. // @@ -67,10 +71,12 @@ type RandomReader interface { // Panic if any internal invariants are violated. CheckInvariants() - // ReadAt Matches the semantics of io.ReaderAt, with the addition of context - // support and cache support. It returns a boolean which represent either - // content is read from fileCache (cacheHit = true) or gcs (cacheHit = false) - ReadAt(ctx context.Context, p []byte, offset int64) (n int, cacheHit bool, err error) + // ReadAt returns the data from the requested offset and upto the size of input + // byte array. It either populates input array i.e., p or returns a different + // byte array. In case input array is populated, the same array will be returned + // as part of response. Hence the callers should use the byte array returned + // as part of response always. + ReadAt(ctx context.Context, p []byte, offset int64) (objectData ObjectData, err error) // Return the record for the object to which the reader is bound. Object() (o *gcs.MinObject) @@ -80,9 +86,32 @@ type RandomReader interface { Destroy() } +// ObjectData specifies the response returned as part of ReadAt call. +type ObjectData struct { + // Byte array populated with the requested data. + DataBuf []byte + // Size of the data returned. + Size int + // Specified whether data is served from cache or not. + CacheHit bool +} + +// ReaderType represents different types of go-sdk gcs readers. +// For eg: NewReader and MRD both point to bidi read api. This enum specifies +// the go-sdk type. +type ReaderType int + +// ReaderType enum values. +const ( + // RangeReader corresponds to NewReader method in bucket_handle.go + RangeReader ReaderType = iota + // MultiRangeReader corresponds to NewMultiRangeDownloader method in bucket_handle.go + MultiRangeReader +) + // NewRandomReader create a random reader for the supplied object record that // reads using the given bucket. -func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb int32, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle) RandomReader { +func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb int32, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle, mrdWrapper *MultiRangeDownloaderWrapper) RandomReader { return &randomReader{ object: o, bucket: bucket, @@ -93,6 +122,7 @@ func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb i sequentialReadSizeMb: sequentialReadSizeMb, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, + mrdWrapper: mrdWrapper, metricHandle: metricHandle, } } @@ -104,7 +134,7 @@ type randomReader struct { // If non-nil, an in-flight read request and a function for cancelling it. // // INVARIANT: (reader == nil) == (cancel == nil) - reader io.ReadCloser + reader gcs.StorageReader cancel func() // The range of the object that we expect reader to yield, when reader is @@ -133,7 +163,19 @@ type randomReader struct { // fileCacheHandle is used to read from the cached location. It is created on the fly // using fileCacheHandler for the given object and bucket. fileCacheHandle *file.CacheHandle - metricHandle common.MetricHandle + + // Stores the handle associated with the previously closed newReader instance. + // This will be used while making the new connection to bypass auth and metadata + // checks. + readHandle []byte + + // mrdWrapper points to the wrapper object within inode. + mrdWrapper *MultiRangeDownloaderWrapper + + // boolean variable to determine if MRD is being used or not. + isMRDInUse bool + + metricHandle common.MetricHandle } func (rr *randomReader) CheckInvariants() { @@ -265,7 +307,12 @@ func captureFileCacheMetrics(ctx context.Context, metricHandle common.MetricHand func (rr *randomReader) ReadAt( ctx context.Context, p []byte, - offset int64) (n int, cacheHit bool, err error) { + offset int64) (objectData ObjectData, err error) { + objectData = ObjectData{ + DataBuf: p, + CacheHit: false, + Size: 0, + } if offset >= int64(rr.object.Size) { err = io.EOF @@ -276,106 +323,66 @@ func (rr *randomReader) ReadAt( // then the file cache behavior is write-through i.e. data is first read from // GCS, cached in file and then served from that file. But the cacheHit is // false in that case. - n, cacheHit, err = rr.tryReadingFromFileCache(ctx, p, offset) + n, cacheHit, err := rr.tryReadingFromFileCache(ctx, p, offset) if err != nil { err = fmt.Errorf("ReadAt: while reading from cache: %w", err) return } // Data was served from cache. if cacheHit || n == len(p) || (n < len(p) && uint64(offset)+uint64(n) == rr.object.Size) { + objectData.CacheHit = cacheHit + objectData.Size = n return } - for len(p) > 0 { - // Have we blown past the end of the object? - if offset >= int64(rr.object.Size) { - err = io.EOF - return - } - - // When the offset is AFTER the reader position, try to seek forward, within reason. - // This happens when the kernel page cache serves some data. It's very common for - // concurrent reads, often by only a few 128kB fuse read requests. The aim is to - // re-use GCS connection and avoid throwing away already read data. - // For parallel sequential reads to a single file, not throwing away the connections - // is a 15-20x improvement in throughput: 150-200 MB/s instead of 10 MB/s. - if rr.reader != nil && rr.start < offset && offset-rr.start < maxReadSize { - bytesToSkip := int64(offset - rr.start) - p := make([]byte, bytesToSkip) - n, _ := io.ReadFull(rr.reader, p) - rr.start += int64(n) - } - - // If we have an existing reader but it's positioned at the wrong place, - // clean it up and throw it away. - if rr.reader != nil && rr.start != offset { - rr.reader.Close() - rr.reader = nil - rr.cancel = nil - rr.seeks++ - } - - // If we don't have a reader, start a read operation. - if rr.reader == nil { - err = rr.startRead(ctx, offset, int64(len(p))) - if err != nil { - err = fmt.Errorf("startRead: %w", err) - return - } - } - - // Now we have a reader positioned at the correct place. Consume as much from - // it as possible. - var tmp int - tmp, err = rr.readFull(ctx, p) - - n += tmp - p = p[tmp:] - rr.start += int64(tmp) - offset += int64(tmp) - rr.totalReadBytes += uint64(tmp) - - // Sanity check. - if rr.start > rr.limit { - err = fmt.Errorf("reader returned %d too many bytes", rr.start-rr.limit) - - // Don't attempt to reuse the reader when it's behaving wackily. - rr.reader.Close() - rr.reader = nil - rr.cancel = nil - rr.start = -1 - rr.limit = -1 - - return - } + // Check first if we can read using existing reader. if not, determine which + // api to use and call gcs accordingly. + + // When the offset is AFTER the reader position, try to seek forward, within reason. + // This happens when the kernel page cache serves some data. It's very common for + // concurrent reads, often by only a few 128kB fuse read requests. The aim is to + // re-use GCS connection and avoid throwing away already read data. + // For parallel sequential reads to a single file, not throwing away the connections + // is a 15-20x improvement in throughput: 150-200 MB/s instead of 10 MB/s. + if rr.reader != nil && rr.start < offset && offset-rr.start < maxReadSize { + bytesToSkip := offset - rr.start + p := make([]byte, bytesToSkip) + n, _ := io.ReadFull(rr.reader, p) + rr.start += int64(n) + } - // Are we finished with this reader now? - if rr.start == rr.limit { - rr.reader.Close() - rr.reader = nil - rr.cancel = nil - } + // If we have an existing reader, but it's positioned at the wrong place, + // clean it up and throw it away. + // We will also clean up the existing reader if it can't serve the entire request. + dataToRead := math.Min(float64(offset+int64(len(p))), float64(rr.object.Size)) + if rr.reader != nil && (rr.start != offset || int64(dataToRead) > rr.limit) { + rr.closeReader() + rr.reader = nil + rr.cancel = nil + rr.seeks++ + } - // Handle errors. - switch { - case err == io.EOF || err == io.ErrUnexpectedEOF: - // For a non-empty buffer, ReadFull returns EOF or ErrUnexpectedEOF only - // if the reader peters out early. That's fine, but it means we should - // have hit the limit above. - if rr.reader != nil { - err = fmt.Errorf("reader returned %d too few bytes", rr.limit-rr.start) - return - } + if rr.reader != nil { + // TODO: Passing readType as "unhandled" here for now. Ideally + // readType should be stored in and obtained from the previous rr.reader. + objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, -1, "unhandled") + return + } - err = nil + // If we don't have a reader, determine whether to read from NewReader or MRR. + readType, end, err := rr.getReadInfo(offset, int64(len(p))) + if err != nil { + err = fmt.Errorf("ReadAt: getReaderInfo: %w", err) + return + } - case err != nil: - // Propagate other errors. - err = fmt.Errorf("readFull: %w", err) - return - } + readerType := readerType(readType, offset, end, rr.bucket.BucketType()) + if readerType == RangeReader { + objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, end, readType) + return } + objectData.Size, err = rr.readFromMultiRangeReader(ctx, p, offset, end, TimeoutForMultiRangeRead) return } @@ -385,14 +392,21 @@ func (rr *randomReader) Object() (o *gcs.MinObject) { } func (rr *randomReader) Destroy() { + defer func() { + if rr.isMRDInUse { + err := rr.mrdWrapper.DecrementRefCount() + if err != nil { + logger.Errorf("randomReader::Destroy:%v", err) + } + rr.isMRDInUse = false + } + }() + // Close out the reader, if we have one. if rr.reader != nil { - err := rr.reader.Close() + rr.closeReader() rr.reader = nil rr.cancel = nil - if err != nil { - logger.Warnf("rr.Destroy(): while closing reader: %v", err) - } } if rr.fileCacheHandle != nil { @@ -443,10 +457,57 @@ func (rr *randomReader) readFull( // Ensure that rr.reader is set up for a range for which [start, start+size) is // a prefix. Irrespective of the size requested, we try to fetch more data // from GCS defined by sequentialReadSizeMb flag to serve future read requests. -func (rr *randomReader) startRead( - ctx context.Context, +func (rr *randomReader) startRead(start int64, end int64) (err error) { + // Begin the read. + ctx, cancel := context.WithCancel(context.Background()) + + rc, err := rr.bucket.NewReaderWithReadHandle( + ctx, + &gcs.ReadObjectRequest{ + Name: rr.object.Name, + Generation: rr.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(start), + Limit: uint64(end), + }, + ReadCompressed: rr.object.HasContentEncodingGzip(), + ReadHandle: rr.readHandle, + }) + + // If a file handle is open locally, but the corresponding object doesn't exist + // in GCS, it indicates a file clobbering scenario. This likely occurred because: + // - The file was deleted in GCS while a local handle was still open. + // - The file content was modified leading to different generation number. + var notFoundError *gcs.NotFoundError + if errors.As(err, ¬FoundError) { + err = &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("NewReader: %w", err), + } + return + } + + if err != nil { + err = fmt.Errorf("NewReaderWithReadHandle: %w", err) + return + } + + rr.reader = rc + rr.cancel = cancel + rr.start = start + rr.limit = end + + requestedDataSize := end - start + common.CaptureGCSReadMetrics(ctx, rr.metricHandle, util.Sequential, requestedDataSize) + + return +} + +// getReaderInfo provides the readType and the range to query GCS. +// Range here is [start, end]. End is computed using the readType, start offset +// and size of the data the callers needs. +func (rr *randomReader) getReadInfo( start int64, - size int64) (err error) { + size int64) (readType string, end int64, err error) { // Make sure start and size are legal. if start < 0 || uint64(start) > rr.object.Size || size < 0 { err = fmt.Errorf( @@ -457,6 +518,10 @@ func (rr *randomReader) startRead( return } + if err != nil { + return + } + // GCS requests are expensive. Prefer to issue read requests defined by // sequentialReadSizeMb flag. Sequential reads will simply sip from the fire house // with each call to ReadAt. In practice, GCS will fill the TCP buffers @@ -468,8 +533,8 @@ func (rr *randomReader) startRead( // But if we notice random read patterns after a minimum number of seeks, // optimise for random reads. Random reads will read data in chunks of // (average read size in bytes rounded up to the next MB). - end := int64(rr.object.Size) - readType := util.Sequential + end = int64(rr.object.Size) + readType = util.Sequential if rr.seeks >= minSeeksForRandom { readType = util.Random averageReadBytes := rr.totalReadBytes / rr.seeks @@ -495,44 +560,102 @@ func (rr *randomReader) startRead( end = start + maxSizeToReadFromGCS } - // Begin the read. - ctx, cancel := context.WithCancel(context.Background()) - rc, err := rr.bucket.NewReader( - ctx, - &gcs.ReadObjectRequest{ - Name: rr.object.Name, - Generation: rr.object.Generation, - Range: &gcs.ByteRange{ - Start: uint64(start), - Limit: uint64(end), - }, - ReadCompressed: rr.object.HasContentEncodingGzip(), - }) + return +} - // If a file handle is open locally, but the corresponding object doesn't exist - // in GCS, it indicates a file clobbering scenario. This likely occurred because: - // - The file was deleted in GCS while a local handle was still open. - // - The file content was modified leading to different generation number. - var notFoundError *gcs.NotFoundError - if errors.As(err, ¬FoundError) { - err = &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("NewReader: %w", err), +// readerType specifies the go-sdk interface to use for reads. +func readerType(readType string, start int64, end int64, bucketType gcs.BucketType) ReaderType { + bytesToBeRead := end - start + if readType == util.Random && bytesToBeRead < maxReadSize && bucketType.Zonal { + return MultiRangeReader + } + return RangeReader +} + +// readFromRangeReader reads using the NewReader interface of go-sdk. Its uses +// the existing reader if available, otherwise makes a call to GCS. +func (rr *randomReader) readFromRangeReader(ctx context.Context, p []byte, offset int64, end int64, readType string) (n int, err error) { + // If we don't have a reader, start a read operation. + if rr.reader == nil { + err = rr.startRead(offset, end) + if err != nil { + err = fmt.Errorf("startRead: %w", err) + return } - return } - if err != nil { - err = fmt.Errorf("NewReader: %w", err) + // Now we have a reader positioned at the correct place. Consume as much from + // it as possible. + n, err = rr.readFull(ctx, p) + rr.start += int64(n) + rr.totalReadBytes += uint64(n) + + // Sanity check. + if rr.start > rr.limit { + err = fmt.Errorf("Reader returned extra bytes: %d", rr.start-rr.limit) + + // Don't attempt to reuse the reader when it's behaving wackily. + rr.closeReader() + rr.reader = nil + rr.cancel = nil + rr.start = -1 + rr.limit = -1 + return } - rr.reader = rc - rr.cancel = cancel - rr.start = start - rr.limit = end + // Are we finished with this reader now? + if rr.start == rr.limit { + rr.closeReader() + rr.reader = nil + rr.cancel = nil + } - requestedDataSize := end - start + // Handle errors. + switch { + case err == io.EOF || err == io.ErrUnexpectedEOF: + // For a non-empty buffer, ReadFull returns EOF or ErrUnexpectedEOF only + // if the reader peters out early. That's fine, but it means we should + // have hit the limit above. + if rr.reader != nil { + err = fmt.Errorf("Reader returned early by skipping %d bytes", rr.limit-rr.start) + return + } + + err = nil + + case err != nil: + // Propagate other errors. + err = fmt.Errorf("readFull: %w", err) + return + } + + requestedDataSize := end - offset common.CaptureGCSReadMetrics(ctx, rr.metricHandle, readType, requestedDataSize) return } + +func (rr *randomReader) readFromMultiRangeReader(ctx context.Context, p []byte, offset, end int64, timeout time.Duration) (bytesRead int, err error) { + if rr.mrdWrapper == nil { + return 0, fmt.Errorf("readFromMultiRangeReader: Invalid MultiRangeDownloaderWrapper") + } + + if !rr.isMRDInUse { + rr.isMRDInUse = true + rr.mrdWrapper.IncrementRefCount() + } + + bytesRead, err = rr.mrdWrapper.Read(ctx, p, offset, end, timeout, rr.metricHandle) + rr.totalReadBytes += uint64(bytesRead) + return +} + +// closeReader fetches the readHandle before closing the reader instance. +func (rr *randomReader) closeReader() { + rr.readHandle = rr.reader.ReadHandle() + err := rr.reader.Close() + if err != nil { + logger.Warnf("error while closing reader: %v", err) + } +} diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go new file mode 100644 index 0000000000..dbe298c4ca --- /dev/null +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -0,0 +1,781 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "context" + "errors" + "fmt" + "io" + "os" + "path" + "strings" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +const TestTimeoutForMultiRangeRead = time.Second + +type RandomReaderStretchrTest struct { + suite.Suite + object *gcs.MinObject + mockBucket *storage.TestifyMockBucket + rr checkingRandomReader + cacheDir string + jobManager *downloader.JobManager + cacheHandler *file.CacheHandler +} + +func TestRandomReaderStretchrTestSuite(t *testing.T) { + suite.Run(t, new(RandomReaderStretchrTest)) +} + +func (t *RandomReaderStretchrTest) SetupTest() { + t.rr.ctx = context.Background() + + // Manufacture an object record. + t.object = &gcs.MinObject{ + Name: "foo", + Size: 17, + Generation: 1234, + } + + // Create the bucket. + t.mockBucket = new(storage.TestifyMockBucket) + + t.cacheDir = path.Join(os.Getenv("HOME"), "cache/dir") + lruCache := lru.NewCache(CacheMaxSize) + t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{ + EnableCrc: false, + }, nil) + t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) + + // Set up the reader. + rr := NewRandomReader(t.object, t.mockBucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics(), nil) + t.rr.wrapped = rr.(*randomReader) +} + +func (t *RandomReaderStretchrTest) TearDownTest() { + t.rr.Destroy() +} + +func (t *RandomReaderStretchrTest) Test_ReadInfo() { + t.object.Size = 10 * MB + testCases := []struct { + name string + start int64 + size int64 + }{ + { + name: "startLessThanZero", + start: -1, + size: 10, + }, + { + name: "sizeLessThanZero", + start: -0, + size: -1, + }, + { + name: "startGreaterThanObjectSize", + start: int64(t.object.Size + 1), + size: int64(t.object.Size), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + _, _, err := t.rr.wrapped.getReadInfo(tc.start, tc.size) + + assert.Error(t.T(), err) + errorString := fmt.Sprintf( + "range [%d, %d) is illegal for %d-byte object", tc.start, tc.start+tc.size, t.object.Size) + assert.Equal(t.T(), errorString, err.Error()) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadInfo_Sequential() { + var testCases = []struct { + testName string + expectedEnd int64 + start int64 + objectSize uint64 + }{ + {"10MBObject", 10 * MB, 0, 10 * MB}, + {"ReadSizeGreaterThanObjectSize", 10 * MB, int64(t.object.Size - 1), 10 * MB}, + {"ObjectSizeGreaterThanReadSize", int64(sequentialReadSizeInBytes), 0, 50 * MB}, + } + + for _, tc := range testCases { + t.Run(tc.testName, func() { + t.object.Size = tc.objectSize + readType, end, err := t.rr.wrapped.getReadInfo(tc.start, 10) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), testutil.Sequential, readType) + assert.Equal(t.T(), tc.expectedEnd, end) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadInfo_Random() { + t.rr.wrapped.seeks = 2 + var testCases = []struct { + testName string + expectedEnd int64 + start int64 + objectSize uint64 + totalReadBytes uint64 + }{ + // TotalReadByte is 10MB, so average is 10/2 = 5MB >1MB and <8MB + {"RangeBetween1And8MB", 6 * MB, 0, 50 * MB, 10 * MB}, + // TotalReadByte is 1MB, so average is 1/2 = 0.5MB which is <1MB + {"ReadSizeLessThan1MB", minReadSize, 0, 50 * MB, 1 * MB}, + // TotalReadByte is 1MB, so average is 10/2 = 5MB which is <8MB + {"ReadSizeLessThan8MB", 6 * MB, 0, 50 * MB, 10 * MB}, + // TotalReadByte is 1MB, so average is 20/2 = 10MB which is >8MB + {"ReadSizeGreaterThan8MB", sequentialReadSizeInBytes, 0, 50 * MB, 20 * MB}, + {"ReadSizeGreaterThanObjectSize", 5 * MB, 5*MB - 1, 5 * MB, 2 * MB}, + } + + for _, tc := range testCases { + t.Run(tc.testName, func() { + t.object.Size = tc.objectSize + t.rr.wrapped.totalReadBytes = tc.totalReadBytes + readType, end, err := t.rr.wrapped.getReadInfo(tc.start, 10) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), testutil.Random, readType) + assert.Equal(t.T(), tc.expectedEnd, end) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReaderType() { + testCases := []struct { + name string + readType string + start int64 + end int64 + bucketType gcs.BucketType + readerType ReaderType + }{ + { + name: "ZonalBucketRandomRead", + readType: testutil.Random, + start: 50, + end: 68, + bucketType: gcs.BucketType{Zonal: true}, + readerType: MultiRangeReader, + }, + { + name: "ZonalBucketRandomReadLargerThan8MB", + readType: testutil.Random, + start: 0, + end: 9 * MB, + bucketType: gcs.BucketType{Zonal: true}, + readerType: RangeReader, + }, + { + name: "ZonalBucketSequentialRead", + readType: testutil.Sequential, + start: 50, + end: 68, + bucketType: gcs.BucketType{Zonal: true}, + readerType: RangeReader, + }, + { + name: "RegularBucketRandomRead", + readType: testutil.Random, + start: 50, + end: 68, + bucketType: gcs.BucketType{Zonal: false}, + readerType: RangeReader, + }, + { + name: "RegularBucketSequentialRead", + readType: testutil.Sequential, + start: 50, + end: 68, + bucketType: gcs.BucketType{Zonal: false}, + readerType: RangeReader, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + readerType := readerType(tc.readType, tc.start, tc.end, tc.bucketType) + assert.Equal(t.T(), readerType, tc.readerType) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenExistingReaderIsNil() { + testCases := []struct { + name string + inputReadHandle []byte + outputReadHandle []byte + }{ + { + name: "ReadHandlePresent", + inputReadHandle: []byte("fake-handle"), + }, + { + name: "ReadHandleAbsent", + inputReadHandle: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.rr.wrapped.readHandle = tc.inputReadHandle + t.rr.wrapped.reader = nil + t.rr.wrapped.start = 0 + t.object.Size = 5 + dataSize := 5 + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rc := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + readObjectRequest := &gcs.ReadObjectRequest{ + Name: t.rr.wrapped.object.Name, + Generation: t.rr.wrapped.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: t.object.Size, + }, + ReadCompressed: t.rr.wrapped.object.HasContentEncodingGzip(), + ReadHandle: t.rr.wrapped.readHandle, + } + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil).Times(1) + buf := make([]byte, dataSize) + + n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, int64(t.object.Size), "unhandled") + + t.mockBucket.AssertExpectations(t.T()) + assert.NoError(t.T(), err) + assert.Equal(t.T(), dataSize, n) + assert.Equal(t.T(), testContent[:dataSize], buf) + // Verify the reader state. + assert.Nil(t.T(), t.rr.wrapped.reader) + assert.Nil(t.T(), t.rr.wrapped.cancel) + assert.Equal(t.T(), int64(5), t.rr.wrapped.start) + assert.Equal(t.T(), int64(5), t.rr.wrapped.limit) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenExistingReaderIsNotNil() { + t.rr.wrapped.start = 4 + t.rr.wrapped.limit = 10 + t.rr.wrapped.totalReadBytes = 4 + t.object.Size = 10 + dataSize := 4 + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rc := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.rr.wrapped.reader = rc + t.rr.wrapped.cancel = func() {} + buf := make([]byte, dataSize) + + n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 4, 8, "unhandled") + + assert.NoError(t.T(), err) + assert.Equal(t.T(), dataSize, n) + // Verify the reader state. + assert.Equal(t.T(), rc, t.rr.wrapped.reader) + assert.NotNil(t.T(), t.rr.wrapped.cancel) + assert.Equal(t.T(), int64(8), t.rr.wrapped.start) + assert.Equal(t.T(), int64(10), t.rr.wrapped.limit) + assert.Equal(t.T(), uint64(8), t.rr.wrapped.totalReadBytes) +} + +func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenAllDataFromReaderIsRead() { + testCases := []struct { + name string + readHandle []byte + }{ + { + name: "GCSReturnedReadHandle", + readHandle: []byte("fake-handle"), + }, + { + name: "GCSReturnedNoReadHandle", + readHandle: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.rr.wrapped.start = 4 + t.rr.wrapped.limit = 10 + t.rr.wrapped.totalReadBytes = 4 + t.object.Size = 10 + dataSize := 6 + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rc := &fake.FakeReader{ + ReadCloser: getReadCloser(testContent), + Handle: tc.readHandle, + } + t.rr.wrapped.reader = rc + t.rr.wrapped.cancel = func() {} + buf := make([]byte, dataSize) + + n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 4, 10, "unhandled") + + assert.NoError(t.T(), err) + assert.Equal(t.T(), dataSize, n) + // Verify the reader state. + assert.Nil(t.T(), t.rr.wrapped.reader) + assert.Nil(t.T(), t.rr.wrapped.cancel) + assert.Equal(t.T(), int64(10), t.rr.wrapped.start) + assert.Equal(t.T(), int64(10), t.rr.wrapped.limit) + assert.Equal(t.T(), uint64(10), t.rr.wrapped.totalReadBytes) + expectedReadHandle := tc.readHandle + assert.Equal(t.T(), expectedReadHandle, t.rr.wrapped.readHandle) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderHasLessDataThanRequested() { + testCases := []struct { + name string + readHandle []byte + }{ + { + name: "GCSReturnedReadHandle", + readHandle: []byte("fake-handle"), + }, + { + name: "GCSReturnedNoReadHandle", + readHandle: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.rr.wrapped.start = 0 + t.rr.wrapped.limit = 6 + t.rr.wrapped.totalReadBytes = 0 + dataSize := 6 + testContent := testutil.GenerateRandomBytes(dataSize) + rc := &fake.FakeReader{ + ReadCloser: getReadCloser(testContent), + Handle: tc.readHandle, + } + t.rr.wrapped.reader = rc + t.rr.wrapped.cancel = func() {} + buf := make([]byte, 10) + + n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, 10, "unhandled") + + assert.NoError(t.T(), err) + assert.Equal(t.T(), dataSize, n) + // Verify the reader state. + assert.Nil(t.T(), t.rr.wrapped.reader) + assert.Nil(t.T(), t.rr.wrapped.cancel) + assert.Equal(t.T(), int64(dataSize), t.rr.wrapped.start) + assert.Equal(t.T(), int64(dataSize), t.rr.wrapped.limit) + assert.Equal(t.T(), uint64(dataSize), t.rr.wrapped.totalReadBytes) + expectedReadHandle := tc.readHandle + assert.Equal(t.T(), expectedReadHandle, t.rr.wrapped.readHandle) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderReturnedMoreData() { + testCases := []struct { + name string + readHandle []byte + }{ + { + name: "GCSReturnedReadHandle", + readHandle: []byte("fake-handle"), + }, + { + name: "GCSReturnedNoReadHandle", + readHandle: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.rr.wrapped.start = 0 + t.rr.wrapped.limit = 6 + t.rr.wrapped.totalReadBytes = 0 + dataSize := 8 + testContent := testutil.GenerateRandomBytes(dataSize) + rc := &fake.FakeReader{ + ReadCloser: getReadCloser(testContent), + Handle: tc.readHandle, + } + t.rr.wrapped.reader = rc + t.rr.wrapped.cancel = func() {} + buf := make([]byte, 10) + + _, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, 10, "unhandled") + + assert.True(t.T(), strings.Contains(err.Error(), "extra bytes: 2")) + assert.Nil(t.T(), t.rr.wrapped.reader) + assert.Nil(t.T(), t.rr.wrapped.cancel) + assert.Equal(t.T(), int64(-1), t.rr.wrapped.start) + assert.Equal(t.T(), int64(-1), t.rr.wrapped.limit) + assert.Equal(t.T(), uint64(8), t.rr.wrapped.totalReadBytes) + expectedReadHandle := tc.readHandle + assert.Equal(t.T(), expectedReadHandle, t.rr.wrapped.readHandle) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderReturnedEOF() { + t.rr.wrapped.start = 0 + t.rr.wrapped.limit = 10 + dataSize := 6 + testContent := testutil.GenerateRandomBytes(dataSize) + rc := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.rr.wrapped.reader = rc + t.rr.wrapped.cancel = func() {} + buf := make([]byte, 10) + + _, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, 10, "unhandled") + + assert.True(t.T(), strings.Contains(err.Error(), "skipping 4 bytes")) +} + +func (t *RandomReaderStretchrTest) Test_ExistingReader_WrongOffset() { + testCases := []struct { + name string + readHandle []byte + }{ + { + name: "ReaderHasReadHandle", + readHandle: []byte("fake-handle"), + }, + { + name: "ReaderHasNoReadHandle", + readHandle: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + // Simulate an existing reader. + t.rr.wrapped.readHandle = tc.readHandle + t.rr.wrapped.reader = &fake.FakeReader{ + ReadCloser: io.NopCloser(strings.NewReader("xxx")), + Handle: tc.readHandle, + } + t.rr.wrapped.cancel = func() {} + t.rr.wrapped.start = 2 + t.rr.wrapped.limit = 5 + readObjectRequest := &gcs.ReadObjectRequest{ + Name: t.rr.wrapped.object.Name, + Generation: t.rr.wrapped.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: t.object.Size, + }, + ReadCompressed: t.rr.wrapped.object.HasContentEncodingGzip(), + ReadHandle: t.rr.wrapped.readHandle, + } + t.mockBucket. + On("NewReaderWithReadHandle", mock.Anything, readObjectRequest). + Return(nil, errors.New(string(tc.readHandle))). + Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + + buf := make([]byte, 1) + + _, err := t.rr.ReadAt(buf, 0) + + t.mockBucket.AssertExpectations(t.T()) + assert.NotNil(t.T(), err) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedDataSize() { + t.object.Size = 10 + // Simulate an existing reader. + t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte("xxx")), Handle: []byte("fake")} + t.rr.wrapped.cancel = func() {} + t.rr.wrapped.start = 2 + t.rr.wrapped.limit = 5 + rc := &fake.FakeReader{ReadCloser: getReadCloser([]byte("abcdefgh"))} + expectedHandleInRequest := []byte(t.rr.wrapped.reader.ReadHandle()) + readObjectRequest := &gcs.ReadObjectRequest{ + Name: t.rr.wrapped.object.Name, + Generation: t.rr.wrapped.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(2), + Limit: t.object.Size, + }, + ReadCompressed: t.rr.wrapped.object.HasContentEncodingGzip(), + ReadHandle: expectedHandleInRequest, + } + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + requestSize := 6 + buf := make([]byte, requestSize) + + data, err := t.rr.ReadAt(buf, 2) + + require.Nil(t.T(), err) + require.Equal(t.T(), rc, t.rr.wrapped.reader) + require.Equal(t.T(), requestSize, data.Size) + require.Equal(t.T(), "abcdef", string(buf[:data.Size])) + assert.Equal(t.T(), uint64(requestSize), t.rr.wrapped.totalReadBytes) + assert.Equal(t.T(), expectedHandleInRequest, t.rr.wrapped.readHandle) +} + +func (t *RandomReaderStretchrTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedObjectSize() { + t.object.Size = 5 + // Simulate an existing reader + t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte("xxx")), Handle: []byte("fake")} + t.rr.wrapped.cancel = func() {} + t.rr.wrapped.start = 0 + t.rr.wrapped.limit = 3 + rc := &fake.FakeReader{ReadCloser: getReadCloser([]byte("abcde"))} + expectedHandleInRequest := t.rr.wrapped.reader.ReadHandle() + readObjectRequest := &gcs.ReadObjectRequest{ + Name: t.rr.wrapped.object.Name, + Generation: t.rr.wrapped.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: t.object.Size, + }, + ReadCompressed: t.rr.wrapped.object.HasContentEncodingGzip(), + ReadHandle: expectedHandleInRequest, + } + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + requestSize := 6 + buf := make([]byte, requestSize) + + data, err := t.rr.ReadAt(buf, 0) + + require.Nil(t.T(), err) + require.Nil(t.T(), t.rr.wrapped.reader) + require.Equal(t.T(), int(t.object.Size), data.Size) + require.Equal(t.T(), "abcde", string(buf[:data.Size])) + assert.Equal(t.T(), t.object.Size, t.rr.wrapped.totalReadBytes) + assert.Equal(t.T(), []byte(nil), t.rr.wrapped.readHandle) +} + +func (t *RandomReaderStretchrTest) Test_ReadAt_InvalidOffset() { + testCases := []struct { + name string + dataSize int + start int + }{ + { + name: "InvalidOffset", + dataSize: 50, + start: 68, + }, + { + name: "NegativeOffset", + dataSize: 100, + start: -50, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.rr.wrapped.reader = nil + t.object.Size = uint64(tc.dataSize) + buf := make([]byte, tc.dataSize) + + _, err := t.rr.wrapped.ReadAt(t.rr.ctx, buf, int64(tc.start)) + + assert.Error(t.T(), err) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadAt_MRDRead() { + testCases := []struct { + name string + dataSize int + offset int + bytesToRead int + }{ + { + name: "ReadChunk", + dataSize: 100, + offset: 37, + bytesToRead: 43, + }, + { + name: "ReadZeroByte", + dataSize: 100, + offset: 37, + bytesToRead: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.rr.wrapped.reader = nil + t.rr.wrapped.isMRDInUse = false + t.rr.wrapped.seeks = minSeeksForRandom + 1 + t.object.Size = uint64(tc.dataSize) + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + t.rr.wrapped.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) + buf := make([]byte, tc.bytesToRead) + + objData, err := t.rr.wrapped.ReadAt(t.rr.ctx, buf, int64(tc.offset)) + + t.mockBucket.AssertNotCalled(t.T(), "NewReaderWithReadHandle", mock.Anything) + assert.NoError(t.T(), err) + assert.Nil(t.T(), t.rr.wrapped.reader) + assert.Equal(t.T(), tc.bytesToRead, objData.Size) + assert.Equal(t.T(), testContent[tc.offset:tc.offset+tc.bytesToRead], objData.DataBuf[:objData.Size]) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadFull() { + testCases := []struct { + name string + dataSize int + extraSize int + }{ + { + name: "ReadFull", + dataSize: 100, + extraSize: 0, + }, + { + name: "ReadWithLargerBuffer", + dataSize: 100, + extraSize: 10, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.rr.wrapped.reader = nil + t.rr.wrapped.isMRDInUse = false + t.object.Size = uint64(tc.dataSize) + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + t.rr.wrapped.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) + buf := make([]byte, tc.dataSize+tc.extraSize) + + bytesRead, err := t.rr.wrapped.readFromMultiRangeReader(t.rr.ctx, buf, 0, int64(t.object.Size), TestTimeoutForMultiRangeRead) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), tc.dataSize, bytesRead) + assert.Equal(t.T(), testContent[:tc.dataSize], buf[:bytesRead]) + }) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadChunk() { + testCases := []struct { + name string + dataSize int + start int + end int + }{ + { + name: "ReadChunk", + dataSize: 100, + start: 37, + end: 93, + }, + } + + for _, tc := range testCases { + t.rr.wrapped.reader = nil + t.object.Size = uint64(tc.dataSize) + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + t.rr.wrapped.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) + buf := make([]byte, tc.end-tc.start) + + bytesRead, err := t.rr.wrapped.readFromMultiRangeReader(t.rr.ctx, buf, int64(tc.start), int64(tc.end), TestTimeoutForMultiRangeRead) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), tc.end-tc.start, bytesRead) + assert.Equal(t.T(), testContent[tc.start:tc.end], buf[:bytesRead]) + } +} + +func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ValidateTimeout() { + testCases := []struct { + name string + dataSize int + sleepTime time.Duration + }{ + { + name: "TimeoutPlusOneMilliSecond", + dataSize: 100, + sleepTime: TestTimeoutForMultiRangeRead + time.Millisecond, + }, + // Ensure that this is always the last test + { + name: "TimeoutValue", + dataSize: 100, + sleepTime: TestTimeoutForMultiRangeRead, + }, + } + + for i, tc := range testCases { + t.Run(tc.name, func() { + t.rr.wrapped.reader = nil + t.rr.wrapped.isMRDInUse = false + t.object.Size = uint64(tc.dataSize) + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + t.rr.wrapped.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, tc.sleepTime)).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) + buf := make([]byte, tc.dataSize) + + bytesRead, err := t.rr.wrapped.readFromMultiRangeReader(t.rr.ctx, buf, 0, int64(t.object.Size), TestTimeoutForMultiRangeRead) + + if i == len(testCases)-1 && bytesRead != 0 { + assert.NoError(t.T(), err) + assert.Equal(t.T(), tc.dataSize, bytesRead) + assert.Equal(t.T(), testContent[:tc.dataSize], buf[:bytesRead]) + return + } + assert.ErrorContains(t.T(), err, "Timeout") + }) + } +} diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index 3c28be6cb2..39c4ab4fd5 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -35,6 +35,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/jacobsa/fuse/fuseops" @@ -44,6 +45,8 @@ import ( "golang.org/x/net/context" ) +// NOTE: Please add new tests in random_reader_stretchr_test.go file. This file +// is deprecated and these tests will be moved to the random_reader_stretchr_test.go func TestRandomReader(t *testing.T) { RunTests(t) } //////////////////////////////////////////////////////////////////////// @@ -55,7 +58,7 @@ type checkingRandomReader struct { wrapped *randomReader } -func (rr *checkingRandomReader) ReadAt(p []byte, offset int64) (int, bool, error) { +func (rr *checkingRandomReader) ReadAt(p []byte, offset int64) (ObjectData, error) { rr.wrapped.CheckInvariants() defer rr.wrapped.CheckInvariants() return rr.wrapped.ReadAt(rr.ctx, p, offset) @@ -183,7 +186,7 @@ func (t *RandomReaderTest) SetUp(ti *TestInfo) { t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) // Set up the reader. - rr := NewRandomReader(t.object, t.bucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics()) + rr := NewRandomReader(t.object, t.bucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics(), nil) t.rr.wrapped = rr.(*randomReader) } @@ -197,11 +200,16 @@ func getReadCloser(content []byte) io.ReadCloser { return rc } -func (t *RandomReaderTest) mockNewReaderCallForTestBucket(start uint64, limit uint64, rc io.ReadCloser) { +func (t *RandomReaderTest) mockNewReaderCallForTestBucket(start uint64, limit uint64, rd io.ReadCloser) { ExpectCall(t.bucket, "NewReader")( - Any(), - AllOf(rangeStartIs(start), rangeLimitIs(limit))). - WillRepeatedly(Return(rc, nil)) + Any(), AllOf(rangeStartIs(start), rangeLimitIs(limit))). + WillRepeatedly(Return(rd, nil)) +} + +func (t *RandomReaderTest) mockNewReaderWithHandleCallForTestBucket(start uint64, limit uint64, rd gcs.StorageReader) { + ExpectCall(t.bucket, "NewReaderWithReadHandle")( + Any(), AllOf(rangeStartIs(start), rangeLimitIs(limit))). + WillRepeatedly(Return(rd, nil)) } //////////////////////////////////////////////////////////////////////// @@ -212,54 +220,39 @@ func (t *RandomReaderTest) EmptyRead() { // Nothing should happen. buf := make([]byte, 0) - n, _, err := t.rr.ReadAt(buf, 0) + objectData, err := t.rr.ReadAt(buf, 0) - ExpectEq(0, n) + ExpectEq(0, objectData.Size) ExpectEq(nil, err) } func (t *RandomReaderTest) ReadAtEndOfObject() { buf := make([]byte, 1) - n, _, err := t.rr.ReadAt(buf, int64(t.object.Size)) + objectData, err := t.rr.ReadAt(buf, int64(t.object.Size)) - ExpectEq(0, n) + ExpectEq(0, objectData.Size) ExpectEq(io.EOF, err) } func (t *RandomReaderTest) ReadPastEndOfObject() { buf := make([]byte, 1) - n, cacheHit, err := t.rr.ReadAt(buf, int64(t.object.Size)+1) + objectData, err := t.rr.ReadAt(buf, int64(t.object.Size)+1) - ExpectFalse(cacheHit) - ExpectEq(0, n) + ExpectFalse(objectData.CacheHit) + ExpectEq(0, objectData.Size) ExpectEq(io.EOF, err) } func (t *RandomReaderTest) NoExistingReader() { // The bucket should be called to set up a new reader. - ExpectCall(t.bucket, "NewReader")(Any(), Any()). - WillOnce(Return(nil, errors.New(""))) - buf := make([]byte, 1) - - _, _, err := t.rr.ReadAt(buf, 0) - - AssertNe(nil, err) -} - -func (t *RandomReaderTest) ExistingReader_WrongOffset() { - // Simulate an existing reader. - t.rr.wrapped.reader = io.NopCloser(strings.NewReader("xxx")) - t.rr.wrapped.cancel = func() {} - t.rr.wrapped.start = 2 - t.rr.wrapped.limit = 5 - // The bucket should be called to set up a new reader. - ExpectCall(t.bucket, "NewReader")(Any(), Any()). + ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). WillOnce(Return(nil, errors.New(""))) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) buf := make([]byte, 1) - _, _, err := t.rr.ReadAt(buf, 0) + _, err := t.rr.ReadAt(buf, 0) AssertNe(nil, err) } @@ -271,14 +264,15 @@ func (t *RandomReaderTest) ExistingReader_ReadAtOffsetAfterTheReaderPosition() { var readSize int64 = 1 var expectedStartOffsetAfterRead = readAtOffset + readSize // Simulate an existing reader. - rc := io.NopCloser(strings.NewReader(strings.Repeat("x", int(readerLimit)))) + nopCloser := io.NopCloser(strings.NewReader(strings.Repeat("x", int(readerLimit)))) + rc := &fake.FakeReader{ReadCloser: nopCloser} t.rr.wrapped.reader = rc t.rr.wrapped.cancel = func() {} t.rr.wrapped.start = currentStartOffset t.rr.wrapped.limit = readerLimit buf := make([]byte, readSize) - _, _, err := t.rr.ReadAt(buf, readAtOffset) + _, err := t.rr.ReadAt(buf, readAtOffset) AssertEq(nil, err) ExpectThat(rc, DeepEquals(t.rr.wrapped.reader)) @@ -287,52 +281,40 @@ func (t *RandomReaderTest) ExistingReader_ReadAtOffsetAfterTheReaderPosition() { } func (t *RandomReaderTest) NewReaderReturnsError() { - ExpectCall(t.bucket, "NewReader")(Any(), Any()). + ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). WillOnce(Return(nil, errors.New("taco"))) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) buf := make([]byte, 1) - _, _, err := t.rr.ReadAt(buf, 0) + _, err := t.rr.ReadAt(buf, 0) - ExpectThat(err, Error(HasSubstr("NewReader"))) + ExpectThat(err, Error(HasSubstr("NewReaderWithReadHandle"))) ExpectThat(err, Error(HasSubstr("taco"))) } func (t *RandomReaderTest) ReaderFails() { // Bucket r := iotest.OneByteReader(iotest.TimeoutReader(strings.NewReader("xxx"))) - rc := io.NopCloser(r) + rc := &fake.FakeReader{ReadCloser: io.NopCloser(r)} - ExpectCall(t.bucket, "NewReader")(Any(), Any()). + ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). WillOnce(Return(rc, nil)) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) // Call buf := make([]byte, 3) - _, _, err := t.rr.ReadAt(buf, 0) + _, err := t.rr.ReadAt(buf, 0) ExpectThat(err, Error(HasSubstr("readFull"))) ExpectThat(err, Error(HasSubstr(iotest.ErrTimeout.Error()))) } -func (t *RandomReaderTest) ReaderOvershootsRange() { - // Simulate a reader that is supposed to return two more bytes, but actually - // returns three when asked to. - t.rr.wrapped.reader = io.NopCloser(strings.NewReader("xxx")) - t.rr.wrapped.cancel = func() {} - t.rr.wrapped.start = 0 - t.rr.wrapped.limit = 2 - - // Try to read three bytes. - buf := make([]byte, 3) - _, _, err := t.rr.ReadAt(buf, 0) - - ExpectThat(err, Error(HasSubstr("1 too many bytes"))) -} - func (t *RandomReaderTest) ReaderNotExhausted() { // Set up a reader that has three bytes left to give. - rc := &countingCloser{ + cc := &countingCloser{ Reader: strings.NewReader("abc"), } + rc := &fake.FakeReader{ReadCloser: cc} t.rr.wrapped.reader = rc t.rr.wrapped.cancel = func() {} @@ -341,14 +323,14 @@ func (t *RandomReaderTest) ReaderNotExhausted() { // Read two bytes. buf := make([]byte, 2) - n, cacheHit, err := t.rr.ReadAt(buf, 1) + objectData, err := t.rr.ReadAt(buf, 1) - ExpectFalse(cacheHit) - ExpectEq(2, n) + ExpectFalse(objectData.CacheHit) + ExpectEq(2, objectData.Size) ExpectEq(nil, err) - ExpectEq("ab", string(buf[:n])) + ExpectEq("ab", string(buf[:objectData.Size])) - ExpectEq(0, rc.closeCount) + ExpectEq(0, cc.closeCount) ExpectEq(rc, t.rr.wrapped.reader) ExpectEq(3, t.rr.wrapped.start) ExpectEq(4, t.rr.wrapped.limit) @@ -360,48 +342,19 @@ func (t *RandomReaderTest) ReaderExhausted_ReadFinished() { Reader: strings.NewReader("abc"), } - t.rr.wrapped.reader = rc + t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: rc} t.rr.wrapped.cancel = func() {} t.rr.wrapped.start = 1 t.rr.wrapped.limit = 4 // Read three bytes. buf := make([]byte, 3) - n, cacheHit, err := t.rr.ReadAt(buf, 1) + objectData, err := t.rr.ReadAt(buf, 1) - ExpectFalse(cacheHit) - ExpectEq(3, n) + ExpectFalse(objectData.CacheHit) + ExpectEq(3, objectData.Size) ExpectEq(nil, err) - ExpectEq("abc", string(buf[:n])) - - ExpectEq(1, rc.closeCount) - ExpectEq(nil, t.rr.wrapped.reader) - ExpectEq(nil, t.rr.wrapped.cancel) - ExpectEq(4, t.rr.wrapped.limit) -} - -func (t *RandomReaderTest) ReaderExhausted_ReadNotFinished() { - // Set up a reader that has three bytes left to give. - rc := &countingCloser{ - Reader: strings.NewReader("abc"), - } - - t.rr.wrapped.reader = rc - t.rr.wrapped.cancel = func() {} - t.rr.wrapped.start = 1 - t.rr.wrapped.limit = 4 - - // The bucket should be called at the previous limit to obtain a new reader. - ExpectCall(t.bucket, "NewReader")(Any(), rangeStartIs(4)). - WillOnce(Return(nil, errors.New(""))) - - // Attempt to read four bytes. - buf := make([]byte, 4) - n, cacheHit, _ := t.rr.ReadAt(buf, 1) - - ExpectFalse(cacheHit) - AssertGe(n, 3) - ExpectEq("abc", string(buf[:3])) + ExpectEq("abc", string(buf[:objectData.Size])) ExpectEq(1, rc.closeCount) ExpectEq(nil, t.rr.wrapped.reader) @@ -414,7 +367,7 @@ func (t *RandomReaderTest) PropagatesCancellation() { finishRead := make(chan struct{}) rc := io.NopCloser(&blockingReader{finishRead}) - t.rr.wrapped.reader = rc + t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: rc} t.rr.wrapped.start = 1 t.rr.wrapped.limit = 4 @@ -452,7 +405,7 @@ func (t *RandomReaderTest) PropagatesCancellation() { func (t *RandomReaderTest) DoesntPropagateCancellationAfterReturning() { // Set up a reader that will return three bytes. - t.rr.wrapped.reader = io.NopCloser(strings.NewReader("xxx")) + t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte("xxx"))} t.rr.wrapped.start = 1 t.rr.wrapped.limit = 4 @@ -463,11 +416,11 @@ func (t *RandomReaderTest) DoesntPropagateCancellationAfterReturning() { // Successfully read two bytes using a context whose cancellation we control. ctx, cancel := context.WithCancel(context.Background()) buf := make([]byte, 2) - n, cacheHit, err := t.rr.wrapped.ReadAt(ctx, buf, 1) + objectData, err := t.rr.wrapped.ReadAt(ctx, buf, 1) - ExpectFalse(cacheHit) AssertEq(nil, err) - AssertEq(2, n) + ExpectFalse(objectData.CacheHit) + AssertEq(2, objectData.Size) // If we cancel the calling context now, it should not cause the underlying // read context to be cancelled. @@ -488,7 +441,7 @@ func (t *RandomReaderTest) UpgradesReadsToObjectSize() { AssertLt(readSize, objectSize) // Simulate an existing reader at a mismatched offset. - t.rr.wrapped.reader = io.NopCloser(strings.NewReader("xxx")) + t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte("xxx"))} t.rr.wrapped.cancel = func() {} t.rr.wrapped.start = 2 t.rr.wrapped.limit = 5 @@ -496,19 +449,20 @@ func (t *RandomReaderTest) UpgradesReadsToObjectSize() { // The bucket should be asked to read the entire object, even though we only // ask for readSize bytes below, to minimize the cost for GCS requests. r := strings.NewReader(strings.Repeat("x", objectSize)) - rc := io.NopCloser(r) + rc := &fake.FakeReader{ReadCloser: io.NopCloser(r)} - ExpectCall(t.bucket, "NewReader")( + ExpectCall(t.bucket, "NewReaderWithReadHandle")( Any(), AllOf(rangeStartIs(1), rangeLimitIs(objectSize))). WillOnce(Return(rc, nil)) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) // Call through. buf := make([]byte, readSize) - _, cacheHit, err := t.rr.ReadAt(buf, 1) + objectData, err := t.rr.ReadAt(buf, 1) // Check the state now. - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) ExpectEq(1+readSize, t.rr.wrapped.start) ExpectEq(objectSize, t.rr.wrapped.limit) @@ -527,28 +481,29 @@ func (t *RandomReaderTest) UpgradeReadsToAverageSize() { // Simulate an existing reader at a mismatched offset. t.rr.wrapped.seeks = numReads t.rr.wrapped.totalReadBytes = totalReadBytes - t.rr.wrapped.reader = io.NopCloser(strings.NewReader("xxx")) + t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte("xxx"))} t.rr.wrapped.cancel = func() {} t.rr.wrapped.start = 2 t.rr.wrapped.limit = 5 // The bucket should be asked to read expectedBytesToRead bytes. r := strings.NewReader(strings.Repeat("x", expectedBytesToRead)) - rc := io.NopCloser(r) + rc := &fake.FakeReader{ReadCloser: io.NopCloser(r)} - ExpectCall(t.bucket, "NewReader")( + ExpectCall(t.bucket, "NewReaderWithReadHandle")( Any(), AllOf( rangeStartIs(start), rangeLimitIs(start+expectedBytesToRead), )).WillOnce(Return(rc, nil)) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) // Call through. buf := make([]byte, readSize) - _, cacheHit, err := t.rr.ReadAt(buf, start) + objectData, err := t.rr.ReadAt(buf, start) // Check the state now. - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) AssertEq(nil, err) ExpectEq(start+expectedBytesToRead, t.rr.wrapped.limit) } @@ -562,37 +517,38 @@ func (t *RandomReaderTest) UpgradesSequentialReads_ExistingReader() { const existingSize = 3 r := strings.NewReader(strings.Repeat("x", existingSize)) - t.rr.wrapped.reader = io.NopCloser(r) + t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: io.NopCloser(r)} t.rr.wrapped.cancel = func() {} t.rr.wrapped.start = 1 t.rr.wrapped.limit = 1 + existingSize // The bucket should be asked to read up to the end of the object. - r = strings.NewReader(strings.Repeat("x", readSize-existingSize)) - rc := io.NopCloser(r) + r = strings.NewReader(strings.Repeat("y", readSize)) + rc := &fake.FakeReader{ReadCloser: io.NopCloser(r)} - ExpectCall(t.bucket, "NewReader")( + ExpectCall(t.bucket, "NewReaderWithReadHandle")( Any(), - AllOf(rangeStartIs(1+existingSize), rangeLimitIs(1+existingSize+sequentialReadSizeInBytes))). + AllOf(rangeStartIs(1), rangeLimitIs(1+sequentialReadSizeInBytes))). WillOnce(Return(rc, nil)) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) // Call through. buf := make([]byte, readSize) - _, cacheHit, err := t.rr.ReadAt(buf, 1) + objectData, err := t.rr.ReadAt(buf, 1) // Check the state now. - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) AssertEq(nil, err) ExpectEq(1+readSize, t.rr.wrapped.start) // Limit is same as the byteRange of last GCS call made. - ExpectEq(1+existingSize+sequentialReadSizeInBytes, t.rr.wrapped.limit) + ExpectEq(1+sequentialReadSizeInBytes, t.rr.wrapped.limit) } func (t *RandomReaderTest) UpgradesSequentialReads_NoExistingReader() { t.object.Size = 1 << 40 const readSize = 1 * MB // Set up the custom randomReader. - rr := NewRandomReader(t.object, t.bucket, readSize/MB, nil, false, common.NewNoopMetrics()) + rr := NewRandomReader(t.object, t.bucket, readSize/MB, nil, false, common.NewNoopMetrics(), nil) t.rr.wrapped = rr.(*randomReader) // Simulate a previous exhausted reader that ended at the offset from which @@ -601,146 +557,46 @@ func (t *RandomReaderTest) UpgradesSequentialReads_NoExistingReader() { t.rr.wrapped.limit = 1 // The bucket should be asked to read up to the end of the object. - r := strings.NewReader(strings.Repeat("x", readSize)) - rc := io.NopCloser(r) + data := strings.Repeat("x", readSize) + r := strings.NewReader(data) + rc := &fake.FakeReader{ReadCloser: io.NopCloser(r)} - ExpectCall(t.bucket, "NewReader")( + ExpectCall(t.bucket, "NewReaderWithReadHandle")( Any(), AllOf(rangeStartIs(1), rangeLimitIs(1+readSize))). WillOnce(Return(rc, nil)) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) // Call through. buf := make([]byte, readSize) - _, cacheHit, err := t.rr.ReadAt(buf, 1) + objectData, err := t.rr.ReadAt(buf, 1) // Check the state now. - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) + ExpectEq(data, string(objectData.DataBuf)) ExpectEq(1+readSize, t.rr.wrapped.start) ExpectEq(1+readSize, t.rr.wrapped.limit) } -func (t *RandomReaderTest) SequentialReads_NoExistingReader_requestedSizeGreaterThanChunkSize() { - t.object.Size = 1 << 40 - const chunkSize = 1 * MB - const readSize = 3 * MB - // Set up the custom randomReader. - rr := NewRandomReader(t.object, t.bucket, chunkSize/MB, nil, false, common.NewNoopMetrics()) - t.rr.wrapped = rr.(*randomReader) - // Create readers for each chunk. - chunk1Reader := strings.NewReader(strings.Repeat("x", chunkSize)) - chunk1RC := io.NopCloser(chunk1Reader) - chunk2Reader := strings.NewReader(strings.Repeat("x", chunkSize)) - chunk2RC := io.NopCloser(chunk2Reader) - chunk3Reader := strings.NewReader(strings.Repeat("x", chunkSize)) - chunk3RC := io.NopCloser(chunk3Reader) - // Mock the NewReader calls to return chunkReaders created above. - // We will make 3 GCS calls to satisfy the requested read size. But since we - // already have a reader with 'existingSize' data, we will first read that data - // and then make GCS calls. So call sequence is - // [0, chunkSize) -> newReader - // [hunkSize, chunkSize*2) -> newReader - // [chunkSize*2, chunkSize*3) -> newReader - ExpectCall(t.bucket, "NewReader")( - Any(), - AllOf(rangeStartIs(0), rangeLimitIs(chunkSize))). - WillOnce(Return(chunk1RC, nil)) - ExpectCall(t.bucket, "NewReader")( - Any(), - AllOf(rangeStartIs(chunkSize), rangeLimitIs(chunkSize*2))). - WillOnce(Return(chunk2RC, nil)) - ExpectCall(t.bucket, "NewReader")( - Any(), - AllOf(rangeStartIs(chunkSize*2), rangeLimitIs(chunkSize*3))). - WillOnce(Return(chunk3RC, nil)) - - // Call through. - buf := make([]byte, readSize) - _, cacheHit, err := t.rr.ReadAt(buf, 0) - - // Check the state now. - ExpectFalse(cacheHit) - ExpectEq(nil, err) - // Start is the total data read. - ExpectEq(readSize, t.rr.wrapped.start) - // Limit is same as the byteRange of last GCS call made. - ExpectEq(readSize, t.rr.wrapped.limit) -} - -func (t *RandomReaderTest) SequentialReads_existingReader_requestedSizeGreaterThanChunkSize() { - t.object.Size = 1 << 40 - const chunkSize = 1 * MB - const readSize = 3 * MB - // Set up the custom randomReader. - rr := NewRandomReader(t.object, t.bucket, chunkSize/MB, nil, false, common.NewNoopMetrics()) - t.rr.wrapped = rr.(*randomReader) - // Simulate an existing reader at the correct offset, which will be exhausted - // by the read below. - const existingSize = 3 - r := strings.NewReader(strings.Repeat("x", existingSize)) - t.rr.wrapped.reader = io.NopCloser(r) - t.rr.wrapped.cancel = func() {} - t.rr.wrapped.start = 0 - t.rr.wrapped.limit = existingSize - // Create readers for each chunk. - chunk1Reader := strings.NewReader(strings.Repeat("x", chunkSize)) - chunk1RC := io.NopCloser(chunk1Reader) - chunk2Reader := strings.NewReader(strings.Repeat("x", chunkSize)) - chunk2RC := io.NopCloser(chunk2Reader) - chunk3Reader := strings.NewReader(strings.Repeat("x", chunkSize)) - chunk3RC := io.NopCloser(chunk3Reader) - // Mock the NewReader calls to return chunkReaders created above. - // We will make 3 GCS calls to satisfy the requested read size. But since we - // already have a reader with 'existingSize' data, we will first read that data - // and then make GCS calls. So call sequence is - // [0, existingSize) -> existing reader - // [existingSize, existingSize+chunkSize) -> newReader - // [existingSize+chunkSize, existingSize+chunkSize*2) -> newReader - // [existingSize+chunkSize*2, existingSize+chunkSize*3) -> newReader - ExpectCall(t.bucket, "NewReader")( - Any(), - AllOf(rangeStartIs(existingSize), rangeLimitIs(existingSize+chunkSize))). - WillOnce(Return(chunk1RC, nil)) - ExpectCall(t.bucket, "NewReader")( - Any(), - AllOf(rangeStartIs(existingSize+chunkSize), rangeLimitIs(existingSize+chunkSize*2))). - WillOnce(Return(chunk2RC, nil)) - ExpectCall(t.bucket, "NewReader")( - Any(), - AllOf(rangeStartIs(existingSize+chunkSize*2), rangeLimitIs(existingSize+chunkSize*3))). - WillOnce(Return(chunk3RC, nil)) - - // Call through. - buf := make([]byte, readSize) - _, cacheHit, err := t.rr.ReadAt(buf, 0) - - // Check the state now. - ExpectFalse(cacheHit) - ExpectEq(nil, err) - // Start is the total data read. - ExpectEq(readSize, t.rr.wrapped.start) - // Limit is same as the byteRange of last GCS call made. - ExpectEq(existingSize+readSize, t.rr.wrapped.limit) -} - /******************* File cache specific tests ***********************/ func (t *RandomReaderTest) Test_ReadAt_SequentialFullObject() { t.rr.wrapped.fileCacheHandler = t.cacheHandler objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) buf := make([]byte, objectSize) - _, cacheHit, err := t.rr.ReadAt(buf, 0) - ExpectFalse(cacheHit) + objectData, err := t.rr.ReadAt(buf, 0) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent, buf)) - _, cacheHit, err = t.rr.ReadAt(buf, 0) + objectData, err = t.rr.ReadAt(buf, 0) - ExpectTrue(cacheHit) + ExpectTrue(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent, buf)) } @@ -749,17 +605,17 @@ func (t *RandomReaderTest) Test_ReadAt_SequentialRangeRead() { t.rr.wrapped.fileCacheHandler = t.cacheHandler objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) start := 0 end := 10 // not included AssertLt(end, objectSize) buf := make([]byte, end-start) - _, cacheHit, err := t.rr.ReadAt(buf, int64(start)) + objectData, err := t.rr.ReadAt(buf, int64(start)) - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent[start:end], buf)) } @@ -769,16 +625,16 @@ func (t *RandomReaderTest) Test_ReadAt_SequentialSubsequentReadOffsetLessThanRea t.object.Size = 20 * util.MiB objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) start1 := 0 end1 := util.MiB // not included AssertLt(end1, objectSize) // First call from offset 0 - sequential read buf := make([]byte, end1-start1) - _, cacheHit, err := t.rr.ReadAt(buf, int64(start1)) - ExpectFalse(cacheHit) + objectData, err := t.rr.ReadAt(buf, int64(start1)) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent[start1:end1], buf)) start2 := 3*util.MiB + 4 @@ -786,9 +642,9 @@ func (t *RandomReaderTest) Test_ReadAt_SequentialSubsequentReadOffsetLessThanRea buf2 := make([]byte, end2-start2) // Assuming start2 offset download in progress - _, cacheHit, err = t.rr.ReadAt(buf2, int64(start2)) + objectData, err = t.rr.ReadAt(buf2, int64(start2)) - ExpectTrue(cacheHit) + ExpectTrue(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent[start2:end2], buf2)) } @@ -800,12 +656,13 @@ func (t *RandomReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCache testContent := testutil.GenerateRandomBytes(int(objectSize)) start := 5 end := 10 // not included - rc := getReadCloser(testContent[start:]) - t.mockNewReaderCallForTestBucket(uint64(start), objectSize, rc) + rc := &fake.FakeReader{ReadCloser: getReadCloser(testContent[start:])} + t.mockNewReaderWithHandleCallForTestBucket(uint64(start), objectSize, rc) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(gcs.BucketType{})) buf := make([]byte, end-start) - _, cacheHit, err := t.rr.ReadAt(buf, int64(start)) - ExpectFalse(cacheHit) + objectData, err := t.rr.ReadAt(buf, int64(start)) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent[start:end], buf)) job := t.jobManager.CreateJobIfNotExists(t.object, t.bucket) @@ -813,10 +670,10 @@ func (t *RandomReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCache ExpectTrue(jobStatus.Name == downloader.NotStarted) // Second read call should be a cache miss - _, cacheHit, err = t.rr.ReadAt(buf, int64(start)) + objectData, err = t.rr.ReadAt(buf, int64(start)) ExpectEq(nil, err) - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) } func (t *RandomReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCacheForRangeReadIsTrue() { @@ -826,16 +683,19 @@ func (t *RandomReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCache testContent := testutil.GenerateRandomBytes(int(objectSize)) start := 5 end := 10 // not included - rc := getReadCloser(testContent[start:]) - t.mockNewReaderCallForTestBucket(uint64(start), objectSize, rc) // Mock for random-reader's NewReader call - rc2 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc2) // Mock for download job's NewReader call + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent[start:])} + // Mock for random-reader's NewReader call + t.mockNewReaderWithHandleCallForTestBucket(uint64(start), objectSize, rd) + rd1 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + // Mock for download job's NewReader call + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd1) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) buf := make([]byte, end-start) - _, cacheHit, err := t.rr.ReadAt(buf, int64(start)) + objectData, err := t.rr.ReadAt(buf, int64(start)) - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent[start:end], buf)) job := t.jobManager.GetJob(t.object.Name, t.bucket.Name()) @@ -847,28 +707,31 @@ func (t *RandomReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffsetMor t.object.Size = 20 * util.MiB objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + // Mock for download job's NewReader call + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) start1 := 0 end1 := util.MiB // not included AssertLt(end1, objectSize) // First call from offset 0 - sequential read buf := make([]byte, end1-start1) - _, cacheHit, err := t.rr.ReadAt(buf, int64(start1)) - ExpectFalse(cacheHit) + objectData, err := t.rr.ReadAt(buf, int64(start1)) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent[start1:end1], buf)) start2 := 16*util.MiB + 4 end2 := start2 + util.MiB - rc2 := getReadCloser(testContent[start2:]) - t.mockNewReaderCallForTestBucket(uint64(start2), objectSize, rc2) + rd2 := &fake.FakeReader{ReadCloser: getReadCloser(testContent[start2:])} + // Mock for random-reader's NewReader call + t.mockNewReaderWithHandleCallForTestBucket(uint64(start2), objectSize, rd2) buf2 := make([]byte, end2-start2) // Assuming start2 offset download in progress - _, cacheHit, err = t.rr.ReadAt(buf2, int64(start2)) + objectData, err = t.rr.ReadAt(buf2, int64(start2)) - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent[start2:end2], buf2)) } @@ -880,34 +743,37 @@ func (t *RandomReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffsetLes testContent := testutil.GenerateRandomBytes(int(objectSize)) rc := getReadCloser(testContent) t.mockNewReaderCallForTestBucket(0, objectSize, rc) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) start1 := 0 end1 := util.MiB // not included AssertLt(end1, objectSize) // First call from offset 0 - sequential read buf := make([]byte, end1-start1) - _, cacheHit, err := t.rr.ReadAt(buf, int64(start1)) - ExpectFalse(cacheHit) + objectData, err := t.rr.ReadAt(buf, int64(start1)) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent[start1:end1], buf)) start2 := 16*util.MiB + 4 end2 := start2 + util.MiB - rc2 := getReadCloser(testContent[start2:]) - t.mockNewReaderCallForTestBucket(uint64(start2), objectSize, rc2) + rc2 := &fake.FakeReader{ReadCloser: getReadCloser(testContent[start2:])} + t.mockNewReaderWithHandleCallForTestBucket(uint64(start2), objectSize, rc2) buf2 := make([]byte, end2-start2) // Assuming start2 offset download in progress - _, cacheHit, err = t.rr.ReadAt(buf2, int64(start2)) - ExpectFalse(cacheHit) + objectData, err = t.rr.ReadAt(buf2, int64(start2)) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent[start2:end2], buf2)) start3 := util.MiB end3 := start3 + util.MiB buf3 := make([]byte, end3-start3) - _, cacheHit, err = t.rr.ReadAt(buf3, int64(start3)) + objectData, err = t.rr.ReadAt(buf3, int64(start3)) ExpectEq(nil, err) - ExpectTrue(cacheHit) + ExpectTrue(objectData.CacheHit) ExpectTrue(reflect.DeepEqual(testContent[start3:end3], buf3)) } @@ -915,30 +781,32 @@ func (t *RandomReaderTest) Test_ReadAt_CacheMissDueToInvalidJob() { t.rr.wrapped.fileCacheHandler = t.cacheHandler objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc1 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc1) + rc1 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rc1) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) buf := make([]byte, objectSize) - _, cacheHit, err := t.rr.ReadAt(buf, 0) + objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) AssertTrue(reflect.DeepEqual(testContent, buf)) job := t.jobManager.GetJob(t.object.Name, t.bucket.Name()) if job != nil { jobStatus := job.GetStatus().Name AssertTrue(jobStatus == downloader.Downloading || jobStatus == downloader.Completed, fmt.Sprintf("the actual status is %v", jobStatus)) } + err = t.rr.wrapped.fileCacheHandler.InvalidateCache(t.object.Name, t.bucket.Name()) AssertEq(nil, err) // Second reader (rc2) is required, since first reader (rc) is completely read. // Reading again will return EOF. - rc2 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc2) + rc2 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rc2) - _, cacheHit, err = t.rr.ReadAt(buf, 0) + objectData, err = t.rr.ReadAt(buf, 0) ExpectEq(nil, err) - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) ExpectTrue(reflect.DeepEqual(testContent, buf)) ExpectEq(nil, t.rr.wrapped.fileCacheHandle) } @@ -947,13 +815,14 @@ func (t *RandomReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInvali t.rr.wrapped.fileCacheHandler = t.cacheHandler objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc1 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc1) + rd1 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd1) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) buf := make([]byte, objectSize) - _, cacheHit, err := t.rr.ReadAt(buf, 0) + objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) - AssertFalse(cacheHit) + AssertFalse(objectData.CacheHit) AssertTrue(reflect.DeepEqual(testContent, buf)) job := t.jobManager.GetJob(t.object.Name, t.bucket.Name()) if job != nil { @@ -964,20 +833,20 @@ func (t *RandomReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInvali AssertEq(nil, err) // Second reader (rc2) is required, since first reader (rc) is completely read. // Reading again will return EOF. - rc2 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc2) - _, cacheHit, err = t.rr.ReadAt(buf, 0) + rc2 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rc2) + objectData, err = t.rr.ReadAt(buf, 0) ExpectEq(nil, err) - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) ExpectTrue(reflect.DeepEqual(testContent, buf)) ExpectEq(nil, t.rr.wrapped.fileCacheHandle) - rc3 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc3) + rd3 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd3) - _, cacheHit, err = t.rr.ReadAt(buf, 0) + objectData, err = t.rr.ReadAt(buf, 0) ExpectEq(nil, err) - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) ExpectTrue(reflect.DeepEqual(testContent, buf)) ExpectNe(nil, t.rr.wrapped.fileCacheHandle) } @@ -986,33 +855,35 @@ func (t *RandomReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInvali t.rr.wrapped.fileCacheHandler = t.cacheHandler objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc1 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc1) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) buf := make([]byte, objectSize) - _, cacheHit, err := t.rr.ReadAt(buf, 0) + objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) - AssertFalse(cacheHit) + AssertFalse(objectData.CacheHit) AssertTrue(reflect.DeepEqual(testContent, buf)) AssertNe(nil, t.rr.wrapped.fileCacheHandle) err = t.rr.wrapped.fileCacheHandle.Close() AssertEq(nil, err) // Second reader (rc2) is required, since first reader (rc) is completely read. // Reading again will return EOF. - rc2 := getReadCloser(testContent) + rc2 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderCallForTestBucket(0, objectSize, rc2) - _, cacheHit, err = t.rr.ReadAt(buf, 0) + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rc2) + objectData, err = t.rr.ReadAt(buf, 0) ExpectEq(nil, err) - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) ExpectTrue(reflect.DeepEqual(testContent, buf)) ExpectEq(nil, t.rr.wrapped.fileCacheHandle) - rc3 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc3) + rc3 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rc3) - _, cacheHit, err = t.rr.ReadAt(buf, 0) + objectData, err = t.rr.ReadAt(buf, 0) ExpectEq(nil, err) - ExpectTrue(cacheHit) + ExpectTrue(objectData.CacheHit) ExpectTrue(reflect.DeepEqual(testContent, buf)) ExpectNe(nil, t.rr.wrapped.fileCacheHandle) } @@ -1021,13 +892,13 @@ func (t *RandomReaderTest) Test_ReadAt_IfCacheFileGetsDeleted() { t.rr.wrapped.fileCacheHandler = t.cacheHandler objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc1 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc1) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) buf := make([]byte, objectSize) - _, cacheHit, err := t.rr.ReadAt(buf, 0) + objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) - AssertFalse(cacheHit) + AssertFalse(objectData.CacheHit) AssertTrue(reflect.DeepEqual(testContent, buf)) AssertNe(nil, t.rr.wrapped.fileCacheHandle) err = t.rr.wrapped.fileCacheHandle.Close() @@ -1039,10 +910,9 @@ func (t *RandomReaderTest) Test_ReadAt_IfCacheFileGetsDeleted() { AssertEq(nil, err) // Second reader (rc2) is required, since first reader (rc) is completely read. // Reading again will return EOF. - rc2 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc2) + t.mockNewReaderCallForTestBucket(0, objectSize, getReadCloser(testContent)) - _, _, err = t.rr.ReadAt(buf, 0) + _, err = t.rr.ReadAt(buf, 0) AssertNe(nil, err) ExpectTrue(strings.Contains(err.Error(), util.FileNotPresentInCacheErrMsg)) @@ -1052,13 +922,13 @@ func (t *RandomReaderTest) Test_ReadAt_IfCacheFileGetsDeletedWithCacheHandleOpen t.rr.wrapped.fileCacheHandler = t.cacheHandler objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc1 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc1) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) buf := make([]byte, objectSize) - _, cacheHit, err := t.rr.ReadAt(buf, 0) + objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) - AssertFalse(cacheHit) + AssertFalse(objectData.CacheHit) AssertTrue(reflect.DeepEqual(testContent, buf)) AssertNe(nil, t.rr.wrapped.fileCacheHandle) // Delete the local cache file. @@ -1068,10 +938,10 @@ func (t *RandomReaderTest) Test_ReadAt_IfCacheFileGetsDeletedWithCacheHandleOpen // Read via cache only, as we have old fileHandle open and linux // doesn't delete the file until the fileHandle count for the file is zero. - _, cacheHit, err = t.rr.ReadAt(buf, 0) + objectData, err = t.rr.ReadAt(buf, 0) AssertEq(nil, err) - ExpectTrue(cacheHit) + ExpectTrue(objectData.CacheHit) ExpectTrue(reflect.DeepEqual(testContent, buf)) ExpectNe(nil, t.rr.wrapped.fileCacheHandle) } @@ -1080,35 +950,37 @@ func (t *RandomReaderTest) Test_ReadAt_FailedJobRestartAndCacheHit() { t.rr.wrapped.fileCacheHandler = t.cacheHandler objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc1 := getReadCloser(testContent) - // First NewReader-call throws error, hence async job fails. + rc := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + // First call goes to file cache succeeded by next call to random reader. + // First NewReaderWithReadHandle-call throws error, hence async job fails. // Later NewReader-call returns a valid readCloser object hence fallback to // GCS read will succeed. - ExpectCall(t.bucket, "NewReader")(Any(), Any()). - WillOnce(Return(nil, errors.New(""))).WillRepeatedly(Return(rc1, nil)) + ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). + WillOnce(Return(nil, errors.New(""))).WillRepeatedly(Return(rc, nil)) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) buf := make([]byte, objectSize) - _, cacheHit, err := t.rr.ReadAt(buf, 0) + objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) AssertTrue(reflect.DeepEqual(testContent, buf)) job := t.jobManager.GetJob(t.object.Name, t.bucket.Name()) AssertTrue(job == nil || job.GetStatus().Name == downloader.Failed) // Second reader (rc2) is required, since first reader (rc) is completely read. // Reading again will return EOF. - rc2 := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc2) + rd2 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd2) // This call will populate the cache again. - _, cacheHit, err = t.rr.ReadAt(buf, 0) + objectData, err = t.rr.ReadAt(buf, 0) ExpectEq(nil, err) - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) ExpectTrue(reflect.DeepEqual(testContent, buf)) ExpectNe(nil, t.rr.wrapped.fileCacheHandle) - _, cacheHit, err = t.rr.ReadAt(buf, 0) + objectData, err = t.rr.ReadAt(buf, 0) ExpectEq(nil, err) - ExpectTrue(cacheHit) + ExpectTrue(objectData.CacheHit) ExpectTrue(reflect.DeepEqual(testContent, buf)) ExpectNe(nil, t.rr.wrapped.fileCacheHandle) } @@ -1119,8 +991,8 @@ func (t *RandomReaderTest) Test_tryReadingFromFileCache_CacheHit() { t.rr.wrapped.fileCacheHandler = t.cacheHandler objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) buf := make([]byte, objectSize) // First read will be a cache miss. @@ -1155,15 +1027,15 @@ func (t *RandomReaderTest) Test_ReadAt_OffsetEqualToObjectSize() { t.object.Size = util.MiB objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) start1 := 0 end1 := util.MiB // equal to objectSize // First call from offset 0 - objectSize buf := make([]byte, end1-start1) - _, cacheHit, err := t.rr.ReadAt(buf, int64(start1)) - ExpectFalse(cacheHit) + objectData, err := t.rr.ReadAt(buf, int64(start1)) + ExpectFalse(objectData.CacheHit) ExpectEq(nil, err) ExpectTrue(reflect.DeepEqual(testContent[start1:end1], buf)) start2 := util.MiB // offset equal to objectSize @@ -1171,12 +1043,12 @@ func (t *RandomReaderTest) Test_ReadAt_OffsetEqualToObjectSize() { buf2 := make([]byte, end2-start2) // read for offset equal to objectSize - n, cacheHit, err := t.rr.ReadAt(buf2, int64(start2)) + objectData, err = t.rr.ReadAt(buf2, int64(start2)) // nothing should be read - ExpectFalse(cacheHit) + ExpectFalse(objectData.CacheHit) ExpectEq(io.EOF, err) - ExpectEq(0, n) + ExpectEq(0, objectData.Size) } func (t *RandomReaderTest) Test_Destroy_NilCacheHandle() { @@ -1191,8 +1063,8 @@ func (t *RandomReaderTest) Test_Destroy_NonNilCacheHandle() { t.rr.wrapped.fileCacheHandler = t.cacheHandler objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) buf := make([]byte, objectSize) _, cacheHit, err := t.rr.wrapped.tryReadingFromFileCache(t.rr.ctx, buf, 0) @@ -1209,10 +1081,10 @@ func (t *RandomReaderTest) Test_Destroy_NonNilCacheHandle() { func (t *RandomReaderTest) TestNewReader_FileClobbered() { var notFoundError *gcs.NotFoundError - ExpectCall(t.bucket, "NewReader")(Any(), Any()). + ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). WillOnce(Return(nil, notFoundError)) - err := t.rr.wrapped.startRead(t.rr.ctx, 0, 1) + err := t.rr.wrapped.startRead(0, 1) AssertNe(nil, err) var clobberedErr *gcsfuse_errors.FileClobberedError diff --git a/internal/gcsx/syncer.go b/internal/gcsx/syncer.go index 3b37fee257..be00105acb 100644 --- a/internal/gcsx/syncer.go +++ b/internal/gcsx/syncer.go @@ -42,14 +42,14 @@ type Syncer interface { // NewSyncer creates a syncer that syncs into the supplied bucket. // // When the source object has been changed only by appending, and the source -// object's size is at least appendThreshold, we will "append" to it by writing +// object's size is at least composeThreshold, we will "append" to it by writing // out a temporary blob and composing it with the source object. // // Temporary blobs have names beginning with tmpObjectPrefix. We make an effort // to delete them, but if we are interrupted for some reason we may not be able // to do so. Therefore the user should arrange for garbage collection. func NewSyncer( - appendThreshold int64, + composeThreshold int64, chunkTransferTimeoutSecs int64, tmpObjectPrefix string, bucket gcs.Bucket) (os Syncer) { @@ -58,12 +58,17 @@ func NewSyncer( bucket: bucket, } - appendCreator := newAppendObjectCreator( - tmpObjectPrefix, - bucket) + // Zonal buckets do not currently support Compose, so we always write objects + // in their entirety. + var composeCreator objectCreator + if !bucket.BucketType().Zonal { + composeCreator = newComposeObjectCreator( + tmpObjectPrefix, + bucket) + } // And the syncer. - os = newSyncer(appendThreshold, chunkTransferTimeoutSecs, fullCreator, appendCreator) + os = newSyncer(composeThreshold, chunkTransferTimeoutSecs, fullCreator, composeCreator) return } @@ -115,33 +120,33 @@ type objectCreator interface { // - fullCreator accepts the source object and the full contents with which it // should be overwritten. // -// - appendCreator accepts the source object and the contents that should be +// - composeCreator accepts the source object and the contents that should be // "appended" to it. // -// appendThreshold controls the source object length at which we consider it +// composeThreshold controls the source object length at which we consider it // worthwhile to make the append optimization. It should be set to a value on // the order of the bandwidth to GCS times three times the round trip latency // to GCS (for a small create, a compose, and a delete). func newSyncer( - appendThreshold int64, + composeThreshold int64, chunkTransferTimeoutSecs int64, fullCreator objectCreator, - appendCreator objectCreator) (os Syncer) { + composeCreator objectCreator) (os Syncer) { os = &syncer{ - appendThreshold: appendThreshold, + composeThreshold: composeThreshold, chunkTransferTimeoutSecs: chunkTransferTimeoutSecs, fullCreator: fullCreator, - appendCreator: appendCreator, + composeCreator: composeCreator, } return } type syncer struct { - appendThreshold int64 + composeThreshold int64 chunkTransferTimeoutSecs int64 fullCreator objectCreator - appendCreator objectCreator + composeCreator objectCreator } func (os *syncer) SyncObject( @@ -197,7 +202,7 @@ func (os *syncer) SyncObject( // Otherwise, we need to create a new generation. If the source object is // long enough, hasn't been dirtied, and has a low enough component count, // then we can make the optimization of not rewriting its contents. - if srcSize >= os.appendThreshold && + if os.composeCreator != nil && srcSize >= os.composeThreshold && sr.DirtyThreshold == srcSize && srcObject.ComponentCount < gcs.MaxComponentCount { _, err = content.Seek(srcSize, 0) @@ -206,7 +211,7 @@ func (os *syncer) SyncObject( return } - o, err = os.appendCreator.Create(ctx, objectName, srcObject, sr.Mtime, os.chunkTransferTimeoutSecs, content) + o, err = os.composeCreator.Create(ctx, objectName, srcObject, sr.Mtime, os.chunkTransferTimeoutSecs, content) } else { _, err = content.Seek(0, 0) if err != nil { diff --git a/internal/gcsx/syncer_test.go b/internal/gcsx/syncer_test.go index 0d42059ca7..b7d8fe39c3 100644 --- a/internal/gcsx/syncer_test.go +++ b/internal/gcsx/syncer_test.go @@ -296,7 +296,7 @@ func (t *SyncerTest) SetUp(ti *TestInfo) { t.ctx = ti.Ctx // Set up dependencies. - t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.NonHierarchical) + t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{}) t.syncer = newSyncer( appendThreshold, chunkTransferTimeoutSecs, diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index ef60e1804e..ad695ff848 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -20,6 +20,7 @@ import ( "io" "time" + storagev2 "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" ) @@ -33,6 +34,10 @@ func recordRequest(ctx context.Context, metricHandle common.MetricHandle, method metricHandle.GCSRequestLatency(ctx, latencyMs, []common.MetricAttr{{Key: common.GCSMethod, Value: method}}) } +func CaptureMultiRangeDownloaderMetrics(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time) { + recordRequest(ctx, metricHandle, method, start) +} + // NewMonitoringBucket returns a gcs.Bucket that exports metrics for monitoring func NewMonitoringBucket(b gcs.Bucket, m common.MetricHandle) gcs.Bucket { return &monitoringBucket{ @@ -54,17 +59,30 @@ func (mb *monitoringBucket) BucketType() gcs.BucketType { return mb.wrapped.BucketType() } -func (mb *monitoringBucket) NewReader( - ctx context.Context, - req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { +func setupReader(ctx context.Context, mb *monitoringBucket, req *gcs.ReadObjectRequest, method string) (gcs.StorageReader, error) { startTime := time.Now() - rc, err = mb.wrapped.NewReader(ctx, req) + rc, err := mb.wrapped.NewReaderWithReadHandle(ctx, req) if err == nil { rc = newMonitoringReadCloser(ctx, req.Name, rc, mb.metricHandle) } - recordRequest(ctx, mb.metricHandle, "NewReader", startTime) + recordRequest(ctx, mb.metricHandle, method, startTime) + return rc, err +} + +func (mb *monitoringBucket) NewReader( + ctx context.Context, + req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { + rc, err = setupReader(ctx, mb, req, "NewReader") + return +} + +func (mb *monitoringBucket) NewReaderWithReadHandle( + ctx context.Context, + req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { + // Using NewReader here also as NewReader() method is not used and will be removed. + rd, err = setupReader(ctx, mb, req, "NewReader") return } @@ -180,13 +198,21 @@ func (mb *monitoringBucket) RenameFolder(ctx context.Context, folderName string, return } +func (mb *monitoringBucket) NewMultiRangeDownloader( + ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (mrd gcs.MultiRangeDownloader, err error) { + startTime := time.Now() + mrd, err = mb.wrapped.NewMultiRangeDownloader(ctx, req) + recordRequest(ctx, mb.metricHandle, "NewMultiRangeDownloader", startTime) + return +} + // recordReader increments the reader count when it's opened or closed. func recordReader(ctx context.Context, metricHandle common.MetricHandle, ioMethod string) { metricHandle.GCSReaderCount(ctx, 1, []common.MetricAttr{{Key: common.IOMethod, Value: ioMethod}}) } // Monitoring on the object reader -func newMonitoringReadCloser(ctx context.Context, object string, rc io.ReadCloser, metricHandle common.MetricHandle) io.ReadCloser { +func newMonitoringReadCloser(ctx context.Context, object string, rc gcs.StorageReader, metricHandle common.MetricHandle) gcs.StorageReader { recordReader(ctx, metricHandle, "opened") return &monitoringReadCloser{ ctx: ctx, @@ -199,7 +225,7 @@ func newMonitoringReadCloser(ctx context.Context, object string, rc io.ReadClose type monitoringReadCloser struct { ctx context.Context object string - wrapped io.ReadCloser + wrapped gcs.StorageReader metricHandle common.MetricHandle } @@ -219,3 +245,9 @@ func (mrc *monitoringReadCloser) Close() (err error) { recordReader(mrc.ctx, mrc.metricHandle, "closed") return } + +func (mrc *monitoringReadCloser) ReadHandle() (rh storagev2.ReadHandle) { + rh = mrc.wrapped.ReadHandle() + recordReader(mrc.ctx, mrc.metricHandle, "ReadHandle") + return +} diff --git a/internal/ratelimit/throttled_bucket.go b/internal/ratelimit/throttled_bucket.go index 25899b1c6b..2a948aa36f 100644 --- a/internal/ratelimit/throttled_bucket.go +++ b/internal/ratelimit/throttled_bucket.go @@ -17,6 +17,7 @@ package ratelimit import ( "io" + storagev2 "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "golang.org/x/net/context" ) @@ -57,6 +58,13 @@ func (b *throttledBucket) BucketType() gcs.BucketType { func (b *throttledBucket) NewReader( ctx context.Context, req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { + rc, err = b.NewReaderWithReadHandle(ctx, req) + return +} + +func (b *throttledBucket) NewReaderWithReadHandle( + ctx context.Context, + req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { // Wait for permission to call through. err = b.opThrottle.Wait(ctx, 1) @@ -65,15 +73,15 @@ func (b *throttledBucket) NewReader( } // Call through. - rc, err = b.wrapped.NewReader(ctx, req) + rd, err = b.wrapped.NewReaderWithReadHandle(ctx, req) if err != nil { return } // Wrap the result in a throttled layer. - rc = &readerCloser{ - Reader: ThrottledReader(ctx, rc, b.egressThrottle), - Closer: rc, + rd = &throttledGCSReader{ + Reader: ThrottledReader(ctx, rd, b.egressThrottle), + Closer: rd, } return @@ -269,23 +277,35 @@ func (b *throttledBucket) CreateFolder(ctx context.Context, folderName string) ( return folder, err } +func (b *throttledBucket) NewMultiRangeDownloader( + ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (mrd gcs.MultiRangeDownloader, err error) { + // Call through. + mrd, err = b.wrapped.NewMultiRangeDownloader(ctx, req) + return +} + //////////////////////////////////////////////////////////////////////// // readerCloser //////////////////////////////////////////////////////////////////////// // An io.ReadCloser that forwards read requests to an io.Reader and close -// requests to an io.Closer. -type readerCloser struct { +// , readHandle requests to gcs.StorageReader. +type throttledGCSReader struct { Reader io.Reader - Closer io.Closer + Closer gcs.StorageReader } -func (rc *readerCloser) Read(p []byte) (n int, err error) { +func (rc *throttledGCSReader) Read(p []byte) (n int, err error) { n, err = rc.Reader.Read(p) return } -func (rc *readerCloser) Close() (err error) { +func (rc *throttledGCSReader) Close() (err error) { err = rc.Closer.Close() return } + +func (rc *throttledGCSReader) ReadHandle() (rh storagev2.ReadHandle) { + rh = rc.Closer.ReadHandle() + return +} diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index a8328be2c5..f06f0faaa3 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -28,7 +28,6 @@ import ( "time" "cloud.google.com/go/storage" - control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googleapis/gax-go/v2" "github.com/googleapis/gax-go/v2/apierror" @@ -48,7 +47,7 @@ type bucketHandle struct { gcs.Bucket bucket *storage.BucketHandle bucketName string - bucketType gcs.BucketType + bucketType *gcs.BucketType controlClient StorageControlClient } @@ -57,43 +56,19 @@ func (bh *bucketHandle) Name() string { } func (bh *bucketHandle) BucketType() gcs.BucketType { - var nilControlClient *control.StorageControlClient = nil - // Note: The first invocation of this method will be slower due to a required Google Cloud Storage (GCS) fetch. - // Subsequent calls will be significantly faster as the results are cached in memory. - // While this operation is thread-safe, parallel calls during the initial fetch can result in redundant GCS requests. - // To avoid this, it's advisable to call this initially while mounting. - if bh.bucketType == gcs.Nil { - if bh.controlClient == nilControlClient { - bh.bucketType = gcs.NonHierarchical - return bh.bucketType - } - startTime := time.Now() - logger.Infof("GetStorageLayout <- (%s)", bh.bucketName) - storageLayout, err := bh.getStorageLayout() - duration := time.Since(startTime) - // In case bucket does not exist, set type unknown instead of panic. - if err != nil { - bh.bucketType = gcs.Unknown - logger.Errorf("Error returned from GetStorageLayout: %v", err) - return bh.bucketType - } - logger.Infof("GetStorageLayout -> (%s) %v msec", bh.bucketName, duration.Milliseconds()) - - hierarchicalNamespace := storageLayout.GetHierarchicalNamespace() - if hierarchicalNamespace != nil && hierarchicalNamespace.Enabled { - bh.bucketType = gcs.Hierarchical - return bh.bucketType - } - - bh.bucketType = gcs.NonHierarchical - } - - return bh.bucketType + return *bh.bucketType } func (bh *bucketHandle) NewReader( ctx context.Context, req *gcs.ReadObjectRequest) (io.ReadCloser, error) { + rc, err := bh.NewReaderWithReadHandle(ctx, req) + return rc, err +} + +func (bh *bucketHandle) NewReaderWithReadHandle( + ctx context.Context, + req *gcs.ReadObjectRequest) (gcs.StorageReader, error) { // Initialising the starting offset and the length to be read by the reader. start := int64(0) length := int64(-1) @@ -116,6 +91,14 @@ func (bh *bucketHandle) NewReader( obj = obj.ReadCompressed(true) } + // Insert ReadHandle into objectHandle if present. + // Objects that have been opened can be opened again using readHandle at lower latency. + // This produces the exact same object and generation and does not check if + // the generation is still the newest one. + if req.ReadHandle != nil { + obj = obj.ReadHandle(req.ReadHandle) + } + // NewRangeReader creates a "storage.Reader" object which is also io.ReadCloser since it contains both Read() and Close() methods present in io.ReadCloser interface. r, err := obj.NewRangeReader(ctx, start, length) @@ -125,6 +108,7 @@ func (bh *bucketHandle) NewReader( return r, err } + func (bh *bucketHandle) DeleteObject(ctx context.Context, req *gcs.DeleteObjectRequest) error { obj := bh.bucket.Object(req.Name) @@ -228,6 +212,8 @@ func (bh *bucketHandle) CreateObject(ctx context.Context, req *gcs.CreateObjectR wc.ProgressFunc = func(bytesUploadedSoFar int64) { logger.Tracef("gcs: Req %#16x: -- CreateObject(%q): %20v bytes uploaded so far", ctx.Value(gcs.ReqIdField), req.Name, bytesUploadedSoFar) } + // All objects in zonal buckets must be appendable. + wc.Append = bh.BucketType().Zonal // Copy the contents to the writer. if _, err = io.Copy(wc, req.Contents); err != nil { @@ -277,6 +263,8 @@ func (bh *bucketHandle) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cr } } wc.ProgressFunc = callBack + // All objects in zonal buckets must be appendable. + wc.Append = bh.BucketType().Zonal return wc, nil } @@ -674,6 +662,22 @@ func (bh *bucketHandle) CreateFolder(ctx context.Context, folderName string) (*g return folder, nil } +func (bh *bucketHandle) NewMultiRangeDownloader( + ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (gcs.MultiRangeDownloader, error) { + obj := bh.bucket.Object(req.Name) + + // Switching to the requested generation of object. + if req.Generation != 0 { + obj = obj.Generation(req.Generation) + } + + if req.ReadCompressed { + obj = obj.ReadCompressed(true) + } + + return obj.NewMultiRangeDownloader(ctx) +} + func isStorageConditionsNotEmpty(conditions storage.Conditions) bool { return conditions != (storage.Conditions{}) } diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 33dd8864aa..e82664b9a2 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -28,6 +28,7 @@ import ( control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googleapis/gax-go/v2/apierror" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -74,6 +75,17 @@ func minObjectsToMinObjectNames(minObjects []*gcs.MinObject) (objectNames []stri return } +func createBucketHandle(testSuite *BucketHandleTest, resp *controlpb.StorageLayout, err1 error) { + var err error + testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). + Return(resp, err1) + testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "") + testSuite.bucketHandle.controlClient = testSuite.mockClient + + assert.NotNil(testSuite.T(), testSuite.bucketHandle) + assert.Nil(testSuite.T(), err) +} + type BucketHandleTest struct { suite.Suite bucketHandle *bucketHandle @@ -87,14 +99,9 @@ func TestBucketHandleTestSuite(testSuite *testing.T) { } func (testSuite *BucketHandleTest) SetupTest() { - testSuite.fakeStorage = NewFakeStorage() - testSuite.storageHandle = testSuite.fakeStorage.CreateStorageHandle() - ctx := context.Background() - testSuite.bucketHandle = testSuite.storageHandle.BucketHandle(ctx, TestBucketName, "") testSuite.mockClient = new(MockStorageControlClient) - testSuite.bucketHandle.controlClient = testSuite.mockClient - - assert.NotNil(testSuite.T(), testSuite.bucketHandle) + testSuite.fakeStorage = NewFakeStorageWithMockClient(testSuite.mockClient, cfg.HTTP2) + testSuite.storageHandle = testSuite.fakeStorage.CreateStorageHandle() } func (testSuite *BucketHandleTest) TearDownTest() { @@ -247,7 +254,50 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithCompressionDisabled() assert.Equal(testSuite.T(), ContentInTestGzipObjectDecompressed, string(buf)) } +// Fakestorage doesn't support readHandle concept +func (testSuite *BucketHandleTest) TestNewReaderWithReadHandleMethodWithoutReadHandle() { + rd, err := testSuite.bucketHandle.NewReaderWithReadHandle(context.Background(), + &gcs.ReadObjectRequest{ + Name: TestObjectName, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: uint64(len(ContentInTestObject)), + }, + ReadHandle: nil, + }) + + assert.Nil(testSuite.T(), err) + defer rd.Close() + buf := make([]byte, len(ContentInTestObject)) + _, err = rd.Read(buf) + assert.Nil(testSuite.T(), err) + //assert.Equal(testSuite.T(), len(rd.ReadHandle()), 0) + assert.Equal(testSuite.T(), ContentInTestObject, string(buf[:])) +} + +// Fakestorage doesn't support readHandle concept +func (testSuite *BucketHandleTest) TestNewReaderWithReadHandleMethodWithReadHandle() { + rd, err := testSuite.bucketHandle.NewReaderWithReadHandle(context.Background(), + &gcs.ReadObjectRequest{ + Name: TestObjectName, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: uint64(len(ContentInTestObject)), + }, + ReadHandle: []byte("opaque-handle"), + }) + + assert.Nil(testSuite.T(), err) + defer rd.Close() + buf := make([]byte, len(ContentInTestObject)) + _, err = rd.Read(buf) + assert.Nil(testSuite.T(), err) + //assert.Equal(testSuite.T(), len(rd.ReadHandle()), 0) + assert.Equal(testSuite.T(), ContentInTestObject, string(buf[:])) +} + func (testSuite *BucketHandleTest) TestDeleteObjectMethodWithValidObject() { + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) err := testSuite.bucketHandle.DeleteObject(context.Background(), &gcs.DeleteObjectRequest{ Name: TestObjectName, @@ -280,7 +330,8 @@ func (testSuite *BucketHandleTest) TestDeleteObjectMethodWithMissingGeneration() } func (testSuite *BucketHandleTest) TestDeleteObjectMethodWithZeroGeneration() { - // Note: fake-gcs-server doesn'testSuite respect Generation or other conditions in + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + // Note: fake-gcs-server doesn't respect Generation or other conditions in // delete operations. This unit test will be helpful when fake-gcs-server // start respecting these conditions, or we move to other testing framework. err := testSuite.bucketHandle.DeleteObject(context.Background(), @@ -378,6 +429,8 @@ func (testSuite *BucketHandleTest) TestCopyObjectMethodWithInvalidGeneration() { } func (testSuite *BucketHandleTest) TestCreateObjectMethodWithValidObject() { + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + content := "Creating a new object" obj, err := testSuite.bucketHandle.CreateObject(context.Background(), &gcs.CreateObjectRequest{ @@ -391,6 +444,8 @@ func (testSuite *BucketHandleTest) TestCreateObjectMethodWithValidObject() { } func (testSuite *BucketHandleTest) TestCreateObjectMethodWithGenerationAsZero() { + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + content := "Creating a new object" var generation int64 = 0 obj, err := testSuite.bucketHandle.CreateObject(context.Background(), @@ -406,6 +461,8 @@ func (testSuite *BucketHandleTest) TestCreateObjectMethodWithGenerationAsZero() } func (testSuite *BucketHandleTest) TestCreateObjectMethodWithGenerationAsZeroWhenObjectAlreadyExists() { + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + content := "Creating a new object" var generation int64 = 0 var precondition *gcs.PreconditionError @@ -432,6 +489,8 @@ func (testSuite *BucketHandleTest) TestCreateObjectMethodWithGenerationAsZeroWhe } func (testSuite *BucketHandleTest) TestCreateObjectMethodWhenGivenGenerationObjectNotExist() { + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + var precondition *gcs.PreconditionError content := "Creating a new object" var crc32 uint32 = 45 @@ -450,6 +509,8 @@ func (testSuite *BucketHandleTest) TestCreateObjectMethodWhenGivenGenerationObje } func (testSuite *BucketHandleTest) TestBucketHandle_CreateObjectChunkWriter() { + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + var generation0 int64 = 0 var generationNon0 int64 = 786 var metaGeneration0 int64 = 0 @@ -547,6 +608,8 @@ func (testSuite *BucketHandleTest) TestBucketHandle_CreateObjectChunkWriterWithN } func (testSuite *BucketHandleTest) TestBucketHandle_FinalizeUploadSuccess() { + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + var generation0 int64 = 0 tests := []struct { @@ -616,6 +679,8 @@ func (testSuite *BucketHandleTest) createObject(objName string) { } func (testSuite *BucketHandleTest) TestFinalizeUploadWithGenerationAsZeroWhenObjectAlreadyExists() { + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + objName := "pre_created_test_object" testSuite.createObject(objName) // Create Object Writer again when object already exists. @@ -962,6 +1027,7 @@ func (testSuite *BucketHandleTest) TestComposeObjectMethodWithDstObjectExist() { } func (testSuite *BucketHandleTest) TestComposeObjectMethodWithOneSrcObject() { + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) var notfound *gcs.NotFoundError // Checking that dstObject does not exist _, _, err := testSuite.bucketHandle.StatObject(context.Background(), @@ -1025,6 +1091,7 @@ func (testSuite *BucketHandleTest) TestComposeObjectMethodWithOneSrcObject() { } func (testSuite *BucketHandleTest) TestComposeObjectMethodWithTwoSrcObjects() { + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) var notfound *gcs.NotFoundError _, _, err := testSuite.bucketHandle.StatObject(context.Background(), &gcs.StatObjectRequest{ @@ -1286,6 +1353,7 @@ func (testSuite *BucketHandleTest) TestComposeObjectMethodWhenDstObjectDoesNotEx func (testSuite *BucketHandleTest) TestComposeObjectMethodWithOneSrcObjectIsDstObject() { // Checking source object 1 exists. This will also be the destination object. + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) srcMinObj1, _, err := testSuite.bucketHandle.StatObject(context.Background(), &gcs.StatObjectRequest{ Name: TestObjectName, @@ -1382,59 +1450,83 @@ func (testSuite *BucketHandleTest) TestComposeObjectMethodWithOneSrcObjectIsDstO } func (testSuite *BucketHandleTest) TestBucketTypeForHierarchicalNameSpaceTrue() { - testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). - Return(&controlpb.StorageLayout{ - HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, - }, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + }, nil) + + testSuite.bucketHandle.BucketType() + + assert.Equal(testSuite.T(), &gcs.BucketType{Hierarchical: true}, testSuite.bucketHandle.bucketType, "Expected Hierarchical bucket type") +} + +func (testSuite *BucketHandleTest) TestBucketTypeForZonalLocationType() { + createBucketHandle(testSuite, &controlpb.StorageLayout{ + LocationType: "zone", + }, nil) testSuite.bucketHandle.BucketType() - assert.Equal(testSuite.T(), gcs.Hierarchical, testSuite.bucketHandle.bucketType, "Expected Hierarchical bucket type") + assert.Equal(testSuite.T(), &gcs.BucketType{Zonal: true}, testSuite.bucketHandle.bucketType, "Expected Zonal bucket type") +} + +func (testSuite *BucketHandleTest) TestBucketTypeForZonalLocationTypeAndHierarchicalNameSpaceTrue() { + createBucketHandle(testSuite, &controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + LocationType: "zone", + }, nil) + + testSuite.bucketHandle.BucketType() + + assert.Equal(testSuite.T(), &gcs.BucketType{Hierarchical: true, Zonal: true}, testSuite.bucketHandle.bucketType, "Expected Zonal bucket type") } func (testSuite *BucketHandleTest) TestBucketTypeForHierarchicalNameSpaceFalse() { - testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). - Return(&controlpb.StorageLayout{ - HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: false}, - }, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: false}, + }, nil) testSuite.bucketHandle.BucketType() - assert.Equal(testSuite.T(), gcs.NonHierarchical, testSuite.bucketHandle.bucketType, "Expected NonHierarchical bucket type") + assert.Equal(testSuite.T(), &gcs.BucketType{}, testSuite.bucketHandle.bucketType, "Expected default bucket type") } -func (testSuite *BucketHandleTest) TestBucketTypeWithError() { +func (testSuite *BucketHandleTest) TestBucketHandleWithError() { var x *controlpb.StorageLayout + var err error // Test when the client returns an error. testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything).Return(x, errors.New("mocked error")) - testSuite.bucketHandle.BucketType() + testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "") - assert.Equal(testSuite.T(), gcs.Unknown, testSuite.bucketHandle.bucketType, "Expected Unknown when there's an error") + assert.Nil(testSuite.T(), testSuite.bucketHandle) + assert.Contains(testSuite.T(), err.Error(), "mocked error") } func (testSuite *BucketHandleTest) TestBucketTypeWithHierarchicalNamespaceIsNil() { - testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). - Return(&controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) testSuite.bucketHandle.BucketType() - assert.Equal(testSuite.T(), gcs.NonHierarchical, testSuite.bucketHandle.bucketType, "Expected NonHierarchical bucket type") + assert.Equal(testSuite.T(), &gcs.BucketType{}, testSuite.bucketHandle.bucketType, "Expected default bucket type") } func (testSuite *BucketHandleTest) TestDefaultBucketTypeWithControlClientNil() { + createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) var nilControlClient *control.StorageControlClient = nil testSuite.bucketHandle.controlClient = nilControlClient testSuite.bucketHandle.BucketType() - assert.Equal(testSuite.T(), gcs.NonHierarchical, testSuite.bucketHandle.bucketType, "Expected NonHierarchical bucket type") + assert.Equal(testSuite.T(), &gcs.BucketType{}, testSuite.bucketHandle.bucketType, "Expected default bucket type") } func (testSuite *BucketHandleTest) TestDeleteFolderWhenFolderExitForHierarchicalBucket() { + createBucketHandle(testSuite, &controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + }, nil) ctx := context.Background() deleteFolderReq := controlpb.DeleteFolderRequest{Name: fmt.Sprintf(FullFolderPathHNS, TestBucketName, TestFolderName)} testSuite.mockClient.On("DeleteFolder", ctx, &deleteFolderReq, mock.Anything).Return(nil) - testSuite.bucketHandle.bucketType = gcs.Hierarchical + testSuite.bucketHandle.bucketType = &gcs.BucketType{Hierarchical: true} err := testSuite.bucketHandle.DeleteFolder(ctx, TestFolderName) @@ -1444,9 +1536,12 @@ func (testSuite *BucketHandleTest) TestDeleteFolderWhenFolderExitForHierarchical func (testSuite *BucketHandleTest) TestDeleteFolderWhenFolderNotExistForHierarchicalBucket() { ctx := context.Background() + createBucketHandle(testSuite, &controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + }, nil) deleteFolderReq := controlpb.DeleteFolderRequest{Name: fmt.Sprintf(FullFolderPathHNS, TestBucketName, missingFolderName)} testSuite.mockClient.On("DeleteFolder", mock.Anything, &deleteFolderReq, mock.Anything).Return(errors.New("mock error")) - testSuite.bucketHandle.bucketType = gcs.Hierarchical + testSuite.bucketHandle.bucketType = &gcs.BucketType{Hierarchical: true} err := testSuite.bucketHandle.DeleteFolder(ctx, missingFolderName) @@ -1456,13 +1551,16 @@ func (testSuite *BucketHandleTest) TestDeleteFolderWhenFolderNotExistForHierarch func (testSuite *BucketHandleTest) TestGetFolderWhenFolderExistsForHierarchicalBucket() { ctx := context.Background() + createBucketHandle(testSuite, &controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + }, nil) folderPath := fmt.Sprintf(FullFolderPathHNS, TestBucketName, TestFolderName) getFolderReq := controlpb.GetFolderRequest{Name: folderPath} mockFolder := controlpb.Folder{ Name: folderPath, } testSuite.mockClient.On("GetFolder", ctx, &getFolderReq, mock.Anything).Return(&mockFolder, nil) - testSuite.bucketHandle.bucketType = gcs.Hierarchical + testSuite.bucketHandle.bucketType = &gcs.BucketType{Hierarchical: true} result, err := testSuite.bucketHandle.GetFolder(ctx, TestFolderName) @@ -1473,10 +1571,13 @@ func (testSuite *BucketHandleTest) TestGetFolderWhenFolderExistsForHierarchicalB func (testSuite *BucketHandleTest) TestGetFolderWhenFolderDoesNotExistsForHierarchicalBucket() { ctx := context.Background() + createBucketHandle(testSuite, &controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + }, nil) folderPath := fmt.Sprintf(FullFolderPathHNS, TestBucketName, missingFolderName) getFolderReq := controlpb.GetFolderRequest{Name: folderPath} testSuite.mockClient.On("GetFolder", ctx, &getFolderReq, mock.Anything).Return(nil, status.Error(codes.NotFound, "folder not found")) - testSuite.bucketHandle.bucketType = gcs.Hierarchical + testSuite.bucketHandle.bucketType = &gcs.BucketType{Hierarchical: true} result, err := testSuite.bucketHandle.GetFolder(ctx, missingFolderName) @@ -1487,9 +1588,12 @@ func (testSuite *BucketHandleTest) TestGetFolderWhenFolderDoesNotExistsForHierar func (testSuite *BucketHandleTest) TestRenameFolderWithError() { ctx := context.Background() + createBucketHandle(testSuite, &controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + }, nil) renameFolderReq := controlpb.RenameFolderRequest{Name: fmt.Sprintf(FullFolderPathHNS, TestBucketName, TestFolderName), DestinationFolderId: TestRenameFolder} testSuite.mockClient.On("RenameFolder", mock.Anything, &renameFolderReq, mock.Anything).Return(nil, errors.New("mock error")) - testSuite.bucketHandle.bucketType = gcs.Hierarchical + testSuite.bucketHandle.bucketType = &gcs.BucketType{Hierarchical: true} _, err := testSuite.bucketHandle.RenameFolder(ctx, TestFolderName, TestRenameFolder) @@ -1498,9 +1602,12 @@ func (testSuite *BucketHandleTest) TestRenameFolderWithError() { } func (testSuite *BucketHandleTest) TestCreateFolderWithError() { + createBucketHandle(testSuite, &controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + }, nil) createFolderReq := controlpb.CreateFolderRequest{Parent: fmt.Sprintf(FullBucketPathHNS, TestBucketName), FolderId: TestFolderName, Recursive: true} testSuite.mockClient.On("CreateFolder", context.Background(), &createFolderReq, mock.Anything).Return(nil, errors.New("mock error")) - testSuite.bucketHandle.bucketType = gcs.Hierarchical + testSuite.bucketHandle.bucketType = &gcs.BucketType{Hierarchical: true} folder, err := testSuite.bucketHandle.CreateFolder(context.Background(), TestFolderName) @@ -1510,12 +1617,15 @@ func (testSuite *BucketHandleTest) TestCreateFolderWithError() { } func (testSuite *BucketHandleTest) TestCreateFolderWithGivenName() { + createBucketHandle(testSuite, &controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, + }, nil) mockFolder := controlpb.Folder{ Name: fmt.Sprintf(FullFolderPathHNS, TestBucketName, TestFolderName), } createFolderReq := controlpb.CreateFolderRequest{Parent: fmt.Sprintf(FullBucketPathHNS, TestBucketName), FolderId: TestFolderName, Recursive: true} testSuite.mockClient.On("CreateFolder", context.Background(), &createFolderReq, mock.Anything).Return(&mockFolder, nil) - testSuite.bucketHandle.bucketType = gcs.Hierarchical + testSuite.bucketHandle.bucketType = &gcs.BucketType{Hierarchical: true} folder, err := testSuite.bucketHandle.CreateFolder(context.Background(), TestFolderName) diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index 2e985d8479..485af93239 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -218,6 +218,13 @@ func (b *fastStatBucket) NewReader( return } +func (b *fastStatBucket) NewReaderWithReadHandle( + ctx context.Context, + req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { + rd, err = b.wrapped.NewReaderWithReadHandle(ctx, req) + return +} + // LOCKS_EXCLUDED(b.mu) func (b *fastStatBucket) CreateObject( ctx context.Context, @@ -340,7 +347,7 @@ func (b *fastStatBucket) ListObjects( return } - if b.BucketType() == gcs.Hierarchical { + if b.BucketType().Hierarchical { b.insertHierarchicalListing(listing) return } @@ -492,3 +499,9 @@ func (b *fastStatBucket) RenameFolder(ctx context.Context, folderName string, de return f, err } + +func (b *fastStatBucket) NewMultiRangeDownloader( + ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (mrd gcs.MultiRangeDownloader, err error) { + mrd, err = b.wrapped.NewMultiRangeDownloader(ctx, req) + return +} diff --git a/internal/storage/caching/fast_stat_bucket_test.go b/internal/storage/caching/fast_stat_bucket_test.go index 84a6e78936..86e370a376 100644 --- a/internal/storage/caching/fast_stat_bucket_test.go +++ b/internal/storage/caching/fast_stat_bucket_test.go @@ -698,7 +698,7 @@ func (t *ListObjectsTest) EmptyListing() { expected := &gcs.Listing{} ExpectCall(t.wrapped, "BucketType")(). - WillOnce(Return(gcs.NonHierarchical)) + WillOnce(Return(gcs.BucketType{})) ExpectCall(t.wrapped, "ListObjects")(Any(), Any()). WillOnce(Return(expected, nil)) @@ -715,7 +715,7 @@ func (t *ListObjectsTest) EmptyListingForHNS() { expected := &gcs.Listing{} ExpectCall(t.wrapped, "BucketType")(). - WillOnce(Return(gcs.Hierarchical)) + WillOnce(Return(gcs.BucketType{Hierarchical: true})) ExpectCall(t.wrapped, "ListObjects")(Any(), Any()). WillOnce(Return(expected, nil)) @@ -737,7 +737,7 @@ func (t *ListObjectsTest) NonEmptyListing() { } ExpectCall(t.wrapped, "BucketType")(). - WillOnce(Return(gcs.NonHierarchical)) + WillOnce(Return(gcs.BucketType{})) ExpectCall(t.wrapped, "ListObjects")(Any(), Any()). WillOnce(Return(expected, nil)) @@ -763,7 +763,7 @@ func (t *ListObjectsTest) NonEmptyListingForHNS() { } ExpectCall(t.wrapped, "BucketType")(). - WillOnce(Return(gcs.Hierarchical)) + WillOnce(Return(gcs.BucketType{Hierarchical: true})) ExpectCall(t.wrapped, "ListObjects")(Any(), Any()). WillOnce(Return(expected, nil)) diff --git a/internal/storage/caching/integration_test.go b/internal/storage/caching/integration_test.go index c450699a37..48b1685c86 100644 --- a/internal/storage/caching/integration_test.go +++ b/internal/storage/caching/integration_test.go @@ -59,7 +59,7 @@ func (t *IntegrationTest) SetUp(ti *TestInfo) { const cacheCapacity = 100 lruCache := lru.NewCache(cfg.AverageSizeOfPositiveStatCacheEntry * cacheCapacity) cache := metadata.NewStatCacheBucketView(lruCache, "") - t.wrapped = fake.NewFakeBucket(&t.clock, bucketName, gcs.NonHierarchical) + t.wrapped = fake.NewFakeBucket(&t.clock, bucketName, gcs.BucketType{}) t.bucket = caching.NewFastStatBucket( primaryCacheTTL, diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index 35212d3988..94bc5735b8 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -20,6 +20,7 @@ import ( "sync/atomic" "time" + storagev2 "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "golang.org/x/net/context" @@ -117,6 +118,11 @@ func (dr *debugReader) Close() (err error) { return } +func (dr *debugReader) ReadHandle() storagev2.ReadHandle { + hd := "opaque-handle" + return []byte(hd) +} + //////////////////////////////////////////////////////////////////////// // Bucket interface //////////////////////////////////////////////////////////////////////// @@ -129,16 +135,14 @@ func (b *debugBucket) BucketType() gcs.BucketType { return b.wrapped.BucketType() } -func (b *debugBucket) NewReader( - ctx context.Context, - req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { - id, desc, start := b.startRequest("Read(%q, %v)", req.Name, req.Range) +func setupReader(ctx context.Context, b *debugBucket, req *gcs.ReadObjectRequest, method string) (gcs.StorageReader, error) { + id, desc, start := b.startRequest("%q(%q, %v)", method, req.Name, req.Range) // Call through. - rc, err = b.wrapped.NewReader(ctx, req) + rc, err := b.wrapped.NewReaderWithReadHandle(ctx, req) if err != nil { b.finishRequest(id, desc, start, &err) - return + return rc, err } // Return a special reader that prings debug info. @@ -149,7 +153,20 @@ func (b *debugBucket) NewReader( startTime: start, wrapped: rc, } + return rc, err +} +func (b *debugBucket) NewReader( + ctx context.Context, + req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { + rc, err = setupReader(ctx, b, req, "Read") + return +} + +func (b *debugBucket) NewReaderWithReadHandle( + ctx context.Context, + req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { + rd, err = setupReader(ctx, b, req, "ReadWithReadHandle") return } @@ -288,3 +305,59 @@ func (b *debugBucket) RenameFolder(ctx context.Context, folderName string, desti o, err = b.wrapped.RenameFolder(ctx, folderName, destinationFolderId) return o, err } + +type debugMultiRangeDownloader struct { + bucket *debugBucket + requestID uint64 + desc string + startTime time.Time + wrapped gcs.MultiRangeDownloader +} + +func (dmrd *debugMultiRangeDownloader) Add(output io.Writer, offset, length int64, callback func(int64, int64, error)) { + id, desc, start := dmrd.bucket.startRequest("MultiRangeDownloader.Add(%v,%v)", offset, length) + wrapperCallback := func(offset int64, length int64, err error) { + defer dmrd.bucket.finishRequest(id, desc, start, &err) + if callback != nil { + callback(offset, length, err) + } + } + dmrd.wrapped.Add(output, offset, length, wrapperCallback) +} + +func (dmrd *debugMultiRangeDownloader) Close() (err error) { + id, desc, start := dmrd.bucket.startRequest("MultiRangeDownloader.Close()") + defer dmrd.bucket.finishRequest(id, desc, start, &err) + err = dmrd.wrapped.Close() + return +} + +func (dmrd *debugMultiRangeDownloader) Wait() { + id, desc, start := dmrd.bucket.startRequest("MultiRangeDownloader.Wait()") + var err error + defer dmrd.bucket.finishRequest(id, desc, start, &err) + dmrd.wrapped.Wait() +} + +func (b *debugBucket) NewMultiRangeDownloader( + ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (mrd gcs.MultiRangeDownloader, err error) { + id, desc, start := b.startRequest("NewMultiRangeDownloader(%q)", req.Name) + defer b.finishRequest(id, desc, start, &err) + + // Call through. + mrd, err = b.wrapped.NewMultiRangeDownloader(ctx, req) + if err != nil { + b.finishRequest(id, desc, start, &err) + return + } + + // Return a special reader that prints debug info. + mrd = &debugMultiRangeDownloader{ + bucket: b, + requestID: id, + desc: desc, + startTime: start, + wrapped: mrd, + } + return +} diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index 919c3bed50..dc2386e8a8 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -405,7 +405,7 @@ func createOrUpdateFakeObject(b *bucket, req *gcs.CreateObjectRequest, contents sort.Sort(b.objects) } - if b.BucketType() == gcs.Hierarchical { + if b.BucketType().Hierarchical { b.addFolderEntry(req.Name) } return @@ -674,6 +674,24 @@ func (b *bucket) NewReader( return } +// LOCKS_EXCLUDED(b.mu) +func (b *bucket) NewReaderWithReadHandle(ctx context.Context, req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { + b.mu.Lock() + defer b.mu.Unlock() + + r, _, err := b.newReaderLocked(req) + if err != nil { + return + } + + rc := io.NopCloser(r) + rd = &FakeReader{ + ReadCloser: rc, + Handle: []byte("opaque-handle"), + } + return +} + // LOCKS_EXCLUDED(b.mu) func (b *bucket) CreateObject( ctx context.Context, @@ -1204,3 +1222,20 @@ func (b *bucket) RenameFolder(ctx context.Context, folderName string, destinatio return folder, nil } + +func (b *bucket) NewMultiRangeDownloader( + ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (gcs.MultiRangeDownloader, error) { + index := b.objects.find(req.Name) + if index >= len(b.objects) || index < 0 { + return nil, &gcs.NotFoundError{Err: fmt.Errorf("not found object %s in fake-bucket", req.Name)} + } + obj := b.objects[index] + if req.Generation != 0 && obj.metadata.Generation != req.Generation { + return nil, &gcs.NotFoundError{Err: fmt.Errorf("not found object %s in fake-bucket with generation %v", req.Name, req.Generation)} + } + if obj.data == nil { + return nil, fmt.Errorf("found no content for object %s in fake-bucket", req.Name) + } + + return &fakeMultiRangeDownloader{obj: &obj}, nil +} diff --git a/internal/storage/fake/bucket_test.go b/internal/storage/fake/bucket_test.go index 50c2d762f5..379159cdf4 100644 --- a/internal/storage/fake/bucket_test.go +++ b/internal/storage/fake/bucket_test.go @@ -35,7 +35,7 @@ func init() { deps.Clock = clock // Set up the bucket. - deps.Bucket = NewFakeBucket(clock, "some_bucket", gcs.NonHierarchical) + deps.Bucket = NewFakeBucket(clock, "some_bucket", gcs.BucketType{}) return } diff --git a/internal/storage/fake/fake_multi_range_downloader.go b/internal/storage/fake/fake_multi_range_downloader.go new file mode 100644 index 0000000000..ec08c2210f --- /dev/null +++ b/internal/storage/fake/fake_multi_range_downloader.go @@ -0,0 +1,113 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fake + +import ( + "fmt" + "io" + "sync" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" +) + +// This struct is an implementation of the gcs.MultiRangeDownloader interface. +type fakeMultiRangeDownloader struct { + gcs.MultiRangeDownloader + obj *fakeObject + wg sync.WaitGroup + err error + sleepTime time.Duration // Sleep time to simulate real-world. +} + +func createFakeObject(obj *gcs.MinObject, data []byte) fakeObject { + fullObj := storageutil.ConvertMinObjectToObject(obj) + return fakeObject{ + metadata: *fullObj, + data: data, + } +} + +func NewFakeMultiRangeDownloader(obj *gcs.MinObject, data []byte) gcs.MultiRangeDownloader { + return NewFakeMultiRangeDownloaderWithSleep(obj, data, time.Millisecond) +} + +func NewFakeMultiRangeDownloaderWithSleep(obj *gcs.MinObject, data []byte, sleepTime time.Duration) gcs.MultiRangeDownloader { + fakeObj := createFakeObject(obj, data) + return &fakeMultiRangeDownloader{ + obj: &fakeObj, + sleepTime: sleepTime, + } +} + +func (fmrd *fakeMultiRangeDownloader) Add(output io.Writer, offset, length int64, callback func(int64, int64, error)) { + obj := fmrd.obj + size := int64(len(obj.data)) + var err error + // Apply input checks as defined at https://github.com/googleapis/go-storage-prelaunch/blob/a5db2abd53775941df67b3337eabaf8d00ef0762/storage/reader.go#L373 . + if length < 0 { + err = fmt.Errorf("length < 0") + } else if offset > size { + err = fmt.Errorf("out of range. offset (%v) > size of content (%v) of %s", offset, size, obj.metadata.Name) + } else if offset <= -size { + offset = 0 + length = size + } else if offset < 0 { + offset = size + offset + length = min(length, size-offset) + } else { + length = min(length, size-offset) + } + if err != nil { + // If inputs aren't correct, fail immediately and return callback. + fmrd.err = err + if callback != nil { + callback(offset, length, err) + } + return + } + + // Record this additional goroutine. + fmrd.wg.Add(1) + + go func() { + // clear this goroutine from waitgroup. + defer fmrd.wg.Done() + + time.Sleep(fmrd.sleepTime) + var n int + n, err = output.Write(obj.data[offset : offset+length]) + if err != nil || int64(n) != length { + err = fmt.Errorf("failed to write %v bytes to writer through multi-range-downloader, bytes written = %v, error = %v", length, n, err) + } + if callback != nil { + callback(offset, length, err) + } + // Don't clear pre-existing error in downloader. + if fmrd.err != nil { + fmrd.err = err + } + }() +} + +func (fmrd *fakeMultiRangeDownloader) Close() error { + fmrd.Wait() + return fmrd.err +} + +func (fmrd *fakeMultiRangeDownloader) Wait() { + fmrd.wg.Wait() +} diff --git a/internal/storage/fake/fake_reader.go b/internal/storage/fake/fake_reader.go new file mode 100644 index 0000000000..7b960b9204 --- /dev/null +++ b/internal/storage/fake/fake_reader.go @@ -0,0 +1,31 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fake + +import ( + "io" + + storagev2 "cloud.google.com/go/storage" +) + +// FakeReader implements gcs.StorageReader interface +type FakeReader struct { + io.ReadCloser + Handle []byte +} + +func (fr *FakeReader) ReadHandle() storagev2.ReadHandle { + return fr.Handle +} diff --git a/internal/storage/fake/testing/bucket_tests.go b/internal/storage/fake/testing/bucket_tests.go index d6bf1680be..55aa1ab834 100644 --- a/internal/storage/fake/testing/bucket_tests.go +++ b/internal/storage/fake/testing/bucket_tests.go @@ -17,6 +17,7 @@ package testing import ( + "bytes" "context" "crypto/md5" "crypto/rand" @@ -292,8 +293,78 @@ func readMultiple( } // Run several workers. - const parallelsim = 32 - for i := 0; i < parallelsim; i++ { + const parallelism = 32 + for i := 0; i < parallelism; i++ { + group.Go(func() (err error) { + for i := range indices { + handleRequest(ctx, i) + } + + return + }) + } + + AssertEq(nil, group.Wait()) + return +} + +// Issue all of the supplied read requests +// using multi-range-read approach, +// with some degree of parallelism. +func readMultipleUsingMultiRangeDownloader( + ctx context.Context, + bucket gcs.Bucket, + req *gcs.MultiRangeDownloaderRequest, + ranges []gcs.ByteRange) (contents []*bytes.Buffer, errs []error) { + group, ctx := errgroup.WithContext(ctx) + if len(ranges) == 0 { + return // nothing to do + } + + // Open a reader. + mrd, err := bucket.NewMultiRangeDownloader(ctx, req) + if err != nil { + AddFailure("failed to get multi-range-downloader for object %s: %v", req.Name, err) + return + } + // Not checking error on mrd.Close() here as it is expected to fail for + // negative test cases. These negative cases are tested through errs[i]. + defer mrd.Close() + + // Feed indices into a channel. + indices := make(chan int, len(ranges)) + for i := range ranges { + indices <- i + } + close(indices) + + // Set up a function that deals with one request. + contents = make([]*bytes.Buffer, len(ranges)) + errs = make([]error, len(ranges)) + + handleRequest := func(ctx context.Context, i int) { + if ranges[i].Limit > ranges[i].Start { + size := ranges[i].Limit - ranges[i].Start + contents[i] = bytes.NewBuffer(make([]byte, 0, size)) + // Add a new range to read. + mrd.Add(contents[i], int64(ranges[i].Start), int64(size), func(offset, length int64, err error) { + // Mark error for this range if the callback returned error for it. + errs[i] = err + }) + } else { + // Create a dummy buffer to avoid unnecessary initialization failures. + contents[i] = bytes.NewBuffer(make([]byte, 0, 1)) + // Add a new range to read. + mrd.Add(contents[i], int64(ranges[i].Start), 0, func(offset, length int64, err error) { + // Mark error for this range if the callback returned error for it. + errs[i] = err + }) + } + } + + // Run several workers. + const parallelism = 32 + for i := 0; i < parallelism; i++ { group.Go(func() (err error) { for i := range indices { handleRequest(ctx, i) @@ -3028,6 +3099,337 @@ func (t *readTest) Ranges_NonEmptyObject() { } } +//////////////////////////////////////////////////////////////////////// +// Read - multirange +//////////////////////////////////////////////////////////////////////// + +type readMultiRangeTest struct { + bucketTest +} + +func (t *readMultiRangeTest) ObjectNameDoesntExist() { + req := &gcs.MultiRangeDownloaderRequest{ + Name: "foobar", + } + + _, err := t.bucket.NewMultiRangeDownloader(t.ctx, req) + + AssertThat(err, HasSameTypeAs(&gcs.NotFoundError{})) + ExpectThat(err, Error(MatchesRegexp("(?i)not found|404"))) +} + +func (t *readMultiRangeTest) EmptyObject() { + // Create + AssertEq(nil, t.createObject("foo", "")) + + // Read + req := &gcs.MultiRangeDownloaderRequest{ + Name: "foo", + } + + mrd, err := t.bucket.NewMultiRangeDownloader(t.ctx, req) + AssertEq(nil, err) + + // Close + AssertEq(nil, mrd.Close()) +} + +func (t *readMultiRangeTest) NonEmptyObject() { + // Create + AssertEq(nil, t.createObject("foo", "taco")) + + // Read + req := &gcs.MultiRangeDownloaderRequest{ + Name: "foo", + } + + mrd, err := t.bucket.NewMultiRangeDownloader(t.ctx, req) + AssertEq(nil, err) + + outBuffer := bytes.NewBufferString("") + callbackCalled := false + mrd.Add(outBuffer, 0, 4, func(offset int64, length int64, err error) { + AssertEq(nil, err) + AssertEq("taco", outBuffer.String()) + callbackCalled = true + }) + + // Close + mrd.Wait() + AssertTrue(callbackCalled) + AssertEq(nil, mrd.Close()) +} + +func (t *readMultiRangeTest) ParticularGeneration_NeverExisted() { + // Create an object. + o, err := storageutil.CreateObject( + t.ctx, + t.bucket, + "foo", + []byte{}) + + AssertEq(nil, err) + AssertGt(o.Generation, 0) + + // Attempt to read a different generation. + req := &gcs.MultiRangeDownloaderRequest{ + Name: "foo", + Generation: o.Generation + 1, + } + _, err = t.bucket.NewMultiRangeDownloader(t.ctx, req) + + AssertThat(err, HasSameTypeAs(&gcs.NotFoundError{})) + ExpectThat(err, Error(MatchesRegexp("(?i)not found|404"))) +} + +func (t *readMultiRangeTest) ParticularGeneration_HasBeenDeleted() { + // Create an object. + o, err := storageutil.CreateObject( + t.ctx, + t.bucket, + "foo", + []byte{}) + + AssertEq(nil, err) + AssertGt(o.Generation, 0) + + // Delete it. + err = t.bucket.DeleteObject( + t.ctx, + &gcs.DeleteObjectRequest{ + Name: "foo", + }) + + AssertEq(nil, err) + + // Attempt to read by generation. + req := &gcs.MultiRangeDownloaderRequest{ + Name: "foo", + Generation: o.Generation, + } + _, err = t.bucket.NewMultiRangeDownloader(t.ctx, req) + + AssertThat(err, HasSameTypeAs(&gcs.NotFoundError{})) + ExpectThat(err, Error(MatchesRegexp("(?i)not found|404"))) +} + +func (t *readMultiRangeTest) ParticularGeneration_Exists() { + // Create an object. + o, err := storageutil.CreateObject( + t.ctx, + t.bucket, + "foo", + []byte("taco")) + + AssertEq(nil, err) + AssertGt(o.Generation, 0) + + // Attempt to read the correct generation. + req := &gcs.MultiRangeDownloaderRequest{ + Name: "foo", + Generation: o.Generation, + } + + mrd, err := t.bucket.NewMultiRangeDownloader(t.ctx, req) + AssertEq(nil, err) + + outBuffer := bytes.NewBufferString("") + callbackCalled := false + mrd.Add(outBuffer, 0, 4, func(offset int64, length int64, err error) { + AssertEq(nil, err) + AssertEq("taco", outBuffer.String()) + callbackCalled = true + }) + + // Close + AssertEq(nil, mrd.Close()) + AssertTrue(callbackCalled) +} + +func (t *readMultiRangeTest) ParticularGeneration_ObjectHasBeenOverwritten() { + // Create an object. + o, err := storageutil.CreateObject( + t.ctx, + t.bucket, + "foo", + []byte("taco")) + + AssertEq(nil, err) + AssertGt(o.Generation, 0) + + // Overwrite with a new generation. + o2, err := storageutil.CreateObject( + t.ctx, + t.bucket, + "foo", + []byte("burrito")) + + AssertEq(nil, err) + AssertGt(o2.Generation, 0) + AssertNe(o.Generation, o2.Generation) + + // Reading by the old generation should fail. + req := &gcs.MultiRangeDownloaderRequest{ + Name: "foo", + Generation: o.Generation, + } + + _, err = t.bucket.NewMultiRangeDownloader(t.ctx, req) + AssertThat(err, HasSameTypeAs(&gcs.NotFoundError{})) + ExpectThat(err, Error(MatchesRegexp("(?i)not found|404"))) + + // Reading by the new generation should work. + req.Generation = o2.Generation + mrd, err := t.bucket.NewMultiRangeDownloader(t.ctx, req) + + AssertEq(nil, err) + AssertNe(nil, mrd) + + size := len("burrito") + callbackCalled := false + writer := bytes.NewBufferString("") + mrd.Add(writer, int64(0), int64(size), func(offset, length int64, err error) { + AssertEq(nil, err) + AssertEq("burrito", writer.String(), + "Failed to match buf=%v, content=%v", writer.Bytes(), []byte("burrito")) + callbackCalled = true + }) + + // Close + AssertEq(nil, mrd.Close()) + AssertTrue(callbackCalled) +} + +func (t *readMultiRangeTest) Ranges_EmptyObject() { + // Create an empty object. + AssertEq(nil, t.createObject("foo", "")) + + // Test cases. + testCases := []struct { + br gcs.ByteRange + expectError bool + }{ + // Empty without knowing object length + {gcs.ByteRange{Start: 0, Limit: 0}, false}, + + {gcs.ByteRange{Start: 1, Limit: 2}, true}, + {gcs.ByteRange{Start: 1, Limit: 1}, true}, + {gcs.ByteRange{Start: 1, Limit: 0}, true}, + + // Not empty without knowing object length + {gcs.ByteRange{Start: 0, Limit: 1}, false}, + {gcs.ByteRange{Start: 0, Limit: 17}, false}, + } + + // Turn test cases into read requests. + req := &gcs.MultiRangeDownloaderRequest{ + Name: "foo", + } + + var ranges []gcs.ByteRange + for _, tc := range testCases { + ranges = append(ranges, tc.br) + } + + // Make each request. + contents, errs := readMultipleUsingMultiRangeDownloader( + t.ctx, + t.bucket, + req, + ranges) + + AssertEq(len(testCases), len(contents)) + AssertEq(len(testCases), len(errs)) + for i, tc := range testCases { + desc := fmt.Sprintf("Test case %d, range %v", i, tc.br) + if tc.expectError { + ExpectNe(nil, errs[i], desc) + } else { + ExpectEq(nil, errs[i], "%s", desc) + ExpectEq("", contents[i].String(), "%s", desc) + } + } +} + +func (t *readMultiRangeTest) Ranges_NonEmptyObject() { + // Create an object of length four. + AssertEq(nil, t.createObject("foo", "taco")) + + // Test cases. + testCases := []struct { + br gcs.ByteRange + expectedContents string + expectError bool + }{ + // Left anchored + {gcs.ByteRange{Start: 0, Limit: 5}, "taco", false}, + {gcs.ByteRange{Start: 0, Limit: 4}, "taco", false}, + {gcs.ByteRange{Start: 0, Limit: 3}, "tac", false}, + {gcs.ByteRange{Start: 0, Limit: 2}, "ta", false}, + {gcs.ByteRange{Start: 0, Limit: 1}, "t", false}, + {gcs.ByteRange{Start: 0, Limit: 0}, "", false}, + + // Floating left edge + {gcs.ByteRange{Start: 1, Limit: 5}, "aco", false}, + {gcs.ByteRange{Start: 1, Limit: 4}, "aco", false}, + {gcs.ByteRange{Start: 1, Limit: 3}, "ac", false}, + {gcs.ByteRange{Start: 1, Limit: 2}, "a", false}, + {gcs.ByteRange{Start: 1, Limit: 1}, "", false}, + {gcs.ByteRange{Start: 1, Limit: 0}, "", false}, + + // Left edge at right edge of object + {gcs.ByteRange{Start: 4, Limit: 17}, "", false}, + {gcs.ByteRange{Start: 4, Limit: 5}, "", false}, + {gcs.ByteRange{Start: 4, Limit: 4}, "", false}, + {gcs.ByteRange{Start: 4, Limit: 1}, "", false}, + {gcs.ByteRange{Start: 4, Limit: 0}, "", false}, + + // Left edge past right edge of object + {gcs.ByteRange{Start: 5, Limit: 17}, "", true}, + {gcs.ByteRange{Start: 5, Limit: 5}, "", true}, + {gcs.ByteRange{Start: 5, Limit: 4}, "", true}, + {gcs.ByteRange{Start: 5, Limit: 1}, "", true}, + {gcs.ByteRange{Start: 5, Limit: 0}, "", true}, + + // Left edge is 2^63 - 1 + {gcs.ByteRange{Start: math.MaxInt64, Limit: 5}, "", true}, + {gcs.ByteRange{Start: math.MaxInt64, Limit: 4}, "", true}, + {gcs.ByteRange{Start: math.MaxInt64, Limit: 1}, "", true}, + {gcs.ByteRange{Start: math.MaxInt64, Limit: 0}, "", true}, + } + + // Turn test cases into read requests. + req := &gcs.MultiRangeDownloaderRequest{ + Name: "foo", + } + + var ranges []gcs.ByteRange + for _, tc := range testCases { + ranges = append(ranges, tc.br) + } + + // Make each request. + contents, errs := readMultipleUsingMultiRangeDownloader( + t.ctx, + t.bucket, + req, + ranges) + + AssertEq(len(testCases), len(contents)) + AssertEq(len(testCases), len(errs)) + for i, tc := range testCases { + desc := fmt.Sprintf("Test case %d, range %v", i, tc.br) + if tc.expectError { + ExpectNe(nil, errs[i], "%q", desc) + } else { + ExpectEq(nil, errs[i], "%q", desc) + if tc.br.Limit > tc.br.Start { + ExpectEq(tc.expectedContents, contents[i].String(), "%s", desc) + } + } + } +} + //////////////////////////////////////////////////////////////////////// // Stat //////////////////////////////////////////////////////////////////////// diff --git a/internal/storage/fake/testing/register_bucket_tests.go b/internal/storage/fake/testing/register_bucket_tests.go index cdcd96be66..c901d01d67 100644 --- a/internal/storage/fake/testing/register_bucket_tests.go +++ b/internal/storage/fake/testing/register_bucket_tests.go @@ -138,6 +138,7 @@ func RegisterBucketTests(makeDeps func(context.Context) BucketTestDeps) { ©Test{}, &composeTest{}, &readTest{}, + &readMultiRangeTest{}, &statTest{}, &updateTest{}, &deleteTest{}, diff --git a/internal/storage/fake_storage_util.go b/internal/storage/fake_storage_util.go index 4d30d02a16..349272cdcc 100644 --- a/internal/storage/fake_storage_util.go +++ b/internal/storage/fake_storage_util.go @@ -16,6 +16,7 @@ package storage import ( "github.com/fsouza/fake-gcs-server/fakestorage" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" ) @@ -53,10 +54,20 @@ type FakeStorage interface { type fakeStorage struct { fakeStorageServer *fakestorage.Server + mockClient *MockStorageControlClient + protocol cfg.Protocol } func (f *fakeStorage) CreateStorageHandle() (sh StorageHandle) { - sh = &storageClient{client: f.fakeStorageServer.Client()} + if f.mockClient == nil { + f.mockClient = new(MockStorageControlClient) + } + sh = &storageClient{ + httpClient: f.fakeStorageServer.Client(), + grpcClient: f.fakeStorageServer.Client(), + storageControlClient: f.mockClient, + clientProtocol: f.protocol, + } return } @@ -75,6 +86,19 @@ func NewFakeStorage() FakeStorage { return fakeStorage } +func NewFakeStorageWithMockClient(mc *MockStorageControlClient, protocol cfg.Protocol) FakeStorage { + f, err := createFakeStorageServer(getTestFakeStorageObject()) + if err != nil { + panic(err) + } + fakeStorage := &fakeStorage{ + fakeStorageServer: f, + mockClient: mc, + protocol: protocol, + } + return fakeStorage +} + func getTestFakeStorageObject() []fakestorage.Object { var fakeObjects []fakestorage.Object testObjectRootFolder := fakestorage.Object{ diff --git a/internal/storage/gcs/bucket.go b/internal/storage/gcs/bucket.go index 909c94fbf0..1714f5c5aa 100644 --- a/internal/storage/gcs/bucket.go +++ b/internal/storage/gcs/bucket.go @@ -21,19 +21,11 @@ import ( "golang.org/x/net/context" ) -// BucketType represents different types of buckets like -// Hierarchical or NonHierarchical as constants. -type BucketType int - -// BucketType enum values. -const ( - // A default value of "nil" indicates that the bucket - // type has not been specified. - Nil BucketType = iota - NonHierarchical - Hierarchical - Unknown -) +// BucketType represents bucket features. +type BucketType struct { + Hierarchical bool + Zonal bool +} const ( // ReqIdField is the key for the value of @@ -64,7 +56,7 @@ type Writer interface { type Bucket interface { Name() string - // Return Type of bucket e.g. Hierarchical or NonHierarchical + // Return Type of bucket. BucketType() BucketType // Create a reader for the contents of a particular generation of an object. @@ -80,6 +72,22 @@ type Bucket interface { ctx context.Context, req *ReadObjectRequest) (io.ReadCloser, error) + // Similar to NewReader. But establishes connection using the readHandle if not nil. + // ReadHandle helps in reducing the latency by eleminating auth/metadata checks when a valid readHandle is passed. + // ReadHandle is valid when its not nil, not expired and belongs to the same client. + NewReaderWithReadHandle( + ctx context.Context, + req *ReadObjectRequest) (StorageReader, error) + + // Create a new multi-range downloader for the contents of a particular generation of an object. + // On a nil error, the caller must arrange for the reader to be closed when + // it is no longer needed. + // + // Non-existent objects cause either this method or the first read from the + // resulting reader to return an error of type *NotFoundError. + NewMultiRangeDownloader( + ctx context.Context, req *MultiRangeDownloaderRequest) (MultiRangeDownloader, error) + // Create or overwrite an object according to the supplied request. The new // object is guaranteed to exist immediately for the purposes of reading (and // eventually for listing) after this method returns a nil error. It is diff --git a/internal/storage/gcs/multi_range_downloader.go b/internal/storage/gcs/multi_range_downloader.go new file mode 100644 index 0000000000..911c1b2a79 --- /dev/null +++ b/internal/storage/gcs/multi_range_downloader.go @@ -0,0 +1,25 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcs + +import "io" + +// An interface to generalize the MultiRangeDownloader +// structure in go-storage module to ease our testing. +type MultiRangeDownloader interface { + Add(output io.Writer, offset, length int64, callback func(int64, int64, error)) + Close() error + Wait() +} diff --git a/internal/storage/gcs/request.go b/internal/storage/gcs/request.go index f32bb2e1e6..42696e1ac5 100644 --- a/internal/storage/gcs/request.go +++ b/internal/storage/gcs/request.go @@ -19,6 +19,7 @@ import ( "fmt" "io" + storagev2 "cloud.google.com/go/storage" storagev1 "google.golang.org/api/storage/v1" ) @@ -167,6 +168,12 @@ type ComposeSource struct { Generation int64 } +// StorageReader implements the storage.Reader. +type StorageReader interface { + ReadHandle() storagev2.ReadHandle + io.ReadCloser +} + // ByteRange is a [start, limit) range of bytes within an object. // // Its semantics are as follows: @@ -201,6 +208,22 @@ type ReadObjectRequest struct { // If present, read the contents of the GCS object as it is on GCS. // This might not be honoured by all the implementations. ReadCompressed bool + + // ReadHandle associated with the object. This would be periodically refreshed. + ReadHandle []byte +} + +// A request to read the contents of an object at a particular generation. +type MultiRangeDownloaderRequest struct { + // The name of the object to read. + Name string + + // The generation of the object to read. Zero means the latest generation. + Generation int64 + + // If present, read the contents of the GCS object as it is on GCS. + // This might not be honoured by all the implementations. + ReadCompressed bool } type StatObjectRequest struct { diff --git a/internal/storage/mock/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go index 46565f7092..7816ad110c 100644 --- a/internal/storage/mock/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -42,6 +42,14 @@ func (m *TestifyMockBucket) NewReader(ctx context.Context, req *gcs.ReadObjectRe return args.Get(0).(io.ReadCloser), args.Error(1) } +func (m *TestifyMockBucket) NewReaderWithReadHandle(ctx context.Context, req *gcs.ReadObjectRequest) (gcs.StorageReader, error) { + args := m.Called(ctx, req) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(gcs.StorageReader), args.Error(1) +} + func (m *TestifyMockBucket) CreateObject(ctx context.Context, req *gcs.CreateObjectRequest) (*gcs.Object, error) { args := m.Called(ctx, req) if args.Get(1) != nil { @@ -132,3 +140,12 @@ func (m *TestifyMockBucket) CreateFolder(ctx context.Context, folderName string) } return nil, args.Error(1) } + +func (m *TestifyMockBucket) NewMultiRangeDownloader( + ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (gcs.MultiRangeDownloader, error) { + args := m.Called(ctx, req) + if args.Get(0) != nil { + return args.Get(0).(gcs.MultiRangeDownloader), nil + } + return nil, args.Error(1) +} diff --git a/internal/storage/mock_bucket.go b/internal/storage/mock_bucket.go index 9ef891c64f..d36608dd74 100644 --- a/internal/storage/mock_bucket.go +++ b/internal/storage/mock_bucket.go @@ -371,6 +371,35 @@ func (m *mockBucket) NewReader(p0 context.Context, p1 *gcs.ReadObjectRequest) (o return } +func (m *mockBucket) NewReaderWithReadHandle(p0 context.Context, p1 *gcs.ReadObjectRequest) (o0 gcs.StorageReader, o1 error) { + // Get a file name and line number for the caller. + _, file, line, _ := runtime.Caller(1) + + // Hand the call off to the controller, which does most of the work. + retVals := m.controller.HandleMethodCall( + m, + "NewReaderWithReadHandle", + file, + line, + []interface{}{p0, p1}) + + if len(retVals) != 2 { + panic(fmt.Sprintf("mockBucket.NewReaderWithReadHandle: invalid return values: %v", retVals)) + } + + // o0 gcs.StorageReader + if retVals[0] != nil { + o0 = retVals[0].(gcs.StorageReader) + } + + // o1 error + if retVals[1] != nil { + o1 = retVals[1].(error) + } + + return +} + func (m *mockBucket) StatObject(p0 context.Context, p1 *gcs.StatObjectRequest) (o0 *gcs.MinObject, o1 *gcs.ExtendedObjectAttributes, o2 error) { // Get a file name and line number for the caller. @@ -517,3 +546,33 @@ func (m *mockBucket) RenameFolder(ctx context.Context, folderName string, destin } return } + +func (m *mockBucket) NewMultiRangeDownloader( + p0 context.Context, p1 *gcs.MultiRangeDownloaderRequest) (o0 gcs.MultiRangeDownloader, o1 error) { + // Get a file name and line number for the caller. + _, file, line, _ := runtime.Caller(1) + + // Hand the call off to the controller, which does most of the work. + retVals := m.controller.HandleMethodCall( + m, + "NewMultiRangeDownloader", + file, + line, + []interface{}{p0, p1}) + + if len(retVals) != 2 { + panic(fmt.Sprintf("mockBucket.NewMultiRangeDownloader: invalid return values: %v", retVals)) + } + + // o0 io.ReadCloser + if retVals[0] != nil { + o0 = retVals[0].(gcs.MultiRangeDownloader) + } + + // o1 error + if retVals[1] != nil { + o1 = retVals[1].(error) + } + + return +} diff --git a/internal/storage/mock_control_client.go b/internal/storage/mock_control_client.go index d76446de5d..3c935454ce 100644 --- a/internal/storage/mock_control_client.go +++ b/internal/storage/mock_control_client.go @@ -34,7 +34,11 @@ func (m *MockStorageControlClient) GetStorageLayout(ctx context.Context, req *controlpb.GetStorageLayoutRequest, opts ...gax.CallOption) (*controlpb.StorageLayout, error) { args := m.Called(ctx, req, opts) - return args.Get(0).(*controlpb.StorageLayout), args.Error(1) + + if args[1] != nil { + return nil, args.Error(1) + } + return args.Get(0).(*controlpb.StorageLayout), nil } // Implement the DeleteFolder method for the mock. diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index 3a38fe884b..1be60ec9cd 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -19,13 +19,16 @@ import ( "net/http" "os" "strconv" + "time" "cloud.google.com/go/storage" control "cloud.google.com/go/storage/control/apiv2" + "cloud.google.com/go/storage/control/apiv2/controlpb" "cloud.google.com/go/storage/experimental" "github.com/googleapis/gax-go/v2" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "golang.org/x/net/context" option "google.golang.org/api/option" @@ -42,6 +45,8 @@ const ( // Ref: https://github.com/googleapis/google-cloud-go/blob/main/storage/option.go#L30 dynamicReadReqIncreaseRateEnv = "DYNAMIC_READ_REQ_INCREASE_RATE" dynamicReadReqInitialTimeoutEnv = "DYNAMIC_READ_REQ_INITIAL_TIMEOUT" + + zonalLocationType = "zone" ) type StorageHandle interface { @@ -50,12 +55,14 @@ type StorageHandle interface { // to that project rather than to the bucket's owning project. // // A user-project is required for all operations on Requester Pays buckets. - BucketHandle(ctx context.Context, bucketName string, billingProject string) (bh *bucketHandle) + BucketHandle(ctx context.Context, bucketName string, billingProject string) (bh *bucketHandle, err error) } type storageClient struct { - client *storage.Client - storageControlClient *control.StorageControlClient + httpClient *storage.Client + grpcClient *storage.Client + clientProtocol cfg.Protocol + storageControlClient StorageControlClient directPathDetector *gRPCDirectPathDetector } @@ -74,27 +81,22 @@ func (pd *gRPCDirectPathDetector) isDirectPathPossible(ctx context.Context, buck func createClientOptionForGRPCClient(clientConfig *storageutil.StorageClientConfig) (clientOpts []option.ClientOption, err error) { // Add Custom endpoint option. if clientConfig.CustomEndpoint != "" { + clientOpts = append(clientOpts, option.WithEndpoint(storageutil.StripScheme(clientConfig.CustomEndpoint))) + // TODO(b/390799251): Check if this line can be merged with below anonymousAccess check. if clientConfig.AnonymousAccess { - clientOpts = append(clientOpts, option.WithEndpoint(storageutil.StripScheme(clientConfig.CustomEndpoint))) - // Explicitly disable auth in case of custom-endpoint, aligned with the http-client. - // TODO: to revisit here when supporting TPC for grpc client. - clientOpts = append(clientOpts, option.WithoutAuthentication()) clientOpts = append(clientOpts, option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials()))) - } else { - err = fmt.Errorf("GRPC client doesn't support auth for custom-endpoint. Please set anonymous-access: true via config-file.") - return } + } + + if clientConfig.AnonymousAccess { + clientOpts = append(clientOpts, option.WithoutAuthentication()) } else { - if clientConfig.AnonymousAccess { - clientOpts = append(clientOpts, option.WithoutAuthentication()) - } else { - tokenSrc, tokenCreationErr := storageutil.CreateTokenSource(clientConfig) - if tokenCreationErr != nil { - err = fmt.Errorf("while fetching tokenSource: %w", tokenCreationErr) - return - } - clientOpts = append(clientOpts, option.WithTokenSource(tokenSrc)) + tokenSrc, tokenCreationErr := storageutil.CreateTokenSource(clientConfig) + if tokenCreationErr != nil { + err = fmt.Errorf("while fetching tokenSource: %w", tokenCreationErr) + return } + clientOpts = append(clientOpts, option.WithTokenSource(tokenSrc)) } clientOpts = append(clientOpts, option.WithGRPCConnectionPool(clientConfig.GrpcConnPoolSize)) @@ -107,9 +109,6 @@ func createClientOptionForGRPCClient(clientConfig *storageutil.StorageClientConf // Followed https://pkg.go.dev/cloud.google.com/go/storage#hdr-Experimental_gRPC_API to create the gRPC client. func createGRPCClientHandle(ctx context.Context, clientConfig *storageutil.StorageClientConfig) (sc *storage.Client, err error) { - if clientConfig.ClientProtocol != cfg.GRPC { - return nil, fmt.Errorf("client-protocol requested is not GRPC: %s", clientConfig.ClientProtocol) - } if err := os.Setenv("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS", "true"); err != nil { logger.Fatal("error setting direct path env var: %v", err) @@ -138,19 +137,15 @@ func createHTTPClientHandle(ctx context.Context, clientConfig *storageutil.Stora var clientOpts []option.ClientOption // Add WithHttpClient option. - if clientConfig.ClientProtocol == cfg.HTTP1 || clientConfig.ClientProtocol == cfg.HTTP2 { - var httpClient *http.Client - httpClient, err = storageutil.CreateHttpClient(clientConfig) - if err != nil { - err = fmt.Errorf("while creating http endpoint: %w", err) - return - } - - clientOpts = append(clientOpts, option.WithHTTPClient(httpClient)) - } else { - return nil, fmt.Errorf("client-protocol requested is not HTTP1 or HTTP2: %s", clientConfig.ClientProtocol) + var httpClient *http.Client + httpClient, err = storageutil.CreateHttpClient(clientConfig) + if err != nil { + err = fmt.Errorf("while creating http endpoint: %w", err) + return } + clientOpts = append(clientOpts, option.WithHTTPClient(httpClient)) + if clientConfig.AnonymousAccess { clientOpts = append(clientOpts, option.WithoutAuthentication()) } @@ -191,45 +186,79 @@ func createHTTPClientHandle(ctx context.Context, clientConfig *storageutil.Stora return storage.NewClient(ctx, clientOpts...) } +func (sh *storageClient) lookupBucketType(bucketName string) (*gcs.BucketType, error) { + var nilControlClient *control.StorageControlClient = nil + if sh.storageControlClient == nilControlClient { + return &gcs.BucketType{}, nil // Assume defaults + } + + startTime := time.Now() + logger.Infof("GetStorageLayout <- (%s)", bucketName) + storageLayout, err := sh.getStorageLayout(bucketName) + duration := time.Since(startTime) + + if err != nil { + return nil, err + } + + logger.Infof("GetStorageLayout -> (%s) %v msec", bucketName, duration.Milliseconds()) + + return &gcs.BucketType{ + Hierarchical: storageLayout.GetHierarchicalNamespace().GetEnabled(), + Zonal: storageLayout.GetLocationType() == zonalLocationType, + }, nil +} + +func (sh *storageClient) getStorageLayout(bucketName string) (*controlpb.StorageLayout, error) { + var callOptions []gax.CallOption + stoargeLayout, err := sh.storageControlClient.GetStorageLayout(context.Background(), &controlpb.GetStorageLayoutRequest{ + Name: fmt.Sprintf("projects/_/buckets/%s/storageLayout", bucketName), + Prefix: "", + RequestId: "", + }, callOptions...) + + return stoargeLayout, err +} + // NewStorageHandle returns the handle of http or grpc Go storage client based on the // provided StorageClientConfig.ClientProtocol. // Please check out the StorageClientConfig to know about the parameters used in // http and gRPC client. func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClientConfig) (sh StorageHandle, err error) { - var sc *storage.Client + var hc, gc *storage.Client // The default protocol for the Go Storage control client's folders API is gRPC. // gcsfuse will initially mirror this behavior due to the client's lack of HTTP support. var controlClient *control.StorageControlClient var clientOpts []option.ClientOption var directPathDetector *gRPCDirectPathDetector - if clientConfig.ClientProtocol == cfg.GRPC { - sc, err = createGRPCClientHandle(ctx, &clientConfig) - if err == nil { - clientOpts, err = createClientOptionForGRPCClient(&clientConfig) - directPathDetector = &gRPCDirectPathDetector{clientOptions: clientOpts} - } - } else if clientConfig.ClientProtocol == cfg.HTTP1 || clientConfig.ClientProtocol == cfg.HTTP2 { - sc, err = createHTTPClientHandle(ctx, &clientConfig) - } else { - err = fmt.Errorf("invalid client-protocol requested: %s", clientConfig.ClientProtocol) + + gc, err = createGRPCClientHandle(ctx, &clientConfig) + if err == nil { + clientOpts, err = createClientOptionForGRPCClient(&clientConfig) + directPathDetector = &gRPCDirectPathDetector{clientOptions: clientOpts} + } + if err != nil { + err = fmt.Errorf("go grpc storage client creation failed: %w", err) + return } + hc, err = createHTTPClientHandle(ctx, &clientConfig) if err != nil { - err = fmt.Errorf("go storage client creation failed: %w", err) + err = fmt.Errorf("go http storage client creation failed: %w", err) return } // TODO: We will implement an additional check for the HTTP control client protocol once the Go SDK supports HTTP. // TODO: Custom endpoints do not currently support gRPC. Remove this additional check once TPC(custom-endpoint) supports gRPC. - if clientConfig.EnableHNS && clientConfig.CustomEndpoint == "" { - clientOpts, err = createClientOptionForGRPCClient(&clientConfig) - if err != nil { - return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) - } - controlClient, err = storageutil.CreateGRPCControlClient(ctx, clientOpts, &clientConfig) - if err != nil { - return nil, fmt.Errorf("could not create StorageControl Client: %w", err) - } + // Create storageControlClient irrespective of whether hns needs to be enabled or not. + // Because we will use storageControlClient to check layout of given bucket. + clientOpts, err = createClientOptionForGRPCClient(&clientConfig) + if err != nil { + return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) + } + controlClient, err = storageutil.CreateGRPCControlClient(ctx, clientOpts, &clientConfig) + if err != nil { + return nil, fmt.Errorf("could not create StorageControl Client: %w", err) } // ShouldRetry function checks if an operation should be retried based on the @@ -239,25 +268,52 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien // Without RetryAlways, only those operations are checked for retries which // are idempotent. // https://github.com/googleapis/google-cloud-go/blob/main/storage/storage.go#L1953 - sc.SetRetry( - storage.WithBackoff(gax.Backoff{ - Max: clientConfig.MaxRetrySleep, - Multiplier: clientConfig.RetryMultiplier, - }), + retryOpts := []storage.RetryOption{storage.WithBackoff(gax.Backoff{ + Max: clientConfig.MaxRetrySleep, + Multiplier: clientConfig.RetryMultiplier, + }), storage.WithPolicy(storage.RetryAlways), - storage.WithErrorFunc(storageutil.ShouldRetry)) + storage.WithErrorFunc(storageutil.ShouldRetry)} + + hc.SetRetry(retryOpts...) + gc.SetRetry(retryOpts...) // The default MaxRetryAttempts value is 0 indicates no limit. if clientConfig.MaxRetryAttempts != 0 { - sc.SetRetry(storage.WithMaxAttempts(clientConfig.MaxRetryAttempts)) + hc.SetRetry(storage.WithMaxAttempts(clientConfig.MaxRetryAttempts)) + gc.SetRetry(storage.WithMaxAttempts(clientConfig.MaxRetryAttempts)) } - sh = &storageClient{client: sc, storageControlClient: controlClient, directPathDetector: directPathDetector} + sh = &storageClient{ + httpClient: hc, + grpcClient: gc, + storageControlClient: controlClient, + clientProtocol: clientConfig.ClientProtocol, + directPathDetector: directPathDetector, + } return } -func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, billingProject string) (bh *bucketHandle) { - storageBucketHandle := sh.client.Bucket(bucketName) +func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, billingProject string) (bh *bucketHandle, err error) { + var client *storage.Client + bucketType, err := sh.lookupBucketType(bucketName) + if err != nil { + return nil, fmt.Errorf("storageLayout call failed: %s", err) + } + + if bucketType.Zonal || sh.clientProtocol == cfg.GRPC { + client = sh.grpcClient + if sh.directPathDetector != nil { + if err := sh.directPathDetector.isDirectPathPossible(ctx, bucketName); err != nil { + logger.Warnf("Direct path connectivity unavailable for %s, reason: %v", bucketName, err) + } + } + } else if sh.clientProtocol == cfg.HTTP1 || sh.clientProtocol == cfg.HTTP2 { + client = sh.httpClient + } else { + return nil, fmt.Errorf("invalid client-protocol requested: %s", sh.clientProtocol) + } + storageBucketHandle := client.Bucket(bucketName) if billingProject != "" { storageBucketHandle = storageBucketHandle.UserProject(billingProject) @@ -267,11 +323,8 @@ func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, bi bucket: storageBucketHandle, bucketName: bucketName, controlClient: sh.storageControlClient, + bucketType: bucketType, } - if sh.directPathDetector != nil { - if err := sh.directPathDetector.isDirectPathPossible(ctx, bucketName); err != nil { - logger.Warnf("Direct path connectivity unavailable for %s, reason: %v", bucketName, err) - } - } + return } diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index a3ded1e24c..796bf5c509 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -21,10 +21,12 @@ import ( "testing" "time" + "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -35,6 +37,7 @@ const projectID string = "valid-project-id" type StorageHandleTest struct { suite.Suite fakeStorage FakeStorage + mockClient *MockStorageControlClient ctx context.Context } @@ -43,7 +46,8 @@ func TestStorageHandleTestSuite(t *testing.T) { } func (testSuite *StorageHandleTest) SetupTest() { - testSuite.fakeStorage = NewFakeStorage() + testSuite.mockClient = new(MockStorageControlClient) + testSuite.fakeStorage = NewFakeStorageWithMockClient(testSuite.mockClient, cfg.HTTP2) testSuite.ctx = context.Background() } @@ -51,36 +55,71 @@ func (testSuite *StorageHandleTest) TearDownTest() { testSuite.fakeStorage.ShutDown() } +func (testSuite *StorageHandleTest) mockStorageLayout(bucketType gcs.BucketType) { + storageLayout := &controlpb.StorageLayout{ + HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: false}, + LocationType: "nil", + } + + if bucketType.Zonal { + storageLayout.HierarchicalNamespace = &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true} + storageLayout.LocationType = "zone" + } + + if bucketType.Hierarchical { + storageLayout.HierarchicalNamespace = &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true} + storageLayout.LocationType = "multiregion" + } + + testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything).Return(storageLayout, nil) +} + func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketExistsWithEmptyBillingProject() { storageHandle := testSuite.fakeStorage.CreateStorageHandle() - bucketHandle := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, "") + testSuite.mockStorageLayout(gcs.BucketType{}) + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, "") assert.NotNil(testSuite.T(), bucketHandle) + assert.Nil(testSuite.T(), err) assert.Equal(testSuite.T(), TestBucketName, bucketHandle.bucketName) - assert.Equal(testSuite.T(), gcs.Nil, bucketHandle.bucketType) + assert.False(testSuite.T(), bucketHandle.bucketType.Zonal) + assert.False(testSuite.T(), bucketHandle.bucketType.Hierarchical) } func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketDoesNotExistWithEmptyBillingProject() { storageHandle := testSuite.fakeStorage.CreateStorageHandle() - bucketHandle := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, "") + testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). + Return(nil, fmt.Errorf("bucket does not exist")) + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, "") - assert.Nil(testSuite.T(), bucketHandle.Bucket) + assert.NotNil(testSuite.T(), err) + assert.Nil(testSuite.T(), bucketHandle) } func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketExistsWithNonEmptyBillingProject() { storageHandle := testSuite.fakeStorage.CreateStorageHandle() - bucketHandle := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, projectID) + testSuite.mockStorageLayout(gcs.BucketType{Hierarchical: true}) + + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, projectID) assert.NotNil(testSuite.T(), bucketHandle) + assert.Nil(testSuite.T(), err) assert.Equal(testSuite.T(), TestBucketName, bucketHandle.bucketName) - assert.Equal(testSuite.T(), gcs.Nil, bucketHandle.bucketType) + assert.False(testSuite.T(), bucketHandle.bucketType.Zonal) + assert.True(testSuite.T(), bucketHandle.bucketType.Hierarchical) + // verify the billing account set. + testHandle := bucketHandle + assert.Equal(testSuite.T(), bucketHandle.bucket, testHandle.bucket.UserProject(projectID)) } func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketDoesNotExistWithNonEmptyBillingProject() { storageHandle := testSuite.fakeStorage.CreateStorageHandle() - bucketHandle := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, projectID) + testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). + Return(nil, fmt.Errorf("bucket does not exist")) + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, projectID) - assert.Nil(testSuite.T(), bucketHandle.Bucket) + assert.Nil(testSuite.T(), bucketHandle) + assert.NotNil(testSuite.T(), err) } func (testSuite *StorageHandleTest) TestNewStorageHandleHttp2Disabled() { @@ -192,37 +231,33 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenJsonReadEnabled() { } func (testSuite *StorageHandleTest) TestNewStorageHandleWithInvalidClientProtocol() { - sc := storageutil.GetDefaultStorageClientConfig() - sc.ExperimentalEnableJsonRead = true - sc.ClientProtocol = "test-protocol" - - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + fakeStorage := NewFakeStorageWithMockClient(testSuite.mockClient, "test-protocol") + testSuite.mockStorageLayout(gcs.BucketType{}) + sh := fakeStorage.CreateStorageHandle() + assert.NotNil(testSuite.T(), sh) + bh, err := sh.BucketHandle(testSuite.ctx, TestBucketName, projectID) + assert.Nil(testSuite.T(), bh) assert.NotNil(testSuite.T(), err) - assert.Nil(testSuite.T(), handleCreated) assert.Contains(testSuite.T(), err.Error(), "invalid client-protocol requested: test-protocol") } func (testSuite *StorageHandleTest) TestNewStorageHandleDirectPathDetector() { testCases := []struct { - name string - clientProtocol cfg.Protocol - expectDirectPathDetector bool + name string + clientProtocol cfg.Protocol }{ { - name: "grpcWithNonNilDirectPathDetector", - clientProtocol: cfg.GRPC, - expectDirectPathDetector: true, + name: "grpcWithNonNilDirectPathDetector", + clientProtocol: cfg.GRPC, }, { - name: "http1WithNilDirectPathDetector", - clientProtocol: cfg.HTTP1, - expectDirectPathDetector: false, + name: "http1WithNilDirectPathDetector", + clientProtocol: cfg.HTTP1, }, { - name: "http2WithNilDirectPathDetector", - clientProtocol: cfg.HTTP2, - expectDirectPathDetector: false, + name: "http2WithNilDirectPathDetector", + clientProtocol: cfg.HTTP2, }, } @@ -239,11 +274,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleDirectPathDetector() { storageClient, ok := handleCreated.(*storageClient) assert.True(testSuite.T(), ok) - if tc.expectDirectPathDetector { - assert.NotNil(testSuite.T(), storageClient.directPathDetector) - } else { - assert.Nil(testSuite.T(), storageClient.directPathDetector) - } + assert.NotNil(testSuite.T(), storageClient.directPathDetector) }) } } @@ -277,28 +308,6 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientProtocol() assert.NotNil(testSuite.T(), storageClient) } -func (testSuite *StorageHandleTest) TestCreateGRPCClientHandle_WithHTTPClientProtocol() { - sc := storageutil.GetDefaultStorageClientConfig() - sc.ClientProtocol = cfg.HTTP1 - - storageClient, err := createGRPCClientHandle(testSuite.ctx, &sc) - - assert.NotNil(testSuite.T(), err) - assert.Nil(testSuite.T(), storageClient) - assert.Contains(testSuite.T(), err.Error(), fmt.Sprintf("client-protocol requested is not GRPC: %s", cfg.HTTP1)) -} - -func (testSuite *StorageHandleTest) TestCreateHTTPClientHandle_WithGRPCClientProtocol() { - sc := storageutil.GetDefaultStorageClientConfig() - sc.ClientProtocol = cfg.GRPC - - storageClient, err := createHTTPClientHandle(testSuite.ctx, &sc) - - assert.NotNil(testSuite.T(), err) - assert.Nil(testSuite.T(), storageClient) - assert.Contains(testSuite.T(), err.Error(), fmt.Sprintf("client-protocol requested is not HTTP1 or HTTP2: %s", cfg.GRPC)) -} - func (testSuite *StorageHandleTest) TestCreateHTTPClientHandle_WithReadStallRetry() { testCases := []struct { name string @@ -497,12 +506,28 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustom sc.CustomEndpoint = url.String() sc.AnonymousAccess = false sc.ClientProtocol = cfg.GRPC + sc.TokenUrl = storageutil.CustomTokenUrl + sc.KeyFile = "" handleCreated, err := NewStorageHandle(testSuite.ctx, sc) - assert.NotNil(testSuite.T(), err) - assert.Contains(testSuite.T(), err.Error(), "GRPC client doesn't support auth for custom-endpoint. Please set anonymous-access: true via config-file.") - assert.Nil(testSuite.T(), handleCreated) + assert.Nil(testSuite.T(), err) + assert.NotNil(testSuite.T(), handleCreated) +} + +func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustomEndpointAndAuthDisabled() { + url, err := url.Parse(storageutil.CustomEndpoint) + assert.Nil(testSuite.T(), err) + sc := storageutil.GetDefaultStorageClientConfig() + sc.CustomEndpoint = url.String() + sc.ClientProtocol = cfg.GRPC + sc.TokenUrl = storageutil.CustomTokenUrl + sc.KeyFile = "" + + handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + + assert.Nil(testSuite.T(), err) + assert.NotNil(testSuite.T(), handleCreated) } func (testSuite *StorageHandleTest) TestCreateStorageHandleWithEnableHNSTrue() { diff --git a/internal/storage/storageutil/client.go b/internal/storage/storageutil/client.go index 48d081eb16..77016b5ece 100644 --- a/internal/storage/storageutil/client.go +++ b/internal/storage/storageutil/client.go @@ -126,6 +126,10 @@ func CreateTokenSource(storageClientConfig *StorageClientConfig) (tokenSrc oauth // StripScheme strips the scheme part of given url. func StripScheme(url string) string { + // Don't strip off the scheme part for google-internal schemes. + if strings.HasPrefix(url, "dns:///") || strings.HasPrefix(url, "google-c2p:///") || strings.HasPrefix(url, "google:///") { + return url + } if strings.Contains(url, urlSchemeSeparator) { url = strings.SplitN(url, urlSchemeSeparator, 2)[1] } diff --git a/internal/storage/storageutil/client_test.go b/internal/storage/storageutil/client_test.go index 3eb8965a11..30795b4c9f 100644 --- a/internal/storage/storageutil/client_test.go +++ b/internal/storage/storageutil/client_test.go @@ -18,29 +18,30 @@ import ( "net/http" "testing" - "github.com/jacobsa/oglematchers" - . "github.com/jacobsa/ogletest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "golang.org/x/oauth2" ) -func TestClient(t *testing.T) { RunTests(t) } +func TestClient(t *testing.T) { + suite.Run(t, new(clientTest)) +} type clientTest struct { + suite.Suite } -func init() { RegisterTestSuite(&clientTest{}) } - // Helpers func (t *clientTest) validateProxyInTransport(httpClient *http.Client) { userAgentRT, ok := httpClient.Transport.(*userAgentRoundTripper) - AssertEq(true, ok) + assert.True(t.T(), ok) oauthTransport, ok := userAgentRT.wrapped.(*oauth2.Transport) - AssertEq(true, ok) + assert.True(t.T(), ok) transport, ok := oauthTransport.Base.(*http.Transport) - AssertEq(true, ok) + assert.True(t.T(), ok) if ok { - ExpectEq(http.ProxyFromEnvironment, transport.Proxy) + assert.Equal(t.T(), http.ProxyFromEnvironment, transport.Proxy) } } @@ -51,9 +52,9 @@ func (t *clientTest) TestCreateHttpClientWithHttp1() { httpClient, err := CreateHttpClient(&sc) - ExpectEq(nil, err) - ExpectNe(nil, httpClient) - ExpectEq(sc.HttpClientTimeout, httpClient.Timeout) + assert.NoError(t.T(), err) + assert.NotNil(t.T(), httpClient) + assert.Equal(t.T(), sc.HttpClientTimeout, httpClient.Timeout) } func (t *clientTest) TestCreateHttpClientWithHttp2() { @@ -61,9 +62,9 @@ func (t *clientTest) TestCreateHttpClientWithHttp2() { httpClient, err := CreateHttpClient(&sc) - ExpectEq(nil, err) - ExpectNe(nil, httpClient) - ExpectEq(sc.HttpClientTimeout, httpClient.Timeout) + assert.NoError(t.T(), err) + assert.NotNil(t.T(), httpClient) + assert.Equal(t.T(), sc.HttpClientTimeout, httpClient.Timeout) } func (t *clientTest) TestCreateHttpClientWithHttp1AndAuthEnabled() { @@ -73,9 +74,9 @@ func (t *clientTest) TestCreateHttpClientWithHttp1AndAuthEnabled() { // Act: this method add tokenSource and clientOptions. httpClient, err := CreateHttpClient(&sc) - AssertNe(nil, err) - ExpectThat(err, oglematchers.Error(oglematchers.HasSubstr("no such file or directory"))) - AssertEq(nil, httpClient) + assert.Error(t.T(), err) + assert.ErrorContains(t.T(), err, "no such file or directory") + assert.Nil(t.T(), httpClient) } func (t *clientTest) TestCreateHttpClientWithHttp2AndAuthEnabled() { @@ -84,9 +85,9 @@ func (t *clientTest) TestCreateHttpClientWithHttp2AndAuthEnabled() { // Act: this method add tokenSource and clientOptions. httpClient, err := CreateHttpClient(&sc) - AssertNe(nil, err) - ExpectThat(err, oglematchers.Error(oglematchers.HasSubstr("no such file or directory"))) - AssertEq(nil, httpClient) + assert.Error(t.T(), err) + assert.ErrorContains(t.T(), err, "no such file or directory") + assert.Nil(t.T(), httpClient) } func (t *clientTest) TestCreateTokenSrc() { @@ -94,9 +95,9 @@ func (t *clientTest) TestCreateTokenSrc() { tokenSrc, err := CreateTokenSource(&sc) - AssertNe(nil, err) - ExpectThat(err, oglematchers.Error(oglematchers.HasSubstr("no such file or directory"))) - ExpectNe(nil, &tokenSrc) + assert.Error(t.T(), err) + assert.ErrorContains(t.T(), err, "no such file or directory") + assert.NotEqual(t.T(), nil, &tokenSrc) } func (t *clientTest) TestStripScheme() { @@ -124,9 +125,21 @@ func (t *clientTest) TestStripScheme() { input: "bad://http://localhost:888://", expectedOutput: "http://localhost:888://", }, + { + input: "dns:///localhost:888://", + expectedOutput: "dns:///localhost:888://", + }, + { + input: "google-c2p:///localhost:888://", + expectedOutput: "google-c2p:///localhost:888://", + }, + { + input: "google:///localhost:888://", + expectedOutput: "google:///localhost:888://", + }, } { output := StripScheme(tc.input) - AssertEq(tc.expectedOutput, output) + assert.Equal(t.T(), tc.expectedOutput, output) } } diff --git a/internal/storage/testify_mock_bucket.go b/internal/storage/testify_mock_bucket.go new file mode 100644 index 0000000000..995515a667 --- /dev/null +++ b/internal/storage/testify_mock_bucket.go @@ -0,0 +1,151 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package storage + +import ( + "context" + "io" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/stretchr/testify/mock" +) + +// TODO: Rename to mock bucket once deprecated ogle mock bucket is removed from all usages in unit tests +type TestifyMockBucket struct { + mock.Mock +} + +func (m *TestifyMockBucket) Name() string { + args := m.Called() + return args.String(0) +} + +func (m *TestifyMockBucket) BucketType() gcs.BucketType { + args := m.Called() + return args.Get(0).(gcs.BucketType) +} + +func (m *TestifyMockBucket) NewReader(ctx context.Context, req *gcs.ReadObjectRequest) (io.ReadCloser, error) { + args := m.Called(ctx, req) + return args.Get(0).(io.ReadCloser), args.Error(1) +} + +func (m *TestifyMockBucket) NewReaderWithReadHandle(ctx context.Context, req *gcs.ReadObjectRequest) (gcs.StorageReader, error) { + args := m.Called(ctx, req) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(gcs.StorageReader), args.Error(1) +} + +func (m *TestifyMockBucket) CreateObject(ctx context.Context, req *gcs.CreateObjectRequest) (*gcs.Object, error) { + args := m.Called(ctx, req) + if args.Get(1) != nil { + return nil, args.Error(1) + } + return args.Get(0).(*gcs.Object), nil +} + +func (m *TestifyMockBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.CreateObjectRequest, chunkSize int, callBack func(bytesUploadedSoFar int64)) (wc gcs.Writer, err error) { + args := m.Called(ctx, req) + if args.Get(1) != nil { + return nil, args.Error(1) + } + return args.Get(0).(gcs.Writer), nil +} + +func (m *TestifyMockBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { + args := m.Called(ctx, w.ObjectName()) + return args.Get(0).(*gcs.MinObject), args.Error(1) +} + +func (m *TestifyMockBucket) CopyObject(ctx context.Context, req *gcs.CopyObjectRequest) (*gcs.Object, error) { + args := m.Called(ctx, req) + return args.Get(0).(*gcs.Object), args.Error(1) +} + +func (m *TestifyMockBucket) ComposeObjects(ctx context.Context, req *gcs.ComposeObjectsRequest) (*gcs.Object, error) { + args := m.Called(ctx, req) + return args.Get(0).(*gcs.Object), args.Error(1) +} + +func (m *TestifyMockBucket) StatObject(ctx context.Context, req *gcs.StatObjectRequest) (*gcs.MinObject, *gcs.ExtendedObjectAttributes, error) { + args := m.Called(ctx, req) + if args.Get(2) != nil { + return nil, nil, args.Error(2) + } + return args.Get(0).(*gcs.MinObject), args.Get(1).(*gcs.ExtendedObjectAttributes), nil +} + +func (m *TestifyMockBucket) ListObjects(ctx context.Context, req *gcs.ListObjectsRequest) (*gcs.Listing, error) { + args := m.Called(ctx, req) + return args.Get(0).(*gcs.Listing), args.Error(1) +} + +func (m *TestifyMockBucket) UpdateObject(ctx context.Context, req *gcs.UpdateObjectRequest) (*gcs.Object, error) { + args := m.Called(ctx, req) + return args.Get(0).(*gcs.Object), args.Error(1) +} + +func (m *TestifyMockBucket) DeleteObject(ctx context.Context, req *gcs.DeleteObjectRequest) error { + args := m.Called(ctx, req) + return args.Error(0) +} + +func (m *TestifyMockBucket) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { + args := m.Called(ctx, req) + if args.Get(0) != nil { + return args.Get(0).(*gcs.Object), nil + } + return nil, args.Error(1) +} + +func (m *TestifyMockBucket) DeleteFolder(ctx context.Context, folderName string) error { + args := m.Called(ctx, folderName) + return args.Error(0) +} + +func (m *TestifyMockBucket) GetFolder(ctx context.Context, folderName string) (*gcs.Folder, error) { + args := m.Called(ctx, folderName) + if args.Get(0) != nil { + return args.Get(0).(*gcs.Folder), nil + } + return nil, args.Error(1) +} + +func (m *TestifyMockBucket) RenameFolder(ctx context.Context, folderName string, destinationFolderId string) (*gcs.Folder, error) { + args := m.Called(ctx, folderName, destinationFolderId) + if args.Get(0) != nil { + return args.Get(0).(*gcs.Folder), nil + } + return nil, args.Error(1) +} + +func (m *TestifyMockBucket) CreateFolder(ctx context.Context, folderName string) (*gcs.Folder, error) { + args := m.Called(ctx, folderName) + if args.Get(0) != nil { + return args.Get(0).(*gcs.Folder), nil + } + return nil, args.Error(1) +} + +func (m *TestifyMockBucket) NewMultiRangeDownloader( + ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (gcs.MultiRangeDownloader, error) { + args := m.Called(ctx, req) + if args.Get(0) != nil { + return args.Get(0).(gcs.MultiRangeDownloader), nil + } + return nil, args.Error(1) +} diff --git a/tools/build_gcsfuse/main_test.go b/tools/build_gcsfuse/main_test.go new file mode 100644 index 0000000000..20003b7a18 --- /dev/null +++ b/tools/build_gcsfuse/main_test.go @@ -0,0 +1,65 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "os" + "os/exec" + "path" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestVersion(t *testing.T) { + dir, err := os.MkdirTemp(os.TempDir(), "gcsfuse-test") + if err != nil { + t.Fatalf("Error while creating temporary directory: %v", err) + } + t.Cleanup(func() { _ = os.RemoveAll(dir) }) + err = buildBinaries(dir, "../../", "99.88.77", nil) + if err != nil { + t.Fatalf("Error while building binary: %v", err) + } + testCases := []struct { + name string + args string + expected string + }{ + { + name: "Version Flag", + args: "--version", + expected: "gcsfuse version 99.88.77", + }, + { + name: "Version Shorthand with Viper config", + args: "-v", + expected: "gcsfuse version 99.88.77", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + cmd := exec.Command(path.Join(dir, "bin/gcsfuse"), tc.args) + + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Error running gcsfuse with args %v: %v", tc.args, err) + } + + assert.Contains(t, string(output), tc.expected) + }) + } +} diff --git a/tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go b/tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go new file mode 100644 index 0000000000..496b7ba57e --- /dev/null +++ b/tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go @@ -0,0 +1,80 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_gcs_algo + +import ( + "bytes" + "io" + "math/rand/v2" + "os" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "golang.org/x/sync/errgroup" +) + +func TestReadSameFileConcurrently(t *testing.T) { + fileSize := 30 * OneMB + filePathInLocalDisk, filePathInMntDir := setup.CreateFileAndCopyToMntDir(t, fileSize, DirForReadAlgoTests) + + var eG errgroup.Group + concurrentReaderCount := 3 + + // We will x numbers of concurrent threads trying to read from the same file. + for i := 0; i < concurrentReaderCount; i++ { + randomOffset := rand.Int64N(int64(fileSize)) + + eG.Go(func() error { + readAndCompare(t, filePathInMntDir, filePathInLocalDisk, randomOffset, 5*OneMB) + return nil + }) + } + + // Wait on threads to end. No error is returned by the read method. Hence, + // nothing handling it. + _ = eG.Wait() +} + +func readAndCompare(t *testing.T, filePathInMntDir string, filePathInLocalDisk string, offset int64, chunkSize int64) { + mountedFile, err := operations.OpenFileAsReadonly(filePathInMntDir) + if err != nil { + t.Fatalf("error in opening file from mounted directory :%d", err) + } + defer operations.CloseFile(mountedFile) + + // Perform 5 reads on each file. + numberOfReads := 5 + for i := 0; i < numberOfReads; i++ { + mountContents := make([]byte, chunkSize) + // Reading chunk size randomly from the file. + _, err = mountedFile.ReadAt(mountContents, offset) + if err == io.EOF { + err = nil + } + if err != nil { + t.Fatalf("error in read file from mounted directory :%d", err) + } + + diskContents, err := operations.ReadChunkFromFile(filePathInLocalDisk, chunkSize, offset, os.O_RDONLY) + if err != nil { + t.Fatalf("error in read file from local directory :%d", err) + } + + if !bytes.Equal(mountContents, diskContents) { + t.Fatalf("data mismatch between mounted directory and local disk") + } + } +} diff --git a/tools/integration_tests/read_gcs_algo/read_gcs_algo_test.go b/tools/integration_tests/read_gcs_algo/read_gcs_algo_test.go new file mode 100644 index 0000000000..6276802c00 --- /dev/null +++ b/tools/integration_tests/read_gcs_algo/read_gcs_algo_test.go @@ -0,0 +1,42 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_gcs_algo + +import ( + "os" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +const OneMB = 1024 * 1024 +const DirForReadAlgoTests = "dirForReadAlgoTests" + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + + // Run tests for mountedDirectory only if --mountedDirectory flag is set. + setup.RunTestsForMountedDirectoryFlag(m) + + // Run tests for testBucket + setup.SetUpTestDirForTestBucketFlag() + // Do not enable fileCache as we want to test gcs read flow. + mountConfigFlags := [][]string{{"--implicit-dirs=true"}} + successCode := static_mounting.RunTests(mountConfigFlags, m) + os.Exit(successCode) +} diff --git a/tools/integration_tests/read_gcs_algo/seq_diff_block_size_test.go b/tools/integration_tests/read_gcs_algo/seq_diff_block_size_test.go new file mode 100644 index 0000000000..ba043e33df --- /dev/null +++ b/tools/integration_tests/read_gcs_algo/seq_diff_block_size_test.go @@ -0,0 +1,62 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_gcs_algo + +import ( + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +type testCase struct { + name string // Name of the test case + offset int64 + chunkSize int64 +} + +func TestReadSequentialWithDifferentBlockSizes(t *testing.T) { + fileSize := 10 * OneMB + filePathInLocalDisk, filePathInMntDir := setup.CreateFileAndCopyToMntDir(t, fileSize, DirForReadAlgoTests) + + tests := []testCase{ + { + name: "0.5MB", // < 1MB + offset: 0, + chunkSize: OneMB / 2, + }, + { + name: "1MB", // Equal to kernel max buffer size i.e, 1MB + offset: 0, + chunkSize: OneMB, + }, + { + name: "1.5MB", // Not multiple of 1MB + offset: 0, + chunkSize: OneMB + (OneMB / 2), + }, + { + name: "5MB", // Multiple of 1MB + offset: 0, + chunkSize: 5 * OneMB, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + operations.ReadAndCompare(t, filePathInMntDir, filePathInLocalDisk, tc.offset, tc.chunkSize) + }) + } +} diff --git a/tools/integration_tests/read_gcs_algo/seq_to_ran_to_seq_read_test.go b/tools/integration_tests/read_gcs_algo/seq_to_ran_to_seq_read_test.go new file mode 100644 index 0000000000..473fee8f86 --- /dev/null +++ b/tools/integration_tests/read_gcs_algo/seq_to_ran_to_seq_read_test.go @@ -0,0 +1,48 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_gcs_algo + +import ( + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +func TestSeqReadThenRandomThenSeqRead(t *testing.T) { + filePathInLocalDisk, filePathInMntDir := setup.CreateFileAndCopyToMntDir(t, 50*OneMB, DirForReadAlgoTests) + + // Current read algorithm: + // https://github.com/GoogleCloudPlatform/gcsfuse/blob/v2.5.1/internal/gcsx/random_reader.go#L275 + // First 2 reads are considered sequential. + offset := int64(40 * OneMB) + chunkSize := int64(OneMB) + operations.ReadAndCompare(t, filePathInMntDir, filePathInLocalDisk, offset, chunkSize) + offset = int64(35 * OneMB) + operations.ReadAndCompare(t, filePathInMntDir, filePathInLocalDisk, offset, chunkSize) + + // Perform a couple of random reads. + offset = int64(30 * OneMB) + operations.ReadAndCompare(t, filePathInMntDir, filePathInLocalDisk, offset, chunkSize) + offset = int64(25 * OneMB) + operations.ReadAndCompare(t, filePathInMntDir, filePathInLocalDisk, offset, chunkSize) + offset = int64(20 * OneMB) + operations.ReadAndCompare(t, filePathInMntDir, filePathInLocalDisk, offset, chunkSize) + + // Here we are reading a chunkSize of 40MB which gets converted to sequential because of our + // current read algorithm. + offset = int64(10 * OneMB) + operations.ReadAndCompare(t, filePathInMntDir, filePathInLocalDisk, offset, 40*OneMB) +} diff --git a/tools/integration_tests/read_large_files/concurrent_read_files_test.go b/tools/integration_tests/read_large_files/concurrent_read_files_test.go index 5643e47bbc..34a5f26d63 100644 --- a/tools/integration_tests/read_large_files/concurrent_read_files_test.go +++ b/tools/integration_tests/read_large_files/concurrent_read_files_test.go @@ -15,8 +15,6 @@ package read_large_files import ( - "bytes" - "fmt" "os" "path" "testing" @@ -32,25 +30,6 @@ var FileThree = "fileThree" + setup.GenerateRandomString(5) + ".txt" const NumberOfFilesInLocalDiskForConcurrentRead = 3 -func readFile(fileInLocalDisk string, fileInMntDir string) error { - dataInMntDirFile, err := operations.ReadFile(fileInMntDir) - if err != nil { - return err - } - - dataInLocalDiskFile, err := operations.ReadFile(fileInLocalDisk) - if err != nil { - return err - } - - // Compare actual content and expect content. - if bytes.Equal(dataInLocalDiskFile, dataInMntDirFile) == false { - return fmt.Errorf("Reading incorrect file.") - } - - return nil -} - func TestReadFilesConcurrently(t *testing.T) { testDir := setup.SetupTestDirectory(DirForReadLargeFilesTests) @@ -65,7 +44,7 @@ func TestReadFilesConcurrently(t *testing.T) { file := path.Join(testDir, filesInLocalDisk[i]) filesPathInMntDir = append(filesPathInMntDir, file) - createFileOnDiskAndCopyToMntDir(fileInLocalDisk, file, FiveHundredMB, t) + setup.CreateFileOnDiskAndCopyToMntDir(t, fileInLocalDisk, file, FiveHundredMB) } var eG errgroup.Group @@ -76,7 +55,8 @@ func TestReadFilesConcurrently(t *testing.T) { // Thread to read the current file. eG.Go(func() error { - return readFile(filesPathInLocalDisk[fileIndex], filesPathInMntDir[fileIndex]) + operations.ReadAndCompare(t, filesPathInMntDir[fileIndex], filesPathInLocalDisk[fileIndex], 0, FiveHundredMB) + return nil }) } diff --git a/tools/integration_tests/read_large_files/random_read_large_file_test.go b/tools/integration_tests/read_large_files/random_read_large_file_test.go index 02140f483c..90286928ab 100644 --- a/tools/integration_tests/read_large_files/random_read_large_file_test.go +++ b/tools/integration_tests/read_large_files/random_read_large_file_test.go @@ -15,10 +15,7 @@ package read_large_files import ( - "bytes" "math/rand" - "os" - "path" "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" @@ -26,30 +23,13 @@ import ( ) func TestReadLargeFileRandomly(t *testing.T) { - testDir := setup.SetupTestDirectory(DirForReadLargeFilesTests) - fileInLocalDisk := path.Join(os.Getenv("HOME"), FiveHundredMBFile) - file := path.Join(testDir, FiveHundredMBFile) // Create and copy the local file in mountedDirectory. - createFileOnDiskAndCopyToMntDir(fileInLocalDisk, file, FiveHundredMB, t) + fileInLocalDisk, fileInMntDir := setup.CreateFileAndCopyToMntDir(t, FiveHundredMB, DirForReadLargeFilesTests) for i := 0; i < NumberOfRandomReadCalls; i++ { offset := rand.Int63n(MaxReadableByteFromFile - MinReadableByteFromFile) // Randomly read the data from file in mountedDirectory. - content, err := operations.ReadChunkFromFile(file, ChunkSize, offset, os.O_RDONLY) - if err != nil { - t.Errorf("Error in reading file: %v", err) - } - - // Read actual content from file located in local disk. - actualContent, err := operations.ReadChunkFromFile(fileInLocalDisk, ChunkSize, offset, os.O_RDONLY) - if err != nil { - t.Errorf("Error in reading file: %v", err) - } - - // Compare actual content and expect content. - if bytes.Equal(actualContent, content) == false { - t.Errorf("Error in reading file randomly.") - } + operations.ReadAndCompare(t, fileInMntDir, fileInLocalDisk, offset, ChunkSize) } // Removing file after testing. diff --git a/tools/integration_tests/read_large_files/read_large_files_test.go b/tools/integration_tests/read_large_files/read_large_files_test.go index cf82ed3ea5..e6713953f8 100644 --- a/tools/integration_tests/read_large_files/read_large_files_test.go +++ b/tools/integration_tests/read_large_files/read_large_files_test.go @@ -26,7 +26,6 @@ import ( "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) @@ -109,11 +108,3 @@ func TestMain(m *testing.M) { successCode := static_mounting.RunTests(flags, m) os.Exit(successCode) } - -func createFileOnDiskAndCopyToMntDir(fileInLocalDisk string, fileInMntDir string, fileSize int, t *testing.T) { - setup.RunScriptForTestData("testdata/write_content_of_fix_size_in_file.sh", fileInLocalDisk, strconv.Itoa(fileSize)) - err := operations.CopyFile(fileInLocalDisk, fileInMntDir) - if err != nil { - t.Errorf("Error in copying file:%v", err) - } -} diff --git a/tools/integration_tests/read_large_files/seq_read_large_file_test.go b/tools/integration_tests/read_large_files/seq_read_large_file_test.go index 978296e179..aa48e73f55 100644 --- a/tools/integration_tests/read_large_files/seq_read_large_file_test.go +++ b/tools/integration_tests/read_large_files/seq_read_large_file_test.go @@ -16,8 +16,6 @@ package read_large_files import ( "bytes" - "os" - "path" "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" @@ -25,14 +23,11 @@ import ( ) func TestReadLargeFileSequentially(t *testing.T) { - testDir := setup.SetupTestDirectory(DirForReadLargeFilesTests) // Create file of 500 MB with random data in local disk and copy it in mntDir. - fileInLocalDisk := path.Join(os.Getenv("HOME"), FiveHundredMBFile) - file := path.Join(testDir, FiveHundredMBFile) - createFileOnDiskAndCopyToMntDir(fileInLocalDisk, file, FiveHundredMB, t) + fileInLocalDisk, fileInMntDir := setup.CreateFileAndCopyToMntDir(t, FiveHundredMB, DirForReadLargeFilesTests) // Sequentially read the data from file. - content, err := operations.ReadFileSequentially(file, ChunkSize) + content, err := operations.ReadFileSequentially(fileInMntDir, ChunkSize) if err != nil { t.Errorf("Error in reading file: %v", err) } diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index e87e97ffbd..60d0ba7f72 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -28,9 +28,9 @@ BUCKET_LOCATION=$3 # Pass "true" to run e2e tests on TPC endpoint. # The default value will be false. RUN_TEST_ON_TPC_ENDPOINT=false -if [ $4 != "" ]; then - RUN_TEST_ON_TPC_ENDPOINT=$4 -fi +# if [ $4 != "" ]; then + # RUN_TEST_ON_TPC_ENDPOINT=$4 +# fi INTEGRATION_TEST_TIMEOUT_IN_MINS=90 RUN_TESTS_WITH_PRESUBMIT_FLAG=false @@ -350,11 +350,11 @@ function main(){ run_e2e_tests_for_flat_bucket & e2e_tests_flat_bucket_pid=$! - run_e2e_tests_for_emulator & - e2e_tests_emulator_pid=$! + # run_e2e_tests_for_emulator & + # e2e_tests_emulator_pid=$! - wait $e2e_tests_emulator_pid - e2e_tests_emulator_status=$? + # wait $e2e_tests_emulator_pid + # e2e_tests_emulator_status=$? wait $e2e_tests_flat_bucket_pid e2e_tests_flat_bucket_status=$? @@ -379,11 +379,11 @@ function main(){ exit_code=1 fi - if [ $e2e_tests_emulator_status != 0 ]; - then - echo "The e2e tests for emulator failed.." - exit_code=1 - fi + # if [ $e2e_tests_emulator_status != 0 ]; + # then + # echo "The e2e tests for emulator failed.." + # exit_code=1 + # fi exit $exit_code } diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 6dadddf87d..cb393b5eb7 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -688,6 +688,24 @@ func CreateLocalTempFile(content string, gzipCompress bool) (string, error) { return writeTextToFile(f, f.Name(), content, len(content)) } +// ReadAndCompare reads content from the given file paths and compares them. +func ReadAndCompare(t *testing.T, filePathInMntDir string, filePathInLocalDisk string, offset int64, chunkSize int64) { + t.Helper() + mountContents, err := ReadChunkFromFile(filePathInMntDir, chunkSize, offset, os.O_RDONLY) + if err != nil { + t.Fatalf("error in read file from mounted directory :%d", err) + } + + diskContents, err := ReadChunkFromFile(filePathInLocalDisk, chunkSize, offset, os.O_RDONLY) + if err != nil { + t.Fatalf("error in read file from local directory :%d", err) + } + + if !bytes.Equal(mountContents, diskContents) { + t.Fatalf("data mismatch between mounted directory and local disk") + } +} + func CreateLocalFile(ctx context.Context, t *testing.T, mntDir string, bucket gcs.Bucket, fileName string) (filePath string, f *os.File) { t.Helper() // Creating a file shouldn't create file on GCS. diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 850fc36f84..03afb0189b 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -25,6 +25,7 @@ import ( "path" "path/filepath" "runtime/debug" + "strconv" "strings" "testing" "time" @@ -533,3 +534,23 @@ func AppendFlagsToAllFlagsInTheFlagsSet(flagsSet *[][]string, newFlags ...string } *flagsSet = resultFlagsSet } + +// CreateFileAndCopyToMntDir creates a file of given size. +// The same file will be copied to the mounted directory as well. +func CreateFileAndCopyToMntDir(t *testing.T, fileSize int, dirName string) (string, string) { + testDir := SetupTestDirectory(dirName) + fileInLocalDisk := "test_file" + GenerateRandomString(5) + ".txt" + filePathInLocalDisk := path.Join(os.TempDir(), fileInLocalDisk) + filePathInMntDir := path.Join(testDir, fileInLocalDisk) + CreateFileOnDiskAndCopyToMntDir(t, filePathInLocalDisk, filePathInMntDir, fileSize) + return filePathInLocalDisk, filePathInMntDir +} + +// CreateFileOnDiskAndCopyToMntDir creates a file of given size and copies to given path. +func CreateFileOnDiskAndCopyToMntDir(t *testing.T, filePathInLocalDisk string, filePathInMntDir string, fileSize int) { + RunScriptForTestData("../util/setup/testdata/write_content_of_fix_size_in_file.sh", filePathInLocalDisk, strconv.Itoa(fileSize)) + err := operations.CopyFile(filePathInLocalDisk, filePathInMntDir) + if err != nil { + t.Errorf("Error in copying file:%v", err) + } +} diff --git a/tools/integration_tests/read_large_files/testdata/write_content_of_fix_size_in_file.sh b/tools/integration_tests/util/setup/testdata/write_content_of_fix_size_in_file.sh similarity index 100% rename from tools/integration_tests/read_large_files/testdata/write_content_of_fix_size_in_file.sh rename to tools/integration_tests/util/setup/testdata/write_content_of_fix_size_in_file.sh From 088fee5497e9a429335417b6f5dcc7712ff22878 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 20 Jan 2025 14:25:04 +0530 Subject: [PATCH 0144/1298] Update Semantics doc about changes in GCSFuse behavior after Write Failure Improvements (#2877) * Update Semantics doc about changes in GCSFuse behavior after Write Failure Improvements. * updated about changes in sync behavior after deletion from same mount * minor fix * minor fix * minor fix * Update semantics.md * Update docs/semantics.md Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * Update docs/semantics.md Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * Update docs/semantics.md Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * Update docs/semantics.md Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * resolved comments * Update semantics.md * Update docs/semantics.md Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * Update docs/semantics.md Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * Changed If to When for consistency. --------- Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> --- docs/semantics.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/semantics.md b/docs/semantics.md index cb627a8d55..f13ad56c38 100644 --- a/docs/semantics.md +++ b/docs/semantics.md @@ -43,9 +43,11 @@ when writing large files. **Concurrency** -Multiple readers can access the same or different objects from the same bucket without issue. Multiple writers can also write to different objects in the same bucket without issue. However, there is no concurrency control for multiple writers to the same file. When multiple writers try to replace a file, the last write wins and all previous writes are lost - there is no merging, version control, or user notification of the subsequent overwrite. Therefore, for data integrity it is recommended that multiple sources do not modify the same object. +Multiple readers can access the same or different objects within a bucket without issue. Likewise, multiple writers can modify different objects in the same bucket simultaneously without any issue. Concurrent writes to the same gcs object are supported from the same mount and behave similar to native file system. -**Write/read consistency** +However, when different mounts try to write to the same object, the flush from first mount wins. Other mounts that have not updated their local file descriptors after the object is modified will encounter a ```syscall.ESTALE``` error when attempting to save their edits due to precondition checks. Therefore, to ensure data is consistently written, it is strongly recommended that multiple sources do not modify the same object. + +**Write/Read consistency** Cloud Storage by nature is [strongly consistent](https://cloud.google.com/storage/docs/consistency). Cloud Storage FUSE offers close-to-open and fsync-to-open consistency. Once a file is closed, consistency is guaranteed in the following open and read immediately. @@ -55,6 +57,17 @@ Examples: - Machine A opens a file and writes then successfully closes or syncs it, and the file was not concurrently unlinked from the point of view of A. Machine B then opens the file after machine A finishes closing or syncing. Machine B will observe a version of the file at least as new as the one created by machine A. - Machine A and B both open the same file, which contains the text ‘ABC’. Machine A modifies the file to ‘ABC-123’ and closes/syncs the file which gets written back to Cloud Storage. After, Machine B, which still has the file open, instead modifies the file to ‘ABC-XYZ’, and saves and closes the file. As the last writer wins, the current state of the file will read ‘ABC-XYZ’. +**Stale File Handle Errors** + +To ensure consistency, Cloud Storage FUSE returns a ```syscall.ESTALE``` error when an application tries to access stale data. This can occur in the following circumstances: + +- **Concurrent Writes**: When multiple mounts have the same file open for writing, and one mount modifies and syncs the file, other mounts with open file descriptors will encounter this error when attempting to sync or close the file. +- **Read During Modification**: When an application is reading a file through a GCSFuse mount, and the same object is modified on GCS (by deleting, renaming, or changing its content or metadata), the GCSFuse reader will encounter this error. This is because GCSFuse detects that the file it was accessing has changed. +- **File Renaming During Write**: When an application is writing to a file through a GCSFuse mount, and the same object is renamed on Google Cloud Storage (via same or different GCSFuse mount or through another interface), the writer will encounter this error when syncing or closing the file. +- **File Deletion During Write**: When an application is writing to a file through a GCSFuse mount, and the same object is deleted on Google Cloud Storage (via different GCSFuse mount or through another interface), the writer will encounter this error when syncing or closing the file. + +These changes in Cloud Storage FUSE prioritize data integrity and provide users with clear indications of potential conflicts, preventing silent data loss and ensuring a more robust and reliable experience. + # Caching Cloud Storage FUSE has four forms of optional caching: stat, type, list, and file. Stat and type caches are enabled by default. Using Cloud Storage FUSE with file caching, list caching, stat caching, or type caching enabled can significantly increase performance but reduces consistency guarantees. From 574e9c4ea3212fcf05be5eb52d3f9bba2b0e1170 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Mon, 20 Jan 2025 09:19:15 +0000 Subject: [PATCH 0145/1298] doubling the wait for downloaded to happen (#2907) --- .../read_cache/cache_file_for_range_read_true_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go index 9f217d8eb3..0ad92c13bf 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go @@ -60,7 +60,7 @@ func (s *cacheFileForRangeReadTrueTest) TestRangeReadsWithCacheHit(t *testing.T) // Do a random read on file and validate from gcs. expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset5000, t) // Wait for the cache to propagate the updates before proceeding to get cache hit. - time.Sleep(2 * time.Second) + time.Sleep(4 * time.Second) // Read file again from zeroOffset 1000 and validate from gcs. expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset1000, t) From c0bf63e8aa7abce41dfff469228a2a2b4edd9caa Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 20 Jan 2025 16:05:56 +0530 Subject: [PATCH 0146/1298] Skip monitoring tests from cd (#2921) --- tools/cd_scripts/e2e_test.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 66026b9103..127bd0d2da 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -142,7 +142,6 @@ git checkout $(sed -n 2p ~/details.txt) |& tee -a ~/logs.txt set +e # Test directory arrays TEST_DIR_PARALLEL=( - "monitoring" "local_file" "log_rotation" "mounting" @@ -158,7 +157,6 @@ TEST_DIR_PARALLEL=( "log_content" "kernel_list_cache" "concurrent_operations" - "benchmarking" "mount_timeout" "stale_handle" "negative_stat_cache" From c0aae001d41d1660b3a858dbbe888ce6de3241ca Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 21 Jan 2025 08:42:51 +0530 Subject: [PATCH 0147/1298] [Move Object] Support on ZB (#2912) * move object support on ZB * updated unit tests * header check fix * creating common suite * creating common suite * remove unnecessary comment --- internal/fs/fs.go | 2 +- internal/fs/hns_bucket_common_test.go | 111 ++++++++++++++++++++++++++ internal/fs/hns_bucket_test.go | 80 +------------------ internal/fs/zonal_bucket_test.go | 61 ++++++++++++++ 4 files changed, 174 insertions(+), 80 deletions(-) create mode 100644 internal/fs/hns_bucket_common_test.go create mode 100644 internal/fs/zonal_bucket_test.go diff --git a/internal/fs/fs.go b/internal/fs/fs.go index b102efc406..0f8529ce3c 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2038,7 +2038,7 @@ func (fs *fileSystem) Rename( } return fs.renameNonHierarchicalDir(ctx, oldParent, op.OldName, newParent, op.NewName) } - if child.Bucket.BucketType().Hierarchical && fs.enableAtomicRenameObject { + if (child.Bucket.BucketType().Hierarchical && fs.enableAtomicRenameObject) || child.Bucket.BucketType().Zonal { return fs.renameHierarchicalFile(ctx, oldParent, op.OldName, child.MinObject, newParent, op.NewName) } return fs.renameNonHierarchicalFile(ctx, oldParent, op.OldName, child.MinObject, newParent, op.NewName) diff --git a/internal/fs/hns_bucket_common_test.go b/internal/fs/hns_bucket_common_test.go new file mode 100644 index 0000000000..7cff8bbd4c --- /dev/null +++ b/internal/fs/hns_bucket_common_test.go @@ -0,0 +1,111 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs_test + +import ( + "os" + "path" + "strings" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type HNSBucketCommonTest struct { + suite.Suite +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *HNSBucketCommonTest) TestRenameFileWithSrcFileDoesNotExist() { + oldFilePath := path.Join(mntDir, "file") + newFilePath := path.Join(mntDir, "file_rename") + + err := os.Rename(oldFilePath, newFilePath) + + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + _, err = os.Stat(newFilePath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) +} + +func (t *HNSBucketCommonTest) TestRenameFileWithDstDestFileExist() { + oldFilePath := path.Join(mntDir, "foo", "file1.txt") + _, err := os.Stat(oldFilePath) + assert.NoError(t.T(), err) + newFilePath := path.Join(mntDir, "foo", "file2.txt") + _, err = os.Stat(newFilePath) + assert.NoError(t.T(), err) + + err = os.Rename(oldFilePath, newFilePath) + + assert.NoError(t.T(), err) + _, err = os.Stat(oldFilePath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + content, err := os.ReadFile(newFilePath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), file1Content, string(content)) +} + +func (t *HNSBucketCommonTest) TestRenameFile() { + testCases := []struct { + name string + oldFilePath string + newFilePath string + wantContent string + }{ + { + name: "DifferentParent", + oldFilePath: path.Join(mntDir, "foo", "file1.txt"), + newFilePath: path.Join(mntDir, "bar", "file3.txt"), + wantContent: file1Content, + }, + { + name: "SameParent", + oldFilePath: path.Join(mntDir, "foo", "file2.txt"), + newFilePath: path.Join(mntDir, "foo", "file3.txt"), + wantContent: file2Content, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + // Ensure file exists before renaming. + _, err := os.Stat(tc.oldFilePath) + require.NoError(t.T(), err) + + // Rename the file. + err = os.Rename(tc.oldFilePath, tc.newFilePath) + assert.NoError(t.T(), err) + + // Verify the old file no longer exists. + _, err = os.Stat(tc.oldFilePath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + // Verify the new file exists and has the correct content. + f, err := os.Stat(tc.newFilePath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), path.Base(tc.newFilePath), f.Name()) + content, err := os.ReadFile(tc.newFilePath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), tc.wantContent, string(content)) + }) + } +} diff --git a/internal/fs/hns_bucket_test.go b/internal/fs/hns_bucket_test.go index dcf662d9bc..13f3ed1160 100644 --- a/internal/fs/hns_bucket_test.go +++ b/internal/fs/hns_bucket_test.go @@ -37,8 +37,8 @@ import ( ) type HNSBucketTests struct { - suite.Suite fsTest + HNSBucketCommonTest } type dirEntry struct { @@ -390,84 +390,6 @@ func (t *HNSBucketTests) TestCreateLocalFileInSamePathAfterDeletingParentDirecto assert.NoError(t.T(), err) } -func (t *HNSBucketTests) TestRenameFileWithSrcFileDoesNotExist() { - oldFilePath := path.Join(mntDir, "file") - newFilePath := path.Join(mntDir, "file_rename") - - err := os.Rename(oldFilePath, newFilePath) - - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - _, err = os.Stat(newFilePath) - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) -} - -func (t *HNSBucketTests) TestRenameFileWithDstDestFileExist() { - oldFilePath := path.Join(mntDir, "foo", "file1.txt") - _, err := os.Stat(oldFilePath) - assert.NoError(t.T(), err) - newFilePath := path.Join(mntDir, "foo", "file2.txt") - _, err = os.Stat(newFilePath) - assert.NoError(t.T(), err) - - err = os.Rename(oldFilePath, newFilePath) - - assert.NoError(t.T(), err) - _, err = os.Stat(oldFilePath) - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - content, err := os.ReadFile(newFilePath) - assert.NoError(t.T(), err) - assert.Equal(t.T(), file1Content, string(content)) -} - -func (t *HNSBucketTests) TestRenameFile() { - testCases := []struct { - name string - oldFilePath string - newFilePath string - wantContent string - }{ - { - name: "DifferentParent", - oldFilePath: path.Join(mntDir, "foo", "file1.txt"), - newFilePath: path.Join(mntDir, "bar", "file3.txt"), - wantContent: file1Content, - }, - { - name: "SameParent", - oldFilePath: path.Join(mntDir, "foo", "file2.txt"), - newFilePath: path.Join(mntDir, "foo", "file3.txt"), - wantContent: file2Content, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func() { - // Ensure file exists before renaming. - _, err := os.Stat(tc.oldFilePath) - require.NoError(t.T(), err) - - // Rename the file. - err = os.Rename(tc.oldFilePath, tc.newFilePath) - assert.NoError(t.T(), err) - - // Verify the old file no longer exists. - _, err = os.Stat(tc.oldFilePath) - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - // Verify the new file exists and has the correct content. - f, err := os.Stat(tc.newFilePath) - assert.NoError(t.T(), err) - assert.Equal(t.T(), path.Base(tc.newFilePath), f.Name()) - content, err := os.ReadFile(tc.newFilePath) - assert.NoError(t.T(), err) - assert.Equal(t.T(), tc.wantContent, string(content)) - }) - } -} - // ////////////////////////////////////////////////////////////////////// // HNS bucket with caching support tests // ////////////////////////////////////////////////////////////////////// diff --git a/internal/fs/zonal_bucket_test.go b/internal/fs/zonal_bucket_test.go new file mode 100644 index 0000000000..302d35600a --- /dev/null +++ b/internal/fs/zonal_bucket_test.go @@ -0,0 +1,61 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs_test + +import ( + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type ZonalBucketTests struct { + HNSBucketCommonTest + fsTest +} + +func TestZonalBucketTests(t *testing.T) { suite.Run(t, new(ZonalBucketTests)) } + +func (t *ZonalBucketTests) SetupSuite() { + t.serverCfg.ImplicitDirectories = false + t.serverCfg.MetricHandle = common.NewNoopMetrics() + bucketType = gcs.BucketType{Zonal: true} + t.fsTest.SetUpTestSuite() +} + +func (t *ZonalBucketTests) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} + +func (t *ZonalBucketTests) SetupTest() { + err := t.createFolders([]string{"foo/", "bar/", "foo/test2/", "foo/test/"}) + require.NoError(t.T(), err) + + err = t.createObjects( + map[string]string{ + "foo/file1.txt": file1Content, + "foo/file2.txt": file2Content, + "foo/test/file3.txt": "xyz", + "foo/implicit_dir/file3.txt": "xxw", + "bar/file1.txt": "-1234556789", + }) + require.NoError(t.T(), err) +} + +func (t *ZonalBucketTests) TearDownTest() { + t.fsTest.TearDown() +} From 0649798b05cb63d82b8911b446c8ff09288c2a20 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 21 Jan 2025 10:15:13 +0530 Subject: [PATCH 0148/1298] Semantic doc update for Kernel List Cache Bug (#2909) --- docs/semantics.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/semantics.md b/docs/semantics.md index f13ad56c38..9b10079dec 100644 --- a/docs/semantics.md +++ b/docs/semantics.md @@ -189,6 +189,7 @@ By default, the list cache is disabled. It can be enabled by configuring the `-- * The kernel-list-cache is kept within the kernel's page-cache. Consequently, this functionality depends upon the availability of page-cache memory on the system. This contrasts with the stat and type caches, which are retained in user memory as part of Cloud Storage Fuse daemon. * The kernel's list cache is maintained on a per-directory level, resulting in either all list entries being retained in the page cache or none at all. * The creation, renaming, or deletion of new files or folders causes the eviction of the page-cache of their immediate parent directory, but not of all ancestral directories. +* The ttl-based eviction doesn't work with kernel versions 6.9.x to 6.12.x ([details](https://github.com/GoogleCloudPlatform/gcsfuse/issues/2792)), because of a [bug](https://lore.kernel.org/linux-fsdevel/CAEW=TRr7CYb4LtsvQPLj-zx5Y+EYBmGfM24SuzwyDoGVNoKm7w@mail.gmail.com/) in kernel-fuse driver, which is [fixed](https://github.com/torvalds/linux/commit/03f275adb8fbd7b4ebe96a1ad5044d8e602692dc) in 6.13.x. Although, eviction because of creation, renaming, or deletion of a file or folders from the same mount works as expected. **Consistency** * Kernel List cache ensures consistency within the mount. That means, creation, deletion or rename of files/folder within a directory evicts the kernel list cache of the directory. From 818d43d9a719cbe1c9904ece6eee3a6777afca0e Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Tue, 21 Jan 2025 15:17:54 +0530 Subject: [PATCH 0149/1298] Enable dynamic creation of storage clients based on bucket type and specified protocol (#2922) Enable dynamic creation of storage clients based on bucket type and specified protocol Reason for not using mutex in getClient: Multiple clients can be created in dynamic mounting case only. But in this case, the root dir will actually be a base_dir & we take a lock before calling LookUpChild for base_dir. So, this will ensure that call to BucketHandle (which means calls to getClient) will be serialized. Reason for not adding a new config EnableBidiConfig in StorageClientConfig (& instead passing a separate parameter for bidi config while creating grpc client): This config is created from the user provided config at the start (& should reflect that) and I would not like to keep a transient field to it (i.e. would not like to keep a field which has to be set every time before grpc client creation happens and does not serve any other purpose apart from reducing an extra parameter to grpc client creation call). Testing: Manually tested mounting/listing/reading on flat, hns and zb bucket with this change using dynamic mounting (with both grpc as well as http) --- internal/storage/fake_storage_util.go | 10 +- internal/storage/storage_handle.go | 150 ++++++++++++++---------- internal/storage/storage_handle_test.go | 14 ++- 3 files changed, 104 insertions(+), 70 deletions(-) diff --git a/internal/storage/fake_storage_util.go b/internal/storage/fake_storage_util.go index 349272cdcc..9a5622d530 100644 --- a/internal/storage/fake_storage_util.go +++ b/internal/storage/fake_storage_util.go @@ -18,6 +18,7 @@ import ( "github.com/fsouza/fake-gcs-server/fakestorage" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" ) const TestBucketName string = "gcsfuse-default-bucket" @@ -63,10 +64,11 @@ func (f *fakeStorage) CreateStorageHandle() (sh StorageHandle) { f.mockClient = new(MockStorageControlClient) } sh = &storageClient{ - httpClient: f.fakeStorageServer.Client(), - grpcClient: f.fakeStorageServer.Client(), - storageControlClient: f.mockClient, - clientProtocol: f.protocol, + httpClient: f.fakeStorageServer.Client(), + grpcClient: f.fakeStorageServer.Client(), + grpcClientWithBidiConfig: f.fakeStorageServer.Client(), + storageControlClient: f.mockClient, + clientConfig: storageutil.StorageClientConfig{ClientProtocol: f.protocol}, } return } diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index 1be60ec9cd..c05ca3a5f1 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -59,11 +59,12 @@ type StorageHandle interface { } type storageClient struct { - httpClient *storage.Client - grpcClient *storage.Client - clientProtocol cfg.Protocol - storageControlClient StorageControlClient - directPathDetector *gRPCDirectPathDetector + httpClient *storage.Client + grpcClient *storage.Client + grpcClientWithBidiConfig *storage.Client + clientConfig storageutil.StorageClientConfig + storageControlClient StorageControlClient + directPathDetector *gRPCDirectPathDetector } type gRPCDirectPathDetector struct { @@ -78,7 +79,7 @@ func (pd *gRPCDirectPathDetector) isDirectPathPossible(ctx context.Context, buck } // Return clientOpts for both gRPC client and control client. -func createClientOptionForGRPCClient(clientConfig *storageutil.StorageClientConfig) (clientOpts []option.ClientOption, err error) { +func createClientOptionForGRPCClient(clientConfig *storageutil.StorageClientConfig, enableBidiConfig bool) (clientOpts []option.ClientOption, err error) { // Add Custom endpoint option. if clientConfig.CustomEndpoint != "" { clientOpts = append(clientOpts, option.WithEndpoint(storageutil.StripScheme(clientConfig.CustomEndpoint))) @@ -99,6 +100,9 @@ func createClientOptionForGRPCClient(clientConfig *storageutil.StorageClientConf clientOpts = append(clientOpts, option.WithTokenSource(tokenSrc)) } + if enableBidiConfig { + clientOpts = append(clientOpts, experimental.WithGRPCBidiReads()) + } clientOpts = append(clientOpts, option.WithGRPCConnectionPool(clientConfig.GrpcConnPoolSize)) clientOpts = append(clientOpts, option.WithUserAgent(clientConfig.UserAgent)) // Turning off the go-sdk metrics exporter to prevent any problems. @@ -107,15 +111,43 @@ func createClientOptionForGRPCClient(clientConfig *storageutil.StorageClientConf return } +func setRetryConfig(sc *storage.Client, clientConfig *storageutil.StorageClientConfig) { + if sc == nil || clientConfig == nil { + logger.Fatal("setRetryConfig: Empty storage client or clientConfig") + return + } + + // ShouldRetry function checks if an operation should be retried based on the + // response of operation (error.Code). + // RetryAlways causes all operations to be checked for retries using + // ShouldRetry function. + // Without RetryAlways, only those operations are checked for retries which + // are idempotent. + // https://github.com/googleapis/google-cloud-go/blob/main/storage/storage.go#L1953 + retryOpts := []storage.RetryOption{storage.WithBackoff(gax.Backoff{ + Max: clientConfig.MaxRetrySleep, + Multiplier: clientConfig.RetryMultiplier, + }), + storage.WithPolicy(storage.RetryAlways), + storage.WithErrorFunc(storageutil.ShouldRetry)} + + sc.SetRetry(retryOpts...) + + // The default MaxRetryAttempts value is 0 indicates no limit. + if clientConfig.MaxRetryAttempts != 0 { + sc.SetRetry(storage.WithMaxAttempts(clientConfig.MaxRetryAttempts)) + } +} + // Followed https://pkg.go.dev/cloud.google.com/go/storage#hdr-Experimental_gRPC_API to create the gRPC client. -func createGRPCClientHandle(ctx context.Context, clientConfig *storageutil.StorageClientConfig) (sc *storage.Client, err error) { +func createGRPCClientHandle(ctx context.Context, clientConfig *storageutil.StorageClientConfig, enableBidiConfig bool) (sc *storage.Client, err error) { if err := os.Setenv("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS", "true"); err != nil { logger.Fatal("error setting direct path env var: %v", err) } var clientOpts []option.ClientOption - clientOpts, err = createClientOptionForGRPCClient(clientConfig) + clientOpts, err = createClientOptionForGRPCClient(clientConfig, enableBidiConfig) if err != nil { return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) } @@ -123,6 +155,8 @@ func createGRPCClientHandle(ctx context.Context, clientConfig *storageutil.Stora sc, err = storage.NewGRPCClient(ctx, clientOpts...) if err != nil { err = fmt.Errorf("NewGRPCClient: %w", err) + } else { + setRetryConfig(sc, clientConfig) } // Unset the environment variable, since it's used only while creation of grpc client. @@ -183,7 +217,13 @@ func createHTTPClientHandle(ctx context.Context, clientConfig *storageutil.Stora TargetPercentile: clientConfig.ReadStallRetryConfig.ReqTargetPercentile, })) } - return storage.NewClient(ctx, clientOpts...) + sc, err = storage.NewClient(ctx, clientOpts...) + if err != nil { + err = fmt.Errorf("go http storage client creation failed: %w", err) + return + } + setRetryConfig(sc, clientConfig) + return } func (sh *storageClient) lookupBucketType(bucketName string) (*gcs.BucketType, error) { @@ -220,39 +260,19 @@ func (sh *storageClient) getStorageLayout(bucketName string) (*controlpb.Storage return stoargeLayout, err } -// NewStorageHandle returns the handle of http or grpc Go storage client based on the -// provided StorageClientConfig.ClientProtocol. -// Please check out the StorageClientConfig to know about the parameters used in -// http and gRPC client. +// NewStorageHandle creates control client and stores client config to allow dynamic +// creation of http or grpc client. func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClientConfig) (sh StorageHandle, err error) { - var hc, gc *storage.Client // The default protocol for the Go Storage control client's folders API is gRPC. // gcsfuse will initially mirror this behavior due to the client's lack of HTTP support. var controlClient *control.StorageControlClient var clientOpts []option.ClientOption - var directPathDetector *gRPCDirectPathDetector - - gc, err = createGRPCClientHandle(ctx, &clientConfig) - if err == nil { - clientOpts, err = createClientOptionForGRPCClient(&clientConfig) - directPathDetector = &gRPCDirectPathDetector{clientOptions: clientOpts} - } - if err != nil { - err = fmt.Errorf("go grpc storage client creation failed: %w", err) - return - } - - hc, err = createHTTPClientHandle(ctx, &clientConfig) - if err != nil { - err = fmt.Errorf("go http storage client creation failed: %w", err) - return - } // TODO: We will implement an additional check for the HTTP control client protocol once the Go SDK supports HTTP. // TODO: Custom endpoints do not currently support gRPC. Remove this additional check once TPC(custom-endpoint) supports gRPC. // Create storageControlClient irrespective of whether hns needs to be enabled or not. // Because we will use storageControlClient to check layout of given bucket. - clientOpts, err = createClientOptionForGRPCClient(&clientConfig) + clientOpts, err = createClientOptionForGRPCClient(&clientConfig, false) if err != nil { return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) } @@ -261,37 +281,38 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien return nil, fmt.Errorf("could not create StorageControl Client: %w", err) } - // ShouldRetry function checks if an operation should be retried based on the - // response of operation (error.Code). - // RetryAlways causes all operations to be checked for retries using - // ShouldRetry function. - // Without RetryAlways, only those operations are checked for retries which - // are idempotent. - // https://github.com/googleapis/google-cloud-go/blob/main/storage/storage.go#L1953 - retryOpts := []storage.RetryOption{storage.WithBackoff(gax.Backoff{ - Max: clientConfig.MaxRetrySleep, - Multiplier: clientConfig.RetryMultiplier, - }), - storage.WithPolicy(storage.RetryAlways), - storage.WithErrorFunc(storageutil.ShouldRetry)} + sh = &storageClient{ + storageControlClient: controlClient, + clientConfig: clientConfig, + directPathDetector: &gRPCDirectPathDetector{clientOptions: clientOpts}, + } + return +} - hc.SetRetry(retryOpts...) - gc.SetRetry(retryOpts...) +func (sh *storageClient) getClient(ctx context.Context, isbucketZonal bool) (*storage.Client, error) { + var err error + if isbucketZonal { + if sh.grpcClientWithBidiConfig == nil { + sh.grpcClientWithBidiConfig, err = createGRPCClientHandle(ctx, &sh.clientConfig, true) + } + return sh.grpcClientWithBidiConfig, err + } - // The default MaxRetryAttempts value is 0 indicates no limit. - if clientConfig.MaxRetryAttempts != 0 { - hc.SetRetry(storage.WithMaxAttempts(clientConfig.MaxRetryAttempts)) - gc.SetRetry(storage.WithMaxAttempts(clientConfig.MaxRetryAttempts)) + if sh.clientConfig.ClientProtocol == cfg.GRPC { + if sh.grpcClient == nil { + sh.grpcClient, err = createGRPCClientHandle(ctx, &sh.clientConfig, false) + } + return sh.grpcClient, err } - sh = &storageClient{ - httpClient: hc, - grpcClient: gc, - storageControlClient: controlClient, - clientProtocol: clientConfig.ClientProtocol, - directPathDetector: directPathDetector, + if sh.clientConfig.ClientProtocol == cfg.HTTP1 || sh.clientConfig.ClientProtocol == cfg.HTTP2 { + if sh.httpClient == nil { + sh.httpClient, err = createHTTPClientHandle(ctx, &sh.clientConfig) + } + return sh.httpClient, err } - return + + return nil, fmt.Errorf("invalid client-protocol requested: %s", sh.clientConfig.ClientProtocol) } func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, billingProject string) (bh *bucketHandle, err error) { @@ -301,18 +322,19 @@ func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, bi return nil, fmt.Errorf("storageLayout call failed: %s", err) } - if bucketType.Zonal || sh.clientProtocol == cfg.GRPC { - client = sh.grpcClient + client, err = sh.getClient(ctx, bucketType.Zonal) + if err != nil { + return nil, err + } + + if bucketType.Zonal || sh.clientConfig.ClientProtocol == cfg.GRPC { if sh.directPathDetector != nil { if err := sh.directPathDetector.isDirectPathPossible(ctx, bucketName); err != nil { logger.Warnf("Direct path connectivity unavailable for %s, reason: %v", bucketName, err) } } - } else if sh.clientProtocol == cfg.HTTP1 || sh.clientProtocol == cfg.HTTP2 { - client = sh.httpClient - } else { - return nil, fmt.Errorf("invalid client-protocol requested: %s", sh.clientProtocol) } + storageBucketHandle := client.Bucket(bucketName) if billingProject != "" { diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index 796bf5c509..849d7ef872 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -283,7 +283,17 @@ func (testSuite *StorageHandleTest) TestCreateGRPCClientHandle() { sc := storageutil.GetDefaultStorageClientConfig() sc.ClientProtocol = cfg.GRPC - storageClient, err := createGRPCClientHandle(testSuite.ctx, &sc) + storageClient, err := createGRPCClientHandle(testSuite.ctx, &sc, false) + + assert.Nil(testSuite.T(), err) + assert.NotNil(testSuite.T(), storageClient) +} + +func (testSuite *StorageHandleTest) TestCreateGRPCClientHandleWithBidiConfig() { + sc := storageutil.GetDefaultStorageClientConfig() + sc.ClientProtocol = cfg.GRPC + + storageClient, err := createGRPCClientHandle(testSuite.ctx, &sc, true) assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), storageClient) @@ -556,7 +566,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithCustomEndpointAndEna func (testSuite *StorageHandleTest) TestCreateClientOptionForGRPCClient() { sc := storageutil.GetDefaultStorageClientConfig() - clientOption, err := createClientOptionForGRPCClient(&sc) + clientOption, err := createClientOptionForGRPCClient(&sc, false) assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), clientOption) From d45540f948a5035bc7b3cf22512df485e2e04a41 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 21 Jan 2025 20:47:15 +0530 Subject: [PATCH 0150/1298] Update troubleshooting doc for crash `unlock of unlocked mutex` (#2895) * Update troubleshooting doc for crash `unlock of unlocked mutex` * review comments --- docs/troubleshooting.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index af4aefc447..1d7389c392 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -139,3 +139,9 @@ It is possible customer is seeing the error "transport endpoint is not connected - Try restarting/rebooting the VM Instance. If it's running on GKE, the issue could be caused by an Out-of-Memory (OOM) error. Consider increasing the memory allocated to the GKE sidecar container. For more info refer [here](https://github.com/GoogleCloudPlatform/gcs-fuse-csi-driver/blob/main/docs/known-issues.md#implications-of-the-sidecar-container-design). + +### GCSFuse crashes with `fatal error: sync: unlock of unlocked mutex` or `Panic: Inode 'a/' cannot have child file ''` + +**Solution:** This happens when the mounting bucket contains an object with suffix `/\n` like, `gs://gcs-bkt/a/\n` +You need to find such objects and replace them with any other valid gcs object names. - [How](https://github.com/GoogleCloudPlatform/gcsfuse/discussions/2894)? + From cdadf09700503564e72116743d69121515bba0f0 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 22 Jan 2025 09:58:19 +0000 Subject: [PATCH 0151/1298] Partially Revert "Disable tpc and emulator tests in e2e" (#2926) This partially reverts commit 9d33fa5340017208be465653ee7dc3f10442663c. This re-enables TPC e2e tests from the code. Instead of this, we'll disable TPC e2e tests from kokoro config which is more efficient. --- tools/integration_tests/run_e2e_tests.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 60d0ba7f72..4f2b5de77d 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -28,9 +28,9 @@ BUCKET_LOCATION=$3 # Pass "true" to run e2e tests on TPC endpoint. # The default value will be false. RUN_TEST_ON_TPC_ENDPOINT=false -# if [ $4 != "" ]; then - # RUN_TEST_ON_TPC_ENDPOINT=$4 -# fi +if [ $4 != "" ]; then + RUN_TEST_ON_TPC_ENDPOINT=$4 +fi INTEGRATION_TEST_TIMEOUT_IN_MINS=90 RUN_TESTS_WITH_PRESUBMIT_FLAG=false From 090f9ce5f531fa21820de385655d57f4664a5e34 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Wed, 22 Jan 2025 10:31:56 +0000 Subject: [PATCH 0152/1298] Empty_gcs_file tests under streaming_writes package (#2910) * New integration tests for streaming_writes * Fix the header check * removed print line --- .../default_mount_empty_gcs_file_test.go | 51 +++++++++++++++++++ .../util/operations/file_operations.go | 8 +++ .../concurrent_write_files_test.go | 26 +++++----- .../write_large_files_test.go | 3 +- 4 files changed, 73 insertions(+), 15 deletions(-) create mode 100644 tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go diff --git a/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go b/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go new file mode 100644 index 0000000000..758e6845e4 --- /dev/null +++ b/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go @@ -0,0 +1,51 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes + +import ( + "path" + "testing" + + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/suite" +) + +type defaultMountEmptyGCSFile struct { + defaultMountCommonTest +} + +func (t *defaultMountEmptyGCSFile) SetupTest() { + t.createEmptyGCSFile() +} + +func (t *defaultMountEmptyGCSFile) SetupSubTest() { + t.createEmptyGCSFile() +} + +func (t *defaultMountEmptyGCSFile) createEmptyGCSFile() { + t.fileName = FileName1 + setup.GenerateRandomString(5) + // Create an empty file on GCS. + CreateObjectInGCSTestDir(ctx, storageClient, testDirName, t.fileName, "", t.T()) + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, "", t.T()) + filePath := path.Join(testDirPath, t.fileName) + t.f1 = operations.OpenFile(filePath, t.T()) +} + +// Executes all tests that run with single streamingWrites configuration for empty GCS Files. +func TestDefaultMountEmptyGCSFileTest(t *testing.T) { + suite.Run(t, new(defaultMountEmptyGCSFile)) +} diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index cb393b5eb7..8ae7a3eb30 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -476,6 +476,14 @@ func CreateFile(filePath string, filePerms os.FileMode, t testing.TB) (f *os.Fil return } +func OpenFile(filePath string, t testing.TB) (f *os.File) { + f, err := os.OpenFile(filePath, os.O_RDWR, FilePermission_0777) + if err != nil { + t.Fatalf("OpenFile(%s): %v", filePath, err) + } + return +} + func CreateSymLink(filePath, symlink string, t *testing.T) { err := os.Symlink(filePath, symlink) diff --git a/tools/integration_tests/write_large_files/concurrent_write_files_test.go b/tools/integration_tests/write_large_files/concurrent_write_files_test.go index 29ecb8fa1d..aba12bed84 100644 --- a/tools/integration_tests/write_large_files/concurrent_write_files_test.go +++ b/tools/integration_tests/write_large_files/concurrent_write_files_test.go @@ -34,8 +34,7 @@ var FileOne = "fileOne" + setup.GenerateRandomString(5) + ".txt" var FileTwo = "fileTwo" + setup.GenerateRandomString(5) + ".txt" var FileThree = "fileThree" + setup.GenerateRandomString(5) + ".txt" -func writeFile(fileName string, fileSize int64, t *testing.T) error { - filePath := path.Join(setup.MntDir(), DirForConcurrentWrite, fileName) +func writeFile(filePath string, fileSize int64) error { f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|syscall.O_DIRECT, WritePermission_0200) if err != nil { return fmt.Errorf("Open file for write at start: %v", err) @@ -43,20 +42,13 @@ func writeFile(fileName string, fileSize int64, t *testing.T) error { // Closing file at the end. defer operations.CloseFile(f) + return operations.WriteChunkOfRandomBytesToFile(f, int(fileSize), 0) +} - err = operations.WriteChunkOfRandomBytesToFile(f, int(fileSize), 0) - if err != nil { - return fmt.Errorf("Error: %v", err) - } - +func validateFileContents(fileName string, mntFilePath string, t *testing.T) error { filePathInGcsBucket := path.Join(DirForConcurrentWrite, fileName) localFilePath := path.Join(TmpDir, fileName) - err = compareFileFromGCSBucketAndMntDir(filePathInGcsBucket, filePath, localFilePath, t) - if err != nil { - return fmt.Errorf("Error: %v", err) - } - - return nil + return compareFileFromGCSBucketAndMntDir(filePathInGcsBucket, mntFilePath, localFilePath, t) } func TestMultipleFilesAtSameTime(t *testing.T) { @@ -77,7 +69,13 @@ func TestMultipleFilesAtSameTime(t *testing.T) { // Thread to write the current file. eG.Go(func() error { - return writeFile(files[fileIndex], FiveHundredMB, t) + mntFilePath := path.Join(setup.MntDir(), DirForConcurrentWrite, files[fileIndex]) + err := writeFile(mntFilePath, FiveHundredMB) + if err != nil { + return fmt.Errorf("WriteError: %v", err) + } + + return validateFileContents(files[fileIndex], mntFilePath, t) }) } diff --git a/tools/integration_tests/write_large_files/write_large_files_test.go b/tools/integration_tests/write_large_files/write_large_files_test.go index 84f749c4d4..38303b6356 100644 --- a/tools/integration_tests/write_large_files/write_large_files_test.go +++ b/tools/integration_tests/write_large_files/write_large_files_test.go @@ -54,7 +54,8 @@ func compareFileFromGCSBucketAndMntDir(gcsFile, mntDirFile, localFilePathToDownl func TestMain(m *testing.M) { setup.ParseSetUpFlags() - flags := [][]string{{"--implicit-dirs"}} + // TODO: remove max-blocks-per-file after the default values are set. + flags := [][]string{{"--enable-streaming-writes=true", "--write-max-blocks-per-file=2"}} setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() From 046f7b846bbb83b10a5c30524266c1474f6b42ae Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 23 Jan 2025 11:03:24 +0530 Subject: [PATCH 0153/1298] Update direct dependencies (#2924) * Update direct dependencies --- go.mod | 26 +++++++++++++------------- go.sum | 52 ++++++++++++++++++++++++++-------------------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index b672ef8006..f13c194e72 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( contrib.go.opencensus.io/exporter/stackdriver v0.13.14 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0 - github.com/fsouza/fake-gcs-server v1.52.0 + github.com/fsouza/fake-gcs-server v1.52.1 github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.1 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec @@ -27,29 +27,29 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/prometheus/client_golang v1.20.5 github.com/prometheus/client_model v0.6.1 - github.com/prometheus/common v0.61.0 + github.com/prometheus/common v0.62.0 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 go.opencensus.io v0.24.0 - go.opentelemetry.io/contrib/detectors/gcp v1.33.0 - go.opentelemetry.io/otel v1.33.0 - go.opentelemetry.io/otel/exporters/prometheus v0.55.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0 - go.opentelemetry.io/otel/metric v1.33.0 - go.opentelemetry.io/otel/sdk v1.33.0 - go.opentelemetry.io/otel/sdk/metric v1.33.0 - go.opentelemetry.io/otel/trace v1.33.0 + go.opentelemetry.io/contrib/detectors/gcp v1.34.0 + go.opentelemetry.io/otel v1.34.0 + go.opentelemetry.io/otel/exporters/prometheus v0.56.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0 + go.opentelemetry.io/otel/metric v1.34.0 + go.opentelemetry.io/otel/sdk v1.34.0 + go.opentelemetry.io/otel/sdk/metric v1.34.0 + go.opentelemetry.io/otel/trace v1.34.0 golang.org/x/net v0.34.0 golang.org/x/oauth2 v0.25.0 golang.org/x/sync v0.10.0 golang.org/x/sys v0.29.0 golang.org/x/text v0.21.0 golang.org/x/time v0.9.0 - google.golang.org/api v0.216.0 - google.golang.org/grpc v1.69.2 - google.golang.org/protobuf v1.36.2 + google.golang.org/api v0.217.0 + google.golang.org/grpc v1.69.4 + google.golang.org/protobuf v1.36.3 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 122fc5a8ac..7aa8315aff 100644 --- a/go.sum +++ b/go.sum @@ -129,8 +129,8 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fsouza/fake-gcs-server v1.52.0 h1:ps23VAKR0pKu+QsdMUo25mtDXp6KpKxksIRkbHNBPug= -github.com/fsouza/fake-gcs-server v1.52.0/go.mod h1:tmfEOHhUOdkk243WkWUPC1MuHEaGdpJrslDijp5nZRs= +github.com/fsouza/fake-gcs-server v1.52.1 h1:Hx3G2ZpyBzHGmW7cHURWWoTm6jM3M5fcWMIAHBYlJyc= +github.com/fsouza/fake-gcs-server v1.52.1/go.mod h1:Paxf25VmSNMN52L+2/cVulF5fkLUA0YJIYjTGJiwf3c= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -342,8 +342,8 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= -github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= +github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= +github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -416,28 +416,28 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.33.0 h1:FVPoXEoILwgbZUu4X7YSgsESsAmGRgoYcnXkzgQPhP4= -go.opentelemetry.io/contrib/detectors/gcp v1.33.0/go.mod h1:ZHrLmr4ikK2AwRj9QL+c9s2SOlgoSRyMpNVzUj2fZqI= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= +go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= -go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= -go.opentelemetry.io/otel/exporters/prometheus v0.55.0 h1:sSPw658Lk2NWAv74lkD3B/RSDb+xRFx46GjkrL3VUZo= -go.opentelemetry.io/otel/exporters/prometheus v0.55.0/go.mod h1:nC00vyCmQixoeaxF6KNyP42II/RHa9UdruK02qBmHvI= +go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= +go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel/exporters/prometheus v0.56.0 h1:GnCIi0QyG0yy2MrJLzVrIM7laaJstj//flf1zEJCG+E= +go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0 h1:W5AWUn/IVe8RFb5pZx1Uh9Laf/4+Qmm4kJL5zPuvR+0= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.33.0/go.mod h1:mzKxJywMNBdEX8TSJais3NnsVZUaJ+bAy6UxPTng2vk= -go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= -go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= -go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= -go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= -go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU= -go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q= -go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= -go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0 h1:jBpDk4HAUsrnVO1FsfCfCOTEc/MkInJmvfCHYLFiT80= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0/go.mod h1:H9LUIM1daaeZaz91vZcfeM0fejXPmgCYE8ZhzqfJuiU= +go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= +go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= +go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= +go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= +go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -657,8 +657,8 @@ google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.216.0 h1:xnEHy+xWFrtYInWPy8OdGFsyIfWJjtVnO39g7pz2BFY= -google.golang.org/api v0.216.0/go.mod h1:K9wzQMvWi47Z9IU7OgdOofvZuw75Ge3PPITImZR/UyI= +google.golang.org/api v0.217.0 h1:GYrUtD289o4zl1AhiTZL0jvQGa2RDLyC+kX1N/lfGOU= +google.golang.org/api v0.217.0/go.mod h1:qMc2E8cBAbQlRypBTBWHklNJlaZZJBwDv81B1Iu8oSI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -715,8 +715,8 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= -google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -731,8 +731,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= -google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 8e0388a6c7408c21814cc9b446eeacb530e4c63d Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 23 Jan 2025 11:19:54 +0530 Subject: [PATCH 0154/1298] Skip test when testing with installed package (#2928) --- tools/integration_tests/monitoring/prom_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/integration_tests/monitoring/prom_test.go b/tools/integration_tests/monitoring/prom_test.go index ce462de8f7..f0191078bb 100644 --- a/tools/integration_tests/monitoring/prom_test.go +++ b/tools/integration_tests/monitoring/prom_test.go @@ -242,9 +242,15 @@ func (testSuite *PromTest) TestReadMetrics() { } func TestPromOCSuite(t *testing.T) { + if setup.TestInstalledPackage() { + t.Skip("Skipping since testing on installed package") + } suite.Run(t, &PromTest{enableOTEL: false}) } func TestPromOTELSuite(t *testing.T) { + if setup.TestInstalledPackage() { + t.Skip("Skipping since testing on installed package") + } suite.Run(t, &PromTest{enableOTEL: true}) } From abff80d37bb817933a3b719bdaaff087f8189486 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 15 Jan 2025 06:52:39 +0000 Subject: [PATCH 0155/1298] fix local file tests for streaming writes. --- .../local_file/local_file_test.go | 14 ++++- .../local_file/read_file_test.go | 11 ++-- .../local_file/stat_file_test.go | 51 ++++++++++++++++--- .../local_file/sym_link_test.go | 11 ++-- .../util/client/gcs_helper.go | 21 +++++++- .../util/mounting/mounting.go | 3 +- tools/integration_tests/util/setup/setup.go | 29 ++++++++--- 7 files changed, 114 insertions(+), 26 deletions(-) diff --git a/tools/integration_tests/local_file/local_file_test.go b/tools/integration_tests/local_file/local_file_test.go index 15393c8a34..9e47baf258 100644 --- a/tools/integration_tests/local_file/local_file_test.go +++ b/tools/integration_tests/local_file/local_file_test.go @@ -53,6 +53,15 @@ func WritingToLocalFileShouldNotWriteToGCS(ctx context.Context, storageClient *s client.ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, fileName, t) } +func ReadingLocalFileShouldHaveContent(ctx context.Context, fh *os.File, content string, t *testing.T) { + buf := make([]byte, len(content)) + n, err := fh.ReadAt(buf, 0) + if err != nil || len(content) != n || content != string(buf) { + t.Fatalf("Read file operation failed on local file: %v "+ + "Expected content: %s, Got Content: %s", err, content, string(buf)) + } +} + func NewFileShouldGetSyncedToGCSAtClose(ctx context.Context, storageClient *storage.Client, testDirPath, fileName string, t *testing.T) { // Create a local file. @@ -98,7 +107,10 @@ func TestMain(m *testing.M) { // Not setting config file explicitly with 'create-empty-file: false' as it is default. flagsSet := [][]string{ {"--implicit-dirs=true", "--rename-dir-limit=3"}, - {"--implicit-dirs=false", "--rename-dir-limit=3"}} + {"--implicit-dirs=false", "--rename-dir-limit=3"}, + {"--implicit-dirs=true", "--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2"}, + {"--implicit-dirs=false", "--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2"}, + } if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(ctx, storageClient); err == nil { flagsSet = append(flagsSet, hnsFlagSet) diff --git a/tools/integration_tests/local_file/read_file_test.go b/tools/integration_tests/local_file/read_file_test.go index f4e113f9df..7abcdd8a39 100644 --- a/tools/integration_tests/local_file/read_file_test.go +++ b/tools/integration_tests/local_file/read_file_test.go @@ -32,12 +32,11 @@ func TestReadLocalFile(t *testing.T) { WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) - // Read the local file contents. - buf := make([]byte, len(content)) - n, err := fh.ReadAt(buf, 0) - if err != nil || len(content) != n || content != string(buf) { - t.Fatalf("Read file operation failed on local file: %v "+ - "Expected content: %s, Got Content: %s", err, content, string(buf)) + if setup.StreamingWritesEnabled() { + // Mounts with streaming writes do not supporting reading files. + ReadingLocalFileShouldHaveContent(ctx, fh, "", t) + } else { + ReadingLocalFileShouldHaveContent(ctx, fh, content, t) } // Close the file and validate that the file is created on GCS. diff --git a/tools/integration_tests/local_file/stat_file_test.go b/tools/integration_tests/local_file/stat_file_test.go index d84d765371..5e71bedc3e 100644 --- a/tools/integration_tests/local_file/stat_file_test.go +++ b/tools/integration_tests/local_file/stat_file_test.go @@ -23,6 +23,7 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/require" ) func TestStatOnLocalFile(t *testing.T) { @@ -57,7 +58,7 @@ func TestStatOnLocalFileWithConflictingFileNameSuffix(t *testing.T) { FileName1, "", t) } -func TestTruncateLocalFile(t *testing.T) { +func TestTruncateLocalFileToSmallerSize(t *testing.T) { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) @@ -67,17 +68,53 @@ func TestTruncateLocalFile(t *testing.T) { // Stat the file to validate if new contents are written. operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t) - // Truncate the file to update the file size. - err := os.Truncate(filePath, SizeTruncate) - if err != nil { - t.Fatalf("os.Truncate err: %v", err) + // Truncate the file to update file size to smaller file size. + err := os.Truncate(filePath, SmallerSizeTruncate) + var expectTruncatedSize int64 = SizeOfFileContents + var expectedContent = FileContents + if setup.StreamingWritesEnabled() { + // Mounts with streaming writes do not supporting truncating files to smaller. + require.Error(t, err) + } else { + if err != nil { + t.Fatalf("os.Truncate err: %v", err) + } + expectTruncatedSize = SmallerSizeTruncate + expectedContent = FileContents[:SmallerSizeTruncate] } + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) // Stat the file to validate if file is truncated correctly. - operations.VerifyStatFile(filePath, SizeTruncate, FilePerms, t) + operations.VerifyStatFile(filePath, expectTruncatedSize, FilePerms, t) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, "testS", t) + FileName1, expectedContent, t) +} + +func TestTruncateLocalFileToBiggerSize(t *testing.T) { + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + // Writing contents to local file . + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) + + // Stat the file to validate if new contents are written. + operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t) + + // Truncate the file to update file size to bigger file size. + err := os.Truncate(filePath, BiggerSizeTruncate) + if err != nil { + t.Fatalf("os.Truncate err: %v", err) + } + + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + + // Stat the file to validate if file is truncated correctly. + operations.VerifyStatFile(filePath, BiggerSizeTruncate, FilePerms, t) + + // Close file and validate that file of expected size is created on GCS. + CloseFileAndValidateSizeFromGCS(ctx, storageClient, fh, testDirName, + FileName1, BiggerSizeTruncate, t) } diff --git a/tools/integration_tests/local_file/sym_link_test.go b/tools/integration_tests/local_file/sym_link_test.go index 0dc0178945..c544dee484 100644 --- a/tools/integration_tests/local_file/sym_link_test.go +++ b/tools/integration_tests/local_file/sym_link_test.go @@ -26,7 +26,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -func createAndVerifySymLink(t *testing.T) (filePath, symlink string, fh *os.File) { +func createAndVerifySymLink(streamingWritesEnabled bool, t *testing.T) (filePath, symlink string, fh *os.File) { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. filePath, fh = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) @@ -38,18 +38,23 @@ func createAndVerifySymLink(t *testing.T) (filePath, symlink string, fh *os.File // Read the link. operations.VerifyReadLink(filePath, symlink, t) + if streamingWritesEnabled { + // Mounts with streaming writes do not support reading files. + return + } operations.VerifyReadFile(symlink, FileContents, t) return } func TestCreateSymlinkForLocalFile(t *testing.T) { - _, _, fh := createAndVerifySymLink(t) + _, _, fh := createAndVerifySymLink(setup.StreamingWritesEnabled(), t) + // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, FileContents, t) } func TestReadSymlinkForDeletedLocalFile(t *testing.T) { - filePath, symlink, fh := createAndVerifySymLink(t) + filePath, symlink, fh := createAndVerifySymLink(setup.StreamingWritesEnabled(), t) // Remove filePath and then close the fileHandle to avoid syncing to GCS. operations.RemoveFile(filePath) operations.CloseFileShouldNotThrowError(fh, t) diff --git a/tools/integration_tests/util/client/gcs_helper.go b/tools/integration_tests/util/client/gcs_helper.go index 9130b60564..0543cdb9d4 100644 --- a/tools/integration_tests/util/client/gcs_helper.go +++ b/tools/integration_tests/util/client/gcs_helper.go @@ -42,7 +42,8 @@ const ( GCSFileContent = "GCSteststring" GCSFileSize = 13 FilePerms = 0644 - SizeTruncate = 5 + SmallerSizeTruncate = 5 + BiggerSizeTruncate = 15 NewFileName = "newName" NewDirName = "newDirName" ) @@ -79,6 +80,18 @@ func ValidateObjectContentsFromGCS(ctx context.Context, storageClient *storage.C } } +func ValidateObjectSizeFromGCS(ctx context.Context, storageClient *storage.Client, + testDirName string, fileName string, expectedSize int64, t *testing.T) { + gotContent, err := ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, fileName)) + if err != nil { + t.Fatalf("Error while reading file from GCS, Err: %v", err) + } + + if int64(len(gotContent)) != expectedSize { + t.Fatalf("GCS file %s size mismatch. Got file size: %d, Expected file size: %d ", fileName, len(gotContent), expectedSize) + } +} + func ValidateObjectChunkFromGCS(ctx context.Context, storageClient *storage.Client, testDirName string, fileName string, offset, size int64, expectedContent string, t *testing.T) { @@ -100,6 +113,12 @@ func CloseFileAndValidateContentFromGCS(ctx context.Context, storageClient *stor ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, fileName, content, t) } +func CloseFileAndValidateSizeFromGCS(ctx context.Context, storageClient *storage.Client, + fh *os.File, testDirName, fileName string, size int64, t *testing.T) { + operations.CloseFileShouldNotThrowError(fh, t) + ValidateObjectSizeFromGCS(ctx, storageClient, testDirName, fileName, size, t) +} + func CreateLocalFileInTestDir(ctx context.Context, storageClient *storage.Client, testDirPath, fileName string, t *testing.T) (string, *os.File) { filePath := path.Join(testDirPath, fileName) diff --git a/tools/integration_tests/util/mounting/mounting.go b/tools/integration_tests/util/mounting/mounting.go index b42690a489..5a99f9cb8f 100644 --- a/tools/integration_tests/util/mounting/mounting.go +++ b/tools/integration_tests/util/mounting/mounting.go @@ -29,7 +29,8 @@ func MountGcsfuse(binaryFile string, flags []string) error { binaryFile, flags..., ) - + // Sets to true iff current mount operation is using streaming writes + setup.SetStreamingWritesEnabled(flags) // Adding mount command in LogFile file, err := os.OpenFile(setup.LogFile(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 03afb0189b..538ab2e0a6 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -49,16 +49,18 @@ const ( DirPermission_0755 = 0755 Charset = "abcdefghijklmnopqrstuvwxyz0123456789" PathEnvVariable = "PATH" + StreamingWritesFlag = "--enable-streaming-writes=true" ) var ( - binFile string - logFile string - testDir string - mntDir string - sbinFile string - onlyDirMounted string - dynamicBucketMounted string + binFile string + logFile string + testDir string + mntDir string + sbinFile string + onlyDirMounted string + dynamicBucketMounted string + streamingWritesEnabled bool ) // Run the shell script to prepare the testData in the specified bucket. @@ -104,6 +106,19 @@ func LogFile() string { return logFile } +func SetStreamingWritesEnabled(flags []string) { + streamingWritesEnabled = false + for _, flag := range flags { + if flag == StreamingWritesFlag { + streamingWritesEnabled = true + } + } +} + +func StreamingWritesEnabled() bool { + return streamingWritesEnabled +} + func BinFile() string { return binFile } From 148655ed07345ea0b3d226e3fd46f11d3bbea7bb Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 15 Jan 2025 08:46:13 +0000 Subject: [PATCH 0156/1298] fix random write tests when streaming writes is enabled --- tools/integration_tests/local_file/write_file_test.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/integration_tests/local_file/write_file_test.go b/tools/integration_tests/local_file/write_file_test.go index 5cdecb0b3a..86b36c1245 100644 --- a/tools/integration_tests/local_file/write_file_test.go +++ b/tools/integration_tests/local_file/write_file_test.go @@ -46,9 +46,16 @@ func TestRandomWritesToLocalFile(t *testing.T) { // Write some contents to file randomly. operations.WriteAt("string1", 0, fh, t) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) operations.WriteAt("string2", 2, fh, t) + if setup.StreamingWritesEnabled() { + // First out of order write ensures the existing sequentially written data is uploaded + // to GCS when streaming writes are enabled. + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, FileName1, "string1", t) + } else { + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + } operations.WriteAt("string3", 3, fh, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, From 7e84a290a2f1fd9f86665d5d97943106f95953c0 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 15 Jan 2025 10:46:34 +0000 Subject: [PATCH 0157/1298] fix flags for local file test and add create-empty-file=true --- .../local_file/local_file_test.go | 13 ++----------- .../integration_tests/local_file/read_file_test.go | 14 +++++++++----- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/tools/integration_tests/local_file/local_file_test.go b/tools/integration_tests/local_file/local_file_test.go index 9e47baf258..501130fcdf 100644 --- a/tools/integration_tests/local_file/local_file_test.go +++ b/tools/integration_tests/local_file/local_file_test.go @@ -53,15 +53,6 @@ func WritingToLocalFileShouldNotWriteToGCS(ctx context.Context, storageClient *s client.ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, fileName, t) } -func ReadingLocalFileShouldHaveContent(ctx context.Context, fh *os.File, content string, t *testing.T) { - buf := make([]byte, len(content)) - n, err := fh.ReadAt(buf, 0) - if err != nil || len(content) != n || content != string(buf) { - t.Fatalf("Read file operation failed on local file: %v "+ - "Expected content: %s, Got Content: %s", err, content, string(buf)) - } -} - func NewFileShouldGetSyncedToGCSAtClose(ctx context.Context, storageClient *storage.Client, testDirPath, fileName string, t *testing.T) { // Create a local file. @@ -108,8 +99,8 @@ func TestMain(m *testing.M) { flagsSet := [][]string{ {"--implicit-dirs=true", "--rename-dir-limit=3"}, {"--implicit-dirs=false", "--rename-dir-limit=3"}, - {"--implicit-dirs=true", "--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2"}, - {"--implicit-dirs=false", "--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2"}, + {"--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2"}, + {"--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2", "create-empty-file: true"}, } if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(ctx, storageClient); err == nil { diff --git a/tools/integration_tests/local_file/read_file_test.go b/tools/integration_tests/local_file/read_file_test.go index 7abcdd8a39..d50a9b3a58 100644 --- a/tools/integration_tests/local_file/read_file_test.go +++ b/tools/integration_tests/local_file/read_file_test.go @@ -32,11 +32,15 @@ func TestReadLocalFile(t *testing.T) { WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) - if setup.StreamingWritesEnabled() { - // Mounts with streaming writes do not supporting reading files. - ReadingLocalFileShouldHaveContent(ctx, fh, "", t) - } else { - ReadingLocalFileShouldHaveContent(ctx, fh, content, t) + // mounts with streaming writes disabled support read operation. + if !setup.StreamingWritesEnabled() { + // Read the local file contents. + buf := make([]byte, len(content)) + n, err := fh.ReadAt(buf, 0) + if err != nil || len(content) != n || content != string(buf) { + t.Fatalf("Read file operation failed on local file: %v "+ + "Expected content: %s, Got Content: %s", err, content, string(buf)) + } } // Close the file and validate that the file is created on GCS. From 640a3d1319b72be4ce1d086b1e06a94ef3cab721 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Thu, 16 Jan 2025 07:52:31 +0000 Subject: [PATCH 0158/1298] Fix review comments --- .../local_file/local_file_test.go | 1 - .../local_file/stat_file_test.go | 26 ------------------- .../util/client/gcs_helper.go | 1 - 3 files changed, 28 deletions(-) diff --git a/tools/integration_tests/local_file/local_file_test.go b/tools/integration_tests/local_file/local_file_test.go index 501130fcdf..f1c1cac8a3 100644 --- a/tools/integration_tests/local_file/local_file_test.go +++ b/tools/integration_tests/local_file/local_file_test.go @@ -100,7 +100,6 @@ func TestMain(m *testing.M) { {"--implicit-dirs=true", "--rename-dir-limit=3"}, {"--implicit-dirs=false", "--rename-dir-limit=3"}, {"--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2"}, - {"--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2", "create-empty-file: true"}, } if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(ctx, storageClient); err == nil { diff --git a/tools/integration_tests/local_file/stat_file_test.go b/tools/integration_tests/local_file/stat_file_test.go index 5e71bedc3e..607f14ae49 100644 --- a/tools/integration_tests/local_file/stat_file_test.go +++ b/tools/integration_tests/local_file/stat_file_test.go @@ -92,29 +92,3 @@ func TestTruncateLocalFileToSmallerSize(t *testing.T) { CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, expectedContent, t) } - -func TestTruncateLocalFileToBiggerSize(t *testing.T) { - testDirPath = setup.SetupTestDirectory(testDirName) - // Create a local file. - filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) - // Writing contents to local file . - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) - - // Stat the file to validate if new contents are written. - operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t) - - // Truncate the file to update file size to bigger file size. - err := os.Truncate(filePath, BiggerSizeTruncate) - if err != nil { - t.Fatalf("os.Truncate err: %v", err) - } - - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) - - // Stat the file to validate if file is truncated correctly. - operations.VerifyStatFile(filePath, BiggerSizeTruncate, FilePerms, t) - - // Close file and validate that file of expected size is created on GCS. - CloseFileAndValidateSizeFromGCS(ctx, storageClient, fh, testDirName, - FileName1, BiggerSizeTruncate, t) -} diff --git a/tools/integration_tests/util/client/gcs_helper.go b/tools/integration_tests/util/client/gcs_helper.go index 0543cdb9d4..5db8c917ad 100644 --- a/tools/integration_tests/util/client/gcs_helper.go +++ b/tools/integration_tests/util/client/gcs_helper.go @@ -43,7 +43,6 @@ const ( GCSFileSize = 13 FilePerms = 0644 SmallerSizeTruncate = 5 - BiggerSizeTruncate = 15 NewFileName = "newName" NewDirName = "newDirName" ) From c56c6e32f05432042143f736dfd2f874383bbc04 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Thu, 16 Jan 2025 08:53:59 +0000 Subject: [PATCH 0159/1298] removed unused gcs_helper function. --- .../util/client/gcs_helper.go | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/tools/integration_tests/util/client/gcs_helper.go b/tools/integration_tests/util/client/gcs_helper.go index 5db8c917ad..226229549f 100644 --- a/tools/integration_tests/util/client/gcs_helper.go +++ b/tools/integration_tests/util/client/gcs_helper.go @@ -79,18 +79,6 @@ func ValidateObjectContentsFromGCS(ctx context.Context, storageClient *storage.C } } -func ValidateObjectSizeFromGCS(ctx context.Context, storageClient *storage.Client, - testDirName string, fileName string, expectedSize int64, t *testing.T) { - gotContent, err := ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, fileName)) - if err != nil { - t.Fatalf("Error while reading file from GCS, Err: %v", err) - } - - if int64(len(gotContent)) != expectedSize { - t.Fatalf("GCS file %s size mismatch. Got file size: %d, Expected file size: %d ", fileName, len(gotContent), expectedSize) - } -} - func ValidateObjectChunkFromGCS(ctx context.Context, storageClient *storage.Client, testDirName string, fileName string, offset, size int64, expectedContent string, t *testing.T) { @@ -112,12 +100,6 @@ func CloseFileAndValidateContentFromGCS(ctx context.Context, storageClient *stor ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, fileName, content, t) } -func CloseFileAndValidateSizeFromGCS(ctx context.Context, storageClient *storage.Client, - fh *os.File, testDirName, fileName string, size int64, t *testing.T) { - operations.CloseFileShouldNotThrowError(fh, t) - ValidateObjectSizeFromGCS(ctx, storageClient, testDirName, fileName, size, t) -} - func CreateLocalFileInTestDir(ctx context.Context, storageClient *storage.Client, testDirPath, fileName string, t *testing.T) (string, *os.File) { filePath := path.Join(testDirPath, fileName) From 2a9e30ca138e9e80ada2922c791fb5c96eb98d8d Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Tue, 21 Jan 2025 06:57:53 +0000 Subject: [PATCH 0160/1298] Refactor tests into test suites. --- .../local_file/create_file_test.go | 42 +++++--- .../local_file/local_file_test.go | 60 +++++++----- .../local_file/local_file_test_suite_test.go | 27 +++++ .../local_file/read_dir_test.go | 98 +++++++++---------- .../local_file/read_file_test.go | 51 ++++++---- .../local_file/remove_dir_test.go | 47 ++++----- .../local_file/rename_test.go | 52 +++++----- .../local_file/stat_file_test.go | 73 ++++++++------ .../local_file/sym_link_test.go | 54 +++++++--- .../local_file/unlinked_file_test.go | 95 +++++++++--------- .../local_file/write_file_test.go | 70 ++++++------- 11 files changed, 387 insertions(+), 282 deletions(-) create mode 100644 tools/integration_tests/local_file/local_file_test_suite_test.go diff --git a/tools/integration_tests/local_file/create_file_test.go b/tools/integration_tests/local_file/create_file_test.go index 8754063d3f..a92694889d 100644 --- a/tools/integration_tests/local_file/create_file_test.go +++ b/tools/integration_tests/local_file/create_file_test.go @@ -17,51 +17,63 @@ package local_file_test import ( "path" - "testing" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/suite" ) -func TestNewFileShouldNotGetSyncedToGCSTillClose(t *testing.T) { +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type commonLocalFileTestSuite struct { + suite.Suite +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *commonLocalFileTestSuite) TestNewFileShouldNotGetSyncedToGCSTillClose() { testDirPath = setup.SetupTestDirectory(testDirName) // Validate. - NewFileShouldGetSyncedToGCSAtClose(ctx, storageClient, testDirPath, FileName1, t) + NewFileShouldGetSyncedToGCSAtClose(ctx, storageClient, testDirPath, FileName1, t.T()) } -func TestNewFileUnderExplicitDirectoryShouldNotGetSyncedToGCSTillClose(t *testing.T) { +func (t *commonLocalFileTestSuite) TestNewFileUnderExplicitDirectoryShouldNotGetSyncedToGCSTillClose() { testDirPath = setup.SetupTestDirectory(testDirName) // Make explicit directory. - operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t) + operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t.T()) // Validate. - NewFileShouldGetSyncedToGCSAtClose(ctx, storageClient, testDirPath, path.Join(ExplicitDirName, ExplicitFileName1), t) + NewFileShouldGetSyncedToGCSAtClose(ctx, storageClient, testDirPath, path.Join(ExplicitDirName, ExplicitFileName1), t.T()) } -func TestCreateNewFileWhenSameFileExistsOnGCS(t *testing.T) { +func (t *commonLocalFileTestSuite) TestCreateNewFileWhenSameFileExistsOnGCS() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Create a file on GCS with the same name. - CreateObjectInGCSTestDir(ctx, storageClient, testDirName, FileName1, GCSFileContent, t) + CreateObjectInGCSTestDir(ctx, storageClient, testDirName, FileName1, GCSFileContent, t.T()) // Write to local file. - operations.WriteWithoutClose(fh, FileContents, t) + operations.WriteWithoutClose(fh, FileContents, t.T()) // Validate closing local file throws error. err := fh.Close() - operations.ValidateStaleNFSFileHandleError(t, err) + operations.ValidateStaleNFSFileHandleError(t.T(), err) // Ensure that the content on GCS is not overwritten. - ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, FileName1, GCSFileContent, t) + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, FileName1, GCSFileContent, t.T()) } -func TestEmptyFileCreation(t *testing.T) { +func (t *commonLocalFileTestSuite) TestEmptyFileCreation() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Close the file and validate that the file is created on GCS. - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, "", t) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, "", t.T()) } diff --git a/tools/integration_tests/local_file/local_file_test.go b/tools/integration_tests/local_file/local_file_test.go index f1c1cac8a3..30fa61ead2 100644 --- a/tools/integration_tests/local_file/local_file_test.go +++ b/tools/integration_tests/local_file/local_file_test.go @@ -30,6 +30,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/suite" ) const ( @@ -38,9 +39,10 @@ const ( ) var ( - testDirPath string - storageClient *storage.Client - ctx context.Context + currentTestSuite suite.TestingSuite + testDirPath string + storageClient *storage.Client + ctx context.Context ) //////////////////////////////////////////////////////////////////////// @@ -66,6 +68,30 @@ func NewFileShouldGetSyncedToGCSAtClose(ctx context.Context, storageClient *stor client.CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, fileName, client.FileContents, t) } +func RunTestsForFlagsSet(flagsSet [][]string, m *testing.M) (successCode int) { + if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(ctx, storageClient); err == nil { + flagsSet = append(flagsSet, hnsFlagSet) + } + + if !testing.Short() { + setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc") + } + + successCode = static_mounting.RunTests(flagsSet, m) + + if successCode == 0 { + successCode = only_dir_mounting.RunTests(flagsSet, onlyDirMounted, m) + } + + // Dynamic mounting tests create a bucket and perform tests on that bucket, + // which is not a hierarchical bucket. So we are not running those tests with + // hierarchical bucket. + if successCode == 0 && !setup.IsHierarchicalBucket(ctx, storageClient) { + successCode = dynamic_mounting.RunTests(ctx, storageClient, flagsSet, m) + } + return successCode +} + //////////////////////////////////////////////////////////////////////// // TestMain //////////////////////////////////////////////////////////////////////// @@ -94,33 +120,23 @@ func TestMain(m *testing.M) { // Set up test directory. setup.SetUpTestDirForTestBucketFlag() - // Set up flags to run tests on. + // Set up flags to run tests on local file test suite. // Not setting config file explicitly with 'create-empty-file: false' as it is default. - flagsSet := [][]string{ + localFileFlagsSet := [][]string{ {"--implicit-dirs=true", "--rename-dir-limit=3"}, {"--implicit-dirs=false", "--rename-dir-limit=3"}, - {"--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2"}, - } - - if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(ctx, storageClient); err == nil { - flagsSet = append(flagsSet, hnsFlagSet) } - if !testing.Short() { - setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc") + // Set up flags to run tests on local file with streaming writes test suite. + localFileWithStreamingWritesFlagsSet := [][]string{ + {"--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2"}, } - successCode := static_mounting.RunTests(flagsSet, m) - + currentTestSuite = new(localFileTestSuite) + successCode := RunTestsForFlagsSet(localFileFlagsSet, m) if successCode == 0 { - successCode = only_dir_mounting.RunTests(flagsSet, onlyDirMounted, m) - } - - // Dynamic mounting tests create a bucket and perform tests on that bucket, - // which is not a hierarchical bucket. So we are not running those tests with - // hierarchical bucket. - if successCode == 0 && !setup.IsHierarchicalBucket(ctx, storageClient) { - successCode = dynamic_mounting.RunTests(ctx, storageClient, flagsSet, m) + currentTestSuite = new(localFileWithStreaminingWritesTestSuite) + successCode = RunTestsForFlagsSet(localFileWithStreamingWritesFlagsSet, m) } // Clean up test directory created. diff --git a/tools/integration_tests/local_file/local_file_test_suite_test.go b/tools/integration_tests/local_file/local_file_test_suite_test.go new file mode 100644 index 0000000000..141c08ae18 --- /dev/null +++ b/tools/integration_tests/local_file/local_file_test_suite_test.go @@ -0,0 +1,27 @@ +package local_file_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type localFileTestSuite struct { + commonLocalFileTestSuite +} + +type localFileWithStreaminingWritesTestSuite struct { + commonLocalFileTestSuite +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func TestCurrentTestSuite(t *testing.T) { + suite.Run(t, currentTestSuite) +} diff --git a/tools/integration_tests/local_file/read_dir_test.go b/tools/integration_tests/local_file/read_dir_test.go index 079a635f51..cf55c51a69 100644 --- a/tools/integration_tests/local_file/read_dir_test.go +++ b/tools/integration_tests/local_file/read_dir_test.go @@ -58,7 +58,7 @@ func readingDirNTimesShouldNotThrowError(n int, wg *sync.WaitGroup, t *testing.T // Tests // ////////////////////////////////////////////////////////////////////// -func TestReadDir(t *testing.T) { +func (t *commonLocalFileTestSuite) TestReadDir() { // Structure // mntDir/ // mntDir/explicit/ --- directory @@ -69,42 +69,42 @@ func TestReadDir(t *testing.T) { testDirPath = setup.SetupTestDirectory(testDirName) // Create explicit dir with 1 local file. - operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t) + operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t.T()) _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, - path.Join(ExplicitDirName, ExplicitFileName1), t) + path.Join(ExplicitDirName, ExplicitFileName1), t.T()) // Create empty local file. - _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Create non-empty local file. - _, fh3 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName2, t) - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh3, testDirName, FileName2, t) + _, fh3 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName2, t.T()) + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh3, testDirName, FileName2, t.T()) // Create GCS synced file. - CreateObjectInGCSTestDir(ctx, storageClient, testDirName, FileName3, GCSFileContent, t) + CreateObjectInGCSTestDir(ctx, storageClient, testDirName, FileName3, GCSFileContent, t.T()) // Attempt to list mnt and explicit directory. - entriesMnt := operations.ReadDirectory(testDirPath, t) - entriesDir := operations.ReadDirectory(path.Join(testDirPath, ExplicitDirName), t) + entriesMnt := operations.ReadDirectory(testDirPath, t.T()) + entriesDir := operations.ReadDirectory(path.Join(testDirPath, ExplicitDirName), t.T()) // Verify entriesMnt received successfully. - operations.VerifyCountOfDirectoryEntries(4, len(entriesMnt), t) - operations.VerifyDirectoryEntry(entriesMnt[0], ExplicitDirName, t) - operations.VerifyFileEntry(entriesMnt[1], FileName1, 0, t) - operations.VerifyFileEntry(entriesMnt[2], FileName2, SizeOfFileContents, t) - operations.VerifyFileEntry(entriesMnt[3], FileName3, GCSFileSize, t) + operations.VerifyCountOfDirectoryEntries(4, len(entriesMnt), t.T()) + operations.VerifyDirectoryEntry(entriesMnt[0], ExplicitDirName, t.T()) + operations.VerifyFileEntry(entriesMnt[1], FileName1, 0, t.T()) + operations.VerifyFileEntry(entriesMnt[2], FileName2, SizeOfFileContents, t.T()) + operations.VerifyFileEntry(entriesMnt[3], FileName3, GCSFileSize, t.T()) // Verify entriesDir received successfully. - operations.VerifyCountOfDirectoryEntries(1, len(entriesDir), t) - operations.VerifyFileEntry(entriesDir[0], ExplicitFileName1, 0, t) + operations.VerifyCountOfDirectoryEntries(1, len(entriesDir), t.T()) + operations.VerifyFileEntry(entriesDir[0], ExplicitFileName1, 0, t.T()) // Close the local files. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh1, testDirName, - path.Join(ExplicitDirName, ExplicitFileName1), "", t) + path.Join(ExplicitDirName, ExplicitFileName1), "", t.T()) CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testDirName, - FileName1, "", t) + FileName1, "", t.T()) CloseFileAndValidateContentFromGCS(ctx, storageClient, fh3, testDirName, - FileName2, FileContents, t) + FileName2, FileContents, t.T()) ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, FileName3, - GCSFileContent, t) + GCSFileContent, t.T()) } -func TestRecursiveListingWithLocalFiles(t *testing.T) { +func (t *commonLocalFileTestSuite) TestRecursiveListingWithLocalFiles() { // Structure // mntDir/ // mntDir/foo1 --- file @@ -113,11 +113,11 @@ func TestRecursiveListingWithLocalFiles(t *testing.T) { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file in mnt/ dir. - _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Create explicit dir with 1 local file. - operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t) + operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t.T()) _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, - path.Join(ExplicitDirName, ExplicitFileName1), t) + path.Join(ExplicitDirName, ExplicitFileName1), t.T()) // Recursively list mntDir/ directory. err := filepath.WalkDir(testDirPath, func(walkPath string, dir fs.DirEntry, err error) error { @@ -129,21 +129,21 @@ func TestRecursiveListingWithLocalFiles(t *testing.T) { return nil } - objs := operations.ReadDirectory(walkPath, t) + objs := operations.ReadDirectory(walkPath, t.T()) // Check if mntDir has correct objects. if walkPath == testDirPath { // numberOfObjects = 2 - operations.VerifyCountOfDirectoryEntries(2, len(objs), t) - operations.VerifyDirectoryEntry(objs[0], ExplicitDirName, t) - operations.VerifyFileEntry(objs[1], FileName1, 0, t) + operations.VerifyCountOfDirectoryEntries(2, len(objs), t.T()) + operations.VerifyDirectoryEntry(objs[0], ExplicitDirName, t.T()) + operations.VerifyFileEntry(objs[1], FileName1, 0, t.T()) } // Check if mntDir/explicit/ has correct objects. if walkPath == path.Join(setup.MntDir(), ExplicitDirName) { // numberOfObjects = 1 - operations.VerifyCountOfDirectoryEntries(1, len(objs), t) - operations.VerifyFileEntry(objs[0], ExplicitFileName1, 0, t) + operations.VerifyCountOfDirectoryEntries(1, len(objs), t.T()) + operations.VerifyFileEntry(objs[0], ExplicitFileName1, 0, t.T()) } return nil @@ -151,58 +151,58 @@ func TestRecursiveListingWithLocalFiles(t *testing.T) { // Validate and close the files. if err != nil { - t.Fatalf("filepath.WalkDir() err: %v", err) + t.T().Fatalf("filepath.WalkDir() err: %v", err) } CloseFileAndValidateContentFromGCS(ctx, storageClient, fh1, testDirName, - FileName1, "", t) + FileName1, "", t.T()) CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testDirName, - path.Join(ExplicitDirName, ExplicitFileName1), "", t) + path.Join(ExplicitDirName, ExplicitFileName1), "", t.T()) } -func TestReadDirWithSameNameLocalAndGCSFile(t *testing.T) { +func (t *commonLocalFileTestSuite) TestReadDirWithSameNameLocalAndGCSFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file. - _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Create same name gcs file. time.Sleep(2 * time.Second) - CreateObjectInGCSTestDir(ctx, storageClient, testDirName, FileName1, GCSFileContent, t) + CreateObjectInGCSTestDir(ctx, storageClient, testDirName, FileName1, GCSFileContent, t.T()) // Attempt to list testDir. _, err := os.ReadDir(testDirPath) if err != nil { - t.Fatalf("ReadDir err: %v", err) + t.T().Fatalf("ReadDir err: %v", err) } // Validate closing local file throws error. err = fh1.Close() - operations.ValidateStaleNFSFileHandleError(t, err) + operations.ValidateStaleNFSFileHandleError(t.T(), err) } -func TestConcurrentReadDirAndCreationOfLocalFiles_DoesNotThrowError(t *testing.T) { +func (t *commonLocalFileTestSuite) TestConcurrentReadDirAndCreationOfLocalFiles_DoesNotThrowError() { testDirPath = setup.SetupTestDirectory(testDirName) var wg sync.WaitGroup wg.Add(2) // Concurrently create 100 local files and read directory 200 times. - go creatingNLocalFilesShouldNotThrowError(100, &wg, t) - go readingDirNTimesShouldNotThrowError(200, &wg, t) + go creatingNLocalFilesShouldNotThrowError(100, &wg, t.T()) + go readingDirNTimesShouldNotThrowError(200, &wg, t.T()) wg.Wait() } -func TestStatLocalFileAfterRecreatingItWithSameName(t *testing.T) { +func (t *commonLocalFileTestSuite) TestStatLocalFileAfterRecreatingItWithSameName() { testDirPath = setup.SetupTestDirectory(testDirName) filePath := path.Join(testDirPath, FileName1) - operations.CreateFile(filePath, FilePerms, t) + operations.CreateFile(filePath, FilePerms, t.T()) _, err := os.Stat(filePath) - require.NoError(t, err) + require.NoError(t.T(), err) err = os.Remove(filePath) - require.NoError(t, err) - operations.CreateFile(filePath, FilePerms, t) + require.NoError(t.T(), err) + operations.CreateFile(filePath, FilePerms, t.T()) f, err := os.Stat(filePath) - assert.NoError(t, err) - assert.Equal(t, FileName1, f.Name()) - assert.False(t, f.IsDir()) + assert.NoError(t.T(), err) + assert.Equal(t.T(), FileName1, f.Name()) + assert.False(t.T(), f.IsDir()) } diff --git a/tools/integration_tests/local_file/read_file_test.go b/tools/integration_tests/local_file/read_file_test.go index d50a9b3a58..c9f5c64cde 100644 --- a/tools/integration_tests/local_file/read_file_test.go +++ b/tools/integration_tests/local_file/read_file_test.go @@ -16,33 +16,50 @@ package local_file_test import ( - "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -func TestReadLocalFile(t *testing.T) { +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *localFileTestSuite) TestReadLocalFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Write FileContents twice to local file. content := FileContents + FileContents - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) - - // mounts with streaming writes disabled support read operation. - if !setup.StreamingWritesEnabled() { - // Read the local file contents. - buf := make([]byte, len(content)) - n, err := fh.ReadAt(buf, 0) - if err != nil || len(content) != n || content != string(buf) { - t.Fatalf("Read file operation failed on local file: %v "+ - "Expected content: %s, Got Content: %s", err, content, string(buf)) - } + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) + + // Read the local file contents. + buf := make([]byte, len(content)) + n, err := fh.ReadAt(buf, 0) + if err != nil || len(content) != n || content != string(buf) { + t.T().Fatalf("Read file operation failed on local file: %v "+ + "Expected content: %s, Got Content: %s", err, content, string(buf)) } // Close the file and validate that the file is created on GCS. - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, content, t) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, content, t.T()) +} + +func (t *localFileWithStreaminingWritesTestSuite) TestReadLocalFileWithStreamingWritesFails() { + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) + + // Write FileContents twice to local file. + content := FileContents + FileContents + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) + + // Read the local file contents. + buf := make([]byte, len(content)) + _, err := fh.ReadAt(buf, 0) + t.NotNil(err) + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, content, t.T()) } diff --git a/tools/integration_tests/local_file/remove_dir_test.go b/tools/integration_tests/local_file/remove_dir_test.go index bba007655e..700fdc2c22 100644 --- a/tools/integration_tests/local_file/remove_dir_test.go +++ b/tools/integration_tests/local_file/remove_dir_test.go @@ -17,57 +17,60 @@ package local_file_test import ( "path" - "testing" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -func TestRmDirOfDirectoryContainingGCSAndLocalFiles(t *testing.T) { +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *commonLocalFileTestSuite) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { testDirPath = setup.SetupTestDirectory(testDirName) // Create explicit directory with one synced and one local file. - operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t) + operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t.T()) syncedFile := path.Join(ExplicitDirName, FileName1) localFile := path.Join(ExplicitDirName, FileName2) - _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, syncedFile, t) - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh1, testDirName, syncedFile, "", t) - _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, localFile, t) + _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, syncedFile, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh1, testDirName, syncedFile, "", t.T()) + _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, localFile, t.T()) // Attempt to remove explicit directory. operations.RemoveDir(path.Join(testDirPath, ExplicitDirName)) // Verify that directory is removed. - operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, ExplicitDirName)) + operations.ValidateNoFileOrDirError(t.T(), path.Join(testDirPath, ExplicitDirName)) // Validate writing content to unlinked local file does not throw error. - operations.WriteWithoutClose(fh2, FileContents, t) + operations.WriteWithoutClose(fh2, FileContents, t.T()) // Validate flush file does not throw error and does not create object on GCS. - operations.CloseFileShouldNotThrowError(fh2, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile, t) + operations.CloseFileShouldNotThrowError(fh2, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile, t.T()) // Validate synced files are also deleted. - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, syncedFile, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, ExplicitDirName, t) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, syncedFile, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, ExplicitDirName, t.T()) } -func TestRmDirOfDirectoryContainingOnlyLocalFiles(t *testing.T) { +func (t *commonLocalFileTestSuite) TestRmDirOfDirectoryContainingOnlyLocalFiles() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a directory with two local files. - operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t) + operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t.T()) localFile1 := path.Join(ExplicitDirName, FileName1) localFile2 := path.Join(ExplicitDirName, FileName2) - _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, localFile1, t) - _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, localFile2, t) + _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, localFile1, t.T()) + _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, localFile2, t.T()) // Attempt to remove explicit directory. operations.RemoveDir(path.Join(testDirPath, ExplicitDirName)) // Verify rmDir operation succeeds. - operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, ExplicitDirName)) + operations.ValidateNoFileOrDirError(t.T(), path.Join(testDirPath, ExplicitDirName)) // Close the local files and validate they are not present on GCS. - operations.CloseFileShouldNotThrowError(fh1, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile1, t) - operations.CloseFileShouldNotThrowError(fh2, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile2, t) + operations.CloseFileShouldNotThrowError(fh1, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile1, t.T()) + operations.CloseFileShouldNotThrowError(fh2, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile2, t.T()) // Validate directory is also deleted. - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, ExplicitDirName, t) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, ExplicitDirName, t.T()) } diff --git a/tools/integration_tests/local_file/rename_test.go b/tools/integration_tests/local_file/rename_test.go index ef058138eb..9e6934cac6 100644 --- a/tools/integration_tests/local_file/rename_test.go +++ b/tools/integration_tests/local_file/rename_test.go @@ -41,11 +41,11 @@ func verifyRenameOperationNotSupported(err error, t *testing.T) { // Tests //////////////////////////////////////////////////////////////////////// -func TestRenameOfLocalFileFails(t *testing.T) { +func (t *commonLocalFileTestSuite) TestRenameOfLocalFileFails() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file with some content. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) // Attempt to rename local file. err := os.Rename( @@ -53,26 +53,26 @@ func TestRenameOfLocalFileFails(t *testing.T) { path.Join(testDirPath, NewFileName)) // Verify rename operation fails. - verifyRenameOperationNotSupported(err, t) + verifyRenameOperationNotSupported(err, t.T()) // write more content to local file. - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) // Close the local file. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, FileContents+FileContents, t) + FileName1, FileContents+FileContents, t.T()) } -func TestRenameOfDirectoryWithLocalFileFails(t *testing.T) { +func (t *commonLocalFileTestSuite) TestRenameOfDirectoryWithLocalFileFails() { testDirPath = setup.SetupTestDirectory(testDirName) //Create directory with 1 synced and 1 local file. - operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t) + operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t.T()) // Create synced file. CreateObjectInGCSTestDir(ctx, storageClient, testDirName, - path.Join(ExplicitDirName, FileName1), GCSFileContent, t) + path.Join(ExplicitDirName, FileName1), GCSFileContent, t.T()) // Create local file with some content. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, - path.Join(ExplicitDirName, FileName2), t) + path.Join(ExplicitDirName, FileName2), t.T()) WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, - path.Join(ExplicitDirName, FileName2), t) + path.Join(ExplicitDirName, FileName2), t.T()) // Attempt to rename directory containing local file. err := os.Rename( @@ -80,16 +80,16 @@ func TestRenameOfDirectoryWithLocalFileFails(t *testing.T) { path.Join(testDirPath, NewDirName)) // Verify rename operation fails. - verifyRenameOperationNotSupported(err, t) + verifyRenameOperationNotSupported(err, t.T()) // Write more content to local file. - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName2, t) + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName2, t.T()) // Close the local file. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - path.Join(ExplicitDirName, FileName2), FileContents+FileContents, t) + path.Join(ExplicitDirName, FileName2), FileContents+FileContents, t.T()) } -func TestRenameOfLocalFileSucceedsAfterSync(t *testing.T) { - TestRenameOfLocalFileFails(t) +func (t *commonLocalFileTestSuite) TestRenameOfLocalFileSucceedsAfterSync() { + t.TestRenameOfLocalFileFails() // Attempt to Rename synced file. err := os.Rename( @@ -98,15 +98,15 @@ func TestRenameOfLocalFileSucceedsAfterSync(t *testing.T) { // Validate. if err != nil { - t.Fatalf("os.Rename() failed on synced file: %v", err) + t.T().Fatalf("os.Rename() failed on synced file: %v", err) } ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, NewFileName, - FileContents+FileContents, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + FileContents+FileContents, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } -func TestRenameOfDirectoryWithLocalFileSucceedsAfterSync(t *testing.T) { - TestRenameOfDirectoryWithLocalFileFails(t) +func (t *localFileTestSuite) TestRenameOfDirectoryWithLocalFileSucceedsAfterSync() { + t.TestRenameOfDirectoryWithLocalFileFails() // Attempt to rename directory again after sync. err := os.Rename( @@ -115,14 +115,14 @@ func TestRenameOfDirectoryWithLocalFileSucceedsAfterSync(t *testing.T) { // Validate. if err != nil { - t.Fatalf("os.Rename() failed on directory containing synced files: %v", err) + t.T().Fatalf("os.Rename() failed on directory containing synced files: %v", err) } ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, - path.Join(NewDirName, FileName1), GCSFileContent, t) + path.Join(NewDirName, FileName1), GCSFileContent, t.T()) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, - path.Join(ExplicitDirName, FileName1), t) + path.Join(ExplicitDirName, FileName1), t.T()) ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, - path.Join(NewDirName, FileName2), FileContents+FileContents, t) + path.Join(NewDirName, FileName2), FileContents+FileContents, t.T()) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, - path.Join(ExplicitDirName, FileName2), t) + path.Join(ExplicitDirName, FileName2), t.T()) } diff --git a/tools/integration_tests/local_file/stat_file_test.go b/tools/integration_tests/local_file/stat_file_test.go index 607f14ae49..36abb8e226 100644 --- a/tools/integration_tests/local_file/stat_file_test.go +++ b/tools/integration_tests/local_file/stat_file_test.go @@ -17,7 +17,6 @@ package local_file_test import ( "os" - "testing" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -26,69 +25,85 @@ import ( "github.com/stretchr/testify/require" ) -func TestStatOnLocalFile(t *testing.T) { +func (t *commonLocalFileTestSuite) TestStatOnLocalFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Stat the local file. - operations.VerifyStatFile(filePath, 0, FilePerms, t) + operations.VerifyStatFile(filePath, 0, FilePerms, t.T()) // Writing contents to local file shouldn't create file on GCS. - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) // Stat the local file again to check if new content is written. - operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t) + operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t.T()) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, FileContents, t) + FileName1, FileContents, t.T()) } -func TestStatOnLocalFileWithConflictingFileNameSuffix(t *testing.T) { +func (t *commonLocalFileTestSuite) TestStatOnLocalFileWithConflictingFileNameSuffix() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Stat the local file. - operations.VerifyStatFile(filePath+inode.ConflictingFileNameSuffix, 0, FilePerms, t) + operations.VerifyStatFile(filePath+inode.ConflictingFileNameSuffix, 0, FilePerms, t.T()) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, "", t) + FileName1, "", t.T()) } -func TestTruncateLocalFileToSmallerSize(t *testing.T) { +func (t *localFileTestSuite) TestTruncateLocalFileToSmallerSize() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Writing contents to local file . - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t) + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) // Stat the file to validate if new contents are written. - operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t) + operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t.T()) // Truncate the file to update file size to smaller file size. err := os.Truncate(filePath, SmallerSizeTruncate) - var expectTruncatedSize int64 = SizeOfFileContents - var expectedContent = FileContents - if setup.StreamingWritesEnabled() { - // Mounts with streaming writes do not supporting truncating files to smaller. - require.Error(t, err) - } else { - if err != nil { - t.Fatalf("os.Truncate err: %v", err) - } - expectTruncatedSize = SmallerSizeTruncate - expectedContent = FileContents[:SmallerSizeTruncate] + if err != nil { + t.T().Fatalf("os.Truncate err: %v", err) } - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) // Stat the file to validate if file is truncated correctly. - operations.VerifyStatFile(filePath, expectTruncatedSize, FilePerms, t) + operations.VerifyStatFile(filePath, SmallerSizeTruncate, FilePerms, t.T()) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, expectedContent, t) + FileName1, FileContents[:SmallerSizeTruncate], t.T()) +} + +func (t *localFileWithStreaminingWritesTestSuite) TestTruncateLocalFileToSmallerSizewithStreamingWritesFails() { + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) + // Writing contents to local file . + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) + + // Stat the file to validate if new contents are written. + operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t.T()) + + // Truncate the file to update file size to smaller file size. + err := os.Truncate(filePath, SmallerSizeTruncate) + // Mounts with streaming writes do not supporting truncating files to smaller. + require.Error(t.T(), err) + + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) + + // Stat the file to validate if file is truncated correctly. + operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t.T()) + + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, + FileName1, FileContents, t.T()) } diff --git a/tools/integration_tests/local_file/sym_link_test.go b/tools/integration_tests/local_file/sym_link_test.go index c544dee484..5a903fcd0b 100644 --- a/tools/integration_tests/local_file/sym_link_test.go +++ b/tools/integration_tests/local_file/sym_link_test.go @@ -26,7 +26,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -func createAndVerifySymLink(streamingWritesEnabled bool, t *testing.T) (filePath, symlink string, fh *os.File) { +func createAndVerifySymLink(t *testing.T) (filePath, symlink string, fh *os.File) { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. filePath, fh = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) @@ -38,31 +38,57 @@ func createAndVerifySymLink(streamingWritesEnabled bool, t *testing.T) (filePath // Read the link. operations.VerifyReadLink(filePath, symlink, t) - if streamingWritesEnabled { - // Mounts with streaming writes do not support reading files. - return - } - operations.VerifyReadFile(symlink, FileContents, t) return } -func TestCreateSymlinkForLocalFile(t *testing.T) { - _, _, fh := createAndVerifySymLink(setup.StreamingWritesEnabled(), t) +func (t *localFileTestSuite) TestCreateSymlinkForLocalFile() { + _, symlink, fh := createAndVerifySymLink(t.T()) + // Read the file from symlink. + operations.VerifyReadFile(symlink, FileContents, t.T()) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, FileContents, t) + FileName1, FileContents, t.T()) } -func TestReadSymlinkForDeletedLocalFile(t *testing.T) { - filePath, symlink, fh := createAndVerifySymLink(setup.StreamingWritesEnabled(), t) +func (t *localFileTestSuite) TestReadSymlinkForDeletedLocalFile() { + filePath, symlink, fh := createAndVerifySymLink(t.T()) + // Read the file from symlink. + operations.VerifyReadFile(symlink, FileContents, t.T()) // Remove filePath and then close the fileHandle to avoid syncing to GCS. operations.RemoveFile(filePath) - operations.CloseFileShouldNotThrowError(fh, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + operations.CloseFileShouldNotThrowError(fh, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) // Reading symlink should fail. _, err := os.Stat(symlink) if err == nil || !strings.Contains(err.Error(), "no such file or directory") { - t.Fatalf("Reading symlink for deleted local file did not fail.") + t.T().Fatalf("Reading symlink for deleted local file did not fail.") + } +} + +func (t *localFileWithStreaminingWritesTestSuite) TestCreateSymlinkForLocalFileWithStreamingWritesReadFails() { + _, symlink, fh := createAndVerifySymLink(t.T()) + // Read the file from symlink fails. + _, err := os.ReadFile(symlink) + t.NotNil(err) + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, + FileName1, FileContents, t.T()) +} + +func (t *localFileWithStreaminingWritesTestSuite) TestReadSymlinkForDeletedLocalFileWithStreamingWritesReadFails() { + filePath, symlink, fh := createAndVerifySymLink(t.T()) + // Read the file from symlink fails + _, err := os.ReadFile(symlink) + t.NotNil(err) + // Remove filePath and then close the fileHandle to avoid syncing to GCS. + operations.RemoveFile(filePath) + operations.CloseFileShouldNotThrowError(fh, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) + + // Reading symlink should fail. + _, err = os.Stat(symlink) + if err == nil || !strings.Contains(err.Error(), "no such file or directory") { + t.T().Fatalf("Reading symlink for deleted local file did not fail.") } } diff --git a/tools/integration_tests/local_file/unlinked_file_test.go b/tools/integration_tests/local_file/unlinked_file_test.go index ec7b2dcc49..3eb9493513 100644 --- a/tools/integration_tests/local_file/unlinked_file_test.go +++ b/tools/integration_tests/local_file/unlinked_file_test.go @@ -17,131 +17,130 @@ package local_file_test import ( "path" - "testing" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -func TestStatOnUnlinkedLocalFile(t *testing.T) { +func (t *commonLocalFileTestSuite) TestStatOnUnlinkedLocalFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Unlink the local file. operations.RemoveFile(filePath) // Stat the local file and validate error. - operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) + operations.ValidateNoFileOrDirError(t.T(), path.Join(testDirPath, FileName1)) // Close the file and validate that file is not created on GCS. - operations.CloseFileShouldNotThrowError(fh, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + operations.CloseFileShouldNotThrowError(fh, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } -func TestReadDirContainingUnlinkedLocalFiles(t *testing.T) { +func (t *commonLocalFileTestSuite) TestReadDirContainingUnlinkedLocalFiles() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local files. - _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) - _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName2, t) - filepath3, fh3 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName3, t) + _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) + _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName2, t.T()) + filepath3, fh3 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName3, t.T()) // Unlink local file 3. operations.RemoveFile(filepath3) // Attempt to list testDir. - entries := operations.ReadDirectory(testDirPath, t) + entries := operations.ReadDirectory(testDirPath, t.T()) // Verify unlinked entries are not listed. - operations.VerifyCountOfDirectoryEntries(2, len(entries), t) - operations.VerifyFileEntry(entries[0], FileName1, 0, t) - operations.VerifyFileEntry(entries[1], FileName2, 0, t) + operations.VerifyCountOfDirectoryEntries(2, len(entries), t.T()) + operations.VerifyFileEntry(entries[0], FileName1, 0, t.T()) + operations.VerifyFileEntry(entries[1], FileName2, 0, t.T()) // Close the local files and validate they are written to GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh1, testDirName, - FileName1, "", t) + FileName1, "", t.T()) CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testDirName, - FileName2, "", t) + FileName2, "", t.T()) // Verify unlinked file is not written to GCS. - operations.CloseFileShouldNotThrowError(fh3, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName3, t) + operations.CloseFileShouldNotThrowError(fh3, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName3, t.T()) } -func TestWriteOnUnlinkedLocalFileSucceeds(t *testing.T) { +func (t *commonLocalFileTestSuite) TestWriteOnUnlinkedLocalFileSucceeds() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file. - filepath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + filepath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Verify unlink operation succeeds. operations.RemoveFile(filepath) - operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) + operations.ValidateNoFileOrDirError(t.T(), path.Join(testDirPath, FileName1)) // Write to unlinked local file. - operations.WriteWithoutClose(fh, FileContents, t) + operations.WriteWithoutClose(fh, FileContents, t.T()) // Validate flush file does not throw error. - operations.CloseFileShouldNotThrowError(fh, t) + operations.CloseFileShouldNotThrowError(fh, t.T()) // Validate unlinked file is not written to GCS. - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } -func TestSyncOnUnlinkedLocalFile(t *testing.T) { +func (t *commonLocalFileTestSuite) TestSyncOnUnlinkedLocalFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file. - filepath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + filepath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Attempt to unlink local file. operations.RemoveFile(filepath) // Verify unlink operation succeeds. - operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) + operations.ValidateNoFileOrDirError(t.T(), path.Join(testDirPath, FileName1)) // Validate sync operation does not write to GCS after unlink. - operations.SyncFile(fh, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + operations.SyncFile(fh, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) // Close the local file and validate it is not present on GCS. - operations.CloseFileShouldNotThrowError(fh, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + operations.CloseFileShouldNotThrowError(fh, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } -func TestFileWithSameNameCanBeCreatedWhenDeletedBeforeSync(t *testing.T) { +func (t *commonLocalFileTestSuite) TestFileWithSameNameCanBeCreatedWhenDeletedBeforeSync() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Write some content. - operations.WriteWithoutClose(fh, FileContents, t) + operations.WriteWithoutClose(fh, FileContents, t.T()) // Remove and close the file. operations.RemoveFile(filePath) // Currently flush calls returns error if unlinked. Ignoring that error here. _ = fh.Close() // Validate that file is not created on GCS - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) // Verify unlink operation succeeds. - operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) + operations.ValidateNoFileOrDirError(t.T(), path.Join(testDirPath, FileName1)) // Create a local file. - _, fh = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) newContents := "newContents" - operations.WriteWithoutClose(fh, newContents, t) - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, newContents, t) + operations.WriteWithoutClose(fh, newContents, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, newContents, t.T()) } -func TestFileWithSameNameCanBeCreatedAfterDelete(t *testing.T) { +func (t *commonLocalFileTestSuite) TestFileWithSameNameCanBeCreatedAfterDelete() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Write some content. - operations.WriteWithoutClose(fh, FileContents, t) + operations.WriteWithoutClose(fh, FileContents, t.T()) CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, FileContents, t) + FileName1, FileContents, t.T()) // Remove the file. operations.RemoveFile(filePath) // Validate that file id deleted from GCS - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) // Verify unlink operation succeeds. - operations.ValidateNoFileOrDirError(t, path.Join(testDirPath, FileName1)) + operations.ValidateNoFileOrDirError(t.T(), path.Join(testDirPath, FileName1)) // Create a local file. - _, fh = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) newContents := "newContents" - operations.WriteWithoutClose(fh, newContents, t) - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, newContents, t) + operations.WriteWithoutClose(fh, newContents, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, newContents, t.T()) } diff --git a/tools/integration_tests/local_file/write_file_test.go b/tools/integration_tests/local_file/write_file_test.go index 86b36c1245..376e96341d 100644 --- a/tools/integration_tests/local_file/write_file_test.go +++ b/tools/integration_tests/local_file/write_file_test.go @@ -16,108 +16,98 @@ package local_file_test import ( - "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -func TestMultipleWritesToLocalFile(t *testing.T) { +func (t *commonLocalFileTestSuite) TestMultipleWritesToLocalFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Write some contents to file sequentially. for i := 0; i < 3; i++ { - operations.WriteWithoutClose(fh, FileContents, t) + operations.WriteWithoutClose(fh, FileContents, t.T()) } - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, FileContents+FileContents+FileContents, t) + FileName1, FileContents+FileContents+FileContents, t.T()) } -func TestRandomWritesToLocalFile(t *testing.T) { +func (t *localFileTestSuite) TestRandomWritesToLocalFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Write some contents to file randomly. - operations.WriteAt("string1", 0, fh, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) - operations.WriteAt("string2", 2, fh, t) - if setup.StreamingWritesEnabled() { - // First out of order write ensures the existing sequentially written data is uploaded - // to GCS when streaming writes are enabled. - ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, FileName1, "string1", t) - } else { - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) - } - operations.WriteAt("string3", 3, fh, t) + operations.WriteAt("string1", 0, fh, t.T()) + operations.WriteAt("string2", 2, fh, t.T()) + operations.WriteAt("string3", 3, fh, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, "stsstring3", t) + FileName1, "stsstring3", t.T()) } -func TestOutOfOrderWritesToNewFile(t *testing.T) { +func (t *commonLocalFileTestSuite) TestOutOfOrderWritesToNewFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Write some contents to file sequentially. for i := 0; i < 2; i++ { - operations.WriteWithoutClose(fh, FileContents, t) + operations.WriteWithoutClose(fh, FileContents, t.T()) } - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) // Write at previous offset. - operations.WriteAt("hello", 0, fh, t) + operations.WriteAt("hello", 0, fh, t.T()) expectedString := "hellotringtestString" // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, expectedString, t) + FileName1, expectedString, t.T()) } -func TestMultipleOutOfOrderWritesToNewFile(t *testing.T) { +func (t *commonLocalFileTestSuite) TestMultipleOutOfOrderWritesToNewFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Write some contents to file sequentially. for i := 0; i < 2; i++ { - operations.WriteWithoutClose(fh, FileContents, t) + operations.WriteWithoutClose(fh, FileContents, t.T()) } - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) // Write at previous offset. - operations.WriteAt("hello", 15, fh, t) - + operations.WriteAt("hello", 15, fh, t.T()) // Write at new offset. - operations.WriteAt("hey", 30, fh, t) + operations.WriteAt("hey", 30, fh, t.T()) emptyBytes := [10]byte{} expectedString := "testStringtestShello" + string(emptyBytes[:]) + "hey" // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, expectedString, t) + FileName1, expectedString, t.T()) } -func TestWritesToNewFileStartingAtNonZeroOffset(t *testing.T) { +func (t *localFileTestSuite) TestWritesToNewFileStartingAtNonZeroOffset() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) // Write at future offset. - operations.WriteAt("hello", 15, fh, t) + operations.WriteAt("hello", 15, fh, t.T()) // Write at zero offset now. - operations.WriteAt("hey", 0, fh, t) + operations.WriteAt("hey", 0, fh, t.T()) emptyBytes := [12]byte{} expectedString := "hey" + string(emptyBytes[:]) + "hello" // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, expectedString, t) + FileName1, expectedString, t.T()) } From 401cad636279e018faaf12165833fd85131cfea9 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Tue, 21 Jan 2025 10:37:03 +0000 Subject: [PATCH 0161/1298] fix test files. --- .../local_file/local_file_test_suite_test.go | 4 ++++ tools/integration_tests/local_file/read_file_test.go | 5 +++-- tools/integration_tests/local_file/rename_test.go | 2 +- tools/integration_tests/local_file/stat_file_test.go | 4 ++-- tools/integration_tests/local_file/sym_link_test.go | 7 +++++-- tools/integration_tests/local_file/write_file_test.go | 2 +- 6 files changed, 16 insertions(+), 8 deletions(-) diff --git a/tools/integration_tests/local_file/local_file_test_suite_test.go b/tools/integration_tests/local_file/local_file_test_suite_test.go index 141c08ae18..1deb94fed1 100644 --- a/tools/integration_tests/local_file/local_file_test_suite_test.go +++ b/tools/integration_tests/local_file/local_file_test_suite_test.go @@ -25,3 +25,7 @@ type localFileWithStreaminingWritesTestSuite struct { func TestCurrentTestSuite(t *testing.T) { suite.Run(t, currentTestSuite) } + +func TestCommonLocalFileTestSuite(t *testing.T) { + suite.Run(t, &commonLocalFileTestSuite{}) +} diff --git a/tools/integration_tests/local_file/read_file_test.go b/tools/integration_tests/local_file/read_file_test.go index c9f5c64cde..d6d5d0db21 100644 --- a/tools/integration_tests/local_file/read_file_test.go +++ b/tools/integration_tests/local_file/read_file_test.go @@ -18,6 +18,7 @@ package local_file_test import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" ) //////////////////////////////////////////////////////////////////////// @@ -56,10 +57,10 @@ func (t *localFileWithStreaminingWritesTestSuite) TestReadLocalFileWithStreaming WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) - // Read the local file contents. + // Reading the local file content fails. buf := make([]byte, len(content)) _, err := fh.ReadAt(buf, 0) - t.NotNil(err) + assert.Error(t.T(), err) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, content, t.T()) } diff --git a/tools/integration_tests/local_file/rename_test.go b/tools/integration_tests/local_file/rename_test.go index 9e6934cac6..59715f69be 100644 --- a/tools/integration_tests/local_file/rename_test.go +++ b/tools/integration_tests/local_file/rename_test.go @@ -105,7 +105,7 @@ func (t *commonLocalFileTestSuite) TestRenameOfLocalFileSucceedsAfterSync() { ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } -func (t *localFileTestSuite) TestRenameOfDirectoryWithLocalFileSucceedsAfterSync() { +func (t *commonLocalFileTestSuite) TestRenameOfDirectoryWithLocalFileSucceedsAfterSync() { t.TestRenameOfDirectoryWithLocalFileFails() // Attempt to rename directory again after sync. diff --git a/tools/integration_tests/local_file/stat_file_test.go b/tools/integration_tests/local_file/stat_file_test.go index 36abb8e226..b76fd5a401 100644 --- a/tools/integration_tests/local_file/stat_file_test.go +++ b/tools/integration_tests/local_file/stat_file_test.go @@ -22,7 +22,7 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" ) func (t *commonLocalFileTestSuite) TestStatOnLocalFile() { @@ -96,7 +96,7 @@ func (t *localFileWithStreaminingWritesTestSuite) TestTruncateLocalFileToSmaller // Truncate the file to update file size to smaller file size. err := os.Truncate(filePath, SmallerSizeTruncate) // Mounts with streaming writes do not supporting truncating files to smaller. - require.Error(t.T(), err) + assert.Error(t.T(), err) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) diff --git a/tools/integration_tests/local_file/sym_link_test.go b/tools/integration_tests/local_file/sym_link_test.go index 5a903fcd0b..1d2e62051e 100644 --- a/tools/integration_tests/local_file/sym_link_test.go +++ b/tools/integration_tests/local_file/sym_link_test.go @@ -24,6 +24,7 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" ) func createAndVerifySymLink(t *testing.T) (filePath, symlink string, fh *os.File) { @@ -70,7 +71,8 @@ func (t *localFileWithStreaminingWritesTestSuite) TestCreateSymlinkForLocalFileW _, symlink, fh := createAndVerifySymLink(t.T()) // Read the file from symlink fails. _, err := os.ReadFile(symlink) - t.NotNil(err) + assert.Error(t.T(), err) + // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, FileContents, t.T()) @@ -80,7 +82,8 @@ func (t *localFileWithStreaminingWritesTestSuite) TestReadSymlinkForDeletedLocal filePath, symlink, fh := createAndVerifySymLink(t.T()) // Read the file from symlink fails _, err := os.ReadFile(symlink) - t.NotNil(err) + assert.Error(t.T(), err) + // Remove filePath and then close the fileHandle to avoid syncing to GCS. operations.RemoveFile(filePath) operations.CloseFileShouldNotThrowError(fh, t.T()) diff --git a/tools/integration_tests/local_file/write_file_test.go b/tools/integration_tests/local_file/write_file_test.go index 376e96341d..f63b90d442 100644 --- a/tools/integration_tests/local_file/write_file_test.go +++ b/tools/integration_tests/local_file/write_file_test.go @@ -96,7 +96,7 @@ func (t *commonLocalFileTestSuite) TestMultipleOutOfOrderWritesToNewFile() { FileName1, expectedString, t.T()) } -func (t *localFileTestSuite) TestWritesToNewFileStartingAtNonZeroOffset() { +func (t *commonLocalFileTestSuite) TestWritesToNewFileStartingAtNonZeroOffset() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) From d572c129ff7371954105ede01b6142b2708d678c Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Tue, 21 Jan 2025 10:48:30 +0000 Subject: [PATCH 0162/1298] Remove redundant functions. --- .../util/mounting/mounting.go | 3 +- tools/integration_tests/util/setup/setup.go | 29 +++++-------------- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/tools/integration_tests/util/mounting/mounting.go b/tools/integration_tests/util/mounting/mounting.go index 5a99f9cb8f..b42690a489 100644 --- a/tools/integration_tests/util/mounting/mounting.go +++ b/tools/integration_tests/util/mounting/mounting.go @@ -29,8 +29,7 @@ func MountGcsfuse(binaryFile string, flags []string) error { binaryFile, flags..., ) - // Sets to true iff current mount operation is using streaming writes - setup.SetStreamingWritesEnabled(flags) + // Adding mount command in LogFile file, err := os.OpenFile(setup.LogFile(), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 538ab2e0a6..03afb0189b 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -49,18 +49,16 @@ const ( DirPermission_0755 = 0755 Charset = "abcdefghijklmnopqrstuvwxyz0123456789" PathEnvVariable = "PATH" - StreamingWritesFlag = "--enable-streaming-writes=true" ) var ( - binFile string - logFile string - testDir string - mntDir string - sbinFile string - onlyDirMounted string - dynamicBucketMounted string - streamingWritesEnabled bool + binFile string + logFile string + testDir string + mntDir string + sbinFile string + onlyDirMounted string + dynamicBucketMounted string ) // Run the shell script to prepare the testData in the specified bucket. @@ -106,19 +104,6 @@ func LogFile() string { return logFile } -func SetStreamingWritesEnabled(flags []string) { - streamingWritesEnabled = false - for _, flag := range flags { - if flag == StreamingWritesFlag { - streamingWritesEnabled = true - } - } -} - -func StreamingWritesEnabled() bool { - return streamingWritesEnabled -} - func BinFile() string { return binFile } From ab1b020a3e1be7366b8ee0bb2fad34eede6c9a02 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 22 Jan 2025 03:54:36 +0000 Subject: [PATCH 0163/1298] fix suite tests. --- .../local_file/local_file_test_suite_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tools/integration_tests/local_file/local_file_test_suite_test.go b/tools/integration_tests/local_file/local_file_test_suite_test.go index 1deb94fed1..141c08ae18 100644 --- a/tools/integration_tests/local_file/local_file_test_suite_test.go +++ b/tools/integration_tests/local_file/local_file_test_suite_test.go @@ -25,7 +25,3 @@ type localFileWithStreaminingWritesTestSuite struct { func TestCurrentTestSuite(t *testing.T) { suite.Run(t, currentTestSuite) } - -func TestCommonLocalFileTestSuite(t *testing.T) { - suite.Run(t, &commonLocalFileTestSuite{}) -} From 83997e9d10a573937e09285f79e3171d89b79638 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 22 Jan 2025 05:08:08 +0000 Subject: [PATCH 0164/1298] skip the failing test from runing. --- tools/integration_tests/local_file/write_file_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/integration_tests/local_file/write_file_test.go b/tools/integration_tests/local_file/write_file_test.go index f63b90d442..1d782daa6c 100644 --- a/tools/integration_tests/local_file/write_file_test.go +++ b/tools/integration_tests/local_file/write_file_test.go @@ -97,6 +97,8 @@ func (t *commonLocalFileTestSuite) TestMultipleOutOfOrderWritesToNewFile() { } func (t *commonLocalFileTestSuite) TestWritesToNewFileStartingAtNonZeroOffset() { + // TODO(mohitkyadav): Start running test again once the fix for this test is done. + t.T().SkipNow() testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) From e0e980fe3f4d821bda1cbae62dedc91d9fbb363e Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 22 Jan 2025 06:13:54 +0000 Subject: [PATCH 0165/1298] add --rename-dir-limit flag to streaming writes flagsSet. --- tools/integration_tests/local_file/local_file_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/local_file/local_file_test.go b/tools/integration_tests/local_file/local_file_test.go index 30fa61ead2..3a03884b90 100644 --- a/tools/integration_tests/local_file/local_file_test.go +++ b/tools/integration_tests/local_file/local_file_test.go @@ -129,7 +129,7 @@ func TestMain(m *testing.M) { // Set up flags to run tests on local file with streaming writes test suite. localFileWithStreamingWritesFlagsSet := [][]string{ - {"--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2"}, + {"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2"}, } currentTestSuite = new(localFileTestSuite) From c5dd52a25c17c9c2c0c7342983882da77ae7bc4d Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 22 Jan 2025 06:16:57 +0000 Subject: [PATCH 0166/1298] add license in suite_test.go. --- .../local_file/local_file_test_suite_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/integration_tests/local_file/local_file_test_suite_test.go b/tools/integration_tests/local_file/local_file_test_suite_test.go index 141c08ae18..3222e97712 100644 --- a/tools/integration_tests/local_file/local_file_test_suite_test.go +++ b/tools/integration_tests/local_file/local_file_test_suite_test.go @@ -1,3 +1,19 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Provides integration tests for create local file. + package local_file_test import ( From 7263d8bc33ac61294a2bf372001fdfd214be0fe8 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 22 Jan 2025 06:19:01 +0000 Subject: [PATCH 0167/1298] fix license. --- .../integration_tests/local_file/local_file_test_suite_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/local_file/local_file_test_suite_test.go b/tools/integration_tests/local_file/local_file_test_suite_test.go index 3222e97712..a8f206911f 100644 --- a/tools/integration_tests/local_file/local_file_test_suite_test.go +++ b/tools/integration_tests/local_file/local_file_test_suite_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From c83221f65a3736461a88113932b4d05146cb33d6 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 22 Jan 2025 10:43:03 +0000 Subject: [PATCH 0168/1298] change tests to run only local file test suite in local file package. --- .../local_file/local_file_test.go | 58 +++++++------------ .../local_file/local_file_test_suite_test.go | 4 +- 2 files changed, 22 insertions(+), 40 deletions(-) diff --git a/tools/integration_tests/local_file/local_file_test.go b/tools/integration_tests/local_file/local_file_test.go index 3a03884b90..d190fd3b73 100644 --- a/tools/integration_tests/local_file/local_file_test.go +++ b/tools/integration_tests/local_file/local_file_test.go @@ -30,7 +30,6 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/stretchr/testify/suite" ) const ( @@ -39,10 +38,9 @@ const ( ) var ( - currentTestSuite suite.TestingSuite - testDirPath string - storageClient *storage.Client - ctx context.Context + testDirPath string + storageClient *storage.Client + ctx context.Context ) //////////////////////////////////////////////////////////////////////// @@ -68,30 +66,6 @@ func NewFileShouldGetSyncedToGCSAtClose(ctx context.Context, storageClient *stor client.CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, fileName, client.FileContents, t) } -func RunTestsForFlagsSet(flagsSet [][]string, m *testing.M) (successCode int) { - if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(ctx, storageClient); err == nil { - flagsSet = append(flagsSet, hnsFlagSet) - } - - if !testing.Short() { - setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc") - } - - successCode = static_mounting.RunTests(flagsSet, m) - - if successCode == 0 { - successCode = only_dir_mounting.RunTests(flagsSet, onlyDirMounted, m) - } - - // Dynamic mounting tests create a bucket and perform tests on that bucket, - // which is not a hierarchical bucket. So we are not running those tests with - // hierarchical bucket. - if successCode == 0 && !setup.IsHierarchicalBucket(ctx, storageClient) { - successCode = dynamic_mounting.RunTests(ctx, storageClient, flagsSet, m) - } - return successCode -} - //////////////////////////////////////////////////////////////////////// // TestMain //////////////////////////////////////////////////////////////////////// @@ -122,21 +96,29 @@ func TestMain(m *testing.M) { // Set up flags to run tests on local file test suite. // Not setting config file explicitly with 'create-empty-file: false' as it is default. - localFileFlagsSet := [][]string{ + flagsSet := [][]string{ {"--implicit-dirs=true", "--rename-dir-limit=3"}, - {"--implicit-dirs=false", "--rename-dir-limit=3"}, + {"--implicit-dirs=false", "--rename-dir-limit=3"}} + + if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(ctx, storageClient); err == nil { + flagsSet = append(flagsSet, hnsFlagSet) } - // Set up flags to run tests on local file with streaming writes test suite. - localFileWithStreamingWritesFlagsSet := [][]string{ - {"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=2", "--write-max-blocks-per-file=2"}, + if !testing.Short() { + setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc") } - currentTestSuite = new(localFileTestSuite) - successCode := RunTestsForFlagsSet(localFileFlagsSet, m) + successCode := static_mounting.RunTests(flagsSet, m) + if successCode == 0 { - currentTestSuite = new(localFileWithStreaminingWritesTestSuite) - successCode = RunTestsForFlagsSet(localFileWithStreamingWritesFlagsSet, m) + successCode = only_dir_mounting.RunTests(flagsSet, onlyDirMounted, m) + } + + // Dynamic mounting tests create a bucket and perform tests on that bucket, + // which is not a hierarchical bucket. So we are not running those tests with + // hierarchical bucket. + if successCode == 0 && !setup.IsHierarchicalBucket(ctx, storageClient) { + successCode = dynamic_mounting.RunTests(ctx, storageClient, flagsSet, m) } // Clean up test directory created. diff --git a/tools/integration_tests/local_file/local_file_test_suite_test.go b/tools/integration_tests/local_file/local_file_test_suite_test.go index a8f206911f..7519acf02d 100644 --- a/tools/integration_tests/local_file/local_file_test_suite_test.go +++ b/tools/integration_tests/local_file/local_file_test_suite_test.go @@ -38,6 +38,6 @@ type localFileWithStreaminingWritesTestSuite struct { // Tests //////////////////////////////////////////////////////////////////////// -func TestCurrentTestSuite(t *testing.T) { - suite.Run(t, currentTestSuite) +func TestLocalFileTestSuite(t *testing.T) { + suite.Run(t, new(localFileTestSuite)) } From 217eb11d4322fab55d894e584310b2989a0f902d Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Thu, 23 Jan 2025 18:20:25 +0000 Subject: [PATCH 0169/1298] remove streaming write tests --- .../local_file/local_file_test_suite_test.go | 4 --- .../local_file/read_file_test.go | 19 ------------ .../local_file/stat_file_test.go | 26 ---------------- .../local_file/sym_link_test.go | 30 ------------------- 4 files changed, 79 deletions(-) diff --git a/tools/integration_tests/local_file/local_file_test_suite_test.go b/tools/integration_tests/local_file/local_file_test_suite_test.go index 7519acf02d..afcceba25a 100644 --- a/tools/integration_tests/local_file/local_file_test_suite_test.go +++ b/tools/integration_tests/local_file/local_file_test_suite_test.go @@ -30,10 +30,6 @@ type localFileTestSuite struct { commonLocalFileTestSuite } -type localFileWithStreaminingWritesTestSuite struct { - commonLocalFileTestSuite -} - //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/local_file/read_file_test.go b/tools/integration_tests/local_file/read_file_test.go index d6d5d0db21..8de245ea68 100644 --- a/tools/integration_tests/local_file/read_file_test.go +++ b/tools/integration_tests/local_file/read_file_test.go @@ -18,7 +18,6 @@ package local_file_test import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/stretchr/testify/assert" ) //////////////////////////////////////////////////////////////////////// @@ -46,21 +45,3 @@ func (t *localFileTestSuite) TestReadLocalFile() { // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, content, t.T()) } - -func (t *localFileWithStreaminingWritesTestSuite) TestReadLocalFileWithStreamingWritesFails() { - testDirPath = setup.SetupTestDirectory(testDirName) - // Create a local file. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) - - // Write FileContents twice to local file. - content := FileContents + FileContents - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) - - // Reading the local file content fails. - buf := make([]byte, len(content)) - _, err := fh.ReadAt(buf, 0) - assert.Error(t.T(), err) - // Close the file and validate that the file is created on GCS. - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, content, t.T()) -} diff --git a/tools/integration_tests/local_file/stat_file_test.go b/tools/integration_tests/local_file/stat_file_test.go index b76fd5a401..ebbdcfc8fd 100644 --- a/tools/integration_tests/local_file/stat_file_test.go +++ b/tools/integration_tests/local_file/stat_file_test.go @@ -22,7 +22,6 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/stretchr/testify/assert" ) func (t *commonLocalFileTestSuite) TestStatOnLocalFile() { @@ -82,28 +81,3 @@ func (t *localFileTestSuite) TestTruncateLocalFileToSmallerSize() { CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, FileContents[:SmallerSizeTruncate], t.T()) } - -func (t *localFileWithStreaminingWritesTestSuite) TestTruncateLocalFileToSmallerSizewithStreamingWritesFails() { - testDirPath = setup.SetupTestDirectory(testDirName) - // Create a local file. - filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) - // Writing contents to local file . - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) - - // Stat the file to validate if new contents are written. - operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t.T()) - - // Truncate the file to update file size to smaller file size. - err := os.Truncate(filePath, SmallerSizeTruncate) - // Mounts with streaming writes do not supporting truncating files to smaller. - assert.Error(t.T(), err) - - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) - - // Stat the file to validate if file is truncated correctly. - operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t.T()) - - // Close the file and validate that the file is created on GCS. - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, FileContents, t.T()) -} diff --git a/tools/integration_tests/local_file/sym_link_test.go b/tools/integration_tests/local_file/sym_link_test.go index 1d2e62051e..8d503136ad 100644 --- a/tools/integration_tests/local_file/sym_link_test.go +++ b/tools/integration_tests/local_file/sym_link_test.go @@ -24,7 +24,6 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/stretchr/testify/assert" ) func createAndVerifySymLink(t *testing.T) (filePath, symlink string, fh *os.File) { @@ -66,32 +65,3 @@ func (t *localFileTestSuite) TestReadSymlinkForDeletedLocalFile() { t.T().Fatalf("Reading symlink for deleted local file did not fail.") } } - -func (t *localFileWithStreaminingWritesTestSuite) TestCreateSymlinkForLocalFileWithStreamingWritesReadFails() { - _, symlink, fh := createAndVerifySymLink(t.T()) - // Read the file from symlink fails. - _, err := os.ReadFile(symlink) - assert.Error(t.T(), err) - - // Close the file and validate that the file is created on GCS. - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, FileContents, t.T()) -} - -func (t *localFileWithStreaminingWritesTestSuite) TestReadSymlinkForDeletedLocalFileWithStreamingWritesReadFails() { - filePath, symlink, fh := createAndVerifySymLink(t.T()) - // Read the file from symlink fails - _, err := os.ReadFile(symlink) - assert.Error(t.T(), err) - - // Remove filePath and then close the fileHandle to avoid syncing to GCS. - operations.RemoveFile(filePath) - operations.CloseFileShouldNotThrowError(fh, t.T()) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) - - // Reading symlink should fail. - _, err = os.Stat(symlink) - if err == nil || !strings.Contains(err.Error(), "no such file or directory") { - t.T().Fatalf("Reading symlink for deleted local file did not fail.") - } -} From 83ffca19cdb449f11a91dbb7c0fe72fd54f09d32 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Thu, 23 Jan 2025 18:24:19 +0000 Subject: [PATCH 0170/1298] fix test --- tools/integration_tests/local_file/sym_link_test.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tools/integration_tests/local_file/sym_link_test.go b/tools/integration_tests/local_file/sym_link_test.go index 8d503136ad..337cd11d2c 100644 --- a/tools/integration_tests/local_file/sym_link_test.go +++ b/tools/integration_tests/local_file/sym_link_test.go @@ -38,22 +38,18 @@ func createAndVerifySymLink(t *testing.T) (filePath, symlink string, fh *os.File // Read the link. operations.VerifyReadLink(filePath, symlink, t) + operations.VerifyReadFile(symlink, FileContents, t) return } func (t *localFileTestSuite) TestCreateSymlinkForLocalFile() { - _, symlink, fh := createAndVerifySymLink(t.T()) - // Read the file from symlink. - operations.VerifyReadFile(symlink, FileContents, t.T()) - // Close the file and validate that the file is created on GCS. + _, _, fh := createAndVerifySymLink(t.T()) CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, FileContents, t.T()) } func (t *localFileTestSuite) TestReadSymlinkForDeletedLocalFile() { filePath, symlink, fh := createAndVerifySymLink(t.T()) - // Read the file from symlink. - operations.VerifyReadFile(symlink, FileContents, t.T()) // Remove filePath and then close the fileHandle to avoid syncing to GCS. operations.RemoveFile(filePath) operations.CloseFileShouldNotThrowError(fh, t.T()) From 271e66e5fd7fac437871ce585f5e8125fd062c64 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Thu, 23 Jan 2025 18:28:16 +0000 Subject: [PATCH 0171/1298] fix tests --- tools/integration_tests/local_file/write_file_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/integration_tests/local_file/write_file_test.go b/tools/integration_tests/local_file/write_file_test.go index 1d782daa6c..f63b90d442 100644 --- a/tools/integration_tests/local_file/write_file_test.go +++ b/tools/integration_tests/local_file/write_file_test.go @@ -97,8 +97,6 @@ func (t *commonLocalFileTestSuite) TestMultipleOutOfOrderWritesToNewFile() { } func (t *commonLocalFileTestSuite) TestWritesToNewFileStartingAtNonZeroOffset() { - // TODO(mohitkyadav): Start running test again once the fix for this test is done. - t.T().SkipNow() testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) From a4d4f0a2630d12f98f9709b5dae418ba57c1af8a Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Fri, 24 Jan 2025 04:04:38 +0000 Subject: [PATCH 0172/1298] make common local file test suite exported. --- .../integration_tests/local_file/create_file_test.go | 10 +++++----- .../local_file/local_file_test_suite_test.go | 2 +- tools/integration_tests/local_file/read_dir_test.go | 10 +++++----- .../integration_tests/local_file/remove_dir_test.go | 4 ++-- tools/integration_tests/local_file/rename_test.go | 8 ++++---- tools/integration_tests/local_file/stat_file_test.go | 4 ++-- .../local_file/unlinked_file_test.go | 12 ++++++------ .../integration_tests/local_file/write_file_test.go | 8 ++++---- 8 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tools/integration_tests/local_file/create_file_test.go b/tools/integration_tests/local_file/create_file_test.go index a92694889d..d3574a5f4e 100644 --- a/tools/integration_tests/local_file/create_file_test.go +++ b/tools/integration_tests/local_file/create_file_test.go @@ -28,7 +28,7 @@ import ( // Boilerplate // ////////////////////////////////////////////////////////////////////// -type commonLocalFileTestSuite struct { +type CommonLocalFileTestSuite struct { suite.Suite } @@ -36,14 +36,14 @@ type commonLocalFileTestSuite struct { // Tests //////////////////////////////////////////////////////////////////////// -func (t *commonLocalFileTestSuite) TestNewFileShouldNotGetSyncedToGCSTillClose() { +func (t *CommonLocalFileTestSuite) TestNewFileShouldNotGetSyncedToGCSTillClose() { testDirPath = setup.SetupTestDirectory(testDirName) // Validate. NewFileShouldGetSyncedToGCSAtClose(ctx, storageClient, testDirPath, FileName1, t.T()) } -func (t *commonLocalFileTestSuite) TestNewFileUnderExplicitDirectoryShouldNotGetSyncedToGCSTillClose() { +func (t *CommonLocalFileTestSuite) TestNewFileUnderExplicitDirectoryShouldNotGetSyncedToGCSTillClose() { testDirPath = setup.SetupTestDirectory(testDirName) // Make explicit directory. operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t.T()) @@ -52,7 +52,7 @@ func (t *commonLocalFileTestSuite) TestNewFileUnderExplicitDirectoryShouldNotGet NewFileShouldGetSyncedToGCSAtClose(ctx, storageClient, testDirPath, path.Join(ExplicitDirName, ExplicitFileName1), t.T()) } -func (t *commonLocalFileTestSuite) TestCreateNewFileWhenSameFileExistsOnGCS() { +func (t *CommonLocalFileTestSuite) TestCreateNewFileWhenSameFileExistsOnGCS() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -69,7 +69,7 @@ func (t *commonLocalFileTestSuite) TestCreateNewFileWhenSameFileExistsOnGCS() { ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, FileName1, GCSFileContent, t.T()) } -func (t *commonLocalFileTestSuite) TestEmptyFileCreation() { +func (t *CommonLocalFileTestSuite) TestEmptyFileCreation() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) diff --git a/tools/integration_tests/local_file/local_file_test_suite_test.go b/tools/integration_tests/local_file/local_file_test_suite_test.go index afcceba25a..411f42a4bd 100644 --- a/tools/integration_tests/local_file/local_file_test_suite_test.go +++ b/tools/integration_tests/local_file/local_file_test_suite_test.go @@ -27,7 +27,7 @@ import ( // ////////////////////////////////////////////////////////////////////// type localFileTestSuite struct { - commonLocalFileTestSuite + CommonLocalFileTestSuite } //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/local_file/read_dir_test.go b/tools/integration_tests/local_file/read_dir_test.go index cf55c51a69..29fa769942 100644 --- a/tools/integration_tests/local_file/read_dir_test.go +++ b/tools/integration_tests/local_file/read_dir_test.go @@ -58,7 +58,7 @@ func readingDirNTimesShouldNotThrowError(n int, wg *sync.WaitGroup, t *testing.T // Tests // ////////////////////////////////////////////////////////////////////// -func (t *commonLocalFileTestSuite) TestReadDir() { +func (t *CommonLocalFileTestSuite) TestReadDir() { // Structure // mntDir/ // mntDir/explicit/ --- directory @@ -104,7 +104,7 @@ func (t *commonLocalFileTestSuite) TestReadDir() { GCSFileContent, t.T()) } -func (t *commonLocalFileTestSuite) TestRecursiveListingWithLocalFiles() { +func (t *CommonLocalFileTestSuite) TestRecursiveListingWithLocalFiles() { // Structure // mntDir/ // mntDir/foo1 --- file @@ -159,7 +159,7 @@ func (t *commonLocalFileTestSuite) TestRecursiveListingWithLocalFiles() { path.Join(ExplicitDirName, ExplicitFileName1), "", t.T()) } -func (t *commonLocalFileTestSuite) TestReadDirWithSameNameLocalAndGCSFile() { +func (t *CommonLocalFileTestSuite) TestReadDirWithSameNameLocalAndGCSFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file. _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -178,7 +178,7 @@ func (t *commonLocalFileTestSuite) TestReadDirWithSameNameLocalAndGCSFile() { operations.ValidateStaleNFSFileHandleError(t.T(), err) } -func (t *commonLocalFileTestSuite) TestConcurrentReadDirAndCreationOfLocalFiles_DoesNotThrowError() { +func (t *CommonLocalFileTestSuite) TestConcurrentReadDirAndCreationOfLocalFiles_DoesNotThrowError() { testDirPath = setup.SetupTestDirectory(testDirName) var wg sync.WaitGroup wg.Add(2) @@ -190,7 +190,7 @@ func (t *commonLocalFileTestSuite) TestConcurrentReadDirAndCreationOfLocalFiles_ wg.Wait() } -func (t *commonLocalFileTestSuite) TestStatLocalFileAfterRecreatingItWithSameName() { +func (t *CommonLocalFileTestSuite) TestStatLocalFileAfterRecreatingItWithSameName() { testDirPath = setup.SetupTestDirectory(testDirName) filePath := path.Join(testDirPath, FileName1) operations.CreateFile(filePath, FilePerms, t.T()) diff --git a/tools/integration_tests/local_file/remove_dir_test.go b/tools/integration_tests/local_file/remove_dir_test.go index 700fdc2c22..437f903375 100644 --- a/tools/integration_tests/local_file/remove_dir_test.go +++ b/tools/integration_tests/local_file/remove_dir_test.go @@ -27,7 +27,7 @@ import ( // Tests //////////////////////////////////////////////////////////////////////// -func (t *commonLocalFileTestSuite) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { +func (t *CommonLocalFileTestSuite) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { testDirPath = setup.SetupTestDirectory(testDirName) // Create explicit directory with one synced and one local file. operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t.T()) @@ -52,7 +52,7 @@ func (t *commonLocalFileTestSuite) TestRmDirOfDirectoryContainingGCSAndLocalFile ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, ExplicitDirName, t.T()) } -func (t *commonLocalFileTestSuite) TestRmDirOfDirectoryContainingOnlyLocalFiles() { +func (t *CommonLocalFileTestSuite) TestRmDirOfDirectoryContainingOnlyLocalFiles() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a directory with two local files. operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t.T()) diff --git a/tools/integration_tests/local_file/rename_test.go b/tools/integration_tests/local_file/rename_test.go index 59715f69be..e4ab82619c 100644 --- a/tools/integration_tests/local_file/rename_test.go +++ b/tools/integration_tests/local_file/rename_test.go @@ -41,7 +41,7 @@ func verifyRenameOperationNotSupported(err error, t *testing.T) { // Tests //////////////////////////////////////////////////////////////////////// -func (t *commonLocalFileTestSuite) TestRenameOfLocalFileFails() { +func (t *CommonLocalFileTestSuite) TestRenameOfLocalFileFails() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file with some content. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -61,7 +61,7 @@ func (t *commonLocalFileTestSuite) TestRenameOfLocalFileFails() { FileName1, FileContents+FileContents, t.T()) } -func (t *commonLocalFileTestSuite) TestRenameOfDirectoryWithLocalFileFails() { +func (t *CommonLocalFileTestSuite) TestRenameOfDirectoryWithLocalFileFails() { testDirPath = setup.SetupTestDirectory(testDirName) //Create directory with 1 synced and 1 local file. operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t.T()) @@ -88,7 +88,7 @@ func (t *commonLocalFileTestSuite) TestRenameOfDirectoryWithLocalFileFails() { path.Join(ExplicitDirName, FileName2), FileContents+FileContents, t.T()) } -func (t *commonLocalFileTestSuite) TestRenameOfLocalFileSucceedsAfterSync() { +func (t *CommonLocalFileTestSuite) TestRenameOfLocalFileSucceedsAfterSync() { t.TestRenameOfLocalFileFails() // Attempt to Rename synced file. @@ -105,7 +105,7 @@ func (t *commonLocalFileTestSuite) TestRenameOfLocalFileSucceedsAfterSync() { ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } -func (t *commonLocalFileTestSuite) TestRenameOfDirectoryWithLocalFileSucceedsAfterSync() { +func (t *CommonLocalFileTestSuite) TestRenameOfDirectoryWithLocalFileSucceedsAfterSync() { t.TestRenameOfDirectoryWithLocalFileFails() // Attempt to rename directory again after sync. diff --git a/tools/integration_tests/local_file/stat_file_test.go b/tools/integration_tests/local_file/stat_file_test.go index ebbdcfc8fd..48f65405b7 100644 --- a/tools/integration_tests/local_file/stat_file_test.go +++ b/tools/integration_tests/local_file/stat_file_test.go @@ -24,7 +24,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -func (t *commonLocalFileTestSuite) TestStatOnLocalFile() { +func (t *CommonLocalFileTestSuite) TestStatOnLocalFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -43,7 +43,7 @@ func (t *commonLocalFileTestSuite) TestStatOnLocalFile() { FileName1, FileContents, t.T()) } -func (t *commonLocalFileTestSuite) TestStatOnLocalFileWithConflictingFileNameSuffix() { +func (t *CommonLocalFileTestSuite) TestStatOnLocalFileWithConflictingFileNameSuffix() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) diff --git a/tools/integration_tests/local_file/unlinked_file_test.go b/tools/integration_tests/local_file/unlinked_file_test.go index 3eb9493513..2e5281586e 100644 --- a/tools/integration_tests/local_file/unlinked_file_test.go +++ b/tools/integration_tests/local_file/unlinked_file_test.go @@ -23,7 +23,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -func (t *commonLocalFileTestSuite) TestStatOnUnlinkedLocalFile() { +func (t *CommonLocalFileTestSuite) TestStatOnUnlinkedLocalFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -38,7 +38,7 @@ func (t *commonLocalFileTestSuite) TestStatOnUnlinkedLocalFile() { ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } -func (t *commonLocalFileTestSuite) TestReadDirContainingUnlinkedLocalFiles() { +func (t *CommonLocalFileTestSuite) TestReadDirContainingUnlinkedLocalFiles() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local files. _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -64,7 +64,7 @@ func (t *commonLocalFileTestSuite) TestReadDirContainingUnlinkedLocalFiles() { ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName3, t.T()) } -func (t *commonLocalFileTestSuite) TestWriteOnUnlinkedLocalFileSucceeds() { +func (t *CommonLocalFileTestSuite) TestWriteOnUnlinkedLocalFileSucceeds() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file. filepath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -81,7 +81,7 @@ func (t *commonLocalFileTestSuite) TestWriteOnUnlinkedLocalFileSucceeds() { ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } -func (t *commonLocalFileTestSuite) TestSyncOnUnlinkedLocalFile() { +func (t *CommonLocalFileTestSuite) TestSyncOnUnlinkedLocalFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file. filepath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -99,7 +99,7 @@ func (t *commonLocalFileTestSuite) TestSyncOnUnlinkedLocalFile() { ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } -func (t *commonLocalFileTestSuite) TestFileWithSameNameCanBeCreatedWhenDeletedBeforeSync() { +func (t *CommonLocalFileTestSuite) TestFileWithSameNameCanBeCreatedWhenDeletedBeforeSync() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -122,7 +122,7 @@ func (t *commonLocalFileTestSuite) TestFileWithSameNameCanBeCreatedWhenDeletedBe CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, newContents, t.T()) } -func (t *commonLocalFileTestSuite) TestFileWithSameNameCanBeCreatedAfterDelete() { +func (t *CommonLocalFileTestSuite) TestFileWithSameNameCanBeCreatedAfterDelete() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) diff --git a/tools/integration_tests/local_file/write_file_test.go b/tools/integration_tests/local_file/write_file_test.go index f63b90d442..076ae14e21 100644 --- a/tools/integration_tests/local_file/write_file_test.go +++ b/tools/integration_tests/local_file/write_file_test.go @@ -21,7 +21,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -func (t *commonLocalFileTestSuite) TestMultipleWritesToLocalFile() { +func (t *CommonLocalFileTestSuite) TestMultipleWritesToLocalFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -53,7 +53,7 @@ func (t *localFileTestSuite) TestRandomWritesToLocalFile() { FileName1, "stsstring3", t.T()) } -func (t *commonLocalFileTestSuite) TestOutOfOrderWritesToNewFile() { +func (t *CommonLocalFileTestSuite) TestOutOfOrderWritesToNewFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -73,7 +73,7 @@ func (t *commonLocalFileTestSuite) TestOutOfOrderWritesToNewFile() { FileName1, expectedString, t.T()) } -func (t *commonLocalFileTestSuite) TestMultipleOutOfOrderWritesToNewFile() { +func (t *CommonLocalFileTestSuite) TestMultipleOutOfOrderWritesToNewFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -96,7 +96,7 @@ func (t *commonLocalFileTestSuite) TestMultipleOutOfOrderWritesToNewFile() { FileName1, expectedString, t.T()) } -func (t *commonLocalFileTestSuite) TestWritesToNewFileStartingAtNonZeroOffset() { +func (t *CommonLocalFileTestSuite) TestWritesToNewFileStartingAtNonZeroOffset() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) From 916fd48ecab981fa044216d8f7615dbf994f476b Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Fri, 24 Jan 2025 12:50:54 +0530 Subject: [PATCH 0173/1298] Fixing reader growth logic & logging errors in MRD::Read (#2932) * Changes to ensure reader size does not stop growing in random reads scenario * Logging error in MRD::Read --- internal/gcsx/multi_range_downloader_wrapper.go | 8 +++++--- internal/gcsx/random_reader.go | 7 ++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go index 13187d27e3..9b9480fb8a 100644 --- a/internal/gcsx/multi_range_downloader_wrapper.go +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -191,15 +191,15 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []b }() if e != nil && e != io.EOF { - e = fmt.Errorf("MultiRangeDownloaderWrapper::Read: Error in Add Call: %w", e) + e = fmt.Errorf("Error in Add Call: %w", e) } }) select { case <-time.After(timeout): - err = fmt.Errorf("MultiRangeDownloaderWrapper::Read: Timeout") + err = fmt.Errorf("Timeout") case <-ctx.Done(): - err = fmt.Errorf("MultiRangeDownloaderWrapper::Read: Context Cancelled: %w", ctx.Err()) + err = fmt.Errorf("Context Cancelled: %w", ctx.Err()) case res := <-done: bytesRead = res.bytesRead err = res.err @@ -209,6 +209,8 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []b errDesc := "OK" if err != nil { errDesc = err.Error() + err = fmt.Errorf("MultiRangeDownloaderWrapper::Read: %w", err) + logger.Errorf("%v", err) } logger.Tracef("%.13v -> MultiRangeDownloader::Add (%s, [%d, %d)) (%v): %v", requestId, mrdWrapper.object.Name, startOffset, endOffset, duration, errDesc) return diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 76e4ee1bee..e05d059ac2 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -359,7 +359,12 @@ func (rr *randomReader) ReadAt( rr.closeReader() rr.reader = nil rr.cancel = nil - rr.seeks++ + if rr.start != offset { + // We should only increase the seek count if we have to discard the reader when it's + // positioned at wrong place. Discarding it if can't serve the entire request would + // result in reader size not growing for random reads scenario. + rr.seeks++ + } } if rr.reader != nil { From e4306976af737187273e784417fa60ddf5771cd6 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Fri, 24 Jan 2025 11:01:41 +0000 Subject: [PATCH 0174/1298] added stat calls in truncate tests (#2923) --- .../default_mount_local_file_test.go | 10 +++++++--- .../streaming_writes/default_mount_test.go | 2 ++ .../streaming_writes/truncate_file_test.go | 17 +++++++++++++++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go index cb19583b12..dd7d198604 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -15,6 +15,7 @@ package streaming_writes import ( + "path" "testing" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -27,15 +28,18 @@ type defaultMountLocalFile struct { } func (t *defaultMountLocalFile) SetupTest() { - t.fileName = FileName1 + setup.GenerateRandomString(5) - // Create a local file. - _, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) + t.createLocalFile() } func (t *defaultMountLocalFile) SetupSubTest() { + t.createLocalFile() +} + +func (t *defaultMountLocalFile) createLocalFile() { t.fileName = FileName1 + setup.GenerateRandomString(5) // Create a local file. _, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) + t.filePath = path.Join(testDirPath, t.fileName) } // Executes all tests that run with single streamingWrites configuration for localFiles. diff --git a/tools/integration_tests/streaming_writes/default_mount_test.go b/tools/integration_tests/streaming_writes/default_mount_test.go index e473d8f64b..25532a66cf 100644 --- a/tools/integration_tests/streaming_writes/default_mount_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_test.go @@ -24,6 +24,8 @@ import ( type defaultMountCommonTest struct { f1 *os.File fileName string + // filePath of the above file in the mounted directory. + filePath string suite.Suite } diff --git a/tools/integration_tests/streaming_writes/truncate_file_test.go b/tools/integration_tests/streaming_writes/truncate_file_test.go index c3fdb5f4b5..37e17b41f3 100644 --- a/tools/integration_tests/streaming_writes/truncate_file_test.go +++ b/tools/integration_tests/streaming_writes/truncate_file_test.go @@ -28,6 +28,8 @@ func (t *defaultMountCommonTest) TestTruncate() { assert.NoError(t.T(), err) data := make([]byte, truncateSize) + // Verify that GCSFuse is returning correct file size before the file is uploaded. + operations.VerifyStatFile(t.filePath, int64(truncateSize), FilePerms, t.T()) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, string(data[:]), t.T()) } @@ -63,12 +65,15 @@ func (t *defaultMountCommonTest) TestWriteAfterTruncate() { // Perform truncate. err := t.f1.Truncate(int64(truncateSize)) require.NoError(t.T(), err) + operations.VerifyStatFile(t.filePath, int64(truncateSize), FilePerms, t.T()) // Triggers writes after truncate. newData := []byte("hi") _, err = t.f1.WriteAt(newData, tc.offset) require.NoError(t.T(), err) + // Verify that GCSFuse is returning correct file size before the file is uploaded. + operations.VerifyStatFile(t.filePath, tc.fileSize, FilePerms, t.T()) data[tc.offset] = newData[0] data[tc.offset+1] = newData[1] // Close the file and validate that the file is created on GCS. @@ -79,13 +84,16 @@ func (t *defaultMountCommonTest) TestWriteAfterTruncate() { } func (t *defaultMountCommonTest) TestWriteAndTruncate() { - truncateSize := 20 + var truncateSize int64 = 20 operations.WriteWithoutClose(t.f1, FileContents, t.T()) + operations.VerifyStatFile(t.filePath, int64(len(FileContents)), FilePerms, t.T()) - err := t.f1.Truncate(int64(truncateSize)) + err := t.f1.Truncate(truncateSize) require.NoError(t.T(), err) data := make([]byte, 10) + // Verify that GCSFuse is returning correct file size before the file is uploaded. + operations.VerifyStatFile(t.filePath, truncateSize, FilePerms, t.T()) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents+string(data[:]), t.T()) } @@ -95,11 +103,14 @@ func (t *defaultMountCommonTest) TestWriteTruncateWrite() { // Write operations.WriteWithoutClose(t.f1, FileContents, t.T()) + operations.VerifyStatFile(t.filePath, int64(len(FileContents)), FilePerms, t.T()) // Perform truncate err := t.f1.Truncate(int64(truncateSize)) require.NoError(t.T(), err) + operations.VerifyStatFile(t.filePath, int64(truncateSize), FilePerms, t.T()) // Write operations.WriteWithoutClose(t.f1, FileContents, t.T()) + operations.VerifyStatFile(t.filePath, int64(truncateSize), FilePerms, t.T()) data := make([]byte, 10) // Close the file and validate that the file is created on GCS. @@ -109,9 +120,11 @@ func (t *defaultMountCommonTest) TestWriteTruncateWrite() { func (t *defaultMountCommonTest) TestTruncateToLowerSizeAfterWrite() { // Write operations.WriteWithoutClose(t.f1, FileContents+FileContents, t.T()) + operations.VerifyStatFile(t.filePath, int64(2*len(FileContents)), FilePerms, t.T()) // Perform truncate err := t.f1.Truncate(int64(5)) // Truncating to lower size after writes are not allowed. require.Error(t.T(), err) + operations.VerifyStatFile(t.filePath, int64(2*len(FileContents)), FilePerms, t.T()) } From ae69821334993fabf3b26f6b79e6fabaecc07adc Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 24 Jan 2025 21:01:48 +0530 Subject: [PATCH 0175/1298] Fix "Already Exist" error code for folder creation (#2935) * fix already exist error for folder creation * lint fix * review comment * review comment * small fix * skip test for flat bucket * run for flat bucket as well --- internal/fs/wrappers/error_mapping.go | 2 ++ internal/fs/wrappers/error_mapping_test.go | 9 +++++++ .../infinite_negative_stat_cache_test.go | 25 +++++++++++++++++++ .../negative_stat_cache/setup_test.go | 15 ++++++++--- .../util/client/control_client.go | 15 +++++++++++ 5 files changed, 63 insertions(+), 3 deletions(-) diff --git a/internal/fs/wrappers/error_mapping.go b/internal/fs/wrappers/error_mapping.go index a68724efd7..52ae1c692c 100644 --- a/internal/fs/wrappers/error_mapping.go +++ b/internal/fs/wrappers/error_mapping.go @@ -82,6 +82,8 @@ func errno(err error, preconditionErrCfg bool) error { switch apiErr.GRPCStatus().Code() { case codes.Canceled: return syscall.EINTR + case codes.AlreadyExists: + return syscall.EEXIST case codes.PermissionDenied, codes.Unauthenticated: return syscall.EACCES case codes.NotFound: diff --git a/internal/fs/wrappers/error_mapping_test.go b/internal/fs/wrappers/error_mapping_test.go index dc012bb747..fbddbe685b 100644 --- a/internal/fs/wrappers/error_mapping_test.go +++ b/internal/fs/wrappers/error_mapping_test.go @@ -47,6 +47,15 @@ func (testSuite *ErrorMapping) TestPermissionDeniedGrpcApiError() { assert.Equal(testSuite.T(), syscall.EACCES, fsErr) } +func (testSuite *ErrorMapping) TestAlreadyExistGrpcApiError() { + statusErr := status.New(codes.AlreadyExists, "already exist") + apiError, _ := apierror.FromError(statusErr.Err()) + + fsErr := errno(apiError, testSuite.preconditionErrCfg) + + assert.Equal(testSuite.T(), syscall.EEXIST, fsErr) +} + func (testSuite *ErrorMapping) TestNotFoundGrpcApiError() { statusErr := status.New(codes.NotFound, "Not found") apiError, _ := apierror.FromError(statusErr.Err()) diff --git a/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go index 4072e48b2f..14042e4062 100644 --- a/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go +++ b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go @@ -18,6 +18,7 @@ import ( "log" "os" "path" + "syscall" "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -67,6 +68,30 @@ func (s *infiniteNegativeStatCacheTest) TestInfiniteNegativeStatCache(t *testing assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") } +func (s *infiniteNegativeStatCacheTest) TestInfiniteNegativeStatCacheForAlreadyExistFolder(t *testing.T) { + targetDir := path.Join(testDirPath, "explicit_dir") + // Create test directory + operations.CreateDirectory(targetDir, t) + dir := path.Join(targetDir, "test_dir") + dirPathOnBucket := path.Join(testDirName, "explicit_dir", "test_dir") + + // Error should be returned as dir does not exist + _, err := os.Stat(dir) + assert.ErrorContains(t, err, "no such file or directory") + + // Adding the same name folder/dir. + if setup.IsHierarchicalBucket(ctx, storageClient) { + _, err = client.CreateFolderInBucket(ctx, storageControlClient, dirPathOnBucket) + } else { + err = client.CreateObjectOnGCS(ctx, storageClient, dirPathOnBucket+"/", "") + } + assert.NoError(t, err) + + // Error should be returned as already exist on trying to create. + err = os.Mkdir(dir, setup.DirPermission_0755) + assert.ErrorIs(t, err, syscall.EEXIST) +} + //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/negative_stat_cache/setup_test.go b/tools/integration_tests/negative_stat_cache/setup_test.go index 3ee184121f..57ba9f2aeb 100644 --- a/tools/integration_tests/negative_stat_cache/setup_test.go +++ b/tools/integration_tests/negative_stat_cache/setup_test.go @@ -22,6 +22,7 @@ import ( "testing" "cloud.google.com/go/storage" + control "cloud.google.com/go/storage/control/apiv2" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" @@ -40,9 +41,10 @@ var ( // mount directory is where our tests run. mountDir string // root directory is the directory to be unmounted. - rootDir string - storageClient *storage.Client - ctx context.Context + rootDir string + storageClient *storage.Client + storageControlClient *control.StorageControlClient + ctx context.Context ) //////////////////////////////////////////////////////////////////////// @@ -76,6 +78,13 @@ func TestMain(m *testing.M) { log.Fatalf("closeStorageClient failed: %v", err) } }() + closeStorageControlClient := client.CreateControlClientWithCancel(&ctx, &storageControlClient) + defer func() { + err := closeStorageControlClient() + if err != nil { + log.Fatalf("closeStorageControlClient failed: %v", err) + } + }() // If Mounted Directory flag is set, run tests for mounted directory. setup.RunTestsForMountedDirectoryFlag(m) diff --git a/tools/integration_tests/util/client/control_client.go b/tools/integration_tests/util/client/control_client.go index fc7f6194e9..5643ab13aa 100644 --- a/tools/integration_tests/util/client/control_client.go +++ b/tools/integration_tests/util/client/control_client.go @@ -22,12 +22,15 @@ import ( "context" "fmt" "log" + "path" "strings" "time" control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googleapis/gax-go/v2" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "google.golang.org/grpc/codes" ) @@ -102,3 +105,15 @@ func CreateManagedFoldersInBucket(ctx context.Context, client *control.StorageCo log.Fatalf("Error while creating managed folder: %v", err) } } + +func CreateFolderInBucket(ctx context.Context, client *control.StorageControlClient, folderPath string) (*controlpb.Folder, error) { + bucket, rootFolder := setup.GetBucketAndObjectBasedOnTypeOfMount("") + req := &controlpb.CreateFolderRequest{ + Parent: fmt.Sprintf(storage.FullBucketPathHNS, bucket), + FolderId: path.Join(rootFolder, folderPath), + } + + f, err := client.CreateFolder(ctx, req) + + return f, err +} From 5b69c60b531cb0bf632fdc7b38c3a40ab85a6b8f Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Mon, 27 Jan 2025 10:44:51 +0530 Subject: [PATCH 0176/1298] Stopping use of timer based cleanup logic for MRD (#2931) Stopping use of timer based cleanup logic for MRD. Have minimized code changes so that reverting change back is easy. --- internal/gcsx/multi_range_downloader_wrapper.go | 5 ++++- internal/gcsx/multi_range_downloader_wrapper_test.go | 7 ++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go index 9b9480fb8a..d58afab83a 100644 --- a/internal/gcsx/multi_range_downloader_wrapper.go +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -105,7 +105,10 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) DecrementRefCount() (err error) { mrdWrapper.refCount-- if mrdWrapper.refCount == 0 { - mrdWrapper.cleanupMultiRangeDownloader() + mrdWrapper.Wrapped.Close() + mrdWrapper.Wrapped = nil + // TODO (b/391508479): Start using cleanup function when MRD recreation is handled + // mrdWrapper.cleanupMultiRangeDownloader() } return } diff --git a/internal/gcsx/multi_range_downloader_wrapper_test.go b/internal/gcsx/multi_range_downloader_wrapper_test.go index 7102008a0f..7010bcec81 100644 --- a/internal/gcsx/multi_range_downloader_wrapper_test.go +++ b/internal/gcsx/multi_range_downloader_wrapper_test.go @@ -80,14 +80,12 @@ func (t *mrdWrapperTest) Test_IncrementRefCount_CancelCleanup() { err := t.mrdWrapper.DecrementRefCount() assert.Nil(t.T(), err) - assert.NotNil(t.T(), t.mrdWrapper.cancelCleanup) - assert.NotNil(t.T(), t.mrdWrapper.Wrapped) + assert.Nil(t.T(), t.mrdWrapper.Wrapped) t.mrdWrapper.IncrementRefCount() assert.Equal(t.T(), finalRefCount, t.mrdWrapper.refCount) assert.Nil(t.T(), t.mrdWrapper.cancelCleanup) - assert.NotNil(t.T(), t.mrdWrapper.Wrapped) } func (t *mrdWrapperTest) Test_DecrementRefCount_ParallelUpdates() { @@ -115,8 +113,7 @@ func (t *mrdWrapperTest) Test_DecrementRefCount_ParallelUpdates() { wg.Wait() assert.Equal(t.T(), finalRefCount, t.mrdWrapper.GetRefCount()) - assert.NotNil(t.T(), t.mrdWrapper.Wrapped) - assert.NotNil(t.T(), t.mrdWrapper.cancelCleanup) + assert.Nil(t.T(), t.mrdWrapper.Wrapped) // Waiting for the cleanup to be done. time.Sleep(t.mrdTimeout + time.Millisecond) assert.Nil(t.T(), t.mrdWrapper.Wrapped) From 842b15b418fb59d5ece24397937038d22f03cef7 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 27 Jan 2025 11:05:43 +0530 Subject: [PATCH 0177/1298] Fix crash when the file name being statted is just "\n" (#2933) --- internal/fs/fs.go | 16 +++++++-- .../operations/stat_file_test.go | 34 +++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 tools/integration_tests/operations/stat_file_test.go diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 0f8529ce3c..d8c3d63946 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -980,7 +980,9 @@ func (fs *fileSystem) lookUpOrCreateChildInode( parent inode.DirInode, childName string) (child inode.Inode, err error) { // First check if the requested child is a localFileInode. - child = fs.lookUpLocalFileInode(parent, childName) + if child, err = fs.lookUpLocalFileInode(parent, childName); err != nil { + return child, err + } if child != nil { return } @@ -1036,7 +1038,12 @@ func (fs *fileSystem) lookUpOrCreateChildInode( // LOCKS_EXCLUDED(parent) // UNLOCK_FUNCTION(fs.mu) // LOCK_FUNCTION(child) -func (fs *fileSystem) lookUpLocalFileInode(parent inode.DirInode, childName string) (child inode.Inode) { +func (fs *fileSystem) lookUpLocalFileInode(parent inode.DirInode, childName string) (child inode.Inode, err error) { + // If the path specified is "a/\n", the child would come as \n which is not a valid childname. + // In such cases, simply return a file-not-found. + if childName == inode.ConflictingFileNameSuffix { + return child, syscall.ENOENT + } // Trim the suffix assigned to fix conflicting names. childName = strings.TrimSuffix(childName, inode.ConflictingFileNameSuffix) fileName := inode.NewFileName(parent.Name(), childName) @@ -2009,7 +2016,10 @@ func (fs *fileSystem) Rename( } // If object to be renamed is a local file inode (un-synced), rename operation is not supported. - localChild := fs.lookUpLocalFileInode(oldParent, op.OldName) + localChild, err := fs.lookUpLocalFileInode(oldParent, op.OldName) + if err != nil { + return err + } if localChild != nil { fs.unlockAndDecrementLookupCount(localChild, 1) return fmt.Errorf("cannot rename open file %q: %w", op.OldName, syscall.ENOTSUP) diff --git a/tools/integration_tests/operations/stat_file_test.go b/tools/integration_tests/operations/stat_file_test.go new file mode 100644 index 0000000000..424812d518 --- /dev/null +++ b/tools/integration_tests/operations/stat_file_test.go @@ -0,0 +1,34 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package operations_test + +import ( + "os" + "syscall" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestStatWithTrailingNewline(t *testing.T) { + testDir := setup.SetupTestDirectory(DirForOperationTests) + + _, err := os.Stat(testDir + "/\n") + + require.Error(t, err) + assert.Equal(t, err.(*os.PathError).Err, syscall.ENOENT) +} From 988fefb51cd6d8990c6ed1fb9cba6754fd26d6ec Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Mon, 27 Jan 2025 10:24:00 +0000 Subject: [PATCH 0178/1298] Fixing the test (#2941) --- .../streaming_writes/default_mount_empty_gcs_file_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go b/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go index 758e6845e4..ed690b64e2 100644 --- a/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go @@ -41,8 +41,8 @@ func (t *defaultMountEmptyGCSFile) createEmptyGCSFile() { // Create an empty file on GCS. CreateObjectInGCSTestDir(ctx, storageClient, testDirName, t.fileName, "", t.T()) ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, "", t.T()) - filePath := path.Join(testDirPath, t.fileName) - t.f1 = operations.OpenFile(filePath, t.T()) + t.filePath = path.Join(testDirPath, t.fileName) + t.f1 = operations.OpenFile(t.filePath, t.T()) } // Executes all tests that run with single streamingWrites configuration for empty GCS Files. From 21f180801b3ac048caf706b3d55e5780f2ce7ae3 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 27 Jan 2025 17:32:12 +0530 Subject: [PATCH 0179/1298] Handle write edge case when first write is out of order write (#2929) * handle write edge case when first write is out of order write followed by write at offset 0 * handle truncate as well * review comments * refactoring * review comment --- internal/fs/inode/file.go | 42 +++--- .../fs/inode/file_streaming_writes_test.go | 132 +++++++++++++++++- internal/fs/inode/file_test.go | 49 ------- 3 files changed, 150 insertions(+), 73 deletions(-) diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 0ca2f80013..10807ede54 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -558,12 +558,10 @@ func (f *FileInode) Write( ctx context.Context, data []byte, offset int64) error { - // For empty GCS files also we will trigger bufferedWrites flow. - if f.src.Size == 0 && f.config.Write.EnableStreamingWrites { - err := f.ensureBufferedWriteHandler(ctx) - if err != nil { - return err - } + + err := f.initBufferedWriteHandlerIfEligible(ctx) + if err != nil { + return err } if f.bwh != nil { @@ -858,12 +856,10 @@ func (f *FileInode) updateInodeStateAfterSync(minObj *gcs.MinObject) { func (f *FileInode) Truncate( ctx context.Context, size int64) (err error) { - // For empty GCS files also, we will trigger bufferedWrites flow. - if f.src.Size == 0 && f.config.Write.EnableStreamingWrites { - err = f.ensureBufferedWriteHandler(ctx) - if err != nil { - return - } + + err = f.initBufferedWriteHandlerIfEligible(ctx) + if err != nil { + return err } if f.bwh != nil { @@ -893,12 +889,12 @@ func (f *FileInode) CacheEnsureContent(ctx context.Context) (err error) { } func (f *FileInode) CreateBufferedOrTempWriter(ctx context.Context) (err error) { - // Skip creating empty file when streaming writes are enabled - if f.local && f.config.Write.EnableStreamingWrites { - err = f.ensureBufferedWriteHandler(ctx) - if err != nil { - return - } + err = f.initBufferedWriteHandlerIfEligible(ctx) + if err != nil { + return err + } + // Skip creating empty file when streaming writes are enabled. + if f.bwh != nil { return } @@ -910,14 +906,20 @@ func (f *FileInode) CreateBufferedOrTempWriter(ctx context.Context) (err error) return } -func (f *FileInode) ensureBufferedWriteHandler(ctx context.Context) error { +func (f *FileInode) initBufferedWriteHandlerIfEligible(ctx context.Context) error { // bwh already initialized, do nothing. if f.bwh != nil { return nil } - var err error + tempFileInUse := f.content != nil + if f.src.Size != 0 || !f.config.Write.EnableStreamingWrites || tempFileInUse { + // bwh should not be initialized under these conditions. + return nil + } + var latestGcsObj *gcs.Object + var err error if !f.local { latestGcsObj, err = f.fetchLatestGcsObject(ctx) if err != nil { diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 28062a92a6..e395e1c2bd 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -166,13 +166,20 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF for _, tc := range testCases { t.Run(tc.name, func() { assert.True(t.T(), t.in.IsLocal()) - createTime := t.in.mtimeClock.Now() + createTime := t.clock.Now() + t.clock.AdvanceTime(15 * time.Minute) + // Sequential Write at offset 0 err := t.in.Write(t.ctx, []byte("taco"), 0) require.Nil(t.T(), err) require.NotNil(t.T(), t.in.bwh) - assert.Equal(t.T(), int64(4), t.in.bwh.WriteFileInfo().TotalSize) + // validate attributes. + attrs, err := t.in.Attributes(t.ctx) + require.Nil(t.T(), err) + assert.WithinDuration(t.T(), attrs.Mtime, createTime, 0) + assert.Equal(t.T(), uint64(4), attrs.Size) // Out of order write. + mtime := t.clock.Now() err = t.in.Write(t.ctx, []byte("hello"), tc.offset) require.Nil(t.T(), err) @@ -180,10 +187,10 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF assert.Nil(t.T(), t.in.bwh) assert.NotNil(t.T(), t.in.content) // The inode should agree about the new mtime and size. - attrs, err := t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx) require.Nil(t.T(), err) assert.Equal(t.T(), uint64(len(tc.expectedContent)), attrs.Size) - assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) + assert.WithinDuration(t.T(), attrs.Mtime, mtime, 0) // sync file and validate content gcsSynced, err := t.in.Sync(t.ctx) require.Nil(t.T(), err) @@ -196,6 +203,44 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF } } +func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { + assert.True(t.T(), t.in.IsLocal()) + createTime := t.in.mtimeClock.Now() + require.NotNil(t.T(), t.in.bwh) + // Out of order write. + err := t.in.Write(t.ctx, []byte("taco"), 6) + require.Nil(t.T(), err) + // Ensure bwh cleared and temp file created. + assert.Nil(t.T(), t.in.bwh) + assert.NotNil(t.T(), t.in.content) + // validate attributes. + attrs, err := t.in.Attributes(t.ctx) + require.Nil(t.T(), err) + assert.WithinDuration(t.T(), attrs.Mtime, createTime, 0) + assert.Equal(t.T(), uint64(10), attrs.Size) + + // Ordered write. + mtime := t.clock.Now() + err = t.in.Write(t.ctx, []byte("hello"), 0) + require.Nil(t.T(), err) + + // Ensure bwh not re-created. + assert.Nil(t.T(), t.in.bwh) + // The inode should agree about the new mtime and size. + attrs, err = t.in.Attributes(t.ctx) + require.Nil(t.T(), err) + assert.Equal(t.T(), uint64(len("hello\x00taco")), attrs.Size) + assert.WithinDuration(t.T(), attrs.Mtime, mtime, 0) + // sync file and validate content + gcsSynced, err := t.in.Sync(t.ctx) + require.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) + // Read the object's contents. + contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "hello\x00taco", string(contents)) +} + func (t *FileStreamingWritesTest) TestOutOfOrderWritesOnClobberedFileThrowsError() { err := t.in.Write(t.ctx, []byte("hi"), 0) require.Nil(t.T(), err) @@ -464,3 +509,82 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndSync() { }) } } + +func (t *FileStreamingWritesTest) TestTruncateOnFileUsingTempFileDoesNotRecreatesBWH() { + assert.True(t.T(), t.in.IsLocal()) + require.NotNil(t.T(), t.in.bwh) + // Out of order write. + err := t.in.Write(t.ctx, []byte("taco"), 2) + require.Nil(t.T(), err) + // Ensure bwh cleared and temp file created. + assert.Nil(t.T(), t.in.bwh) + assert.NotNil(t.T(), t.in.content) + + err = t.in.Truncate(t.ctx, 10) + require.Nil(t.T(), err) + + // Ensure bwh not re-created. + assert.Nil(t.T(), t.in.bwh) + // The inode should agree about the new size. + attrs, err := t.in.Attributes(t.ctx) + require.Nil(t.T(), err) + assert.Equal(t.T(), uint64(10), attrs.Size) + // sync file and validate content + gcsSynced, err := t.in.Sync(t.ctx) + require.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) + // Read the object's contents. + contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + assert.Nil(t.T(), err) + assert.Equal(t.T(), "\x00\x00taco\x00\x00\x00\x00", string(contents)) +} + +func (t *FileStreamingWritesTest) TestDeRegisterFileHandle() { + tbl := []struct { + name string + readonly bool + currentVal int32 + expectedVal int32 + isBwhNil bool + }{ + { + name: "ReadOnlyHandle", + readonly: true, + currentVal: 10, + expectedVal: 10, + isBwhNil: false, + }, + { + name: "NonZeroCurrentValueForWriteHandle", + readonly: false, + currentVal: 10, + expectedVal: 9, + isBwhNil: false, + }, + { + name: "LastWriteHandleToDeregister", + readonly: false, + currentVal: 1, + expectedVal: 0, + isBwhNil: true, + }, + } + for _, tc := range tbl { + t.Run(tc.name, func() { + t.in.config = &cfg.Config{Write: *getWriteConfig()} + t.in.writeHandleCount = tc.currentVal + err := t.in.initBufferedWriteHandlerIfEligible(t.ctx) + require.NoError(t.T(), err) + require.NotNil(t.T(), t.in.bwh) + + t.in.DeRegisterFileHandle(tc.readonly) + + assert.Equal(t.T(), tc.expectedVal, t.in.writeHandleCount) + if tc.isBwhNil { + assert.Nil(t.T(), t.in.bwh) + } else { + assert.NotNil(t.T(), t.in.bwh) + } + }) + } +} diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index fad6dd33cd..73b3308e18 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -1592,55 +1592,6 @@ func (t *FileTest) TestRegisterFileHandle() { } } -func (t *FileTest) TestDeRegisterFileHandle() { - tbl := []struct { - name string - readonly bool - currentVal int32 - expectedVal int32 - isBwhNil bool - }{ - { - name: "ReadOnlyHandle", - readonly: true, - currentVal: 10, - expectedVal: 10, - isBwhNil: false, - }, - { - name: "NonZeroCurrentValueForWriteHandle", - readonly: false, - currentVal: 10, - expectedVal: 9, - isBwhNil: false, - }, - { - name: "LastWriteHandleToDeregister", - readonly: false, - currentVal: 1, - expectedVal: 0, - isBwhNil: true, - }, - } - for _, tc := range tbl { - t.Run(tc.name, func() { - t.in.config = &cfg.Config{Write: *getWriteConfig()} - t.in.writeHandleCount = tc.currentVal - err := t.in.ensureBufferedWriteHandler(t.ctx) - require.NoError(t.T(), err) - - t.in.DeRegisterFileHandle(tc.readonly) - - assert.Equal(t.T(), tc.expectedVal, t.in.writeHandleCount) - if tc.isBwhNil { - assert.Nil(t.T(), t.in.bwh) - } else { - assert.NotNil(t.T(), t.in.bwh) - } - }) - } -} - func getWriteConfig() *cfg.WriteConfig { return &cfg.WriteConfig{ MaxBlocksPerFile: 10, From 77559f220a2ebcba556b54b289bff9ec850de7ea Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Fri, 24 Jan 2025 13:33:05 +0000 Subject: [PATCH 0180/1298] test running common file test from streaming package. --- .../common_local_file_test_suite.go | 29 +++++++++++++++++++ .../local_file/create_file_test.go | 11 +------ .../local_file/local_file_test.go | 2 +- .../local_file/local_file_test_suite_test.go | 2 +- .../local_file/read_dir_test.go | 2 +- .../local_file/read_file_test.go | 2 +- .../local_file/remove_dir_test.go | 2 +- .../local_file/rename_test.go | 2 +- .../local_file/stat_file_test.go | 2 +- .../local_file/sym_link_test.go | 2 +- .../local_file/unlinked_file_test.go | 2 +- .../local_file/write_file_test.go | 2 +- .../default_mount_local_file_test.go | 5 ++++ 13 files changed, 45 insertions(+), 20 deletions(-) create mode 100644 tools/integration_tests/local_file/common_local_file_test_suite.go diff --git a/tools/integration_tests/local_file/common_local_file_test_suite.go b/tools/integration_tests/local_file/common_local_file_test_suite.go new file mode 100644 index 0000000000..0840639e77 --- /dev/null +++ b/tools/integration_tests/local_file/common_local_file_test_suite.go @@ -0,0 +1,29 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Provides integration tests for create local file. + +package local_file + +import ( + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type CommonLocalFileTestSuite struct { + suite.Suite +} diff --git a/tools/integration_tests/local_file/create_file_test.go b/tools/integration_tests/local_file/create_file_test.go index d3574a5f4e..3ec50282fe 100644 --- a/tools/integration_tests/local_file/create_file_test.go +++ b/tools/integration_tests/local_file/create_file_test.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for create local file. -package local_file_test +package local_file import ( "path" @@ -21,17 +21,8 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/stretchr/testify/suite" ) -// ////////////////////////////////////////////////////////////////////// -// Boilerplate -// ////////////////////////////////////////////////////////////////////// - -type CommonLocalFileTestSuite struct { - suite.Suite -} - //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/local_file/local_file_test.go b/tools/integration_tests/local_file/local_file_test.go index d190fd3b73..1214ba506b 100644 --- a/tools/integration_tests/local_file/local_file_test.go +++ b/tools/integration_tests/local_file/local_file_test.go @@ -14,7 +14,7 @@ // Provides integration tests for file and directory operations. -package local_file_test +package local_file import ( "context" diff --git a/tools/integration_tests/local_file/local_file_test_suite_test.go b/tools/integration_tests/local_file/local_file_test_suite_test.go index 411f42a4bd..ebd55c987d 100644 --- a/tools/integration_tests/local_file/local_file_test_suite_test.go +++ b/tools/integration_tests/local_file/local_file_test_suite_test.go @@ -14,7 +14,7 @@ // Provides integration tests for create local file. -package local_file_test +package local_file import ( "testing" diff --git a/tools/integration_tests/local_file/read_dir_test.go b/tools/integration_tests/local_file/read_dir_test.go index 29fa769942..fe13de6f52 100644 --- a/tools/integration_tests/local_file/read_dir_test.go +++ b/tools/integration_tests/local_file/read_dir_test.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for readDir call containing local files. -package local_file_test +package local_file import ( "io/fs" diff --git a/tools/integration_tests/local_file/read_file_test.go b/tools/integration_tests/local_file/read_file_test.go index 8de245ea68..d6e2ec6046 100644 --- a/tools/integration_tests/local_file/read_file_test.go +++ b/tools/integration_tests/local_file/read_file_test.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for read operation on local files. -package local_file_test +package local_file import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" diff --git a/tools/integration_tests/local_file/remove_dir_test.go b/tools/integration_tests/local_file/remove_dir_test.go index 437f903375..35ddb0070f 100644 --- a/tools/integration_tests/local_file/remove_dir_test.go +++ b/tools/integration_tests/local_file/remove_dir_test.go @@ -13,7 +13,7 @@ // limitations under the License. // // Provides integration tests for removeDir operation on directories containing local files. -package local_file_test +package local_file import ( "path" diff --git a/tools/integration_tests/local_file/rename_test.go b/tools/integration_tests/local_file/rename_test.go index e4ab82619c..8c8cd66dad 100644 --- a/tools/integration_tests/local_file/rename_test.go +++ b/tools/integration_tests/local_file/rename_test.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for rename operation on local files. -package local_file_test +package local_file import ( "os" diff --git a/tools/integration_tests/local_file/stat_file_test.go b/tools/integration_tests/local_file/stat_file_test.go index 48f65405b7..14f4f59304 100644 --- a/tools/integration_tests/local_file/stat_file_test.go +++ b/tools/integration_tests/local_file/stat_file_test.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for stat operation on local files. -package local_file_test +package local_file import ( "os" diff --git a/tools/integration_tests/local_file/sym_link_test.go b/tools/integration_tests/local_file/sym_link_test.go index 337cd11d2c..6110ebdbdd 100644 --- a/tools/integration_tests/local_file/sym_link_test.go +++ b/tools/integration_tests/local_file/sym_link_test.go @@ -13,7 +13,7 @@ // limitations under the License. // // Provides integration tests for symlink operation on local files. -package local_file_test +package local_file import ( "os" diff --git a/tools/integration_tests/local_file/unlinked_file_test.go b/tools/integration_tests/local_file/unlinked_file_test.go index 2e5281586e..fe2df21a9a 100644 --- a/tools/integration_tests/local_file/unlinked_file_test.go +++ b/tools/integration_tests/local_file/unlinked_file_test.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for operation on unlinked local files. -package local_file_test +package local_file import ( "path" diff --git a/tools/integration_tests/local_file/write_file_test.go b/tools/integration_tests/local_file/write_file_test.go index 076ae14e21..f8045ee879 100644 --- a/tools/integration_tests/local_file/write_file_test.go +++ b/tools/integration_tests/local_file/write_file_test.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for write on local files. -package local_file_test +package local_file import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go index cb19583b12..84a009a5bc 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -17,6 +17,7 @@ package streaming_writes import ( "testing" + local_file "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/stretchr/testify/suite" @@ -42,3 +43,7 @@ func (t *defaultMountLocalFile) SetupSubTest() { func TestDefaultMountLocalFileTest(t *testing.T) { suite.Run(t, new(defaultMountLocalFile)) } + +func TestDefaultMountLocalFileTestSuite(t *testing.T) { + suite.Run(t, new(local_file.CommonLocalFileTestSuite)) +} From d8441fe6c19277668a015ac464d034ababac43a4 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 27 Jan 2025 05:01:34 +0000 Subject: [PATCH 0181/1298] Add tests for read and symlink. --- .../streaming_writes/read_file_test.go | 33 +++++++++ .../streaming_writes/symlink_file_test.go | 69 +++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 tools/integration_tests/streaming_writes/read_file_test.go create mode 100644 tools/integration_tests/streaming_writes/symlink_file_test.go diff --git a/tools/integration_tests/streaming_writes/read_file_test.go b/tools/integration_tests/streaming_writes/read_file_test.go new file mode 100644 index 0000000000..68e6393f85 --- /dev/null +++ b/tools/integration_tests/streaming_writes/read_file_test.go @@ -0,0 +1,33 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes + +import ( + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/stretchr/testify/assert" +) + +func (t *defaultMountLocalFile) TestReadLocalFileFails() { + // Write some content to local file. + t.f1.WriteAt([]byte(FileContents), 0) + + // Reading the local file content fails. + buf := make([]byte, len(FileContents)) + _, err := t.f1.ReadAt(buf, 0) + assert.Error(t.T(), err) + + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents, t.T()) +} diff --git a/tools/integration_tests/streaming_writes/symlink_file_test.go b/tools/integration_tests/streaming_writes/symlink_file_test.go new file mode 100644 index 0000000000..541cd80fff --- /dev/null +++ b/tools/integration_tests/streaming_writes/symlink_file_test.go @@ -0,0 +1,69 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes + +import ( + "os" + "path" + "strings" + + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" +) + +func (t *defaultMountLocalFile) TestCreateSymlinkForLocalFileReadFails() { + // Create Symlink. + symlink := path.Join(testDirPath, setup.GenerateRandomString(5)) + operations.CreateSymLink(t.filePath, symlink, t.T()) + t.f1.WriteAt([]byte(FileContents), 0) + + // Verify read link. + operations.VerifyReadLink(t.filePath, symlink, t.T()) + + // Reading file from symlink fails. + _, err := os.ReadFile(symlink) + assert.Error(t.T(), err) + + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, + t.fileName, FileContents, t.T()) +} + +func (t *defaultMountLocalFile) TestReadSymlinkForDeletedLocalFileFails() { + // Create Symlink. + symlink := path.Join(testDirPath, setup.GenerateRandomString(5)) + operations.CreateSymLink(t.filePath, symlink, t.T()) + t.f1.WriteAt([]byte(FileContents), 0) + + // Verify read link. + operations.VerifyReadLink(t.filePath, symlink, t.T()) + + // Read the file from symlink fails + _, err := os.ReadFile(symlink) + assert.Error(t.T(), err) + + // Remove filePath and then close the fileHandle to avoid syncing to GCS. + operations.RemoveFile(t.filePath) + operations.CloseFileShouldNotThrowError(t.f1, t.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) + + // Reading symlink should fail. + _, err = os.Stat(symlink) + if err == nil || !strings.Contains(err.Error(), "no such file or directory") { + t.T().Fatalf("Reading symlink for deleted local file did not fail.") + } +} From 8796d7b8216bb19249cfe3629b8d55a358df3040 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 27 Jan 2025 10:04:11 +0000 Subject: [PATCH 0182/1298] start running common local file test from streaming writes package. --- .../{create_file_test.go => create_file.go} | 2 + .../local_file/local_file_helper.go | 80 +++++++++++++++++++ .../local_file/local_file_suite.go | 31 +++++++ ...suite_test.go => local_file_suite_test.go} | 8 -- .../local_file/local_file_test.go | 39 +-------- .../{read_dir_test.go => read_dir.go} | 0 .../{read_file_test.go => read_file.go} | 0 .../{remove_dir_test.go => remove_dir.go} | 0 .../local_file/{rename_test.go => rename.go} | 0 .../{stat_file_test.go => stat_file.go} | 0 .../{sym_link_test.go => sym_link.go} | 0 ...unlinked_file_test.go => unlinked_file.go} | 0 .../{write_file_test.go => write_file.go} | 0 .../default_mount_local_file_test.go | 26 +++++- 14 files changed, 139 insertions(+), 47 deletions(-) rename tools/integration_tests/local_file/{create_file_test.go => create_file.go} (98%) create mode 100644 tools/integration_tests/local_file/local_file_helper.go create mode 100644 tools/integration_tests/local_file/local_file_suite.go rename tools/integration_tests/local_file/{local_file_test_suite_test.go => local_file_suite_test.go} (81%) rename tools/integration_tests/local_file/{read_dir_test.go => read_dir.go} (100%) rename tools/integration_tests/local_file/{read_file_test.go => read_file.go} (100%) rename tools/integration_tests/local_file/{remove_dir_test.go => remove_dir.go} (100%) rename tools/integration_tests/local_file/{rename_test.go => rename.go} (100%) rename tools/integration_tests/local_file/{stat_file_test.go => stat_file.go} (100%) rename tools/integration_tests/local_file/{sym_link_test.go => sym_link.go} (100%) rename tools/integration_tests/local_file/{unlinked_file_test.go => unlinked_file.go} (100%) rename tools/integration_tests/local_file/{write_file_test.go => write_file.go} (100%) diff --git a/tools/integration_tests/local_file/create_file_test.go b/tools/integration_tests/local_file/create_file.go similarity index 98% rename from tools/integration_tests/local_file/create_file_test.go rename to tools/integration_tests/local_file/create_file.go index 3ec50282fe..7301a19bc4 100644 --- a/tools/integration_tests/local_file/create_file_test.go +++ b/tools/integration_tests/local_file/create_file.go @@ -16,6 +16,7 @@ package local_file import ( + "log" "path" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -29,6 +30,7 @@ import ( func (t *CommonLocalFileTestSuite) TestNewFileShouldNotGetSyncedToGCSTillClose() { testDirPath = setup.SetupTestDirectory(testDirName) + log.Printf("testDirPath: %v", testDirPath) // Validate. NewFileShouldGetSyncedToGCSAtClose(ctx, storageClient, testDirPath, FileName1, t.T()) diff --git a/tools/integration_tests/local_file/local_file_helper.go b/tools/integration_tests/local_file/local_file_helper.go new file mode 100644 index 0000000000..9bd784d020 --- /dev/null +++ b/tools/integration_tests/local_file/local_file_helper.go @@ -0,0 +1,80 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package local_file + +import ( + "context" + "os" + "testing" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" +) + +const ( + onlyDirMounted = "OnlyDirMountLocalFiles" + testDirLocalFileTest = "LocalFileTest" +) + +var ( + testDirName string + testDirPath string + storageClient *storage.Client + ctx context.Context +) + +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func WritingToLocalFileShouldNotWriteToGCS(ctx context.Context, storageClient *storage.Client, + fh *os.File, testDirName, fileName string, t *testing.T) { + operations.WriteWithoutClose(fh, client.FileContents, t) + client.ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, fileName, t) +} + +func NewFileShouldGetSyncedToGCSAtClose(ctx context.Context, storageClient *storage.Client, + testDirPath, fileName string, t *testing.T) { + // Create a local file. + _, fh := client.CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName, t) + + // Writing contents to local file shouldn't create file on GCS. + testDirName := client.GetDirName(testDirPath) + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, fileName, t) + + // Close the file and validate if the file is created on GCS. + client.CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, fileName, client.FileContents, t) +} + +// Following setters are required to setup ctx, storageClient, testDirName for the local file tests +// getting executed from different package. +func SetCtx(otherCtx context.Context) { + if ctx == nil { + ctx = otherCtx + } +} + +func SetStorageClient(otherStorageClient *storage.Client) { + if storageClient == nil { + storageClient = otherStorageClient + } +} + +func SetTestDirName(otherTestDirName string) { + if testDirName == "" { + testDirName = otherTestDirName + } +} diff --git a/tools/integration_tests/local_file/local_file_suite.go b/tools/integration_tests/local_file/local_file_suite.go new file mode 100644 index 0000000000..d1b5e35d14 --- /dev/null +++ b/tools/integration_tests/local_file/local_file_suite.go @@ -0,0 +1,31 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package local_file + +import ( + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type CommonLocalFileTestSuite struct { + suite.Suite +} + +type localFileTestSuite struct { + CommonLocalFileTestSuite +} diff --git a/tools/integration_tests/local_file/local_file_test_suite_test.go b/tools/integration_tests/local_file/local_file_suite_test.go similarity index 81% rename from tools/integration_tests/local_file/local_file_test_suite_test.go rename to tools/integration_tests/local_file/local_file_suite_test.go index ebd55c987d..3291ea6117 100644 --- a/tools/integration_tests/local_file/local_file_test_suite_test.go +++ b/tools/integration_tests/local_file/local_file_suite_test.go @@ -22,14 +22,6 @@ import ( "github.com/stretchr/testify/suite" ) -// ////////////////////////////////////////////////////////////////////// -// Boilerplate -// ////////////////////////////////////////////////////////////////////// - -type localFileTestSuite struct { - CommonLocalFileTestSuite -} - //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/local_file/local_file_test.go b/tools/integration_tests/local_file/local_file_test.go index 1214ba506b..69965106a6 100644 --- a/tools/integration_tests/local_file/local_file_test.go +++ b/tools/integration_tests/local_file/local_file_test.go @@ -23,49 +23,13 @@ import ( "path" "testing" - "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -const ( - testDirName = "LocalFileTest" - onlyDirMounted = "OnlyDirMountLocalFiles" -) - -var ( - testDirPath string - storageClient *storage.Client - ctx context.Context -) - -//////////////////////////////////////////////////////////////////////// -// Helpers -//////////////////////////////////////////////////////////////////////// - -func WritingToLocalFileShouldNotWriteToGCS(ctx context.Context, storageClient *storage.Client, - fh *os.File, testDirName, fileName string, t *testing.T) { - operations.WriteWithoutClose(fh, client.FileContents, t) - client.ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, fileName, t) -} - -func NewFileShouldGetSyncedToGCSAtClose(ctx context.Context, storageClient *storage.Client, - testDirPath, fileName string, t *testing.T) { - // Create a local file. - _, fh := client.CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName, t) - - // Writing contents to local file shouldn't create file on GCS. - testDirName := client.GetDirName(testDirPath) - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, fileName, t) - - // Close the file and validate if the file is created on GCS. - client.CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, fileName, client.FileContents, t) -} - //////////////////////////////////////////////////////////////////////// // TestMain //////////////////////////////////////////////////////////////////////// @@ -75,6 +39,9 @@ func TestMain(m *testing.M) { setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + // set the test dir to local file test + testDirName = testDirLocalFileTest + // Create storage client before running tests. ctx = context.Background() closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) diff --git a/tools/integration_tests/local_file/read_dir_test.go b/tools/integration_tests/local_file/read_dir.go similarity index 100% rename from tools/integration_tests/local_file/read_dir_test.go rename to tools/integration_tests/local_file/read_dir.go diff --git a/tools/integration_tests/local_file/read_file_test.go b/tools/integration_tests/local_file/read_file.go similarity index 100% rename from tools/integration_tests/local_file/read_file_test.go rename to tools/integration_tests/local_file/read_file.go diff --git a/tools/integration_tests/local_file/remove_dir_test.go b/tools/integration_tests/local_file/remove_dir.go similarity index 100% rename from tools/integration_tests/local_file/remove_dir_test.go rename to tools/integration_tests/local_file/remove_dir.go diff --git a/tools/integration_tests/local_file/rename_test.go b/tools/integration_tests/local_file/rename.go similarity index 100% rename from tools/integration_tests/local_file/rename_test.go rename to tools/integration_tests/local_file/rename.go diff --git a/tools/integration_tests/local_file/stat_file_test.go b/tools/integration_tests/local_file/stat_file.go similarity index 100% rename from tools/integration_tests/local_file/stat_file_test.go rename to tools/integration_tests/local_file/stat_file.go diff --git a/tools/integration_tests/local_file/sym_link_test.go b/tools/integration_tests/local_file/sym_link.go similarity index 100% rename from tools/integration_tests/local_file/sym_link_test.go rename to tools/integration_tests/local_file/sym_link.go diff --git a/tools/integration_tests/local_file/unlinked_file_test.go b/tools/integration_tests/local_file/unlinked_file.go similarity index 100% rename from tools/integration_tests/local_file/unlinked_file_test.go rename to tools/integration_tests/local_file/unlinked_file.go diff --git a/tools/integration_tests/local_file/write_file_test.go b/tools/integration_tests/local_file/write_file.go similarity index 100% rename from tools/integration_tests/local_file/write_file_test.go rename to tools/integration_tests/local_file/write_file.go diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go index 84a009a5bc..b0e46980d8 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -17,7 +17,7 @@ package streaming_writes import ( "testing" - local_file "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/stretchr/testify/suite" @@ -27,6 +27,10 @@ type defaultMountLocalFile struct { defaultMountCommonTest } +type defaultMountCommonLocalFileTestSuite struct { + CommonLocalFileTestSuite +} + func (t *defaultMountLocalFile) SetupTest() { t.fileName = FileName1 + setup.GenerateRandomString(5) // Create a local file. @@ -39,11 +43,27 @@ func (t *defaultMountLocalFile) SetupSubTest() { _, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) } +func (t *defaultMountCommonLocalFileTestSuite) SetupSuite() { + flags := []string{"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"} + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) + testDirPath = setup.SetupTestDirectory(testDirName) +} + +func (t *defaultMountCommonLocalFileTestSuite) TearDownSuite() { + setup.UnmountGCSFuse(rootDir) +} + // Executes all tests that run with single streamingWrites configuration for localFiles. func TestDefaultMountLocalFileTest(t *testing.T) { suite.Run(t, new(defaultMountLocalFile)) } -func TestDefaultMountLocalFileTestSuite(t *testing.T) { - suite.Run(t, new(local_file.CommonLocalFileTestSuite)) +// Executes all common tests that are part of local file package with single streamingWrites configuration for localFiles. +func TestDefaultCommonLocalFileTestSuite(t *testing.T) { + // Set ctx,storageClient,testDirName before running tests in local file package. + SetCtx(ctx) + SetStorageClient(storageClient) + SetTestDirName(testDirName) + + suite.Run(t, new(defaultMountCommonLocalFileTestSuite)) } From 738fa4f8b8669c5c9f3abb4c5043bce34e8b1585 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 27 Jan 2025 10:06:01 +0000 Subject: [PATCH 0183/1298] remove logs. --- tools/integration_tests/local_file/create_file.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/integration_tests/local_file/create_file.go b/tools/integration_tests/local_file/create_file.go index 7301a19bc4..3ec50282fe 100644 --- a/tools/integration_tests/local_file/create_file.go +++ b/tools/integration_tests/local_file/create_file.go @@ -16,7 +16,6 @@ package local_file import ( - "log" "path" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -30,7 +29,6 @@ import ( func (t *CommonLocalFileTestSuite) TestNewFileShouldNotGetSyncedToGCSTillClose() { testDirPath = setup.SetupTestDirectory(testDirName) - log.Printf("testDirPath: %v", testDirPath) // Validate. NewFileShouldGetSyncedToGCSAtClose(ctx, storageClient, testDirPath, FileName1, t.T()) From 555b56ccaec56037691338f83ed21ee728137458 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 27 Jan 2025 10:28:15 +0000 Subject: [PATCH 0184/1298] fix lint --- .../streaming_writes/read_file_test.go | 5 +++-- .../streaming_writes/symlink_file_test.go | 10 ++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/tools/integration_tests/streaming_writes/read_file_test.go b/tools/integration_tests/streaming_writes/read_file_test.go index 68e6393f85..a39e2331de 100644 --- a/tools/integration_tests/streaming_writes/read_file_test.go +++ b/tools/integration_tests/streaming_writes/read_file_test.go @@ -21,11 +21,12 @@ import ( func (t *defaultMountLocalFile) TestReadLocalFileFails() { // Write some content to local file. - t.f1.WriteAt([]byte(FileContents), 0) + _, err := t.f1.WriteAt([]byte(FileContents), 0) + assert.NoError(t.T(), err) // Reading the local file content fails. buf := make([]byte, len(FileContents)) - _, err := t.f1.ReadAt(buf, 0) + _, err = t.f1.ReadAt(buf, 0) assert.Error(t.T(), err) // Close the file and validate that the file is created on GCS. diff --git a/tools/integration_tests/streaming_writes/symlink_file_test.go b/tools/integration_tests/streaming_writes/symlink_file_test.go index 541cd80fff..66cb83244f 100644 --- a/tools/integration_tests/streaming_writes/symlink_file_test.go +++ b/tools/integration_tests/streaming_writes/symlink_file_test.go @@ -29,13 +29,14 @@ func (t *defaultMountLocalFile) TestCreateSymlinkForLocalFileReadFails() { // Create Symlink. symlink := path.Join(testDirPath, setup.GenerateRandomString(5)) operations.CreateSymLink(t.filePath, symlink, t.T()) - t.f1.WriteAt([]byte(FileContents), 0) + _, err := t.f1.WriteAt([]byte(FileContents), 0) + assert.NoError(t.T(), err) // Verify read link. operations.VerifyReadLink(t.filePath, symlink, t.T()) // Reading file from symlink fails. - _, err := os.ReadFile(symlink) + _, err = os.ReadFile(symlink) assert.Error(t.T(), err) // Close the file and validate that the file is created on GCS. @@ -47,13 +48,14 @@ func (t *defaultMountLocalFile) TestReadSymlinkForDeletedLocalFileFails() { // Create Symlink. symlink := path.Join(testDirPath, setup.GenerateRandomString(5)) operations.CreateSymLink(t.filePath, symlink, t.T()) - t.f1.WriteAt([]byte(FileContents), 0) + _, err := t.f1.WriteAt([]byte(FileContents), 0) + assert.NoError(t.T(), err) // Verify read link. operations.VerifyReadLink(t.filePath, symlink, t.T()) // Read the file from symlink fails - _, err := os.ReadFile(symlink) + _, err = os.ReadFile(symlink) assert.Error(t.T(), err) // Remove filePath and then close the fileHandle to avoid syncing to GCS. From a42a76330314773e07a9b309192d2c6f5c0203f3 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 27 Jan 2025 10:51:58 +0000 Subject: [PATCH 0185/1298] move tests to common default mount suite --- tools/integration_tests/streaming_writes/read_file_test.go | 2 +- tools/integration_tests/streaming_writes/symlink_file_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/streaming_writes/read_file_test.go b/tools/integration_tests/streaming_writes/read_file_test.go index a39e2331de..406a55e5f6 100644 --- a/tools/integration_tests/streaming_writes/read_file_test.go +++ b/tools/integration_tests/streaming_writes/read_file_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" ) -func (t *defaultMountLocalFile) TestReadLocalFileFails() { +func (t *defaultMountCommonTest) TestReadLocalFileFails() { // Write some content to local file. _, err := t.f1.WriteAt([]byte(FileContents), 0) assert.NoError(t.T(), err) diff --git a/tools/integration_tests/streaming_writes/symlink_file_test.go b/tools/integration_tests/streaming_writes/symlink_file_test.go index 66cb83244f..799687ff69 100644 --- a/tools/integration_tests/streaming_writes/symlink_file_test.go +++ b/tools/integration_tests/streaming_writes/symlink_file_test.go @@ -25,7 +25,7 @@ import ( "github.com/stretchr/testify/assert" ) -func (t *defaultMountLocalFile) TestCreateSymlinkForLocalFileReadFails() { +func (t *defaultMountCommonTest) TestCreateSymlinkForLocalFileReadFails() { // Create Symlink. symlink := path.Join(testDirPath, setup.GenerateRandomString(5)) operations.CreateSymLink(t.filePath, symlink, t.T()) @@ -44,7 +44,7 @@ func (t *defaultMountLocalFile) TestCreateSymlinkForLocalFileReadFails() { t.fileName, FileContents, t.T()) } -func (t *defaultMountLocalFile) TestReadSymlinkForDeletedLocalFileFails() { +func (t *defaultMountCommonTest) TestReadSymlinkForDeletedLocalFileFails() { // Create Symlink. symlink := path.Join(testDirPath, setup.GenerateRandomString(5)) operations.CreateSymLink(t.filePath, symlink, t.T()) From de207b44819214076a2b42c555dc74b65e7274f1 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Fri, 24 Jan 2025 13:33:05 +0000 Subject: [PATCH 0186/1298] test running common file test from streaming package. --- .../streaming_writes/default_mount_local_file_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go index b0e46980d8..16e87da9f7 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -17,7 +17,11 @@ package streaming_writes import ( "testing" +<<<<<<< HEAD . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" +======= + local_file "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" +>>>>>>> 142a6205a (test running common file test from streaming package.) . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/stretchr/testify/suite" From bdd78e85add3b8da62fb07c6ca9bb4d8ca562ae6 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Fri, 24 Jan 2025 13:33:05 +0000 Subject: [PATCH 0187/1298] test running common file test from streaming package. --- .../streaming_writes/default_mount_local_file_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go index 16e87da9f7..b0e46980d8 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -17,11 +17,7 @@ package streaming_writes import ( "testing" -<<<<<<< HEAD . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" -======= - local_file "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" ->>>>>>> 142a6205a (test running common file test from streaming package.) . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/stretchr/testify/suite" From 9023d11dbb1f1859c36f988188d1bb7ff059f69c Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 27 Jan 2025 05:06:25 +0000 Subject: [PATCH 0188/1298] fix tests --- .../common_local_file_test_suite.go | 29 ------------------- .../local_file/create_file.go | 2 +- .../local_file/local_file_suite_test.go | 2 +- .../local_file/local_file_test.go | 2 +- .../integration_tests/local_file/read_dir.go | 2 +- .../integration_tests/local_file/read_file.go | 2 +- .../local_file/remove_dir.go | 2 +- tools/integration_tests/local_file/rename.go | 2 +- .../integration_tests/local_file/stat_file.go | 2 +- .../integration_tests/local_file/sym_link.go | 2 +- .../local_file/unlinked_file.go | 2 +- .../local_file/write_file.go | 2 +- 12 files changed, 11 insertions(+), 40 deletions(-) delete mode 100644 tools/integration_tests/local_file/common_local_file_test_suite.go diff --git a/tools/integration_tests/local_file/common_local_file_test_suite.go b/tools/integration_tests/local_file/common_local_file_test_suite.go deleted file mode 100644 index 0840639e77..0000000000 --- a/tools/integration_tests/local_file/common_local_file_test_suite.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Provides integration tests for create local file. - -package local_file - -import ( - "github.com/stretchr/testify/suite" -) - -// ////////////////////////////////////////////////////////////////////// -// Boilerplate -// ////////////////////////////////////////////////////////////////////// - -type CommonLocalFileTestSuite struct { - suite.Suite -} diff --git a/tools/integration_tests/local_file/create_file.go b/tools/integration_tests/local_file/create_file.go index 3ec50282fe..8f2c4be593 100644 --- a/tools/integration_tests/local_file/create_file.go +++ b/tools/integration_tests/local_file/create_file.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for create local file. -package local_file +package local_file_test import ( "path" diff --git a/tools/integration_tests/local_file/local_file_suite_test.go b/tools/integration_tests/local_file/local_file_suite_test.go index 3291ea6117..dc242ac4e4 100644 --- a/tools/integration_tests/local_file/local_file_suite_test.go +++ b/tools/integration_tests/local_file/local_file_suite_test.go @@ -14,7 +14,7 @@ // Provides integration tests for create local file. -package local_file +package local_file_test import ( "testing" diff --git a/tools/integration_tests/local_file/local_file_test.go b/tools/integration_tests/local_file/local_file_test.go index 69965106a6..4c4d8ee5e0 100644 --- a/tools/integration_tests/local_file/local_file_test.go +++ b/tools/integration_tests/local_file/local_file_test.go @@ -14,7 +14,7 @@ // Provides integration tests for file and directory operations. -package local_file +package local_file_test import ( "context" diff --git a/tools/integration_tests/local_file/read_dir.go b/tools/integration_tests/local_file/read_dir.go index fe13de6f52..29fa769942 100644 --- a/tools/integration_tests/local_file/read_dir.go +++ b/tools/integration_tests/local_file/read_dir.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for readDir call containing local files. -package local_file +package local_file_test import ( "io/fs" diff --git a/tools/integration_tests/local_file/read_file.go b/tools/integration_tests/local_file/read_file.go index d6e2ec6046..8de245ea68 100644 --- a/tools/integration_tests/local_file/read_file.go +++ b/tools/integration_tests/local_file/read_file.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for read operation on local files. -package local_file +package local_file_test import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" diff --git a/tools/integration_tests/local_file/remove_dir.go b/tools/integration_tests/local_file/remove_dir.go index 35ddb0070f..437f903375 100644 --- a/tools/integration_tests/local_file/remove_dir.go +++ b/tools/integration_tests/local_file/remove_dir.go @@ -13,7 +13,7 @@ // limitations under the License. // // Provides integration tests for removeDir operation on directories containing local files. -package local_file +package local_file_test import ( "path" diff --git a/tools/integration_tests/local_file/rename.go b/tools/integration_tests/local_file/rename.go index 8c8cd66dad..e4ab82619c 100644 --- a/tools/integration_tests/local_file/rename.go +++ b/tools/integration_tests/local_file/rename.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for rename operation on local files. -package local_file +package local_file_test import ( "os" diff --git a/tools/integration_tests/local_file/stat_file.go b/tools/integration_tests/local_file/stat_file.go index 14f4f59304..48f65405b7 100644 --- a/tools/integration_tests/local_file/stat_file.go +++ b/tools/integration_tests/local_file/stat_file.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for stat operation on local files. -package local_file +package local_file_test import ( "os" diff --git a/tools/integration_tests/local_file/sym_link.go b/tools/integration_tests/local_file/sym_link.go index 6110ebdbdd..337cd11d2c 100644 --- a/tools/integration_tests/local_file/sym_link.go +++ b/tools/integration_tests/local_file/sym_link.go @@ -13,7 +13,7 @@ // limitations under the License. // // Provides integration tests for symlink operation on local files. -package local_file +package local_file_test import ( "os" diff --git a/tools/integration_tests/local_file/unlinked_file.go b/tools/integration_tests/local_file/unlinked_file.go index fe2df21a9a..2e5281586e 100644 --- a/tools/integration_tests/local_file/unlinked_file.go +++ b/tools/integration_tests/local_file/unlinked_file.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for operation on unlinked local files. -package local_file +package local_file_test import ( "path" diff --git a/tools/integration_tests/local_file/write_file.go b/tools/integration_tests/local_file/write_file.go index f8045ee879..076ae14e21 100644 --- a/tools/integration_tests/local_file/write_file.go +++ b/tools/integration_tests/local_file/write_file.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for write on local files. -package local_file +package local_file_test import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" From c5f4564ea3929977fa5d22b216cfb34a42f38dbe Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 27 Jan 2025 05:09:24 +0000 Subject: [PATCH 0189/1298] fix tests --- tools/integration_tests/local_file/create_file.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tools/integration_tests/local_file/create_file.go b/tools/integration_tests/local_file/create_file.go index 8f2c4be593..d3574a5f4e 100644 --- a/tools/integration_tests/local_file/create_file.go +++ b/tools/integration_tests/local_file/create_file.go @@ -21,8 +21,17 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/suite" ) +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type CommonLocalFileTestSuite struct { + suite.Suite +} + //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// From f6bea9b4fbfed504548da1c419a59704d4d53611 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 27 Jan 2025 10:04:11 +0000 Subject: [PATCH 0190/1298] start running common local file test from streaming writes package. --- tools/integration_tests/local_file/create_file.go | 13 +++---------- .../local_file/local_file_suite_test.go | 2 +- .../integration_tests/local_file/local_file_test.go | 2 +- tools/integration_tests/local_file/read_dir.go | 2 +- tools/integration_tests/local_file/read_file.go | 2 +- tools/integration_tests/local_file/remove_dir.go | 2 +- tools/integration_tests/local_file/rename.go | 2 +- tools/integration_tests/local_file/stat_file.go | 2 +- tools/integration_tests/local_file/sym_link.go | 2 +- tools/integration_tests/local_file/unlinked_file.go | 2 +- tools/integration_tests/local_file/write_file.go | 2 +- 11 files changed, 13 insertions(+), 20 deletions(-) diff --git a/tools/integration_tests/local_file/create_file.go b/tools/integration_tests/local_file/create_file.go index d3574a5f4e..7301a19bc4 100644 --- a/tools/integration_tests/local_file/create_file.go +++ b/tools/integration_tests/local_file/create_file.go @@ -13,31 +13,24 @@ // limitations under the License. // Provides integration tests for create local file. -package local_file_test +package local_file import ( + "log" "path" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/stretchr/testify/suite" ) -// ////////////////////////////////////////////////////////////////////// -// Boilerplate -// ////////////////////////////////////////////////////////////////////// - -type CommonLocalFileTestSuite struct { - suite.Suite -} - //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// func (t *CommonLocalFileTestSuite) TestNewFileShouldNotGetSyncedToGCSTillClose() { testDirPath = setup.SetupTestDirectory(testDirName) + log.Printf("testDirPath: %v", testDirPath) // Validate. NewFileShouldGetSyncedToGCSAtClose(ctx, storageClient, testDirPath, FileName1, t.T()) diff --git a/tools/integration_tests/local_file/local_file_suite_test.go b/tools/integration_tests/local_file/local_file_suite_test.go index dc242ac4e4..3291ea6117 100644 --- a/tools/integration_tests/local_file/local_file_suite_test.go +++ b/tools/integration_tests/local_file/local_file_suite_test.go @@ -14,7 +14,7 @@ // Provides integration tests for create local file. -package local_file_test +package local_file import ( "testing" diff --git a/tools/integration_tests/local_file/local_file_test.go b/tools/integration_tests/local_file/local_file_test.go index 4c4d8ee5e0..69965106a6 100644 --- a/tools/integration_tests/local_file/local_file_test.go +++ b/tools/integration_tests/local_file/local_file_test.go @@ -14,7 +14,7 @@ // Provides integration tests for file and directory operations. -package local_file_test +package local_file import ( "context" diff --git a/tools/integration_tests/local_file/read_dir.go b/tools/integration_tests/local_file/read_dir.go index 29fa769942..fe13de6f52 100644 --- a/tools/integration_tests/local_file/read_dir.go +++ b/tools/integration_tests/local_file/read_dir.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for readDir call containing local files. -package local_file_test +package local_file import ( "io/fs" diff --git a/tools/integration_tests/local_file/read_file.go b/tools/integration_tests/local_file/read_file.go index 8de245ea68..d6e2ec6046 100644 --- a/tools/integration_tests/local_file/read_file.go +++ b/tools/integration_tests/local_file/read_file.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for read operation on local files. -package local_file_test +package local_file import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" diff --git a/tools/integration_tests/local_file/remove_dir.go b/tools/integration_tests/local_file/remove_dir.go index 437f903375..35ddb0070f 100644 --- a/tools/integration_tests/local_file/remove_dir.go +++ b/tools/integration_tests/local_file/remove_dir.go @@ -13,7 +13,7 @@ // limitations under the License. // // Provides integration tests for removeDir operation on directories containing local files. -package local_file_test +package local_file import ( "path" diff --git a/tools/integration_tests/local_file/rename.go b/tools/integration_tests/local_file/rename.go index e4ab82619c..8c8cd66dad 100644 --- a/tools/integration_tests/local_file/rename.go +++ b/tools/integration_tests/local_file/rename.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for rename operation on local files. -package local_file_test +package local_file import ( "os" diff --git a/tools/integration_tests/local_file/stat_file.go b/tools/integration_tests/local_file/stat_file.go index 48f65405b7..14f4f59304 100644 --- a/tools/integration_tests/local_file/stat_file.go +++ b/tools/integration_tests/local_file/stat_file.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for stat operation on local files. -package local_file_test +package local_file import ( "os" diff --git a/tools/integration_tests/local_file/sym_link.go b/tools/integration_tests/local_file/sym_link.go index 337cd11d2c..6110ebdbdd 100644 --- a/tools/integration_tests/local_file/sym_link.go +++ b/tools/integration_tests/local_file/sym_link.go @@ -13,7 +13,7 @@ // limitations under the License. // // Provides integration tests for symlink operation on local files. -package local_file_test +package local_file import ( "os" diff --git a/tools/integration_tests/local_file/unlinked_file.go b/tools/integration_tests/local_file/unlinked_file.go index 2e5281586e..fe2df21a9a 100644 --- a/tools/integration_tests/local_file/unlinked_file.go +++ b/tools/integration_tests/local_file/unlinked_file.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for operation on unlinked local files. -package local_file_test +package local_file import ( "path" diff --git a/tools/integration_tests/local_file/write_file.go b/tools/integration_tests/local_file/write_file.go index 076ae14e21..f8045ee879 100644 --- a/tools/integration_tests/local_file/write_file.go +++ b/tools/integration_tests/local_file/write_file.go @@ -13,7 +13,7 @@ // limitations under the License. // Provides integration tests for write on local files. -package local_file_test +package local_file import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" From 03c71303de8a0d1307215b95db873ed585930d52 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 27 Jan 2025 10:06:01 +0000 Subject: [PATCH 0191/1298] remove logs. --- tools/integration_tests/local_file/create_file.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/integration_tests/local_file/create_file.go b/tools/integration_tests/local_file/create_file.go index 7301a19bc4..3ec50282fe 100644 --- a/tools/integration_tests/local_file/create_file.go +++ b/tools/integration_tests/local_file/create_file.go @@ -16,7 +16,6 @@ package local_file import ( - "log" "path" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -30,7 +29,6 @@ import ( func (t *CommonLocalFileTestSuite) TestNewFileShouldNotGetSyncedToGCSTillClose() { testDirPath = setup.SetupTestDirectory(testDirName) - log.Printf("testDirPath: %v", testDirPath) // Validate. NewFileShouldGetSyncedToGCSAtClose(ctx, storageClient, testDirPath, FileName1, t.T()) From 7721c336c622bf3eb8e2379ae648cd0eb22c5bd7 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 27 Jan 2025 15:21:38 +0000 Subject: [PATCH 0192/1298] fix filepath --- .../streaming_writes/default_mount_empty_gcs_file_test.go | 4 ++-- .../streaming_writes/default_mount_local_file_test.go | 4 ++-- .../integration_tests/streaming_writes/default_mount_test.go | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go b/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go index 758e6845e4..ed690b64e2 100644 --- a/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go @@ -41,8 +41,8 @@ func (t *defaultMountEmptyGCSFile) createEmptyGCSFile() { // Create an empty file on GCS. CreateObjectInGCSTestDir(ctx, storageClient, testDirName, t.fileName, "", t.T()) ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, "", t.T()) - filePath := path.Join(testDirPath, t.fileName) - t.f1 = operations.OpenFile(filePath, t.T()) + t.filePath = path.Join(testDirPath, t.fileName) + t.f1 = operations.OpenFile(t.filePath, t.T()) } // Executes all tests that run with single streamingWrites configuration for empty GCS Files. diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go index b0e46980d8..350d1149f8 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -34,13 +34,13 @@ type defaultMountCommonLocalFileTestSuite struct { func (t *defaultMountLocalFile) SetupTest() { t.fileName = FileName1 + setup.GenerateRandomString(5) // Create a local file. - _, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) + t.filePath, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) } func (t *defaultMountLocalFile) SetupSubTest() { t.fileName = FileName1 + setup.GenerateRandomString(5) // Create a local file. - _, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) + t.filePath, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) } func (t *defaultMountCommonLocalFileTestSuite) SetupSuite() { diff --git a/tools/integration_tests/streaming_writes/default_mount_test.go b/tools/integration_tests/streaming_writes/default_mount_test.go index e473d8f64b..25532a66cf 100644 --- a/tools/integration_tests/streaming_writes/default_mount_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_test.go @@ -24,6 +24,8 @@ import ( type defaultMountCommonTest struct { f1 *os.File fileName string + // filePath of the above file in the mounted directory. + filePath string suite.Suite } From 3a939e9deb9ca74b788bb779d30fe1253d494e8b Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Tue, 28 Jan 2025 03:00:59 +0000 Subject: [PATCH 0193/1298] resolve conflicts --- .../streaming_writes/default_mount_local_file_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go index 350d1149f8..199b1e6559 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -32,12 +32,14 @@ type defaultMountCommonLocalFileTestSuite struct { } func (t *defaultMountLocalFile) SetupTest() { - t.fileName = FileName1 + setup.GenerateRandomString(5) - // Create a local file. - t.filePath, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) + t.createLocalFile() } func (t *defaultMountLocalFile) SetupSubTest() { + t.createLocalFile() +} + +func (t *defaultMountLocalFile) createLocalFile() { t.fileName = FileName1 + setup.GenerateRandomString(5) // Create a local file. t.filePath, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) From 91785244d0a707ac4cc6f30e82a4589c0b624536 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Tue, 28 Jan 2025 03:03:53 +0000 Subject: [PATCH 0194/1298] resolve conflicts --- .../streaming_writes/default_mount_local_file_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go index 199b1e6559..6aed7d23fd 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -15,6 +15,7 @@ package streaming_writes import ( + "path" "testing" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" @@ -42,7 +43,8 @@ func (t *defaultMountLocalFile) SetupSubTest() { func (t *defaultMountLocalFile) createLocalFile() { t.fileName = FileName1 + setup.GenerateRandomString(5) // Create a local file. - t.filePath, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) + _, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) + t.filePath = path.Join(testDirPath, t.fileName) } func (t *defaultMountCommonLocalFileTestSuite) SetupSuite() { From 75da9695ed25c12b6d856fb482d17bae45ebf022 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 28 Jan 2025 05:41:37 +0000 Subject: [PATCH 0195/1298] Fix TPC (#2940) * Fix TPC Don't create control-client if custom-endpoint is set. This also fixes emulator tests. * re-enable tpc e2e tests * fix unit tests * address a review comment * add execute permission to perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh * fix failing storage_handle UTs --- internal/storage/storage_handle.go | 23 +++++++++++-------- internal/storage/storage_handle_test.go | 20 +++++++--------- .../gcp_ubuntu/e2e_tests/tpc_build.sh | 0 tools/integration_tests/run_e2e_tests.sh | 18 +++++++-------- 4 files changed, 30 insertions(+), 31 deletions(-) mode change 100644 => 100755 perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index c05ca3a5f1..067560dab1 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -269,16 +269,19 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien var clientOpts []option.ClientOption // TODO: We will implement an additional check for the HTTP control client protocol once the Go SDK supports HTTP. - // TODO: Custom endpoints do not currently support gRPC. Remove this additional check once TPC(custom-endpoint) supports gRPC. - // Create storageControlClient irrespective of whether hns needs to be enabled or not. - // Because we will use storageControlClient to check layout of given bucket. - clientOpts, err = createClientOptionForGRPCClient(&clientConfig, false) - if err != nil { - return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) - } - controlClient, err = storageutil.CreateGRPCControlClient(ctx, clientOpts, &clientConfig) - if err != nil { - return nil, fmt.Errorf("could not create StorageControl Client: %w", err) + // Control-client is needed for folder APIs and for getting storage-layout of the bucket. + // GetStorageLayout API is not supported for storage-testbench and for TPC, both of which are identified by non-nil custom-endpoint. + // Change this check once TPC(custom-endpoint) supports gRPC. + // TODO: Enable creation of control-client for preprod endpoint. + if clientConfig.EnableHNS && clientConfig.CustomEndpoint == "" { + clientOpts, err = createClientOptionForGRPCClient(&clientConfig, false) + if err != nil { + return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) + } + controlClient, err = storageutil.CreateGRPCControlClient(ctx, clientOpts, &clientConfig) + if err != nil { + return nil, fmt.Errorf("could not create StorageControl Client: %w", err) + } } sh = &storageClient{ diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index 849d7ef872..aa907fae09 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -138,9 +138,8 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleHttp2EnabledAndAuthEnabl handleCreated, err := NewStorageHandle(testSuite.ctx, sc) - assert.NotNil(testSuite.T(), err) - assert.Contains(testSuite.T(), err.Error(), "no such file or directory") - assert.Nil(testSuite.T(), handleCreated) + assert.NoError(testSuite.T(), err) + assert.NotNil(testSuite.T(), handleCreated) } func (testSuite *StorageHandleTest) TestNewStorageHandleWithZeroMaxConnsPerHost() { @@ -172,9 +171,8 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithCustomEndpointAndAut handleCreated, err := NewStorageHandle(testSuite.ctx, sc) - assert.NotNil(testSuite.T(), err) - assert.Contains(testSuite.T(), err.Error(), "no such file or directory") - assert.Nil(testSuite.T(), handleCreated) + assert.NoError(testSuite.T(), err) + assert.NotNil(testSuite.T(), handleCreated) } // This will fail while fetching the token-source, since key-file doesn't exist. @@ -185,9 +183,8 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenCustomEndpointIsNilA handleCreated, err := NewStorageHandle(testSuite.ctx, sc) - assert.NotNil(testSuite.T(), err) - assert.Contains(testSuite.T(), err.Error(), "no such file or directory") - assert.Nil(testSuite.T(), handleCreated) + assert.NoError(testSuite.T(), err) + assert.NotNil(testSuite.T(), handleCreated) } func (testSuite *StorageHandleTest) TestNewStorageHandleWhenKeyFileIsEmpty() { @@ -504,9 +501,8 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustom handleCreated, err := NewStorageHandle(testSuite.ctx, sc) - assert.NotNil(testSuite.T(), err) - assert.Contains(testSuite.T(), err.Error(), "no such file or directory") - assert.Nil(testSuite.T(), handleCreated) + assert.NoError(testSuite.T(), err) + assert.NotNil(testSuite.T(), handleCreated) } func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustomEndpointAndAuthEnabled() { diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh old mode 100644 new mode 100755 diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 4f2b5de77d..e87e97ffbd 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -350,11 +350,11 @@ function main(){ run_e2e_tests_for_flat_bucket & e2e_tests_flat_bucket_pid=$! - # run_e2e_tests_for_emulator & - # e2e_tests_emulator_pid=$! + run_e2e_tests_for_emulator & + e2e_tests_emulator_pid=$! - # wait $e2e_tests_emulator_pid - # e2e_tests_emulator_status=$? + wait $e2e_tests_emulator_pid + e2e_tests_emulator_status=$? wait $e2e_tests_flat_bucket_pid e2e_tests_flat_bucket_status=$? @@ -379,11 +379,11 @@ function main(){ exit_code=1 fi - # if [ $e2e_tests_emulator_status != 0 ]; - # then - # echo "The e2e tests for emulator failed.." - # exit_code=1 - # fi + if [ $e2e_tests_emulator_status != 0 ]; + then + echo "The e2e tests for emulator failed.." + exit_code=1 + fi exit $exit_code } From b84b6ac4e921f79e6cae7a75e05f3f1dd02281bc Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Tue, 28 Jan 2025 06:25:28 +0000 Subject: [PATCH 0196/1298] fix review comments. --- .../streaming_writes/symlink_file_test.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/tools/integration_tests/streaming_writes/symlink_file_test.go b/tools/integration_tests/streaming_writes/symlink_file_test.go index 799687ff69..3ffcec37fe 100644 --- a/tools/integration_tests/streaming_writes/symlink_file_test.go +++ b/tools/integration_tests/streaming_writes/symlink_file_test.go @@ -17,7 +17,6 @@ package streaming_writes import ( "os" "path" - "strings" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" @@ -31,17 +30,15 @@ func (t *defaultMountCommonTest) TestCreateSymlinkForLocalFileReadFails() { operations.CreateSymLink(t.filePath, symlink, t.T()) _, err := t.f1.WriteAt([]byte(FileContents), 0) assert.NoError(t.T(), err) - // Verify read link. operations.VerifyReadLink(t.filePath, symlink, t.T()) // Reading file from symlink fails. _, err = os.ReadFile(symlink) - assert.Error(t.T(), err) + assert.Error(t.T(), err) // Close the file and validate that the file is created on GCS. - CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, - t.fileName, FileContents, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents, t.T()) } func (t *defaultMountCommonTest) TestReadSymlinkForDeletedLocalFileFails() { @@ -50,22 +47,18 @@ func (t *defaultMountCommonTest) TestReadSymlinkForDeletedLocalFileFails() { operations.CreateSymLink(t.filePath, symlink, t.T()) _, err := t.f1.WriteAt([]byte(FileContents), 0) assert.NoError(t.T(), err) - // Verify read link. operations.VerifyReadLink(t.filePath, symlink, t.T()) - // Read the file from symlink fails + // Reading the file from symlink fails. _, err = os.ReadFile(symlink) - assert.Error(t.T(), err) + assert.Error(t.T(), err) // Remove filePath and then close the fileHandle to avoid syncing to GCS. operations.RemoveFile(t.filePath) operations.CloseFileShouldNotThrowError(t.f1, t.T()) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) - // Reading symlink should fail. _, err = os.Stat(symlink) - if err == nil || !strings.Contains(err.Error(), "no such file or directory") { - t.T().Fatalf("Reading symlink for deleted local file did not fail.") - } + assert.Error(t.T(), err) } From cce2741f9ee84a4cb6daeb72c26eff633012eb1f Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 28 Jan 2025 12:25:48 +0530 Subject: [PATCH 0197/1298] Streaming writes config validation changes (#2947) * change config to allow for 0 blocks * format * fix test * fix test --- cfg/config.go | 4 +- cfg/params.yaml | 5 ++- cfg/rationalize.go | 4 +- cfg/rationalize_test.go | 2 +- cfg/validate.go | 8 ++-- cfg/validate_test.go | 42 +++++++++---------- cmd/config_validation_test.go | 6 +-- ...fig_due_to_invalid_global_max_blocks.yaml} | 2 +- ...nfig_due_to_zero_max_blocks_per_file.yaml} | 2 +- internal/fs/fs.go | 3 ++ 10 files changed, 42 insertions(+), 36 deletions(-) rename cmd/testdata/write_config/{invalid_write_config_due_to_small_global_max_blocks.yaml => invalid_write_config_due_to_invalid_global_max_blocks.yaml} (82%) rename cmd/testdata/write_config/{invalid_write_config_due_to_small_max_blocks_per_file.yaml => invalid_write_config_due_to_zero_max_blocks_per_file.yaml} (81%) diff --git a/cfg/config.go b/cfg/config.go index fabc4d3141..f3e55e6bf6 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -575,13 +575,13 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.IntP("write-global-max-blocks", "", -1, "Specifies the maximum number of blocks to be used by all files for streaming writes. The value should be >= 2 or -1 (for infinite blocks).") + flagSet.IntP("write-global-max-blocks", "", -1, "Specifies the maximum number of blocks to be used by all files for streaming writes. The value should be >= 0 (1 block per file is not counted towards this limit) or -1 (for infinite blocks).") if err := flagSet.MarkHidden("write-global-max-blocks"); err != nil { return err } - flagSet.IntP("write-max-blocks-per-file", "", -1, "Specifies the maximum number of blocks to be used by a single file for streaming writes. The value should be >= 2 or -1 (for infinite blocks).") + flagSet.IntP("write-max-blocks-per-file", "", -1, "Specifies the maximum number of blocks to be used by a single file for streaming writes. The value should be >= 1 or -1 (for infinite blocks).") if err := flagSet.MarkHidden("write-max-blocks-per-file"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index e43245e9dc..e2d73b904a 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -675,7 +675,8 @@ type: "int" usage: >- Specifies the maximum number of blocks to be used by all files for - streaming writes. The value should be >= 2 or -1 (for infinite blocks). + streaming writes. The value should be >= 0 (1 block per file is not counted + towards this limit) or -1 (for infinite blocks). default: -1 #TODO: revisit default value after perf testing. hide-flag: true @@ -684,7 +685,7 @@ type: "int" usage: >- Specifies the maximum number of blocks to be used by a single file for - streaming writes. The value should be >= 2 or -1 (for infinite blocks). + streaming writes. The value should be >= 1 or -1 (for infinite blocks). default: -1 #TODO: revisit default value after perf testing. hide-flag: true diff --git a/cfg/rationalize.go b/cfg/rationalize.go index fbb46e844d..5cb9a465f7 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -85,7 +85,9 @@ func resolveStreamingWriteConfig(w *WriteConfig) { } if w.MaxBlocksPerFile == -1 { - w.MaxBlocksPerFile = math.MaxInt64 + // Setting a reasonable value here because if enough heap space is not + // available, make channel results in panic. + w.MaxBlocksPerFile = math.MaxInt16 } if w.GlobalMaxBlocks < w.MaxBlocksPerFile { diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 7de1831ab5..87ebc335a3 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -332,7 +332,7 @@ func TestRationalize_WriteConfig(t *testing.T) { }, }, expectedCreateEmptyFile: false, - expectedMaxBlocksPerFile: math.MaxInt64, + expectedMaxBlocksPerFile: math.MaxInt16, expectedBlockSizeMB: 10 * 1024 * 1024, }, { diff --git a/cfg/validate.go b/cfg/validate.go index 9b29c54307..426e0218f7 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -169,11 +169,11 @@ func isValidWriteStreamingConfig(wc *WriteConfig) error { if wc.BlockSizeMb <= 0 || wc.BlockSizeMb > util.MaxMiBsInInt64 { return fmt.Errorf("invalid value of write-block-size-mb; can't be less than 1 or more than %d", util.MaxMiBsInInt64) } - if !(wc.MaxBlocksPerFile == -1 || wc.MaxBlocksPerFile >= 2) { - return fmt.Errorf("invalid value of write-max-blocks-per-file: %d; should be >=2 or -1 (for infinite)", wc.MaxBlocksPerFile) + if !(wc.MaxBlocksPerFile == -1 || wc.MaxBlocksPerFile >= 1) { + return fmt.Errorf("invalid value of write-max-blocks-per-file: %d; should be >=1 or -1 (for infinite)", wc.MaxBlocksPerFile) } - if !(wc.GlobalMaxBlocks == -1 || wc.GlobalMaxBlocks >= 2) { - return fmt.Errorf("invalid value of write-global-max-blocks: %d; should be >=2 or -1 (for infinite)", wc.GlobalMaxBlocks) + if wc.GlobalMaxBlocks < -1 { + return fmt.Errorf("invalid value of write-global-max-blocks: %d; should be >=0 or -1 (for infinite)", wc.GlobalMaxBlocks) } return nil } diff --git a/cfg/validate_test.go b/cfg/validate_test.go index f3d7ca6a9d..4a9fc4d780 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -473,20 +473,6 @@ func Test_isValidWriteStreamingConfig_ErrorScenarios(t *testing.T) { GlobalMaxBlocks: -2, MaxBlocksPerFile: -1, }}, - {"0_global_max_blocks", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - EnableStreamingWrites: true, - GlobalMaxBlocks: 0, - MaxBlocksPerFile: -1, - }}, - {"1_global_max_blocks", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - EnableStreamingWrites: true, - GlobalMaxBlocks: 1, - MaxBlocksPerFile: -1, - }}, {"-2_max_blocks_per_file", WriteConfig{ BlockSizeMb: 10, CreateEmptyFile: false, @@ -501,13 +487,6 @@ func Test_isValidWriteStreamingConfig_ErrorScenarios(t *testing.T) { GlobalMaxBlocks: 20, MaxBlocksPerFile: 0, }}, - {"1_max_blocks_per_file", WriteConfig{ - BlockSizeMb: 10, - CreateEmptyFile: false, - EnableStreamingWrites: true, - GlobalMaxBlocks: 20, - MaxBlocksPerFile: 1, - }}, } for _, tc := range testCases { @@ -557,6 +536,27 @@ func Test_isValidWriteStreamingConfig_SuccessScenarios(t *testing.T) { GlobalMaxBlocks: 40, MaxBlocksPerFile: 20, }}, + {"0_global_max_blocks", WriteConfig{ + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 0, + MaxBlocksPerFile: -1, + }}, + {"1_global_max_blocks", WriteConfig{ + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 1, + MaxBlocksPerFile: -1, + }}, + {"1_max_blocks_per_file", WriteConfig{ + BlockSizeMb: 10, + CreateEmptyFile: false, + EnableStreamingWrites: true, + GlobalMaxBlocks: 20, + MaxBlocksPerFile: 1, + }}, } for _, tc := range testCases { diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index f30ffc6026..55c01ebffd 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -194,7 +194,7 @@ func TestValidateConfigFile_WriteConfig(t *testing.T) { BlockSizeMb: 64 * util.MiB, EnableStreamingWrites: false, GlobalMaxBlocks: math.MaxInt64, - MaxBlocksPerFile: math.MaxInt64}, + MaxBlocksPerFile: math.MaxInt16}, }, }, { @@ -302,11 +302,11 @@ func TestValidateConfigFile_InvalidConfigThrowsError(t *testing.T) { }, { name: "small_global_max_blocks", - configFile: "testdata/write_config/invalid_write_config_due_to_small_global_max_blocks.yaml", + configFile: "testdata/write_config/invalid_write_config_due_to_invalid_global_max_blocks.yaml", }, { name: "small_max_blocks_per_file", - configFile: "testdata/write_config/invalid_write_config_due_to_small_max_blocks_per_file.yaml", + configFile: "testdata/write_config/invalid_write_config_due_to_zero_max_blocks_per_file.yaml", }, { name: "negative req_increase_rate", diff --git a/cmd/testdata/write_config/invalid_write_config_due_to_small_global_max_blocks.yaml b/cmd/testdata/write_config/invalid_write_config_due_to_invalid_global_max_blocks.yaml similarity index 82% rename from cmd/testdata/write_config/invalid_write_config_due_to_small_global_max_blocks.yaml rename to cmd/testdata/write_config/invalid_write_config_due_to_invalid_global_max_blocks.yaml index f468c5fe12..c9c7722387 100644 --- a/cmd/testdata/write_config/invalid_write_config_due_to_small_global_max_blocks.yaml +++ b/cmd/testdata/write_config/invalid_write_config_due_to_invalid_global_max_blocks.yaml @@ -1,6 +1,6 @@ write: create-empty-file: true enable-streaming-writes: true - global-max-blocks: 0 + global-max-blocks: -2 block-size-mb: 10 max-blocks-per-file: 2 diff --git a/cmd/testdata/write_config/invalid_write_config_due_to_small_max_blocks_per_file.yaml b/cmd/testdata/write_config/invalid_write_config_due_to_zero_max_blocks_per_file.yaml similarity index 81% rename from cmd/testdata/write_config/invalid_write_config_due_to_small_max_blocks_per_file.yaml rename to cmd/testdata/write_config/invalid_write_config_due_to_zero_max_blocks_per_file.yaml index e612c3ea8e..d6b6605ef5 100644 --- a/cmd/testdata/write_config/invalid_write_config_due_to_small_max_blocks_per_file.yaml +++ b/cmd/testdata/write_config/invalid_write_config_due_to_zero_max_blocks_per_file.yaml @@ -3,4 +3,4 @@ write: enable-streaming-writes: true global-max-blocks: 20 block-size-mb: 10 - max-blocks-per-file: 1 + max-blocks-per-file: 0 diff --git a/internal/fs/fs.go b/internal/fs/fs.go index d8c3d63946..9d2fdf5b8d 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1714,6 +1714,9 @@ func (fs *fileSystem) createLocalFile(ctx context.Context, parentID fuseops.Inod defer func() { if err != nil { + if child == nil { + return + } // fs.mu lock is already taken delete(fs.localFileInodes, child.Name()) } From cdc876d520ba627180b7a9b1aebd348d24be3f2b Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Tue, 28 Jan 2025 07:05:57 +0000 Subject: [PATCH 0198/1298] fix review comments --- .../local_file/local_file_suite_test.go | 31 ------------------- .../{local_file_test.go => setup_test.go} | 5 +++ 2 files changed, 5 insertions(+), 31 deletions(-) delete mode 100644 tools/integration_tests/local_file/local_file_suite_test.go rename tools/integration_tests/local_file/{local_file_test.go => setup_test.go} (96%) diff --git a/tools/integration_tests/local_file/local_file_suite_test.go b/tools/integration_tests/local_file/local_file_suite_test.go deleted file mode 100644 index 3291ea6117..0000000000 --- a/tools/integration_tests/local_file/local_file_suite_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Provides integration tests for create local file. - -package local_file - -import ( - "testing" - - "github.com/stretchr/testify/suite" -) - -//////////////////////////////////////////////////////////////////////// -// Tests -//////////////////////////////////////////////////////////////////////// - -func TestLocalFileTestSuite(t *testing.T) { - suite.Run(t, new(localFileTestSuite)) -} diff --git a/tools/integration_tests/local_file/local_file_test.go b/tools/integration_tests/local_file/setup_test.go similarity index 96% rename from tools/integration_tests/local_file/local_file_test.go rename to tools/integration_tests/local_file/setup_test.go index 69965106a6..8399f52eef 100644 --- a/tools/integration_tests/local_file/local_file_test.go +++ b/tools/integration_tests/local_file/setup_test.go @@ -28,6 +28,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -92,3 +93,7 @@ func TestMain(m *testing.M) { setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) os.Exit(successCode) } + +func TestLocalFileTestSuite(t *testing.T) { + suite.Run(t, new(localFileTestSuite)) +} From 25d2b989a0427804095ff02ef5ccd29caa9c80ae Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 29 Jan 2025 11:50:44 +0530 Subject: [PATCH 0199/1298] Finalize object for any error in buffered writes flow (#2948) * finalize object for any error in bw flow * lint fix * lint fix * review comments --- .../bufferedwrites/buffered_write_handler.go | 63 ++++++---- .../buffered_write_handler_test.go | 115 +++++++++++------- internal/fs/inode/file.go | 6 +- .../fs/inode/file_streaming_writes_test.go | 78 ++++++++++++ 4 files changed, 193 insertions(+), 69 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 28a9b8e851..f496ecbd88 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -29,9 +29,37 @@ import ( // Note: All the write operations take inode lock in fs.go, hence we don't need any locks here // as we will get write operations serially. -// BufferedWriteHandler is responsible for filling up the buffers with the data +type BufferedWriteHandler interface { + // Write writes the given data to the buffer. It writes to an existing buffer if + // the capacity is available otherwise writes to a new buffer. + Write(data []byte, offset int64) (err error) + + // Sync uploads all the pending full buffers to GCS. + Sync() (err error) + + // Flush finalizes the upload. + Flush() (*gcs.MinObject, error) + + // SetMtime stores the mtime with the bufferedWriteHandler. + SetMtime(mtime time.Time) + + // Truncate allows truncating the file to a larger size. + Truncate(size int64) error + + // WriteFileInfo returns the file info i.e, how much data has been buffered so far + // and the mtime. + WriteFileInfo() WriteFileInfo + + // Destroy destroys the upload handler and then free up the buffers. + Destroy() error + + // Unlink cancels the ongoing upload and free up the buffers. + Unlink() +} + +// bufferedWriteHandlerImpl is responsible for filling up the buffers with the data // as it receives and handing over to uploadHandler which uploads to GCS. -type BufferedWriteHandler struct { +type bufferedWriteHandlerImpl struct { current block.Block blockPool *block.BlockPool uploadHandler *UploadHandler @@ -70,13 +98,13 @@ type CreateBWHandlerRequest struct { } // NewBWHandler creates the bufferedWriteHandler struct. -func NewBWHandler(req *CreateBWHandlerRequest) (bwh *BufferedWriteHandler, err error) { +func NewBWHandler(req *CreateBWHandlerRequest) (bwh BufferedWriteHandler, err error) { bp, err := block.NewBlockPool(req.BlockSize, req.MaxBlocksPerFile, req.GlobalMaxBlocksSem) if err != nil { return } - bwh = &BufferedWriteHandler{ + bwh = &bufferedWriteHandlerImpl{ current: nil, blockPool: bp, uploadHandler: newUploadHandler(&CreateUploadHandlerRequest{ @@ -95,9 +123,7 @@ func NewBWHandler(req *CreateBWHandlerRequest) (bwh *BufferedWriteHandler, err e return } -// Write writes the given data to the buffer. It writes to an existing buffer if -// the capacity is available otherwise writes to a new buffer. -func (wh *BufferedWriteHandler) Write(data []byte, offset int64) (err error) { +func (wh *bufferedWriteHandlerImpl) Write(data []byte, offset int64) (err error) { // Fail early if the uploadHandler has failed. select { case <-wh.uploadHandler.SignalUploadFailure(): @@ -123,7 +149,7 @@ func (wh *BufferedWriteHandler) Write(data []byte, offset int64) (err error) { return wh.appendBuffer(data) } -func (wh *BufferedWriteHandler) appendBuffer(data []byte) (err error) { +func (wh *bufferedWriteHandlerImpl) appendBuffer(data []byte) (err error) { dataWritten := 0 for dataWritten < len(data) { if wh.current == nil { @@ -156,8 +182,7 @@ func (wh *BufferedWriteHandler) appendBuffer(data []byte) (err error) { return } -// Sync uploads all the pending full buffers to GCS. -func (wh *BufferedWriteHandler) Sync() (err error) { +func (wh *bufferedWriteHandlerImpl) Sync() (err error) { // Upload all the pending buffers and release the buffers. wh.uploadHandler.AwaitBlocksUpload() err = wh.blockPool.ClearFreeBlockChannel() @@ -175,7 +200,7 @@ func (wh *BufferedWriteHandler) Sync() (err error) { } // Flush finalizes the upload. -func (wh *BufferedWriteHandler) Flush() (*gcs.MinObject, error) { +func (wh *bufferedWriteHandlerImpl) Flush() (*gcs.MinObject, error) { // In case it is a truncated file, upload empty blocks as required. err := wh.writeDataForTruncatedSize() if err != nil { @@ -212,12 +237,11 @@ func (wh *BufferedWriteHandler) Flush() (*gcs.MinObject, error) { return obj, nil } -// SetMtime stores the mtime with the bufferedWriteHandler. -func (wh *BufferedWriteHandler) SetMtime(mtime time.Time) { +func (wh *bufferedWriteHandlerImpl) SetMtime(mtime time.Time) { wh.mtime = mtime } -func (wh *BufferedWriteHandler) Truncate(size int64) error { +func (wh *bufferedWriteHandlerImpl) Truncate(size int64) error { if size < wh.totalSize { return fmt.Errorf("cannot truncate to lesser size when upload is in progress") } @@ -226,22 +250,19 @@ func (wh *BufferedWriteHandler) Truncate(size int64) error { return nil } -// WriteFileInfo returns the file info i.e, how much data has been buffered so far -// and the mtime. -func (wh *BufferedWriteHandler) WriteFileInfo() WriteFileInfo { +func (wh *bufferedWriteHandlerImpl) WriteFileInfo() WriteFileInfo { return WriteFileInfo{ TotalSize: int64(math.Max(float64(wh.totalSize), float64(wh.truncatedSize))), Mtime: wh.mtime, } } -func (wh *BufferedWriteHandler) Destroy() error { - // Destroy the upload handler and then free up the buffers. +func (wh *bufferedWriteHandlerImpl) Destroy() error { wh.uploadHandler.Destroy() return wh.blockPool.ClearFreeBlockChannel() } -func (wh *BufferedWriteHandler) writeDataForTruncatedSize() error { +func (wh *bufferedWriteHandlerImpl) writeDataForTruncatedSize() error { // If totalSize is greater than truncatedSize, that means user has // written more data than they actually truncated in the beginning. if wh.totalSize >= wh.truncatedSize { @@ -263,7 +284,7 @@ func (wh *BufferedWriteHandler) writeDataForTruncatedSize() error { return nil } -func (wh *BufferedWriteHandler) Unlink() { +func (wh *bufferedWriteHandlerImpl) Unlink() { wh.uploadHandler.CancelUpload() err := wh.blockPool.ClearFreeBlockChannel() if err != nil { diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 7457427ff7..d7dc7b0446 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -32,7 +32,7 @@ import ( const chunkTransferTimeoutSecs int64 = 10 type BufferedWriteTest struct { - bwh *BufferedWriteHandler + bwh BufferedWriteHandler suite.Suite } @@ -69,7 +69,8 @@ func (testSuite *BufferedWriteTest) TestWrite() { require.Nil(testSuite.T(), err) fileInfo := testSuite.bwh.WriteFileInfo() - assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Equal(testSuite.T(), bwhImpl.mtime, fileInfo.Mtime) assert.Equal(testSuite.T(), int64(2), fileInfo.TotalSize) } @@ -78,7 +79,8 @@ func (testSuite *BufferedWriteTest) TestWriteWithEmptyBuffer() { require.Nil(testSuite.T(), err) fileInfo := testSuite.bwh.WriteFileInfo() - assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Equal(testSuite.T(), bwhImpl.mtime, fileInfo.Mtime) assert.Equal(testSuite.T(), int64(0), fileInfo.TotalSize) } @@ -90,7 +92,8 @@ func (testSuite *BufferedWriteTest) TestWriteEqualToBlockSize() { require.Nil(testSuite.T(), err) fileInfo := testSuite.bwh.WriteFileInfo() - assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Equal(testSuite.T(), bwhImpl.mtime, fileInfo.Mtime) assert.Equal(testSuite.T(), int64(size), fileInfo.TotalSize) } @@ -102,7 +105,8 @@ func (testSuite *BufferedWriteTest) TestWriteDataSizeGreaterThanBlockSize() { require.Nil(testSuite.T(), err) fileInfo := testSuite.bwh.WriteFileInfo() - assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Equal(testSuite.T(), bwhImpl.mtime, fileInfo.Mtime) assert.Equal(testSuite.T(), int64(size), fileInfo.TotalSize) } @@ -116,7 +120,8 @@ func (testSuite *BufferedWriteTest) TestWriteWhenNextOffsetIsGreaterThanExpected require.NotNil(testSuite.T(), err) require.Equal(testSuite.T(), err, ErrOutOfOrderWrite) fileInfo := testSuite.bwh.WriteFileInfo() - assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Equal(testSuite.T(), bwhImpl.mtime, fileInfo.Mtime) assert.Equal(testSuite.T(), int64(2), fileInfo.TotalSize) } @@ -130,7 +135,8 @@ func (testSuite *BufferedWriteTest) TestWriteWhenNextOffsetIsLessThanExpected() require.NotNil(testSuite.T(), err) require.Equal(testSuite.T(), err, ErrOutOfOrderWrite) fileInfo := testSuite.bwh.WriteFileInfo() - assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Equal(testSuite.T(), bwhImpl.mtime, fileInfo.Mtime) assert.Equal(testSuite.T(), int64(5), fileInfo.TotalSize) } @@ -142,7 +148,8 @@ func (testSuite *BufferedWriteTest) TestMultipleWrites() { require.Nil(testSuite.T(), err) fileInfo := testSuite.bwh.WriteFileInfo() - assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Equal(testSuite.T(), bwhImpl.mtime, fileInfo.Mtime) assert.Equal(testSuite.T(), int64(13), fileInfo.TotalSize) } @@ -150,11 +157,12 @@ func (testSuite *BufferedWriteTest) TestWriteWithSignalUploadFailureInBetween() err := testSuite.bwh.Write([]byte("hello"), 0) require.Nil(testSuite.T(), err) fileInfo := testSuite.bwh.WriteFileInfo() - assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Equal(testSuite.T(), bwhImpl.mtime, fileInfo.Mtime) assert.Equal(testSuite.T(), int64(5), fileInfo.TotalSize) // Close the channel to simulate failure in uploader. - close(testSuite.bwh.uploadHandler.SignalUploadFailure()) + close(bwhImpl.uploadHandler.SignalUploadFailure()) err = testSuite.bwh.Write([]byte("hello"), 5) require.Error(testSuite.T(), err) @@ -165,32 +173,34 @@ func (testSuite *BufferedWriteTest) TestWriteAtTruncatedOffset() { // Truncate err := testSuite.bwh.Truncate(2) require.NoError(testSuite.T(), err) - require.Equal(testSuite.T(), int64(2), testSuite.bwh.truncatedSize) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + require.Equal(testSuite.T(), int64(2), bwhImpl.truncatedSize) // Write at offset = truncatedSize err = testSuite.bwh.Write([]byte("hello"), 2) require.Nil(testSuite.T(), err) fileInfo := testSuite.bwh.WriteFileInfo() - assert.Equal(testSuite.T(), testSuite.bwh.mtime, fileInfo.Mtime) + assert.Equal(testSuite.T(), bwhImpl.mtime, fileInfo.Mtime) assert.Equal(testSuite.T(), int64(7), fileInfo.TotalSize) } func (testSuite *BufferedWriteTest) TestWriteAfterTruncateAtCurrentSize() { err := testSuite.bwh.Write([]byte("hello"), 0) require.Nil(testSuite.T(), err) - require.Equal(testSuite.T(), int64(5), testSuite.bwh.totalSize) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + require.Equal(testSuite.T(), int64(5), bwhImpl.totalSize) // Truncate err = testSuite.bwh.Truncate(20) require.NoError(testSuite.T(), err) - require.Equal(testSuite.T(), int64(20), testSuite.bwh.truncatedSize) + require.Equal(testSuite.T(), int64(20), bwhImpl.truncatedSize) require.Equal(testSuite.T(), int64(20), testSuite.bwh.WriteFileInfo().TotalSize) // Write at offset=bwh.totalSize err = testSuite.bwh.Write([]byte("abcde"), 5) require.Nil(testSuite.T(), err) - assert.Equal(testSuite.T(), int64(10), testSuite.bwh.totalSize) + assert.Equal(testSuite.T(), int64(10), bwhImpl.totalSize) assert.Equal(testSuite.T(), int64(20), testSuite.bwh.WriteFileInfo().TotalSize) } @@ -201,16 +211,18 @@ func (testSuite *BufferedWriteTest) TestFlushWithNonNilCurrentBlock() { obj, err := testSuite.bwh.Flush() require.NoError(testSuite.T(), err) - assert.Equal(testSuite.T(), nil, testSuite.bwh.current) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Equal(testSuite.T(), nil, bwhImpl.current) // Validate object. assert.NotNil(testSuite.T(), obj) assert.Equal(testSuite.T(), uint64(2), obj.Size) // Validate that all blocks have been freed up. - assert.Equal(testSuite.T(), 0, len(testSuite.bwh.blockPool.FreeBlocksChannel())) + assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) } func (testSuite *BufferedWriteTest) TestFlushWithNilCurrentBlock() { - require.Nil(testSuite.T(), testSuite.bwh.current) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + require.Nil(testSuite.T(), bwhImpl.current) obj, err := testSuite.bwh.Flush() @@ -223,9 +235,10 @@ func (testSuite *BufferedWriteTest) TestFlushWithNilCurrentBlock() { func (testSuite *BufferedWriteTest) TestFlushWithSignalUploadFailureDuringWrite() { err := testSuite.bwh.Write([]byte("hi"), 0) require.Nil(testSuite.T(), err) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) // Close the channel to simulate failure in uploader. - close(testSuite.bwh.uploadHandler.SignalUploadFailure()) + close(bwhImpl.uploadHandler.SignalUploadFailure()) obj, err := testSuite.bwh.Flush() require.Error(testSuite.T(), err) @@ -241,7 +254,8 @@ func (testSuite *BufferedWriteTest) TestFlushWithMultiBlockWritesAndSignalUpload // Upload and sync 5 blocks. testSuite.TestSync5InProgressBlocks() // Close the channel to simulate failure in uploader. - close(testSuite.bwh.uploadHandler.SignalUploadFailure()) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + close(bwhImpl.uploadHandler.SignalUploadFailure()) // Write 5 more blocks. for i := 0; i < 5; i++ { err := testSuite.bwh.Write(buffer, int64(blockSize*(i+5))) @@ -271,8 +285,9 @@ func (testSuite *BufferedWriteTest) TestSync5InProgressBlocks() { err = testSuite.bwh.Sync() assert.NoError(testSuite.T(), err) - assert.Equal(testSuite.T(), 0, len(testSuite.bwh.uploadHandler.uploadCh)) - assert.Equal(testSuite.T(), 0, len(testSuite.bwh.blockPool.FreeBlocksChannel())) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) + assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) } func (testSuite *BufferedWriteTest) TestSyncBlocksWithError() { @@ -284,7 +299,8 @@ func (testSuite *BufferedWriteTest) TestSyncBlocksWithError() { require.Nil(testSuite.T(), err) } // Close the channel to simulate failure in uploader. - close(testSuite.bwh.uploadHandler.SignalUploadFailure()) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + close(bwhImpl.uploadHandler.SignalUploadFailure()) err = testSuite.bwh.Sync() @@ -293,28 +309,31 @@ func (testSuite *BufferedWriteTest) TestSyncBlocksWithError() { } func (testSuite *BufferedWriteTest) TestFlushWithNonZeroTruncatedLengthForEmptyObject() { - require.Nil(testSuite.T(), testSuite.bwh.current) - testSuite.bwh.truncatedSize = 10 + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + require.Nil(testSuite.T(), bwhImpl.current) + bwhImpl.truncatedSize = 10 _, err := testSuite.bwh.Flush() assert.NoError(testSuite.T(), err) - assert.Equal(testSuite.T(), testSuite.bwh.truncatedSize, testSuite.bwh.totalSize) + assert.Equal(testSuite.T(), bwhImpl.truncatedSize, bwhImpl.totalSize) } func (testSuite *BufferedWriteTest) TestFlushWithTruncatedLengthGreaterThanObjectSize() { err := testSuite.bwh.Write([]byte("hi"), 0) require.Nil(testSuite.T(), err) - testSuite.bwh.truncatedSize = 10 + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + bwhImpl.truncatedSize = 10 _, err = testSuite.bwh.Flush() assert.NoError(testSuite.T(), err) - assert.Equal(testSuite.T(), testSuite.bwh.truncatedSize, testSuite.bwh.totalSize) + assert.Equal(testSuite.T(), bwhImpl.truncatedSize, bwhImpl.totalSize) } func (testSuite *BufferedWriteTest) TestTruncateWithLesserSize() { - testSuite.bwh.totalSize = 10 + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + bwhImpl.totalSize = 10 err := testSuite.bwh.Truncate(2) @@ -322,30 +341,33 @@ func (testSuite *BufferedWriteTest) TestTruncateWithLesserSize() { } func (testSuite *BufferedWriteTest) TestTruncateWithSizeGreaterThanCurrentObjectSize() { - testSuite.bwh.totalSize = 10 + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + bwhImpl.totalSize = 10 err := testSuite.bwh.Truncate(12) assert.NoError(testSuite.T(), err) - assert.Equal(testSuite.T(), int64(12), testSuite.bwh.truncatedSize) + assert.Equal(testSuite.T(), int64(12), bwhImpl.truncatedSize) } func (testSuite *BufferedWriteTest) TestWriteFileInfoWithTruncatedLengthLessThanTotalSize() { - testSuite.bwh.totalSize = 10 - testSuite.bwh.truncatedSize = 5 + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + bwhImpl.totalSize = 10 + bwhImpl.truncatedSize = 5 fileInfo := testSuite.bwh.WriteFileInfo() - assert.Equal(testSuite.T(), testSuite.bwh.totalSize, fileInfo.TotalSize) + assert.Equal(testSuite.T(), bwhImpl.totalSize, fileInfo.TotalSize) } func (testSuite *BufferedWriteTest) TestWriteFileInfoWithTruncatedLengthGreaterThanTotalSize() { - testSuite.bwh.totalSize = 10 - testSuite.bwh.truncatedSize = 20 + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + bwhImpl.totalSize = 10 + bwhImpl.truncatedSize = 20 fileInfo := testSuite.bwh.WriteFileInfo() - assert.Equal(testSuite.T(), testSuite.bwh.truncatedSize, fileInfo.TotalSize) + assert.Equal(testSuite.T(), bwhImpl.truncatedSize, fileInfo.TotalSize) } func (testSuite *BufferedWriteTest) TestDestroyShouldClearFreeBlockChannel() { // Try to write 4 blocks of data. @@ -356,16 +378,18 @@ func (testSuite *BufferedWriteTest) TestDestroyShouldClearFreeBlockChannel() { err = testSuite.bwh.Destroy() require.Nil(testSuite.T(), err) - assert.Equal(testSuite.T(), 0, len(testSuite.bwh.blockPool.FreeBlocksChannel())) - assert.Equal(testSuite.T(), 0, len(testSuite.bwh.uploadHandler.uploadCh)) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) + assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) } func (testSuite *BufferedWriteTest) TestUnlinkBeforeWrite() { testSuite.bwh.Unlink() - assert.Nil(testSuite.T(), testSuite.bwh.uploadHandler.cancelFunc) - assert.Equal(testSuite.T(), 0, len(testSuite.bwh.uploadHandler.uploadCh)) - assert.Equal(testSuite.T(), 0, len(testSuite.bwh.blockPool.FreeBlocksChannel())) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + assert.Nil(testSuite.T(), bwhImpl.uploadHandler.cancelFunc) + assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) + assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) } func (testSuite *BufferedWriteTest) TestUnlinkAfterWrite() { @@ -377,11 +401,12 @@ func (testSuite *BufferedWriteTest) TestUnlinkAfterWrite() { require.Nil(testSuite.T(), err) } cancelCalled := false - testSuite.bwh.uploadHandler.cancelFunc = func() { cancelCalled = true } + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + bwhImpl.uploadHandler.cancelFunc = func() { cancelCalled = true } testSuite.bwh.Unlink() assert.True(testSuite.T(), cancelCalled) - assert.Equal(testSuite.T(), 0, len(testSuite.bwh.uploadHandler.uploadCh)) - assert.Equal(testSuite.T(), 0, len(testSuite.bwh.blockPool.FreeBlocksChannel())) + assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) + assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) } diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 10807ede54..45f0b2e4e9 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -101,7 +101,7 @@ type FileInode struct { // code. MRDWrapper gcsx.MultiRangeDownloaderWrapper - bwh *bufferedwrites.BufferedWriteHandler + bwh bufferedwrites.BufferedWriteHandler config *cfg.Config // Once write is started on the file i.e, bwh is initialized, any fileHandles @@ -594,7 +594,7 @@ func (f *FileInode) writeUsingTempFile(ctx context.Context, data []byte, offset // LOCKS_REQUIRED(f.mu) func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, offset int64) error { err := f.bwh.Write(data, offset) - if errors.Is(err, bufferedwrites.ErrOutOfOrderWrite) || errors.Is(err, bufferedwrites.ErrUploadFailure) { + if err != nil { // Finalize the object. flushErr := f.flushUsingBufferedWriteHandler() if flushErr != nil { @@ -603,7 +603,7 @@ func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, o } // Fall back to temp file for Out-Of-Order Writes. - if err == bufferedwrites.ErrOutOfOrderWrite { + if errors.Is(err, bufferedwrites.ErrOutOfOrderWrite) { return f.writeUsingTempFile(ctx, data, offset) } diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index e395e1c2bd..27400cc180 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -16,11 +16,13 @@ package inode import ( "context" + "errors" "math" "testing" "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/bufferedwrites" "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" @@ -588,3 +590,79 @@ func (t *FileStreamingWritesTest) TestDeRegisterFileHandle() { }) } } + +// FakeBufferedWriteHandler is a test double for BufferedWriteHandler. +type FakeBufferedWriteHandler struct { + WriteFunc func(data []byte, offset int64) error + FlushFunc func() (*gcs.MinObject, error) +} + +func (t *FakeBufferedWriteHandler) Write(data []byte, offset int64) error { + if t.WriteFunc != nil { + return t.WriteFunc(data, offset) + } + return nil +} + +func (t *FakeBufferedWriteHandler) Flush() (*gcs.MinObject, error) { + if t.FlushFunc != nil { + return t.FlushFunc() + } + return nil, nil +} + +func (t *FakeBufferedWriteHandler) WriteFileInfo() bufferedwrites.WriteFileInfo { + return bufferedwrites.WriteFileInfo{ + TotalSize: 0, + Mtime: time.Time{}, + } +} + +func (t *FakeBufferedWriteHandler) Sync() (err error) { return nil } +func (t *FakeBufferedWriteHandler) SetMtime(_ time.Time) {} +func (t *FakeBufferedWriteHandler) Truncate(_ int64) error { return nil } +func (t *FakeBufferedWriteHandler) Destroy() error { return nil } +func (t *FakeBufferedWriteHandler) Unlink() {} + +func (t *FileStreamingWritesTest) TestWriteUsingBufferedWritesErrorScenarios() { + assert.True(t.T(), t.in.IsLocal()) + require.NotNil(t.T(), t.in.bwh) + + testCases := []struct { + name string + writeErr error + flushErr error + expectedErr string + }{ + { + name: "Write_error_flush_succeeds", + writeErr: errors.New("write error"), + flushErr: nil, + expectedErr: "write error", + }, + { + name: "Write_error_flush_fails", + writeErr: errors.New("write error"), + flushErr: errors.New("flush error"), + expectedErr: "write error.*flush error", // Use regex for multiple errors + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.in.bwh = &FakeBufferedWriteHandler{ + WriteFunc: func(data []byte, offset int64) error { + return tc.writeErr + }, + FlushFunc: func() (*gcs.MinObject, error) { + return nil, tc.flushErr + }, + } + + err := t.in.Write(context.Background(), []byte("hello"), 0) + + require.Error(t.T(), err) + assert.Regexp(t.T(), tc.expectedErr, err.Error()) + }) + } +} From b2d4339f0336b437806399be07fb6234edd35530 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 29 Jan 2025 12:03:17 +0530 Subject: [PATCH 0200/1298] [Readability fix] Be explicit that nil child is being returned. (#2943) * [Readability fix] Be explicit that nil child is being returned. --- internal/fs/fs.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 9d2fdf5b8d..e9cb31ca82 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -980,8 +980,9 @@ func (fs *fileSystem) lookUpOrCreateChildInode( parent inode.DirInode, childName string) (child inode.Inode, err error) { // First check if the requested child is a localFileInode. - if child, err = fs.lookUpLocalFileInode(parent, childName); err != nil { - return child, err + child, err = fs.lookUpLocalFileInode(parent, childName) + if err != nil { + return nil, err } if child != nil { return @@ -1042,7 +1043,7 @@ func (fs *fileSystem) lookUpLocalFileInode(parent inode.DirInode, childName stri // If the path specified is "a/\n", the child would come as \n which is not a valid childname. // In such cases, simply return a file-not-found. if childName == inode.ConflictingFileNameSuffix { - return child, syscall.ENOENT + return nil, syscall.ENOENT } // Trim the suffix assigned to fix conflicting names. childName = strings.TrimSuffix(childName, inode.ConflictingFileNameSuffix) From e9ec49c532da6a2f5ad5f4f817b467c17a08de50 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 29 Jan 2025 15:39:48 +0530 Subject: [PATCH 0201/1298] Add streaming writes config to useragent (#2949) * add streaming writes config to useragent * review comment --- cmd/legacy_main.go | 6 +++++- cmd/legacy_main_test.go | 48 +++++++++++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 68e64ac2fd..ba16e48259 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -113,7 +113,11 @@ func getConfigForUserAgent(mountConfig *cfg.Config) string { if cfg.IsParallelDownloadsEnabled(mountConfig) { isParallelDownloadsEnabled = "1" } - return fmt.Sprintf("%s:%s:%s", isFileCacheEnabled, isFileCacheForRangeReadEnabled, isParallelDownloadsEnabled) + areStreamingWritesEnabled := "0" + if mountConfig.Write.EnableStreamingWrites { + areStreamingWritesEnabled = "1" + } + return fmt.Sprintf("%s:%s:%s:%s", isFileCacheEnabled, isFileCacheForRangeReadEnabled, isParallelDownloadsEnabled, areStreamingWritesEnabled) } func createStorageHandle(newConfig *cfg.Config, userAgent string) (storageHandle storage.StorageHandle, err error) { storageClientConfig := storageutil.StorageClientConfig{ diff --git a/cmd/legacy_main_test.go b/cmd/legacy_main_test.go index 970f1f5d37..eec6de22b4 100644 --- a/cmd/legacy_main_test.go +++ b/cmd/legacy_main_test.go @@ -69,7 +69,7 @@ func (t *MainTest) TestGetUserAgentWhenMetadataImageTypeEnvVarIsSet() { mountConfig := &cfg.Config{} userAgent := getUserAgent("AppName", getConfigForUserAgent(mountConfig)) - expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s AppName (GPN:gcsfuse-DLVM) (Cfg:0:0:0)", common.GetVersion())) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s AppName (GPN:gcsfuse-DLVM) (Cfg:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } @@ -77,7 +77,7 @@ func (t *MainTest) TestGetUserAgentWhenMetadataImageTypeEnvVarIsSet() { func (t *MainTest) TestGetUserAgentWhenMetadataImageTypeEnvVarIsNotSet() { mountConfig := &cfg.Config{} userAgent := getUserAgent("AppName", getConfigForUserAgent(mountConfig)) - expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0)", common.GetVersion())) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } @@ -85,7 +85,7 @@ func (t *MainTest) TestGetUserAgentWhenMetadataImageTypeEnvVarIsNotSet() { func (t *MainTest) TestGetUserAgentConfigWithNoFileCache() { mountConfig := &cfg.Config{} userAgent := getUserAgent("AppName", getConfigForUserAgent(mountConfig)) - expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0)", common.GetVersion())) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } @@ -103,7 +103,7 @@ func (t *MainTest) TestGetUserAgentConfig() { MaxSizeMb: 0, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())), }, { name: "Config with file cache disabled where maxsize is set but cache dir is not set.", @@ -112,7 +112,7 @@ func (t *MainTest) TestGetUserAgentConfig() { MaxSizeMb: -1, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())), }, { name: "Config with file cache enabled but random read disabled.", @@ -122,7 +122,7 @@ func (t *MainTest) TestGetUserAgentConfig() { MaxSizeMb: -1, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:0:0)", common.GetVersion())), }, { name: "Config with file cache and random read enabled.", @@ -133,7 +133,7 @@ func (t *MainTest) TestGetUserAgentConfig() { CacheFileForRangeRead: true, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:1:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:1:0:0)", common.GetVersion())), }, { name: "Config with file cache disabled and enable parallel downloads set.", @@ -144,7 +144,7 @@ func (t *MainTest) TestGetUserAgentConfig() { EnableParallelDownloads: true, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())), }, { name: "Config with file cache and parallel downloads enabled.", @@ -155,7 +155,7 @@ func (t *MainTest) TestGetUserAgentConfig() { EnableParallelDownloads: true, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:1)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:1:0)", common.GetVersion())), }, { name: "Config with file cache, random reads and parallel downloads enabled.", @@ -167,7 +167,33 @@ func (t *MainTest) TestGetUserAgentConfig() { EnableParallelDownloads: true, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:1:1)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:1:1:0)", common.GetVersion())), + }, + { + name: "streaming_writes_enabled", + mountConfig: &cfg.Config{ + CacheDir: "/cache/path", + FileCache: cfg.FileCacheConfig{ + MaxSizeMb: -1, + CacheFileForRangeRead: false, + EnableParallelDownloads: true, + }, + Write: cfg.WriteConfig{EnableStreamingWrites: true}, + }, + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:1:1)", common.GetVersion())), + }, + { + name: "streaming_writes_disabled", + mountConfig: &cfg.Config{ + CacheDir: "/cache/path", + FileCache: cfg.FileCacheConfig{ + MaxSizeMb: -1, + CacheFileForRangeRead: true, + EnableParallelDownloads: false, + }, + Write: cfg.WriteConfig{EnableStreamingWrites: false}, + }, + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:1:0:0)", common.GetVersion())), }, } @@ -185,7 +211,7 @@ func (t *MainTest) TestGetUserAgentWhenMetadataImageTypeEnvVarSetAndAppNameNotSe mountConfig := &cfg.Config{} userAgent := getUserAgent("", getConfigForUserAgent(mountConfig)) - expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-DLVM) (Cfg:0:0:0)", common.GetVersion())) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-DLVM) (Cfg:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } From e62b5a6fce85cad18d30fa46d7f114fcfc1a3cd6 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 29 Jan 2025 17:36:01 +0530 Subject: [PATCH 0202/1298] Make test readable (#2944) * make test readable * adding test comment * nit * nit * lint fix --- .../infinite_negative_stat_cache_test.go | 38 +++++++++++-------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go index 14042e4062..1b3019a1dc 100644 --- a/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go +++ b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go @@ -26,6 +26,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type infiniteNegativeStatCacheTest struct { @@ -68,27 +69,34 @@ func (s *infiniteNegativeStatCacheTest) TestInfiniteNegativeStatCache(t *testing assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") } -func (s *infiniteNegativeStatCacheTest) TestInfiniteNegativeStatCacheForAlreadyExistFolder(t *testing.T) { - targetDir := path.Join(testDirPath, "explicit_dir") - // Create test directory - operations.CreateDirectory(targetDir, t) - dir := path.Join(targetDir, "test_dir") - dirPathOnBucket := path.Join(testDirName, "explicit_dir", "test_dir") - - // Error should be returned as dir does not exist - _, err := os.Stat(dir) - assert.ErrorContains(t, err, "no such file or directory") - - // Adding the same name folder/dir. +// TestAlreadyExistFolder tests the scenario where a folder creation attempt fails +// with EEXIST. The infinite negative cache is essential because LookUpInode must +// return a "not found" error to trigger the subsequent create operation. This occurs +// when a folder is created externally after gcsfuse has cached a negative stat entry for that path. +// The negative cache prevents gcsfuse from seeing the externally created folder, +// leading to an EEXIST error when attempting to create the same folder again. +func (s *infiniteNegativeStatCacheTest) TestAlreadyExistFolder(t *testing.T) { + dirName := "testAlreadyExistFolder" + dirPath := path.Join(testDirPath, dirName) + dirPathOnBucket := path.Join(testDirName, dirName) + // Stat should return an error because the directory doesn't exist yet, + // populating the negative metadata cache. + _, err := os.Stat(dirPath) + require.Error(t, err) + require.True(t, os.IsNotExist(err)) + // Create the directory in the bucket using a different client outside of gcsfuse. if setup.IsHierarchicalBucket(ctx, storageClient) { _, err = client.CreateFolderInBucket(ctx, storageControlClient, dirPathOnBucket) } else { err = client.CreateObjectOnGCS(ctx, storageClient, dirPathOnBucket+"/", "") } - assert.NoError(t, err) + require.NoError(t, err) + + // Attempting to create the directory again should fail with EEXIST because the + // negative stat cache entry persists, causing LookUpInode to return a "not found" error + // and triggering a directory creation attempt despite the directory already existing in GCS. + err = os.Mkdir(dirPath, setup.DirPermission_0755) - // Error should be returned as already exist on trying to create. - err = os.Mkdir(dir, setup.DirPermission_0755) assert.ErrorIs(t, err, syscall.EEXIST) } From 582233cf97ec5aa185c8dc7fc4118a7c0d82562b Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 30 Jan 2025 10:43:44 +0530 Subject: [PATCH 0203/1298] remove chunk retry timeout for buffered writes writer (#2954) --- internal/bufferedwrites/upload_handler.go | 3 ++- internal/bufferedwrites/upload_handler_test.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 8a72faa27d..4e95084c95 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -103,7 +103,8 @@ func (uh *UploadHandler) Upload(block block.Block) error { // createObjectWriter creates a GCS object writer. func (uh *UploadHandler) createObjectWriter() (err error) { - req := gcs.NewCreateObjectRequest(uh.obj, uh.objectName, nil, uh.chunkTransferTimeout) + // TODO: b/381479965: Dynamically set chunkTransferTimeoutSecs based on chunk size. 0 here means no timeout. + req := gcs.NewCreateObjectRequest(uh.obj, uh.objectName, nil, 0) // We need a new context here, since the first writeFile() call will be complete // (and context will be cancelled) by the time complete upload is done. var ctx context.Context diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 55c9b70f18..0fa44aed2f 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -298,7 +298,7 @@ func (t *UploadHandlerTest) TestCreateObjectChunkWriterIsCalledWithCorrectReques *req.MetaGenerationPrecondition == t.uh.obj.MetaGeneration && req.ContentEncoding == t.uh.obj.ContentEncoding && req.ContentType == t.uh.obj.ContentType && - req.ChunkTransferTimeoutSecs == chunkTransferTimeoutSecs + req.ChunkTransferTimeoutSecs == 0 }), mock.Anything, mock.Anything).Return(writer, nil) @@ -324,7 +324,7 @@ func (t *UploadHandlerTest) TestCreateObjectChunkWriterIsCalledWithCorrectReques return req.Name == t.uh.objectName && *req.GenerationPrecondition == 0 && req.MetaGenerationPrecondition == nil && - req.ChunkTransferTimeoutSecs == chunkTransferTimeoutSecs + req.ChunkTransferTimeoutSecs == 0 }), mock.Anything, mock.Anything).Return(writer, nil) From b29f539f8df0aaef4250ba4e11f3cb10ae2eb02f Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Thu, 30 Jan 2025 06:59:57 +0000 Subject: [PATCH 0204/1298] Add testify interface to the test suites. --- .../local_file/local_file_suite.go | 8 +++-- .../local_file/setup_test.go | 4 ++- .../default_mount_empty_gcs_file_test.go | 5 +++- .../default_mount_local_file_test.go | 29 ++++--------------- .../streaming_writes/default_mount_test.go | 12 ++++++-- .../util/test_suite/testify_interface.go | 22 ++++++++++++++ 6 files changed, 48 insertions(+), 32 deletions(-) create mode 100644 tools/integration_tests/util/test_suite/testify_interface.go diff --git a/tools/integration_tests/local_file/local_file_suite.go b/tools/integration_tests/local_file/local_file_suite.go index d1b5e35d14..e946876055 100644 --- a/tools/integration_tests/local_file/local_file_suite.go +++ b/tools/integration_tests/local_file/local_file_suite.go @@ -15,6 +15,7 @@ package local_file import ( + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_suite" "github.com/stretchr/testify/suite" ) @@ -22,10 +23,11 @@ import ( // Boilerplate // ////////////////////////////////////////////////////////////////////// -type CommonLocalFileTestSuite struct { +type localFileTestSuite struct { + CommonLocalFileTestSuite suite.Suite } -type localFileTestSuite struct { - CommonLocalFileTestSuite +type CommonLocalFileTestSuite struct { + test_suite.TestifySuite } diff --git a/tools/integration_tests/local_file/setup_test.go b/tools/integration_tests/local_file/setup_test.go index 8399f52eef..62d552c8be 100644 --- a/tools/integration_tests/local_file/setup_test.go +++ b/tools/integration_tests/local_file/setup_test.go @@ -95,5 +95,7 @@ func TestMain(m *testing.M) { } func TestLocalFileTestSuite(t *testing.T) { - suite.Run(t, new(localFileTestSuite)) + s := new(localFileTestSuite) + s.CommonLocalFileTestSuite.TestifySuite = &s.Suite + suite.Run(t, s) } diff --git a/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go b/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go index ed690b64e2..d7bdaf21f3 100644 --- a/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go @@ -26,6 +26,7 @@ import ( type defaultMountEmptyGCSFile struct { defaultMountCommonTest + suite.Suite } func (t *defaultMountEmptyGCSFile) SetupTest() { @@ -47,5 +48,7 @@ func (t *defaultMountEmptyGCSFile) createEmptyGCSFile() { // Executes all tests that run with single streamingWrites configuration for empty GCS Files. func TestDefaultMountEmptyGCSFileTest(t *testing.T) { - suite.Run(t, new(defaultMountEmptyGCSFile)) + s := new(defaultMountEmptyGCSFile) + s.defaultMountCommonTest.TestifySuite = &s.Suite + suite.Run(t, s) } diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go index 199b1e6559..f2f742a228 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -25,10 +25,8 @@ import ( type defaultMountLocalFile struct { defaultMountCommonTest -} - -type defaultMountCommonLocalFileTestSuite struct { CommonLocalFileTestSuite + suite.Suite } func (t *defaultMountLocalFile) SetupTest() { @@ -45,27 +43,10 @@ func (t *defaultMountLocalFile) createLocalFile() { t.filePath, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) } -func (t *defaultMountCommonLocalFileTestSuite) SetupSuite() { - flags := []string{"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"} - setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) - testDirPath = setup.SetupTestDirectory(testDirName) -} - -func (t *defaultMountCommonLocalFileTestSuite) TearDownSuite() { - setup.UnmountGCSFuse(rootDir) -} - // Executes all tests that run with single streamingWrites configuration for localFiles. func TestDefaultMountLocalFileTest(t *testing.T) { - suite.Run(t, new(defaultMountLocalFile)) -} - -// Executes all common tests that are part of local file package with single streamingWrites configuration for localFiles. -func TestDefaultCommonLocalFileTestSuite(t *testing.T) { - // Set ctx,storageClient,testDirName before running tests in local file package. - SetCtx(ctx) - SetStorageClient(storageClient) - SetTestDirName(testDirName) - - suite.Run(t, new(defaultMountCommonLocalFileTestSuite)) + s := new(defaultMountLocalFile) + s.CommonLocalFileTestSuite.TestifySuite = &s.Suite + s.defaultMountCommonTest.TestifySuite = &s.Suite + suite.Run(t, s) } diff --git a/tools/integration_tests/streaming_writes/default_mount_test.go b/tools/integration_tests/streaming_writes/default_mount_test.go index 25532a66cf..3ddb32838f 100644 --- a/tools/integration_tests/streaming_writes/default_mount_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_test.go @@ -17,8 +17,9 @@ package streaming_writes import ( "os" + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/stretchr/testify/suite" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_suite" ) type defaultMountCommonTest struct { @@ -26,11 +27,16 @@ type defaultMountCommonTest struct { fileName string // filePath of the above file in the mounted directory. filePath string - suite.Suite + test_suite.TestifySuite } func (t *defaultMountCommonTest) SetupSuite() { - flags := []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"} + // TODO(mohitkyadav): Make these part of test suite after refactoring. + SetCtx(ctx) + SetStorageClient(storageClient) + SetTestDirName(testDirName) + + flags := []string{"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"} setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) testDirPath = setup.SetupTestDirectory(testDirName) } diff --git a/tools/integration_tests/util/test_suite/testify_interface.go b/tools/integration_tests/util/test_suite/testify_interface.go new file mode 100644 index 0000000000..9320b11576 --- /dev/null +++ b/tools/integration_tests/util/test_suite/testify_interface.go @@ -0,0 +1,22 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test_suite + +import "github.com/stretchr/testify/suite" + +type TestifySuite interface { + suite.TestingSuite + Run(name string, subtest func()) bool +} From 3c616bff206d37abe48e56a3d9b010b13994a3ac Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 30 Jan 2025 14:10:29 +0530 Subject: [PATCH 0205/1298] Streaming writes config defaults (#2955) * set default configs and make enable-streaming-writes flag visible * remove rationalize based on global blocks * review comment --- cfg/config.go | 8 ++------ cfg/params.yaml | 8 ++++---- cfg/rationalize.go | 4 ---- cfg/rationalize_test.go | 2 +- cmd/config_validation_test.go | 4 ++-- cmd/root_test.go | 28 ++++++++++++++-------------- 6 files changed, 23 insertions(+), 31 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index f3e55e6bf6..6d779ffdd7 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -351,10 +351,6 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-streaming-writes", "", false, "Enables streaming uploads during write file operation.") - if err := flagSet.MarkHidden("enable-streaming-writes"); err != nil { - return err - } - flagSet.BoolP("experimental-enable-json-read", "", false, "By default, GCSFuse uses the GCS XML API to get and read objects. When this flag is specified, GCSFuse uses the GCS JSON API instead.\"") if err := flagSet.MarkDeprecated("experimental-enable-json-read", "Experimental flag: could be dropped even in a minor release."); err != nil { @@ -569,7 +565,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.IntP("uid", "", -1, "UID owner of all inodes.") - flagSet.IntP("write-block-size-mb", "", 64, "Specifies the block size for streaming writes. The value should be more than 0.") + flagSet.IntP("write-block-size-mb", "", 32, "Specifies the block size for streaming writes. The value should be more than 0.") if err := flagSet.MarkHidden("write-block-size-mb"); err != nil { return err @@ -581,7 +577,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.IntP("write-max-blocks-per-file", "", -1, "Specifies the maximum number of blocks to be used by a single file for streaming writes. The value should be >= 1 or -1 (for infinite blocks).") + flagSet.IntP("write-max-blocks-per-file", "", 1, "Specifies the maximum number of blocks to be used by a single file for streaming writes. The value should be >= 1 or -1 (for infinite blocks).") if err := flagSet.MarkHidden("write-max-blocks-per-file"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index e2d73b904a..84110b1c96 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -653,7 +653,7 @@ usage: >- Specifies the block size for streaming writes. The value should be more than 0. - default: 64 #TODO: revisit default value after perf testing. + default: 32 hide-flag: true - config-path: "write.create-empty-file" @@ -668,7 +668,7 @@ type: "bool" usage: "Enables streaming uploads during write file operation." default: false - hide-flag: true + hide-flag: false - config-path: "write.global-max-blocks" flag-name: "write-global-max-blocks" @@ -677,7 +677,7 @@ Specifies the maximum number of blocks to be used by all files for streaming writes. The value should be >= 0 (1 block per file is not counted towards this limit) or -1 (for infinite blocks). - default: -1 #TODO: revisit default value after perf testing. + default: -1 hide-flag: true - config-path: "write.max-blocks-per-file" @@ -686,7 +686,7 @@ usage: >- Specifies the maximum number of blocks to be used by a single file for streaming writes. The value should be >= 1 or -1 (for infinite blocks). - default: -1 #TODO: revisit default value after perf testing. + default: 1 hide-flag: true - flag-name: "debug_fs" diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 5cb9a465f7..9d0319078e 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -89,10 +89,6 @@ func resolveStreamingWriteConfig(w *WriteConfig) { // available, make channel results in panic. w.MaxBlocksPerFile = math.MaxInt16 } - - if w.GlobalMaxBlocks < w.MaxBlocksPerFile { - w.MaxBlocksPerFile = w.GlobalMaxBlocks - } } func resolveCloudMetricsUploadIntervalSecs(m *MetricsConfig) { diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 87ebc335a3..f1373c8222 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -347,7 +347,7 @@ func TestRationalize_WriteConfig(t *testing.T) { }, }, expectedCreateEmptyFile: false, - expectedMaxBlocksPerFile: 10, + expectedMaxBlocksPerFile: 20, expectedBlockSizeMB: 5 * 1024 * 1024, }, { diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 55c01ebffd..aba895f23a 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -191,10 +191,10 @@ func TestValidateConfigFile_WriteConfig(t *testing.T) { expectedConfig: &cfg.Config{ Write: cfg.WriteConfig{ CreateEmptyFile: false, - BlockSizeMb: 64 * util.MiB, + BlockSizeMb: 32 * util.MiB, EnableStreamingWrites: false, GlobalMaxBlocks: math.MaxInt64, - MaxBlocksPerFile: math.MaxInt16}, + MaxBlocksPerFile: 1}, }, }, { diff --git a/cmd/root_test.go b/cmd/root_test.go index 8d7ed87a52..18f39068bb 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -208,45 +208,45 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { args: []string{"gcsfuse", "--create-empty-file=true", "abc", "pqr"}, expectedCreateEmptyFile: true, expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 64 * util.MiB, + expectedWriteBlockSizeMB: 32 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, }, { name: "Test create-empty-file flag false.", args: []string{"gcsfuse", "--create-empty-file=false", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 64 * util.MiB, + expectedWriteBlockSizeMB: 32 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, }, { name: "Test default flags.", args: []string{"gcsfuse", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 64 * util.MiB, + expectedWriteBlockSizeMB: 32 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, }, { name: "Test enable-streaming-writes flag true.", args: []string{"gcsfuse", "--enable-streaming-writes", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 64 * util.MiB, + expectedWriteBlockSizeMB: 32 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, }, { name: "Test enable-streaming-writes flag false.", args: []string{"gcsfuse", "--enable-streaming-writes=false", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 64 * util.MiB, + expectedWriteBlockSizeMB: 32 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, }, { name: "Test positive write-block-size-mb flag.", @@ -255,23 +255,23 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedEnableStreamingWrites: true, expectedWriteBlockSizeMB: 10 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, }, { name: "Test positive write-global-max-blocks flag.", args: []string{"gcsfuse", "--enable-streaming-writes", "--write-global-max-blocks=10", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 64 * util.MiB, + expectedWriteBlockSizeMB: 32 * util.MiB, expectedWriteGlobalMaxBlocks: 10, - expectedWriteMaxBlocksPerFile: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, }, { name: "Test positive write-max-blocks-per-file flag.", args: []string{"gcsfuse", "--enable-streaming-writes", "--write-max-blocks-per-file=10", "abc", "pqr"}, expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 64 * util.MiB, + expectedWriteBlockSizeMB: 32 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, expectedWriteMaxBlocksPerFile: 10, }, From 45a8f47ec0e8980f4b29a0824cf244c6996d4414 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 30 Jan 2025 15:42:49 +0530 Subject: [PATCH 0206/1298] fix re-flush for streaming writes (#2956) * fix re-flush for streaming writes * flush only for out of order writes * add info log --- .../bufferedwrites/buffered_write_handler.go | 16 +++---- .../buffered_write_handler_test.go | 19 +++++--- internal/bufferedwrites/upload_handler.go | 3 ++ internal/fs/inode/file.go | 20 ++++----- .../fs/inode/file_streaming_writes_test.go | 43 ++++--------------- 5 files changed, 41 insertions(+), 60 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index f496ecbd88..5d7f285979 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -201,6 +201,14 @@ func (wh *bufferedWriteHandlerImpl) Sync() (err error) { // Flush finalizes the upload. func (wh *bufferedWriteHandlerImpl) Flush() (*gcs.MinObject, error) { + // Fail early if upload already failed. + select { + case <-wh.uploadHandler.SignalUploadFailure(): + return nil, ErrUploadFailure + default: + break + } + // In case it is a truncated file, upload empty blocks as required. err := wh.writeDataForTruncatedSize() if err != nil { @@ -226,14 +234,6 @@ func (wh *bufferedWriteHandlerImpl) Flush() (*gcs.MinObject, error) { logger.Errorf("blockPool.ClearFreeBlockChannel() failed: %v", err) } - // Return an error along with object if the uploadHandler failed in between. - select { - case <-wh.uploadHandler.SignalUploadFailure(): - return obj, ErrUploadFailure - default: - break - } - return obj, nil } diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index d7dc7b0446..55a62ba734 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -243,9 +243,7 @@ func (testSuite *BufferedWriteTest) TestFlushWithSignalUploadFailureDuringWrite( obj, err := testSuite.bwh.Flush() require.Error(testSuite.T(), err) assert.Equal(testSuite.T(), err, ErrUploadFailure) - // Whatever could be finalized, got finalized (empty object in this case). - assert.NotNil(testSuite.T(), obj) - assert.Equal(testSuite.T(), uint64(0), obj.Size) + assert.Nil(testSuite.T(), obj) } func (testSuite *BufferedWriteTest) TestFlushWithMultiBlockWritesAndSignalUploadFailureInBetween() { @@ -267,9 +265,7 @@ func (testSuite *BufferedWriteTest) TestFlushWithMultiBlockWritesAndSignalUpload require.Error(testSuite.T(), err) assert.Equal(testSuite.T(), err, ErrUploadFailure) - // Whatever could be finalized, got finalized. - assert.NotNil(testSuite.T(), obj) - assert.Equal(testSuite.T(), uint64(5*blockSize), obj.Size) + assert.Nil(testSuite.T(), obj) } func (testSuite *BufferedWriteTest) TestSync5InProgressBlocks() { @@ -410,3 +406,14 @@ func (testSuite *BufferedWriteTest) TestUnlinkAfterWrite() { assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) } + +func (testSuite *BufferedWriteTest) TestReFlushAfterUploadFails() { + testSuite.TestFlushWithMultiBlockWritesAndSignalUploadFailureInBetween() + + // Re-flush. + obj, err := testSuite.bwh.Flush() + + require.Error(testSuite.T(), err) + assert.Nil(testSuite.T(), obj) + assert.ErrorContains(testSuite.T(), err, ErrUploadFailure.Error()) +} diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 4e95084c95..ea3e32ac3d 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Note: All the write operations take inode lock in fs.go, hence we don't need +// any locks here as we will get calls to these methods serially. + package bufferedwrites import ( diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 45f0b2e4e9..319daa78f9 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -594,16 +594,18 @@ func (f *FileInode) writeUsingTempFile(ctx context.Context, data []byte, offset // LOCKS_REQUIRED(f.mu) func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, offset int64) error { err := f.bwh.Write(data, offset) - if err != nil { - // Finalize the object. - flushErr := f.flushUsingBufferedWriteHandler() - if flushErr != nil { - return fmt.Errorf("bwh.Write failed: %v, could not finalize what has been written so far: %w", err, flushErr) - } + if err != nil && !errors.Is(err, bufferedwrites.ErrOutOfOrderWrite) { + return fmt.Errorf("write to buffered write handler failed: %w", err) } // Fall back to temp file for Out-Of-Order Writes. if errors.Is(err, bufferedwrites.ErrOutOfOrderWrite) { + logger.Infof("Out-of-order write detected. Falling back to temporary file on disk.") + // Finalize the object. + err = f.flushUsingBufferedWriteHandler() + if err != nil { + return fmt.Errorf("could not finalize what has been written so far: %w", err) + } return f.writeUsingTempFile(ctx, data, offset) } @@ -616,21 +618,17 @@ func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, o // LOCKS_REQUIRED(f.mu) func (f *FileInode) flushUsingBufferedWriteHandler() error { obj, err := f.bwh.Flush() - var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { return &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("f.bwh.Flush(): %w", err), } } - - // bwh can return a partially synced object along with an error so updating - // inode state before returning error. - f.updateInodeStateAfterSync(obj) if err != nil { return fmt.Errorf("f.bwh.Flush(): %w", err) } + f.updateInodeStateAfterSync(obj) return nil } diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 27400cc180..a68159287c 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -624,45 +624,18 @@ func (t *FakeBufferedWriteHandler) Truncate(_ int64) error { return nil } func (t *FakeBufferedWriteHandler) Destroy() error { return nil } func (t *FakeBufferedWriteHandler) Unlink() {} -func (t *FileStreamingWritesTest) TestWriteUsingBufferedWritesErrorScenarios() { +func (t *FileStreamingWritesTest) TestWriteUsingBufferedWritesFails() { assert.True(t.T(), t.in.IsLocal()) require.NotNil(t.T(), t.in.bwh) - - testCases := []struct { - name string - writeErr error - flushErr error - expectedErr string - }{ - { - name: "Write_error_flush_succeeds", - writeErr: errors.New("write error"), - flushErr: nil, - expectedErr: "write error", - }, - { - name: "Write_error_flush_fails", - writeErr: errors.New("write error"), - flushErr: errors.New("flush error"), - expectedErr: "write error.*flush error", // Use regex for multiple errors + writeErr := errors.New("write error") + t.in.bwh = &FakeBufferedWriteHandler{ + WriteFunc: func(data []byte, offset int64) error { + return writeErr }, } - for _, tc := range testCases { - t.Run(tc.name, func() { - t.in.bwh = &FakeBufferedWriteHandler{ - WriteFunc: func(data []byte, offset int64) error { - return tc.writeErr - }, - FlushFunc: func() (*gcs.MinObject, error) { - return nil, tc.flushErr - }, - } + err := t.in.Write(context.Background(), []byte("hello"), 0) - err := t.in.Write(context.Background(), []byte("hello"), 0) - - require.Error(t.T(), err) - assert.Regexp(t.T(), tc.expectedErr, err.Error()) - }) - } + require.Error(t.T(), err) + assert.Regexp(t.T(), writeErr.Error(), err.Error()) } From d0c81624df410845b31da95bf663718491dfdb67 Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Fri, 31 Jan 2025 10:33:29 +0000 Subject: [PATCH 0207/1298] Fixing MinObject related issues in MRDWrapper (#2936) Ensure MRD's minObject is updated when inode's minObject is updated. Ensure that we set MRD to nil if there were any errors while creating it. (We observed that NewMultiRangeDownloader returned a non nil value for MRD even in case of error) b/391534052 --- internal/fs/inode/file.go | 18 ++- internal/fs/inode/file_test.go | 22 ++++ .../gcsx/multi_range_downloader_wrapper.go | 31 ++++- .../multi_range_downloader_wrapper_test.go | 116 +++++++++++++++++- internal/gcsx/random_reader_stretchr_test.go | 12 +- 5 files changed, 190 insertions(+), 9 deletions(-) diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 319daa78f9..221379f29d 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -158,7 +158,11 @@ func NewFileInode( unlinked: false, config: cfg, globalMaxWriteBlocksSem: globalMaxBlocksSem, - MRDWrapper: gcsx.NewMultiRangeDownloaderWrapper(bucket, &minObj), + } + var err error + f.MRDWrapper, err = gcsx.NewMultiRangeDownloaderWrapper(bucket, &f.src) + if err != nil { + logger.Errorf("NewFileInode: Error in creating MRDWrapper %v", err) } f.lc.Init(id) @@ -697,6 +701,7 @@ func (f *FileInode) SetMtime( minObj = *minObjPtr } f.src = minObj + f.updateMRDWrapper() return } @@ -832,6 +837,8 @@ func (f *FileInode) Flush(ctx context.Context) (err error) { func (f *FileInode) updateInodeStateAfterSync(minObj *gcs.MinObject) { if minObj != nil && !f.localFileCache { f.src = *minObj + // Update MRDWrapper + f.updateMRDWrapper() // Convert localFile to nonLocalFile after it is synced to GCS. if f.IsLocal() { f.local = false @@ -848,6 +855,15 @@ func (f *FileInode) updateInodeStateAfterSync(minObj *gcs.MinObject) { return } +// Updates the min object stored in MRDWrapper corresponding to the inode. +// Should be called when minObject associated with inode is updated. +func (f *FileInode) updateMRDWrapper() { + err := f.MRDWrapper.SetMinObject(&f.src) + if err != nil { + logger.Errorf("FileInode::updateMRDWrapper Error in setting minObject %v", err) + } +} + // Truncate the file to the specified size. // // LOCKS_REQUIRED(f.mu) diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 73b3308e18..1029a0d94d 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -407,6 +407,9 @@ func (t *FileTest) TestWriteThenSync() { // The generation should have advanced. assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) + // Validate MinObject in MRDWrapper is same as the MinObject in inode. + assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) @@ -489,6 +492,8 @@ func (t *FileTest) TestWriteToLocalFileThenSync() { assert.Equal(t.T(), writeTime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) + // Validate MinObject in MRDWrapper is same as the MinObject in inode. + assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) assert.Nil(t.T(), err) @@ -547,6 +552,8 @@ func (t *FileTest) TestSyncEmptyLocalFile() { assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) assert.Equal(t.T(), uint64(0), m.Size) + // Validate MinObject in MRDWrapper is same as the MinObject in inode. + assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) // Validate the mtime. mtimeInBucket, ok := m.Metadata["gcsfuse_mtime"] assert.True(t.T(), ok) @@ -620,6 +627,9 @@ func (t *FileTest) TestAppendThenSync() { writeTime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) + // Validate MinObject in MRDWrapper is same as the MinObject in inode. + assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) @@ -676,6 +686,9 @@ func (t *FileTest) TestTruncateDownwardThenSync() { // The generation should have advanced. assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) + // Validate MinObject in MRDWrapper is same as the MinObject in inode. + assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) @@ -742,6 +755,9 @@ func (t *FileTest) TestTruncateUpwardThenFlush() { // The generation should have advanced. assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) + // Validate MinObject in MRDWrapper is same as the MinObject in inode. + assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) @@ -1031,6 +1047,9 @@ func (t *FileTest) TestSyncFlush_Clobbered() { err = t.in.Flush(t.ctx) } + // Validate MinObject in MRDWrapper is same as the MinObject in inode. + assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Check if the error is a FileClobberedError var fcErr *gcsfuse_errors.FileClobberedError assert.True(t.T(), errors.As(err, &fcErr), "expected FileClobberedError but got %v", err) @@ -1155,6 +1174,9 @@ func (t *FileTest) TestSetMtime_ContentDirty() { statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) + // Validate MinObject in MRDWrapper is same as the MinObject in inode. + assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + assert.Nil(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go index d58afab83a..bdd2b97de8 100644 --- a/internal/gcsx/multi_range_downloader_wrapper.go +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -34,16 +34,21 @@ import ( // it's refcount reaches 0. const multiRangeDownloaderTimeout = 60 * time.Second -func NewMultiRangeDownloaderWrapper(bucket gcs.Bucket, object *gcs.MinObject) MultiRangeDownloaderWrapper { +func NewMultiRangeDownloaderWrapper(bucket gcs.Bucket, object *gcs.MinObject) (MultiRangeDownloaderWrapper, error) { return NewMultiRangeDownloaderWrapperWithClock(bucket, object, clock.RealClock{}) } -func NewMultiRangeDownloaderWrapperWithClock(bucket gcs.Bucket, object *gcs.MinObject, clock clock.Clock) MultiRangeDownloaderWrapper { +func NewMultiRangeDownloaderWrapperWithClock(bucket gcs.Bucket, object *gcs.MinObject, clock clock.Clock) (MultiRangeDownloaderWrapper, error) { + if object == nil { + return MultiRangeDownloaderWrapper{}, fmt.Errorf("NewMultiRangeDownloaderWrapperWithClock: Missing MinObject") + } + // In case of a local inode, MRDWrapper would be created with an empty minObject (i.e. with a minObject without any information) + // and when the object is actually created, MRDWrapper would be updated using SetMinObject method. return MultiRangeDownloaderWrapper{ clock: clock, bucket: bucket, object: object, - } + }, nil } type readResult struct { @@ -56,6 +61,7 @@ type MultiRangeDownloaderWrapper struct { Wrapped gcs.MultiRangeDownloader // Bucket and object details for MultiRangeDownloader. + // Object should not be nil. object *gcs.MinObject bucket gcs.Bucket @@ -69,6 +75,20 @@ type MultiRangeDownloaderWrapper struct { clock clock.Clock } +// Sets the gcs.MinObject stored in the wrapper to passed value, only if it's non nil. +func (mrdWrapper *MultiRangeDownloaderWrapper) SetMinObject(minObj *gcs.MinObject) error { + if minObj == nil { + return fmt.Errorf("MultiRangeDownloaderWrapper::SetMinObject: Missing MinObject") + } + mrdWrapper.object = minObj + return nil +} + +// Returns the minObject stored in MultiRangeDownloaderWrapper. Used only for unit testing. +func (mrdWrapper *MultiRangeDownloaderWrapper) GetMinObject() *gcs.MinObject { + return mrdWrapper.object +} + // Returns current refcount. func (mrdWrapper *MultiRangeDownloaderWrapper) GetRefCount() int { mrdWrapper.mu.Lock() @@ -139,6 +159,10 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) cleanupMultiRangeDownloader() { // Ensures that MultiRangeDownloader exists, creating it if it does not exist. func (mrdWrapper *MultiRangeDownloaderWrapper) ensureMultiRangeDownloader() (err error) { + if mrdWrapper.object == nil || mrdWrapper.bucket == nil { + return fmt.Errorf("ensureMultiRangeDownloader error: Missing minObject or bucket") + } + if mrdWrapper.Wrapped == nil { mrdWrapper.Wrapped, err = mrdWrapper.bucket.NewMultiRangeDownloader(context.Background(), &gcs.MultiRangeDownloaderRequest{ Name: mrdWrapper.object.Name, @@ -161,6 +185,7 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []b err = mrdWrapper.ensureMultiRangeDownloader() if err != nil { err = fmt.Errorf("MultiRangeDownloaderWrapper::Read: Error in creating MultiRangeDownloader: %v", err) + mrdWrapper.Wrapped = nil return } diff --git a/internal/gcsx/multi_range_downloader_wrapper_test.go b/internal/gcsx/multi_range_downloader_wrapper_test.go index 7010bcec81..3b5134feb6 100644 --- a/internal/gcsx/multi_range_downloader_wrapper_test.go +++ b/internal/gcsx/multi_range_downloader_wrapper_test.go @@ -16,6 +16,7 @@ package gcsx import ( "context" + "fmt" "sync" "testing" "time" @@ -45,6 +46,7 @@ func TestMRDWrapperTestSuite(t *testing.T) { } func (t *mrdWrapperTest) SetupTest() { + var err error t.object = &gcs.MinObject{ Name: "foo", Size: 100, @@ -54,7 +56,8 @@ func (t *mrdWrapperTest) SetupTest() { // Create the bucket. t.mockBucket = new(storage.TestifyMockBucket) t.mrdTimeout = time.Millisecond - t.mrdWrapper = NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{WaitTime: t.mrdTimeout}) + t.mrdWrapper, err = NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{WaitTime: t.mrdTimeout}) + assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.mrdWrapper.Wrapped = fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond) t.mrdWrapper.refCount = 0 } @@ -156,3 +159,114 @@ func (t *mrdWrapperTest) Test_Read() { }) } } + +func (t *mrdWrapperTest) Test_NewMultiRangeDownloaderWrapper() { + testCases := []struct { + name string + bucket gcs.Bucket + obj *gcs.MinObject + err error + }{ + { + name: "ValidParameters", + bucket: t.mockBucket, + obj: t.object, + err: nil, + }, + { + name: "NilMinObject", + bucket: t.mockBucket, + obj: nil, + err: fmt.Errorf("NewMultiRangeDownloaderWrapperWithClock: Missing MinObject"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + _, err := NewMultiRangeDownloaderWrapper(tc.bucket, tc.obj) + if tc.err == nil { + assert.NoError(t.T(), err) + } else { + assert.Error(t.T(), err) + assert.EqualError(t.T(), err, tc.err.Error()) + } + }) + } +} + +func (t *mrdWrapperTest) Test_SetMinObject() { + testCases := []struct { + name string + obj *gcs.MinObject + err error + }{ + { + name: "ValidMinObject", + obj: t.object, + err: nil, + }, + { + name: "NilMinObject", + obj: nil, + err: fmt.Errorf("MultiRangeDownloaderWrapper::SetMinObject: Missing MinObject"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + err := t.mrdWrapper.SetMinObject(tc.obj) + if tc.err == nil { + assert.NoError(t.T(), err) + } else { + assert.Error(t.T(), err) + assert.EqualError(t.T(), err, tc.err.Error()) + } + }) + } +} + +func (t *mrdWrapperTest) Test_EnsureMultiRangeDownloader() { + testCases := []struct { + name string + obj *gcs.MinObject + bucket gcs.Bucket + err error + }{ + { + name: "ValidMinObject", + obj: t.object, + bucket: t.mockBucket, + err: nil, + }, + { + name: "NilMinObject", + obj: nil, + bucket: t.mockBucket, + err: fmt.Errorf("ensureMultiRangeDownloader error: Missing minObject or bucket"), + }, + { + name: "NilBucket", + obj: t.object, + bucket: nil, + err: fmt.Errorf("ensureMultiRangeDownloader error: Missing minObject or bucket"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.mrdWrapper.bucket = tc.bucket + t.mrdWrapper.object = tc.obj + t.mrdWrapper.Wrapped = nil + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond)) + err := t.mrdWrapper.ensureMultiRangeDownloader() + if tc.err == nil { + assert.NoError(t.T(), err) + assert.NotNil(t.T(), t.mrdWrapper.Wrapped) + } else { + assert.Error(t.T(), err) + assert.EqualError(t.T(), err, tc.err.Error()) + assert.Nil(t.T(), t.mrdWrapper.Wrapped) + } + }) + } +} diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index dbe298c4ca..0666c80440 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -647,7 +647,8 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_MRDRead() { t.rr.wrapped.seeks = minSeeksForRandom + 1 t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) @@ -688,7 +689,8 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadFull() { t.rr.wrapped.isMRDInUse = false t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) @@ -722,7 +724,8 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadChunk() { t.rr.wrapped.reader = nil t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) @@ -761,7 +764,8 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ValidateTimeout t.rr.wrapped.isMRDInUse = false t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, tc.sleepTime)).Times(1) t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) From e836cc5867c38c8feaa8b4ab815bc0ef5883859b Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Fri, 31 Jan 2025 15:16:11 +0000 Subject: [PATCH 0208/1298] Change to properly parse the error (#2953) gRPC returns status messages on error (https://google.aip.dev/193#error_model). So, it should be used to handle gRPC errors. apiError is a wrapper over status & googleApi.Error. So, ideally, we should just be using apiError.Error and then check whether the underlying error was http or gRPC and then proceed (i.e. try to convert error to apiError.Err and if it succeeds, then use appropriately). But the http error that we got returned success when converted to both status as well as googleApi.Error (although with unknown code in converted status). Hence, we have to check for both scenarios (http & grpc). So, there is no use of using apiError. Hence, started using status directly for gRPC. --- internal/fs/wrappers/error_mapping.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/internal/fs/wrappers/error_mapping.go b/internal/fs/wrappers/error_mapping.go index 52ae1c692c..89d1c07d4a 100644 --- a/internal/fs/wrappers/error_mapping.go +++ b/internal/fs/wrappers/error_mapping.go @@ -22,13 +22,13 @@ import ( "syscall" "cloud.google.com/go/storage" - "github.com/googleapis/gax-go/v2/apierror" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" "google.golang.org/api/googleapi" "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) var ( @@ -74,12 +74,8 @@ func errno(err error, preconditionErrCfg bool) error { return syscall.EACCES } - // The control client API returns an RPC error code instead of googleapi code. - // Currently, we only have gRPC control client APIs, so we are checking the gRPC status code. - // TODO: Add a check for the HTTP status code when the HTTP client is initiated for control client APIs. - var apiErr *apierror.APIError - if errors.As(err, &apiErr) { - switch apiErr.GRPCStatus().Code() { + if grpcStatus, ok := status.FromError(err); ok { + switch grpcStatus.Code() { case codes.Canceled: return syscall.EINTR case codes.AlreadyExists: From caa266508357ef2a82d2d7b90ce8220342c373f0 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 3 Feb 2025 10:51:34 +0530 Subject: [PATCH 0209/1298] close upload failure channel in case of error during finalize (#2957) --- internal/bufferedwrites/upload_handler.go | 14 ++++++-- .../bufferedwrites/upload_handler_test.go | 33 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index ea3e32ac3d..3c62d34078 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -125,8 +125,7 @@ func (uh *UploadHandler) uploader() { _, err := io.Copy(uh.writer, currBlock.Reader()) if err != nil { logger.Errorf("buffered write upload failed for object %s: error in io.Copy: %v", uh.objectName, err) - // Close the channel to signal upload failure. - close(uh.signalUploadFailure) + uh.closeUploadFailureChannel() } } // Put back the uploaded block on the freeBlocksChannel for re-use. @@ -151,6 +150,7 @@ func (uh *UploadHandler) Finalize() (*gcs.MinObject, error) { obj, err := uh.bucket.FinalizeUpload(context.Background(), uh.writer) if err != nil { + uh.closeUploadFailureChannel() return nil, fmt.Errorf("FinalizeUpload failed for object %s: %w", uh.objectName, err) } return obj, nil @@ -192,3 +192,13 @@ func (uh *UploadHandler) Destroy() { } } } + +// Closes the channel if not already closed to signal upload failure. +func (uh *UploadHandler) closeUploadFailureChannel() { + select { + case <-uh.signalUploadFailure: + break + default: + close(uh.signalUploadFailure) + } +} diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 0fa44aed2f..0a93701198 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -161,6 +161,7 @@ func (t *UploadHandlerTest) TestFinalizeWhenFinalizeUploadFails() { assert.Nil(t.T(), obj) assert.ErrorContains(t.T(), err, "taco") assert.ErrorContains(t.T(), err, "FinalizeUpload failed for object") + assertUploadFailureSignal(t.T(), t.uh) } func (t *UploadHandlerTest) TestUploadSingleBlockThrowsErrorInCopy() { @@ -389,3 +390,35 @@ func (t *UploadHandlerTest) createBlocks(count int) []block.Block { return blocks } + +func (t *UploadHandlerTest) TestUploadHandler_closeUploadFailureChannel() { + testCases := []struct { + name string + initialState chan error + }{ + { + name: "Channel_initially_open", + initialState: make(chan error), + }, + { + name: "Channel_already_closed", + initialState: func() chan error { ch := make(chan error); close(ch); return ch }(), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + uh := &UploadHandler{ + signalUploadFailure: tc.initialState, + } + + uh.closeUploadFailureChannel() + + select { + case <-uh.signalUploadFailure: + default: + t.T().Error("expected channel to be closed but it was not") + } + }) + } +} From c1f919859ca15e46b4117d5d982d6afb15809d36 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Mon, 3 Feb 2025 09:01:09 +0000 Subject: [PATCH 0210/1298] Doing flush for pending writes on rename file (#2958) * fixing moveobject * fixing moveobject * fixing lint * fixing header check * flushing pending files --- internal/fs/fs.go | 54 +++++++++++++++++-- tools/integration_tests/local_file/rename.go | 11 ++-- .../streaming_writes/move_file_test.go | 43 +++++++++++++++ 3 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 tools/integration_tests/streaming_writes/move_file_test.go diff --git a/internal/fs/fs.go b/internal/fs/fs.go index e9cb31ca82..617c959df9 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2024,7 +2024,8 @@ func (fs *fileSystem) Rename( if err != nil { return err } - if localChild != nil { + // For streaming writes, we will finalize localChild and do rename. + if localChild != nil && !fs.newConfig.Write.EnableStreamingWrites { fs.unlockAndDecrementLookupCount(localChild, 1) return fmt.Errorf("cannot rename open file %q: %w", op.OldName, syscall.ENOTSUP) } @@ -2052,10 +2053,57 @@ func (fs *fileSystem) Rename( } return fs.renameNonHierarchicalDir(ctx, oldParent, op.OldName, newParent, op.NewName) } + + return fs.renameFile(ctx, op, child, oldParent, newParent) +} + +// LOCKS_EXCLUDED(oldParent) +// LOCKS_EXCLUDED(newParent) +func (fs *fileSystem) renameFile(ctx context.Context, op *fuseops.RenameOp, child *inode.Core, oldParent inode.DirInode, newParent inode.DirInode) error { + updatedMinObject, err := fs.flushPendingWrites(ctx, child) + if err != nil { + return fmt.Errorf("flushBeforeRename error :%v", err) + } + if (child.Bucket.BucketType().Hierarchical && fs.enableAtomicRenameObject) || child.Bucket.BucketType().Zonal { - return fs.renameHierarchicalFile(ctx, oldParent, op.OldName, child.MinObject, newParent, op.NewName) + return fs.renameHierarchicalFile(ctx, oldParent, op.OldName, updatedMinObject, newParent, op.NewName) + } + return fs.renameNonHierarchicalFile(ctx, oldParent, op.OldName, updatedMinObject, newParent, op.NewName) +} + +// LOCKS_EXCLUDED(fs.mu) +// LOCKS_EXCLUDED(fileInode) +func (fs *fileSystem) flushPendingWrites(ctx context.Context, child *inode.Core) (minObject *gcs.MinObject, err error) { + // We will return modified minObject if flush is done, otherwise the original + // minObject is returned. Original minObject is the one passed in the request. + minObject = child.MinObject + if !fs.newConfig.Write.EnableStreamingWrites { + return + } + + fs.mu.Lock() + existingInode, ok := fs.generationBackedInodes[child.FullName] + fs.mu.Unlock() + // If this is not an existing file, then nothing to do. + // We shouldn't hit scenario ideally since we checked for local file and + // explicit-dirs (flat bucket) before reaching this step. + if !ok { + logger.Warnf("Encountered a non-fileInode in rename file path %d", existingInode.ID()) + return + } + + fileInode, isFileInode := existingInode.(*inode.FileInode) + // Same as above comment. This should be a file for sure. + if !isFileInode { + logger.Errorf("Encountered a non-fileInode in rename file path %d", existingInode.ID()) + return } - return fs.renameNonHierarchicalFile(ctx, oldParent, op.OldName, child.MinObject, newParent, op.NewName) + + fileInode.Lock() + defer fileInode.Unlock() + // Try to flush if there are any pending writes. + err = fs.flushFile(ctx, fileInode) + return } // LOCKS_EXCLUDED(oldParent) diff --git a/tools/integration_tests/local_file/rename.go b/tools/integration_tests/local_file/rename.go index 8c8cd66dad..8f31976bc1 100644 --- a/tools/integration_tests/local_file/rename.go +++ b/tools/integration_tests/local_file/rename.go @@ -41,7 +41,7 @@ func verifyRenameOperationNotSupported(err error, t *testing.T) { // Tests //////////////////////////////////////////////////////////////////////// -func (t *CommonLocalFileTestSuite) TestRenameOfLocalFileFails() { +func (t *localFileTestSuite) TestRenameOfLocalFileFails() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file with some content. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) @@ -89,7 +89,11 @@ func (t *CommonLocalFileTestSuite) TestRenameOfDirectoryWithLocalFileFails() { } func (t *CommonLocalFileTestSuite) TestRenameOfLocalFileSucceedsAfterSync() { - t.TestRenameOfLocalFileFails() + testDirPath = setup.SetupTestDirectory(testDirName) + // Create local file with some content. + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) + WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, FileContents, t.T()) // Attempt to Rename synced file. err := os.Rename( @@ -100,8 +104,7 @@ func (t *CommonLocalFileTestSuite) TestRenameOfLocalFileSucceedsAfterSync() { if err != nil { t.T().Fatalf("os.Rename() failed on synced file: %v", err) } - ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, NewFileName, - FileContents+FileContents, t.T()) + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, NewFileName, FileContents, t.T()) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } diff --git a/tools/integration_tests/streaming_writes/move_file_test.go b/tools/integration_tests/streaming_writes/move_file_test.go new file mode 100644 index 0000000000..96bc3517bc --- /dev/null +++ b/tools/integration_tests/streaming_writes/move_file_test.go @@ -0,0 +1,43 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes + +import ( + "path" + + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/require" +) + +func (t *defaultMountCommonTest) TestMoveBeforeFileIsFlushed() { + operations.WriteWithoutClose(t.f1, FileContents, t.T()) + operations.WriteWithoutClose(t.f1, FileContents, t.T()) + operations.VerifyStatFile(t.filePath, int64(2*len(FileContents)), FilePerms, t.T()) + err := t.f1.Sync() + require.NoError(t.T(), err) + + newFile := "newFile.txt" + destDirPath := path.Join(testDirPath, newFile) + err = operations.Move(t.filePath, destDirPath) + + // Validate that move didn't throw any error. + require.NoError(t.T(), err) + // Verify the new object contents. + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, FileContents+FileContents, t.T()) + require.NoError(t.T(), t.f1.Close()) + // Check if old object is deleted. + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) +} From 7cf94ae6c5766111ca29fb01c764d794cfea8c21 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 3 Feb 2025 19:43:32 +0530 Subject: [PATCH 0211/1298] add mounted directory tests (#2966) --- .../run_tests_mounted_directory.sh | 11 +++++++++++ .../streaming_writes/buffer_size_test.go | 4 ++++ .../streaming_writes/streaming_writes_test.go | 13 +++++++------ .../write_large_files/write_large_files_test.go | 3 +-- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index 6cf5a7a162..97f59e558a 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -605,3 +605,14 @@ sudo umount $MOUNT_DIR mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o metadata_cache_ttl_secs=0,precondition_errors=true GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/stale_handle/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME sudo umount $MOUNT_DIR + +# Test package: streaming_writes +# Run streaming_writes tests. +gcsfuse --rename-dir-limit=3 --enable-streaming-writes=true --write-block-size-mb=1 --write-max-blocks-per-file=2 $TEST_BUCKET_NAME $MOUNT_DIR +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/streaming_writes/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +sudo umount $MOUNT_DIR + +# Run write_large_files tests with streaming writes enabled. +gcsfuse --enable-streaming-writes=true $TEST_BUCKET_NAME $MOUNT_DIR +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/write_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +sudo umount $MOUNT_DIR diff --git a/tools/integration_tests/streaming_writes/buffer_size_test.go b/tools/integration_tests/streaming_writes/buffer_size_test.go index 209eb8ee33..db204b45cb 100644 --- a/tools/integration_tests/streaming_writes/buffer_size_test.go +++ b/tools/integration_tests/streaming_writes/buffer_size_test.go @@ -23,6 +23,10 @@ import ( ) func TestWritesWithDifferentConfig(t *testing.T) { + // Do not run this test with mounted directory flag. + if setup.MountedDirectory() != "" { + t.SkipNow() + } testCases := []struct { name string flags []string diff --git a/tools/integration_tests/streaming_writes/streaming_writes_test.go b/tools/integration_tests/streaming_writes/streaming_writes_test.go index 6b73b47bfb..aea4493b5a 100644 --- a/tools/integration_tests/streaming_writes/streaming_writes_test.go +++ b/tools/integration_tests/streaming_writes/streaming_writes_test.go @@ -42,12 +42,6 @@ var ( func TestMain(m *testing.M) { setup.ParseSetUpFlags() - if setup.MountedDirectory() != "" { - // Once streaming writes are enabled by default, we can run all defaultMount tests here. - log.Printf("These tests will not run with mounted directory..") - return - } - // Create storage client before running tests. ctx = context.Background() closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) @@ -58,6 +52,13 @@ func TestMain(m *testing.M) { } }() + // To run mountedDirectory tests, we need both testBucket and mountedDirectory + // flags to be set, as operations tests validates content from the bucket. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + rootDir = setup.MountedDirectory() + setup.RunTestsForMountedDirectoryFlag(m) + } + // Set up test directory. setup.SetUpTestDirForTestBucketFlag() rootDir = setup.MntDir() diff --git a/tools/integration_tests/write_large_files/write_large_files_test.go b/tools/integration_tests/write_large_files/write_large_files_test.go index 38303b6356..26003f7540 100644 --- a/tools/integration_tests/write_large_files/write_large_files_test.go +++ b/tools/integration_tests/write_large_files/write_large_files_test.go @@ -54,8 +54,7 @@ func compareFileFromGCSBucketAndMntDir(gcsFile, mntDirFile, localFilePathToDownl func TestMain(m *testing.M) { setup.ParseSetUpFlags() - // TODO: remove max-blocks-per-file after the default values are set. - flags := [][]string{{"--enable-streaming-writes=true", "--write-max-blocks-per-file=2"}} + flags := [][]string{{"--enable-streaming-writes=true"}} setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() From da12050e424ade34c524cbc7d3ed0ad77c58bc78 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 3 Feb 2025 19:45:29 +0530 Subject: [PATCH 0212/1298] upgrade to golang 1.23.5 (#2965) --- Dockerfile | 2 +- go.mod | 2 +- perfmetrics/scripts/ml_tests/pytorch/run_model.sh | 2 +- .../ml_tests/tf/resnet/setup_scripts/setup_container.sh | 4 ++-- perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh | 4 ++-- perfmetrics/scripts/read_cache/setup.sh | 2 +- tools/cd_scripts/e2e_test.sh | 2 +- tools/containerize_gcsfuse_docker/Dockerfile | 2 +- tools/integration_tests/run_e2e_tests.sh | 4 ++-- tools/package_gcsfuse_docker/Dockerfile | 2 +- 10 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 23e7b2c152..fb6d0961b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ # Mount the gcsfuse to /mnt/gcs: # > docker run --privileged --device /fuse -v /mnt/gcs:/gcs:rw,rshared gcsfuse -FROM golang:1.23.4-alpine AS builder +FROM golang:1.23.5-alpine AS builder RUN apk add git diff --git a/go.mod b/go.mod index f13c194e72..50bd5f30b6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/googlecloudplatform/gcsfuse/v2 -go 1.23.4 +go 1.23.5 require ( cloud.google.com/go/compute/metadata v0.6.0 diff --git a/perfmetrics/scripts/ml_tests/pytorch/run_model.sh b/perfmetrics/scripts/ml_tests/pytorch/run_model.sh index 35f65a5120..1cd3c2b8d4 100755 --- a/perfmetrics/scripts/ml_tests/pytorch/run_model.sh +++ b/perfmetrics/scripts/ml_tests/pytorch/run_model.sh @@ -20,7 +20,7 @@ NUM_EPOCHS=80 TEST_BUCKET="gcsfuse-ml-data" # Install golang -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.4.linux-amd64.tar.gz -q +wget -O go_tar.tar.gz https://go.dev/dl/go1.23.5.linux-amd64.tar.gz -q rm -rf /usr/local/go && tar -C /usr/local -xzf go_tar.tar.gz export PATH=$PATH:/usr/local/go/bin diff --git a/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh b/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh index 4ab2e1477e..0c8c7cfbac 100755 --- a/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh +++ b/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh @@ -13,13 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Installs go1.23.4 on the container, builds gcsfuse using log_rotation file +# Installs go1.23.5 on the container, builds gcsfuse using log_rotation file # and installs tf-models-official v2.13.2, makes update to include clear_kernel_cache # and epochs functionality, and runs the model # Install go lang BUCKET_TYPE=$1 -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.4.linux-amd64.tar.gz -q +wget -O go_tar.tar.gz https://go.dev/dl/go1.23.5.linux-amd64.tar.gz -q sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local export PATH=$PATH:/usr/local/go/bin diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index df3d104813..b28b652c6d 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -43,8 +43,8 @@ set -e sudo apt-get update echo Installing git sudo apt-get install git -echo Installing go-lang 1.23.4 -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.4.linux-amd64.tar.gz -q +echo Installing go-lang 1.23.5 +wget -O go_tar.tar.gz https://go.dev/dl/go1.23.5.linux-amd64.tar.gz -q sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local export PATH=$PATH:/usr/local/go/bin export CGO_ENABLED=0 diff --git a/perfmetrics/scripts/read_cache/setup.sh b/perfmetrics/scripts/read_cache/setup.sh index fa0847f36e..8392620075 100755 --- a/perfmetrics/scripts/read_cache/setup.sh +++ b/perfmetrics/scripts/read_cache/setup.sh @@ -64,7 +64,7 @@ sed -i 's/define \+FIO_IO_U_PLAT_GROUP_NR \+\([0-9]\+\)/define FIO_IO_U_PLAT_GRO cd - # Install and validate go. -version=1.23.4 +version=1.23.5 wget -O go_tar.tar.gz https://go.dev/dl/go${version}.linux-amd64.tar.gz -q sudo rm -rf /usr/local/go tar -xzf go_tar.tar.gz && sudo mv go /usr/local diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 127bd0d2da..417e43773b 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -112,7 +112,7 @@ else fi # install go -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.4.linux-${architecture}.tar.gz +wget -O go_tar.tar.gz https://go.dev/dl/go1.23.5.linux-${architecture}.tar.gz sudo tar -C /usr/local -xzf go_tar.tar.gz export PATH=${PATH}:/usr/local/go/bin #Write gcsfuse and go version to log file diff --git a/tools/containerize_gcsfuse_docker/Dockerfile b/tools/containerize_gcsfuse_docker/Dockerfile index c0e7732c71..95c4f99c4b 100644 --- a/tools/containerize_gcsfuse_docker/Dockerfile +++ b/tools/containerize_gcsfuse_docker/Dockerfile @@ -34,7 +34,7 @@ ARG OS_VERSION ARG OS_NAME # Image with gcsfuse installed and its package (.deb) -FROM golang:1.23.4 as gcsfuse-package +FROM golang:1.23.5 as gcsfuse-package RUN apt-get update -qq && apt-get install -y ruby ruby-dev rubygems build-essential rpm fuse && gem install --no-document bundler diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index e87e97ffbd..eb495e1826 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -116,8 +116,8 @@ function upgrade_gcloud_version() { function install_packages() { # e.g. architecture=arm64 or amd64 architecture=$(dpkg --print-architecture) - echo "Installing go-lang 1.23.4..." - wget -O go_tar.tar.gz https://go.dev/dl/go1.23.4.linux-${architecture}.tar.gz -q + echo "Installing go-lang 1.23.5..." + wget -O go_tar.tar.gz https://go.dev/dl/go1.23.5.linux-${architecture}.tar.gz -q sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local export PATH=$PATH:/usr/local/go/bin sudo apt-get install -y python3 diff --git a/tools/package_gcsfuse_docker/Dockerfile b/tools/package_gcsfuse_docker/Dockerfile index 8d3715d7f4..83435483bc 100644 --- a/tools/package_gcsfuse_docker/Dockerfile +++ b/tools/package_gcsfuse_docker/Dockerfile @@ -17,7 +17,7 @@ # Copy the gcsfuse packages to the host: # > docker run -it -v /tmp:/output gcsfuse-release cp -r /packages /output -FROM golang:1.23.4 as builder +FROM golang:1.23.5 as builder RUN apt-get update -qq && apt-get install -y ruby ruby-dev rubygems build-essential rpm && gem install --no-document bundler -v "2.4.12" From 4ad2e1846e348b8d58eee83d85332d2198976a76 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 4 Feb 2025 10:16:36 +0530 Subject: [PATCH 0213/1298] Enable OTel by default (#2967) * Reapply "Enable OTel metrics by default (#2859)" This reverts commit 11f54611d4d15b11a7ff9513bde25d4dc30e83c6. * Fix metrics test --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/config_validation_test.go | 2 ++ cmd/root_test.go | 35 ++++++++++++------- perfmetrics/scripts/vm_metrics/vm_metrics.py | 4 +-- .../scripts/vm_metrics/vm_metrics_test.py | 2 +- 6 files changed, 30 insertions(+), 17 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 6d779ffdd7..7aee16a980 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -337,7 +337,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-nonexistent-type-cache", "", false, "Once set, if an inode is not found in GCS, a type cache entry with type NonexistentType will be created. This also means new file/dir created might not be seen. For example, if this flag is set, and metadata-cache-ttl-secs is set, then if we create the same file/node in the meantime using the same mount, since we are not refreshing the cache, it will still return nil.") - flagSet.BoolP("enable-otel", "", false, "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus.") + flagSet.BoolP("enable-otel", "", true, "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus.") if err := flagSet.MarkHidden("enable-otel"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 84110b1c96..7805fc8c6a 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -599,7 +599,7 @@ flag-name: "enable-otel" type: "bool" usage: "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus." - default: false + default: true hide-flag: true - config-path: "metrics.prometheus-port" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index aba895f23a..64d4b4c729 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -807,6 +807,7 @@ func TestValidateConfigFile_MetricsConfigSuccessful(t *testing.T) { StackdriverExportInterval: 0, CloudMetricsExportIntervalSecs: 0, PrometheusPort: 0, + EnableOtel: true, }, }, { @@ -814,6 +815,7 @@ func TestValidateConfigFile_MetricsConfigSuccessful(t *testing.T) { configFile: "testdata/valid_config.yaml", expectedConfig: &cfg.MetricsConfig{ CloudMetricsExportIntervalSecs: 10, + EnableOtel: true, }, }, } diff --git a/cmd/root_test.go b/cmd/root_test.go index 18f39068bb..4f02911d06 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -882,7 +882,7 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { name: "default", args: []string{"gcsfuse", "abc", "pqr"}, expected: &cfg.MetricsConfig{ - EnableOtel: false, + EnableOtel: true, }, }, { @@ -907,14 +907,21 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { }, }, { - name: "cloud-metrics-export-interval-secs-positive", - args: []string{"gcsfuse", "--cloud-metrics-export-interval-secs=10", "abc", "pqr"}, - expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 10}, + name: "cloud-metrics-export-interval-secs-positive", + args: []string{"gcsfuse", "--cloud-metrics-export-interval-secs=10", "abc", "pqr"}, + expected: &cfg.MetricsConfig{ + CloudMetricsExportIntervalSecs: 10, + EnableOtel: true, + }, }, { - name: "stackdriver-export-interval-positive", - args: []string{"gcsfuse", "--stackdriver-export-interval=10h", "abc", "pqr"}, - expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 10 * 3600, StackdriverExportInterval: time.Duration(10) * time.Hour}, + name: "stackdriver-export-interval-positive", + args: []string{"gcsfuse", "--stackdriver-export-interval=10h", "abc", "pqr"}, + expected: &cfg.MetricsConfig{ + CloudMetricsExportIntervalSecs: 10 * 3600, + StackdriverExportInterval: time.Duration(10) * time.Hour, + EnableOtel: true, + }, }, } for _, tc := range tests { @@ -946,7 +953,7 @@ func TestArgsParsing_MetricsViewConfig(t *testing.T) { name: "default", cfgFile: "empty.yml", expected: &cfg.MetricsConfig{ - EnableOtel: false, + EnableOtel: true, }, }, { @@ -966,12 +973,16 @@ func TestArgsParsing_MetricsViewConfig(t *testing.T) { { name: "cloud-metrics-export-interval-secs-positive", cfgFile: "metrics_export_interval_positive.yml", - expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 100}, + expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 100, EnableOtel: true}, }, { - name: "stackdriver-export-interval-positive", - cfgFile: "stackdriver_export_interval_positive.yml", - expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 12 * 3600, StackdriverExportInterval: 12 * time.Hour}, + name: "stackdriver-export-interval-positive", + cfgFile: "stackdriver_export_interval_positive.yml", + expected: &cfg.MetricsConfig{ + CloudMetricsExportIntervalSecs: 12 * 3600, + StackdriverExportInterval: 12 * time.Hour, + EnableOtel: true, + }, }, } for _, tc := range tests { diff --git a/perfmetrics/scripts/vm_metrics/vm_metrics.py b/perfmetrics/scripts/vm_metrics/vm_metrics.py index bd42a7277b..6c6fa0f1cd 100644 --- a/perfmetrics/scripts/vm_metrics/vm_metrics.py +++ b/perfmetrics/scripts/vm_metrics/vm_metrics.py @@ -187,8 +187,8 @@ def _get_metric_filter(type, metric_type, instance, extra_filter): metric_type=metric_type, instance_name=instance) elif (type == 'custom'): metric_filter = ( - 'metric.type = "{metric_type}" AND metric.labels.opencensus_task = ' - 'ends_with("{instance_name}")').format( + 'metric.type = "{metric_type}" AND metadata.system_labels.name = ' + '"{instance_name}"').format( metric_type=metric_type, instance_name=instance) elif (type == 'agent'): # Fetch the instance ID here diff --git a/perfmetrics/scripts/vm_metrics/vm_metrics_test.py b/perfmetrics/scripts/vm_metrics/vm_metrics_test.py index 6ebbdc3baf..c1cd4f2c04 100644 --- a/perfmetrics/scripts/vm_metrics/vm_metrics_test.py +++ b/perfmetrics/scripts/vm_metrics/vm_metrics_test.py @@ -288,7 +288,7 @@ def test_get_metric_filter_compute(self): def test_get_metric_filter_custom(self): metric_type = "metric_type" extra_filter = "extra_filter" - expected_metric_filter = 'metric.type = "{}" AND metric.labels.opencensus_task = ends_with("{}") AND {}'.format(metric_type, TEST_INSTANCE, extra_filter) + expected_metric_filter = 'metric.type = "{}" AND metadata.system_labels.name = "{}" AND {}'.format(metric_type, TEST_INSTANCE, extra_filter) self.assertEqual(vm_metrics._get_metric_filter('custom', metric_type, TEST_INSTANCE, extra_filter), expected_metric_filter) @patch('vm_metrics._get_instance_id') From 22a5354945dcec8bb6a19bfe050e46f7d573b1d2 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:36:06 +0530 Subject: [PATCH 0214/1298] Persist logs in read_cache test (#2968) * get logs from cache test * remove "/" from test name --- .../managed_folders/admin_permissions_test.go | 2 +- .../managed_folders/view_permissions_test.go | 2 +- .../cache_file_for_range_read_false_test.go | 4 ++++ .../cache_file_for_range_read_true_test.go | 4 ++++ .../read_cache/disabled_cache_ttl_test.go | 4 ++++ .../read_cache/job_chunk_test.go | 4 ++++ .../read_cache/local_modification_test.go | 4 ++++ .../read_cache/range_read_test.go | 4 ++++ .../read_cache/read_only_test.go | 4 ++++ .../integration_tests/read_cache/remount_test.go | 4 ++++ .../read_cache/small_cache_ttl_test.go | 3 +++ tools/integration_tests/util/setup/setup.go | 16 ++++++++++------ 12 files changed, 47 insertions(+), 8 deletions(-) diff --git a/tools/integration_tests/managed_folders/admin_permissions_test.go b/tools/integration_tests/managed_folders/admin_permissions_test.go index 57cff598d8..f202494e4e 100644 --- a/tools/integration_tests/managed_folders/admin_permissions_test.go +++ b/tools/integration_tests/managed_folders/admin_permissions_test.go @@ -210,7 +210,7 @@ func TestManagedFolders_FolderAdminPermission(t *testing.T) { } setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) - defer setup.UnmountGCSFuseAndDeleteLogFile(rootDir) + defer setup.UnmountGCSFuse(rootDir) setup.SetMntDir(mountDir) // Run tests on given {Bucket permission, Managed folder permission}. diff --git a/tools/integration_tests/managed_folders/view_permissions_test.go b/tools/integration_tests/managed_folders/view_permissions_test.go index a18b3cadc4..7f84bd1a97 100644 --- a/tools/integration_tests/managed_folders/view_permissions_test.go +++ b/tools/integration_tests/managed_folders/view_permissions_test.go @@ -147,7 +147,7 @@ func TestManagedFolders_FolderViewPermission(t *testing.T) { flags = append(flags, "--key-file="+localKeyFilePath) } setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) - defer setup.UnmountGCSFuseAndDeleteLogFile(rootDir) + defer setup.UnmountGCSFuse(rootDir) setup.SetMntDir(mountDir) bucket, testDir = setup.GetBucketAndObjectBasedOnTypeOfMount(TestDirForManagedFolderTest) diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go index a003a7d5b1..06d0d5daa0 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go @@ -18,6 +18,7 @@ import ( "context" "log" "path" + "strings" "sync" "testing" @@ -50,6 +51,9 @@ func (s *cacheFileForRangeReadFalseTest) Setup(t *testing.T) { } func (s *cacheFileForRangeReadFalseTest) Teardown(t *testing.T) { + if t.Failed() { + setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) + } setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go index 0ad92c13bf..3c6408616d 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go @@ -18,6 +18,7 @@ import ( "context" "log" "path" + "strings" "testing" "time" @@ -47,6 +48,9 @@ func (s *cacheFileForRangeReadTrueTest) Setup(t *testing.T) { } func (s *cacheFileForRangeReadTrueTest) Teardown(t *testing.T) { + if t.Failed() { + setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) + } setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/disabled_cache_ttl_test.go b/tools/integration_tests/read_cache/disabled_cache_ttl_test.go index 10c1f36391..3469a6724e 100644 --- a/tools/integration_tests/read_cache/disabled_cache_ttl_test.go +++ b/tools/integration_tests/read_cache/disabled_cache_ttl_test.go @@ -17,6 +17,7 @@ package read_cache import ( "context" "log" + "strings" "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -46,6 +47,9 @@ func (s *disabledCacheTTLTest) Setup(t *testing.T) { } func (s *disabledCacheTTLTest) Teardown(t *testing.T) { + if t.Failed() { + setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) + } setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/job_chunk_test.go b/tools/integration_tests/read_cache/job_chunk_test.go index 9ad7f45bfd..63a40259b3 100644 --- a/tools/integration_tests/read_cache/job_chunk_test.go +++ b/tools/integration_tests/read_cache/job_chunk_test.go @@ -18,6 +18,7 @@ import ( "context" "log" "path" + "strings" "sync" "testing" @@ -51,6 +52,9 @@ func (s *jobChunkTest) Setup(t *testing.T) { } func (s *jobChunkTest) Teardown(t *testing.T) { + if t.Failed() { + setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) + } setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/local_modification_test.go b/tools/integration_tests/read_cache/local_modification_test.go index a03e98b92f..e3ee0386cc 100644 --- a/tools/integration_tests/read_cache/local_modification_test.go +++ b/tools/integration_tests/read_cache/local_modification_test.go @@ -18,6 +18,7 @@ import ( "context" "log" "path" + "strings" "testing" "cloud.google.com/go/storage" @@ -45,6 +46,9 @@ func (s *localModificationTest) Setup(t *testing.T) { } func (s *localModificationTest) Teardown(t *testing.T) { + if t.Failed() { + setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) + } setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/range_read_test.go b/tools/integration_tests/read_cache/range_read_test.go index b9f01d1e5b..95fb1a0d67 100644 --- a/tools/integration_tests/read_cache/range_read_test.go +++ b/tools/integration_tests/read_cache/range_read_test.go @@ -17,6 +17,7 @@ package read_cache import ( "context" "log" + "strings" "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -47,6 +48,9 @@ func (s *rangeReadTest) Setup(t *testing.T) { } func (s *rangeReadTest) Teardown(t *testing.T) { + if t.Failed() { + setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) + } setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/read_only_test.go b/tools/integration_tests/read_cache/read_only_test.go index f79458a5e5..e1524d4628 100644 --- a/tools/integration_tests/read_cache/read_only_test.go +++ b/tools/integration_tests/read_cache/read_only_test.go @@ -17,6 +17,7 @@ package read_cache import ( "context" "log" + "strings" "testing" "cloud.google.com/go/storage" @@ -45,6 +46,9 @@ func (s *readOnlyTest) Setup(t *testing.T) { } func (s *readOnlyTest) Teardown(t *testing.T) { + if t.Failed() { + setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) + } setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/remount_test.go b/tools/integration_tests/read_cache/remount_test.go index f600eb281d..e45fd61118 100644 --- a/tools/integration_tests/read_cache/remount_test.go +++ b/tools/integration_tests/read_cache/remount_test.go @@ -18,6 +18,7 @@ import ( "context" "log" "path" + "strings" "testing" "time" @@ -47,6 +48,9 @@ func (s *remountTest) Setup(t *testing.T) { } func (s *remountTest) Teardown(t *testing.T) { + if t.Failed() { + setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) + } setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/small_cache_ttl_test.go b/tools/integration_tests/read_cache/small_cache_ttl_test.go index 8b9e008443..cb99a5f87e 100644 --- a/tools/integration_tests/read_cache/small_cache_ttl_test.go +++ b/tools/integration_tests/read_cache/small_cache_ttl_test.go @@ -48,6 +48,9 @@ func (s *smallCacheTTLTest) Setup(t *testing.T) { } func (s *smallCacheTTLTest) Teardown(t *testing.T) { + if t.Failed() { + setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) + } setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 03afb0189b..5ddc6503ce 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -230,12 +230,16 @@ func SaveLogFileInCaseOfFailure(successCode int) { if successCode != 0 { // Logfile name will be gcsfuse-failed-integration-test-log-xxxxx failedlogsFileName := "gcsfuse-failed-integration-test-logs-" + GenerateRandomString(5) - log.Printf("log file is available on kokoro artifacts with file name: %s", failedlogsFileName) - logFileInKokoroArtifact := path.Join(os.Getenv("KOKORO_ARTIFACTS_DIR"), failedlogsFileName) - err := operations.CopyFile(logFile, logFileInKokoroArtifact) - if err != nil { - log.Fatalf("Error in coping logfile in kokoro artifact: %v", err) - } + SaveLogFileToKOKOROArtifact(failedlogsFileName) + } +} + +func SaveLogFileToKOKOROArtifact(artifactName string) { + log.Printf("log file is available on kokoro artifacts with file name: %s", artifactName) + logFileInKokoroArtifact := path.Join(os.Getenv("KOKORO_ARTIFACTS_DIR"), artifactName) + err := operations.CopyFile(logFile, logFileInKokoroArtifact) + if err != nil { + log.Fatalf("Error in coping logfile in kokoro artifact: %v", err) } } From 366f4bfc1f5ec549454355a8219754763ef0d3bb Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 6 Feb 2025 03:06:32 +0000 Subject: [PATCH 0215/1298] Create zonal-bucket flag for e2e tests (#2969) * Create and pass -zonal flag to e2e tests This only defines and passes the flag, but doesn't implement its usage in the e2e tests themselves. * address review comments And a minor bug-fix --- tools/integration_tests/run_e2e_tests.sh | 17 ++++++++++++++--- tools/integration_tests/util/setup/setup.go | 5 +++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index eb495e1826..72bd98c7c8 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -39,6 +39,17 @@ if [ $# -ge 5 ] ; then RUN_TESTS_WITH_PRESUBMIT_FLAG=$5 fi +# 6th parameter is set to enable/disable run for zonal bucket. +RUN_TESTS_WITH_ZONAL_BUCKET=false +if [[ $# -ge 6 ]] ; then + if [[ "$6" == "true" ]]; then + RUN_TESTS_WITH_ZONAL_BUCKET=true + elif [[ "$6" != "false" ]]; then + echo "Error: Invalid value for 6th argument: "$6" . Expected: true or false." + exit 1 + fi +fi + if [ "$#" -lt 3 ] then echo "Incorrect number of arguments passed, please refer to the script and pass the three arguments required..." @@ -162,7 +173,7 @@ function run_non_parallel_tests() { echo $log_file >> $TEST_LOGS_FILE # Executing integration tests - GODEBUG=asyncpreemptoff=1 go test $test_path_non_parallel -p 1 $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --integrationTest -v --testbucket=$bucket_name_non_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 + GODEBUG=asyncpreemptoff=1 go test $test_path_non_parallel -p 1 $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${RUN_TESTS_WITH_ZONAL_BUCKET} --integrationTest -v --testbucket=$bucket_name_non_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 exit_code_non_parallel=$? if [ $exit_code_non_parallel != 0 ]; then exit_code=$exit_code_non_parallel @@ -193,7 +204,7 @@ function run_parallel_tests() { local log_file="/tmp/${test_dir_p}_${bucket_name_parallel}.log" echo $log_file >> $TEST_LOGS_FILE # Executing integration tests - GODEBUG=asyncpreemptoff=1 go test $test_path_parallel $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG $benchmark_flags -p 1 --integrationTest -v --testbucket=$bucket_name_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & + GODEBUG=asyncpreemptoff=1 go test $test_path_parallel $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${RUN_TESTS_WITH_ZONAL_BUCKET} $benchmark_flags -p 1 --integrationTest -v --testbucket=$bucket_name_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & pid=$! # Store the PID of the background process pids+=("$pid") # Optionally add the PID to an array for later done @@ -293,7 +304,7 @@ function run_e2e_tests_for_tpc() { gcloud storage rm -r gs://gcsfuse-e2e-tests-tpc/** # Run Operations e2e tests in TPC to validate all the functionality. - GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... --testOnTPCEndPoint=$RUN_TEST_ON_TPC_ENDPOINT $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG -p 1 --integrationTest -v --testbucket=gcsfuse-e2e-tests-tpc --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT + GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... --testOnTPCEndPoint=$RUN_TEST_ON_TPC_ENDPOINT $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${RUN_TESTS_WITH_ZONAL_BUCKET} -p 1 --integrationTest -v --testbucket=gcsfuse-e2e-tests-tpc --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT exit_code=$? set -e diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 5ddc6503ce..ed2b04d988 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -37,6 +37,7 @@ import ( ) var isPresubmitRun = flag.Bool("presubmit", false, "Boolean flag to indicate if test-run is a presubmit run.") +var isZonalBucketRun = flag.Bool("zonal", false, "Boolean flag to indicate if test-run should use a zonal bucket.") var testBucket = flag.String("testbucket", "", "The GCS bucket used for the test.") var mountedDirectory = flag.String("mountedDirectory", "", "The GCSFuse mounted directory used for the test.") var integrationTest = flag.Bool("integrationTest", false, "Run tests only when the flag value is true.") @@ -76,6 +77,10 @@ func IsPresubmitRun() bool { return *isPresubmitRun } +func IsZonalBucketRun() bool { + return *isZonalBucketRun +} + func IsIntegrationTest() bool { return *integrationTest } From 10acb13826a725b94dcc10fa62a8311058d39da3 Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Thu, 6 Feb 2025 05:28:25 +0000 Subject: [PATCH 0216/1298] Maintain readType for random reader (#2971) We collect readType as part of our read metrics and hence need to ensure that proper readType value is being maintained and shared for metrics collection. b/394500505 --- internal/gcsx/random_reader.go | 21 +++--- internal/gcsx/random_reader_stretchr_test.go | 76 ++++++++++++++++++-- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index e05d059ac2..c7ec214181 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -119,6 +119,7 @@ func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb i limit: -1, seeks: 0, totalReadBytes: 0, + readType: util.Sequential, sequentialReadSizeMb: sequentialReadSizeMb, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, @@ -150,6 +151,9 @@ type randomReader struct { seeks uint64 totalReadBytes uint64 + // ReadType of the reader. Will be sequential by default. + readType string + sequentialReadSizeMb int32 // fileCacheHandler is used to get file cache handle and read happens using that. @@ -368,22 +372,20 @@ func (rr *randomReader) ReadAt( } if rr.reader != nil { - // TODO: Passing readType as "unhandled" here for now. Ideally - // readType should be stored in and obtained from the previous rr.reader. - objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, -1, "unhandled") + objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, -1, rr.readType) return } // If we don't have a reader, determine whether to read from NewReader or MRR. - readType, end, err := rr.getReadInfo(offset, int64(len(p))) + end, err := rr.getReadInfo(offset, int64(len(p))) if err != nil { err = fmt.Errorf("ReadAt: getReaderInfo: %w", err) return } - readerType := readerType(readType, offset, end, rr.bucket.BucketType()) + readerType := readerType(rr.readType, offset, end, rr.bucket.BucketType()) if readerType == RangeReader { - objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, end, readType) + objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, end, rr.readType) return } @@ -507,12 +509,12 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { return } -// getReaderInfo provides the readType and the range to query GCS. +// getReaderInfo determines the readType and provides the range to query GCS. // Range here is [start, end]. End is computed using the readType, start offset // and size of the data the callers needs. func (rr *randomReader) getReadInfo( start int64, - size int64) (readType string, end int64, err error) { + size int64) (end int64, err error) { // Make sure start and size are legal. if start < 0 || uint64(start) > rr.object.Size || size < 0 { err = fmt.Errorf( @@ -539,9 +541,8 @@ func (rr *randomReader) getReadInfo( // optimise for random reads. Random reads will read data in chunks of // (average read size in bytes rounded up to the next MB). end = int64(rr.object.Size) - readType = util.Sequential if rr.seeks >= minSeeksForRandom { - readType = util.Random + rr.readType = util.Random averageReadBytes := rr.totalReadBytes / rr.seeks if averageReadBytes < maxReadSize { randomReadSize := int64(((averageReadBytes / MB) + 1) * MB) diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 0666c80440..98bedb7096 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -113,7 +113,7 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo() { for _, tc := range testCases { t.Run(tc.name, func() { - _, _, err := t.rr.wrapped.getReadInfo(tc.start, tc.size) + _, err := t.rr.wrapped.getReadInfo(tc.start, tc.size) assert.Error(t.T(), err) errorString := fmt.Sprintf( @@ -138,10 +138,10 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo_Sequential() { for _, tc := range testCases { t.Run(tc.testName, func() { t.object.Size = tc.objectSize - readType, end, err := t.rr.wrapped.getReadInfo(tc.start, 10) + end, err := t.rr.wrapped.getReadInfo(tc.start, 10) assert.NoError(t.T(), err) - assert.Equal(t.T(), testutil.Sequential, readType) + assert.Equal(t.T(), testutil.Sequential, t.rr.wrapped.readType) assert.Equal(t.T(), tc.expectedEnd, end) }) } @@ -171,10 +171,10 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo_Random() { t.Run(tc.testName, func() { t.object.Size = tc.objectSize t.rr.wrapped.totalReadBytes = tc.totalReadBytes - readType, end, err := t.rr.wrapped.getReadInfo(tc.start, 10) + end, err := t.rr.wrapped.getReadInfo(tc.start, 10) assert.NoError(t.T(), err) - assert.Equal(t.T(), testutil.Random, readType) + assert.Equal(t.T(), testutil.Random, t.rr.wrapped.readType) assert.Equal(t.T(), tc.expectedEnd, end) }) } @@ -619,6 +619,72 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_InvalidOffset() { } } +func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { + testCases := []struct { + name string + dataSize int + bucketType gcs.BucketType + readRanges [][]int + expectedReadTypes []string + }{ + { + name: "SequentialReadFlat", + dataSize: 100, + bucketType: gcs.BucketType{Zonal: false}, + readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, + expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Sequential, testutil.Sequential}, + }, + { + name: "SequentialReadZonal", + dataSize: 100, + bucketType: gcs.BucketType{Zonal: true}, + readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, + expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Sequential, testutil.Sequential}, + }, + { + name: "RandomReadFlat", + dataSize: 100, + bucketType: gcs.BucketType{Zonal: false}, + readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, + expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Random, testutil.Random, testutil.Random}, + }, + { + name: "RandomReadZonal", + dataSize: 100, + bucketType: gcs.BucketType{Zonal: true}, + readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, + expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Random, testutil.Random, testutil.Random}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + assert.Equal(t.T(), len(tc.readRanges), len(tc.expectedReadTypes), "Test Parameter Error: readRanges and expectedReadTypes should have same length") + t.rr.wrapped.reader = nil + t.rr.wrapped.isMRDInUse = false + t.rr.wrapped.seeks = 0 + t.rr.wrapped.readType = testutil.Sequential + t.object.Size = uint64(tc.dataSize) + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + assert.Nil(t.T(), err, "Error in creating MRDWrapper") + t.rr.wrapped.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)) + t.mockBucket.On("BucketType", mock.Anything).Return(tc.bucketType).Times(len(tc.readRanges)) + + for i, readRange := range tc.readRanges { + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(&fake.FakeReader{ReadCloser: getReadCloser(testContent)}, nil).Once() + buf := make([]byte, readRange[1]-readRange[0]) + + _, err := t.rr.wrapped.ReadAt(t.rr.ctx, buf, int64(readRange[0])) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), tc.expectedReadTypes[i], t.rr.wrapped.readType) + } + }) + } +} + func (t *RandomReaderStretchrTest) Test_ReadAt_MRDRead() { testCases := []struct { name string From a6a037eb1aac85d571d101cd96c0603cf830121f Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Thu, 6 Feb 2025 06:16:42 +0000 Subject: [PATCH 0217/1298] Ensuring MRD in MRDWrapper is always valid (#2973) Since we have not added synchronization in the MRD creation logic, to ensure that there are no edge cases, we are updating MRD in MRDWrapper only when it is a valid MRD. This takes care of the edge case scenario where a nil MRD could have been used. b/394728598 --- internal/gcsx/multi_range_downloader_wrapper.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go index bdd2b97de8..632152dfd5 100644 --- a/internal/gcsx/multi_range_downloader_wrapper.go +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -124,7 +124,7 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) DecrementRefCount() (err error) { } mrdWrapper.refCount-- - if mrdWrapper.refCount == 0 { + if mrdWrapper.refCount == 0 && mrdWrapper.Wrapped != nil { mrdWrapper.Wrapped.Close() mrdWrapper.Wrapped = nil // TODO (b/391508479): Start using cleanup function when MRD recreation is handled @@ -164,11 +164,16 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) ensureMultiRangeDownloader() (err } if mrdWrapper.Wrapped == nil { - mrdWrapper.Wrapped, err = mrdWrapper.bucket.NewMultiRangeDownloader(context.Background(), &gcs.MultiRangeDownloaderRequest{ + var mrd gcs.MultiRangeDownloader + mrd, err = mrdWrapper.bucket.NewMultiRangeDownloader(context.Background(), &gcs.MultiRangeDownloaderRequest{ Name: mrdWrapper.object.Name, Generation: mrdWrapper.object.Generation, ReadCompressed: mrdWrapper.object.HasContentEncodingGzip(), }) + if err == nil { + // Updating mrdWrapper.Wrapped only when MRD creation was successful. + mrdWrapper.Wrapped = mrd + } } return } @@ -185,7 +190,6 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []b err = mrdWrapper.ensureMultiRangeDownloader() if err != nil { err = fmt.Errorf("MultiRangeDownloaderWrapper::Read: Error in creating MultiRangeDownloader: %v", err) - mrdWrapper.Wrapped = nil return } From 7bbde84a2502eec7c6792fceeafe89178b9b6d7e Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Fri, 7 Feb 2025 10:48:16 +0000 Subject: [PATCH 0218/1298] Fixing flushPendingWrites method (#2975) * Fixing flushPendingWrites method * fixing struct name * Fixing move_file test * Not flushing pending writes for local file * running rename test for buffered writes --- internal/fs/fs.go | 6 +++--- .../streaming_writes_empty_gcs_object_test.go | 21 +++++++++++++++++++ tools/integration_tests/local_file/rename.go | 2 +- ...{move_file_test.go => rename_file_test.go} | 6 ++++-- 4 files changed, 29 insertions(+), 6 deletions(-) rename tools/integration_tests/streaming_writes/{move_file_test.go => rename_file_test.go} (85%) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 617c959df9..e0ed806ac6 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2024,8 +2024,7 @@ func (fs *fileSystem) Rename( if err != nil { return err } - // For streaming writes, we will finalize localChild and do rename. - if localChild != nil && !fs.newConfig.Write.EnableStreamingWrites { + if localChild != nil { fs.unlockAndDecrementLookupCount(localChild, 1) return fmt.Errorf("cannot rename open file %q: %w", op.OldName, syscall.ENOTSUP) } @@ -2062,7 +2061,7 @@ func (fs *fileSystem) Rename( func (fs *fileSystem) renameFile(ctx context.Context, op *fuseops.RenameOp, child *inode.Core, oldParent inode.DirInode, newParent inode.DirInode) error { updatedMinObject, err := fs.flushPendingWrites(ctx, child) if err != nil { - return fmt.Errorf("flushBeforeRename error :%v", err) + return fmt.Errorf("flushPendingWrites error :%v", err) } if (child.Bucket.BucketType().Hierarchical && fs.enableAtomicRenameObject) || child.Bucket.BucketType().Zonal { @@ -2103,6 +2102,7 @@ func (fs *fileSystem) flushPendingWrites(ctx context.Context, child *inode.Core) defer fileInode.Unlock() // Try to flush if there are any pending writes. err = fs.flushFile(ctx, fileInode) + minObject = fileInode.Source() return } diff --git a/internal/fs/streaming_writes_empty_gcs_object_test.go b/internal/fs/streaming_writes_empty_gcs_object_test.go index 3cafa4e988..3774dc64be 100644 --- a/internal/fs/streaming_writes_empty_gcs_object_test.go +++ b/internal/fs/streaming_writes_empty_gcs_object_test.go @@ -19,6 +19,7 @@ package fs_test import ( "os" "path" + "strings" "syscall" "testing" @@ -65,6 +66,26 @@ func (t *StreamingWritesEmptyGCSObjectTest) SetupTest() { assert.NoError(t.T(), err) } +func (t *StreamingWritesEmptyGCSObjectTest) TestRenameFileWithPendingWrites() { + _, err := t.f1.Write([]byte("tacos")) + assert.NoError(t.T(), err) + newFilePath := path.Join(mntDir, "test.txt") + // Check that new file doesn't exist. + _, err = os.Stat(newFilePath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + + err = os.Rename(t.f1.Name(), newFilePath) + + assert.NoError(t.T(), err) + _, err = os.Stat(t.f1.Name()) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + content, err := os.ReadFile(newFilePath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), "tacos", string(content)) +} + func (t *StreamingWritesEmptyGCSObjectTest) TearDownTest() { t.fsTest.TearDown() } diff --git a/tools/integration_tests/local_file/rename.go b/tools/integration_tests/local_file/rename.go index 8f31976bc1..d4c50e4978 100644 --- a/tools/integration_tests/local_file/rename.go +++ b/tools/integration_tests/local_file/rename.go @@ -41,7 +41,7 @@ func verifyRenameOperationNotSupported(err error, t *testing.T) { // Tests //////////////////////////////////////////////////////////////////////// -func (t *localFileTestSuite) TestRenameOfLocalFileFails() { +func (t *CommonLocalFileTestSuite) TestRenameOfLocalFileFails() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file with some content. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) diff --git a/tools/integration_tests/streaming_writes/move_file_test.go b/tools/integration_tests/streaming_writes/rename_file_test.go similarity index 85% rename from tools/integration_tests/streaming_writes/move_file_test.go rename to tools/integration_tests/streaming_writes/rename_file_test.go index 96bc3517bc..77da2580f5 100644 --- a/tools/integration_tests/streaming_writes/move_file_test.go +++ b/tools/integration_tests/streaming_writes/rename_file_test.go @@ -22,7 +22,9 @@ import ( "github.com/stretchr/testify/require" ) -func (t *defaultMountCommonTest) TestMoveBeforeFileIsFlushed() { +// os.Rename can't be invoked over local files. It's failing with file not found error. +// Hence running this test only for empty GCS file. +func (t *defaultMountEmptyGCSFile) TestRenameBeforeFileIsFlushed() { operations.WriteWithoutClose(t.f1, FileContents, t.T()) operations.WriteWithoutClose(t.f1, FileContents, t.T()) operations.VerifyStatFile(t.filePath, int64(2*len(FileContents)), FilePerms, t.T()) @@ -31,7 +33,7 @@ func (t *defaultMountCommonTest) TestMoveBeforeFileIsFlushed() { newFile := "newFile.txt" destDirPath := path.Join(testDirPath, newFile) - err = operations.Move(t.filePath, destDirPath) + err = operations.RenameFile(t.filePath, destDirPath) // Validate that move didn't throw any error. require.NoError(t.T(), err) From 96dbe39a3b04e95cf7897c0bf576b64d01529160 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 11 Feb 2025 15:28:37 +0530 Subject: [PATCH 0219/1298] keep mapping between pid and package name (#2990) --- tools/integration_tests/run_e2e_tests.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 72bd98c7c8..54952ddab6 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -207,6 +207,7 @@ function run_parallel_tests() { GODEBUG=asyncpreemptoff=1 go test $test_path_parallel $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${RUN_TESTS_WITH_ZONAL_BUCKET} $benchmark_flags -p 1 --integrationTest -v --testbucket=$bucket_name_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & pid=$! # Store the PID of the background process pids+=("$pid") # Optionally add the PID to an array for later + package_names[$pid]=$test_dir_p # Keep mapping between package name and PID to print failed package un case of failure. done # Wait for processes and collect exit codes @@ -215,7 +216,8 @@ function run_parallel_tests() { exit_code_parallel=$? if [ $exit_code_parallel != 0 ]; then exit_code=$exit_code_parallel - echo "test fail in parallel on package: " $test_dir_p + package_name="${package_names[$pid]}" # Retrieve the package name + echo "test fail in parallel on package: " $package_name fi done return $exit_code From 114a94e3fb9a0ac4ae59546d12599be1a11fd475 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 11 Feb 2025 18:28:27 +0530 Subject: [PATCH 0220/1298] ignore benchmark tests in presubmit (#2992) Co-authored-by: Nitin Garg --- .../benchmarking/benchmark_delete_test.go | 2 ++ .../benchmarking/benchmark_rename_test.go | 2 ++ .../integration_tests/benchmarking/benchmark_stat_test.go | 2 ++ tools/integration_tests/util/setup/setup.go | 8 ++++++++ 4 files changed, 14 insertions(+) diff --git a/tools/integration_tests/benchmarking/benchmark_delete_test.go b/tools/integration_tests/benchmarking/benchmark_delete_test.go index 68e840e843..5e61051aa5 100644 --- a/tools/integration_tests/benchmarking/benchmark_delete_test.go +++ b/tools/integration_tests/benchmarking/benchmark_delete_test.go @@ -65,6 +65,8 @@ func (s *benchmarkDeleteTest) Benchmark_Delete(b *testing.B) { //////////////////////////////////////////////////////////////////////// func Benchmark_Delete(b *testing.B) { + setup.IgnoreTestIfPresubmitFlagIsSet(b) + ts := &benchmarkDeleteTest{} flagsSet := [][]string{ diff --git a/tools/integration_tests/benchmarking/benchmark_rename_test.go b/tools/integration_tests/benchmarking/benchmark_rename_test.go index eee265235b..3b9b69a8a6 100644 --- a/tools/integration_tests/benchmarking/benchmark_rename_test.go +++ b/tools/integration_tests/benchmarking/benchmark_rename_test.go @@ -65,6 +65,8 @@ func (s *benchmarkRenameTest) Benchmark_Rename(b *testing.B) { //////////////////////////////////////////////////////////////////////// func Benchmark_Rename(b *testing.B) { + setup.IgnoreTestIfPresubmitFlagIsSet(b) + ts := &benchmarkRenameTest{} flagsSet := [][]string{ {"--stat-cache-ttl=0", "--enable-atomic-rename-object=true"}, {"--client-protocol=grpc", "--stat-cache-ttl=0", "--enable-atomic-rename-object=true"}, diff --git a/tools/integration_tests/benchmarking/benchmark_stat_test.go b/tools/integration_tests/benchmarking/benchmark_stat_test.go index 2a2ba2c765..bda401f73c 100644 --- a/tools/integration_tests/benchmarking/benchmark_stat_test.go +++ b/tools/integration_tests/benchmarking/benchmark_stat_test.go @@ -74,6 +74,8 @@ func (s *benchmarkStatTest) Benchmark_Stat(b *testing.B) { //////////////////////////////////////////////////////////////////////// func Benchmark_Stat(b *testing.B) { + setup.IgnoreTestIfPresubmitFlagIsSet(b) + ts := &benchmarkStatTest{} flagsSet := [][]string{ {"--stat-cache-ttl=0"}, {"--client-protocol=grpc", "--stat-cache-ttl=0"}, diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index ed2b04d988..1cbb4ce39b 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -294,6 +294,14 @@ func IgnoreTestIfIntegrationTestFlagIsNotSet(t *testing.T) { } } +func IgnoreTestIfPresubmitFlagIsSet(b *testing.B) { + flag.Parse() + + if *isPresubmitRun { + b.SkipNow() + } +} + func ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() { ParseSetUpFlags() From 078d892227c588f48efdfb79d8653a83932ef6b2 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:04:45 +0530 Subject: [PATCH 0221/1298] Changes in client for zonal buckets in e2e tests (#2974) * use grpc client for zonal bucket * empty commit * address a review comment * address a review comment+code reuse * Fail if zonal bucket found in non-zonal run If a go-client is passed a zonal bucket i.e. with storage-type 'RAPID', with the e2e setup not expecting a zonal bucket (i.e. run with '--zonal=false'), then test run should fail. * error-handling * Rename NewWriterExt * fix lint error * address a review comment --- .../readonly/readonly_test.go | 20 +++++++--- .../util/client/storage_client.go | 40 +++++++++++++++++-- 2 files changed, 52 insertions(+), 8 deletions(-) diff --git a/tools/integration_tests/readonly/readonly_test.go b/tools/integration_tests/readonly/readonly_test.go index 218c05761c..824e13acc7 100644 --- a/tools/integration_tests/readonly/readonly_test.go +++ b/tools/integration_tests/readonly/readonly_test.go @@ -17,6 +17,7 @@ package readonly_test import ( "context" + "fmt" "log" "os" "path" @@ -55,7 +56,7 @@ var ( cacheDir string ) -func createTestDataForReadOnlyTests(ctx context.Context, storageClient *storage.Client) { +func createTestDataForReadOnlyTests(ctx context.Context, storageClient *storage.Client) error { // Define the text to write and the files to create files := []struct { fileContent string @@ -74,17 +75,23 @@ func createTestDataForReadOnlyTests(ctx context.Context, storageClient *storage. filePath := path.Join(dirPath, file.filePath) // Create a storage writer for the destination object object := bucketHandle.Object(filePath) - writer := object.NewWriter(ctx) + writer, err := client.NewWriter(ctx, object, storageClient) + if err != nil { + return fmt.Errorf("Error opening writer for object %s: %w\n", file.filePath, err) + } // Write the text to the object - if _, err := writer.Write([]byte(file.fileContent + "\n")); err != nil { + if _, err = writer.Write([]byte(file.fileContent + "\n")); err != nil { log.Printf("Error writing to object %s: %v\n", file.filePath, err) } - err := writer.Close() + err = writer.Close() if err != nil { log.Printf("Error in closing writer: %v", err) + return err } } + + return nil } func checkErrorForObjectNotExist(err error, t *testing.T) { @@ -139,7 +146,10 @@ func TestMain(m *testing.M) { } // Create test data. - createTestDataForReadOnlyTests(ctx, storageClient) + if err := createTestDataForReadOnlyTests(ctx, storageClient); err != nil { + log.Printf("Failed creating test data for readonly tests: %v", err) + os.Exit(1) + } // Run tests for mountedDirectory only if --mountedDirectory flag is set. setup.RunTestsForMountedDirectoryFlag(m) diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index a1a8013425..5ef70ae5b4 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -28,6 +28,7 @@ import ( "time" "cloud.google.com/go/storage" + "cloud.google.com/go/storage/experimental" "github.com/googleapis/gax-go/v2" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" @@ -50,7 +51,11 @@ func CreateStorageClient(ctx context.Context) (client *storage.Client, err error } client, err = storage.NewClient(ctx, option.WithEndpoint("storage.apis-tpczero.goog:443"), option.WithTokenSource(ts)) } else { - client, err = storage.NewClient(ctx) + if setup.IsZonalBucketRun() { + client, err = storage.NewGRPCClient(ctx, experimental.WithGRPCBidiReads()) + } else { + client, err = storage.NewClient(ctx) + } } if err != nil { return nil, fmt.Errorf("storage.NewClient: %w", err) @@ -123,6 +128,29 @@ func ReadChunkFromGCS(ctx context.Context, client *storage.Client, object string return string(content), nil } +// NewWriter is a wrapper over storage.NewWriter which +// extends support to zonal buckets. +func NewWriter(ctx context.Context, o *storage.ObjectHandle, client *storage.Client) (wc *storage.Writer, err error) { + wc = o.NewWriter(ctx) + + // Changes specific to zonal bucket + var attrs *storage.BucketAttrs + attrs, err = client.Bucket(o.BucketName()).Attrs(ctx) + if err != nil { + return nil, fmt.Errorf("failed to get attributes for bucket %q: %w", o.BucketName(), err) + } + if attrs.StorageClass == "RAPID" { + if setup.IsZonalBucketRun() { + // Zonal bucket writers require append-flag to be set. + wc.Append = true + } else { + return nil, fmt.Errorf("found zonal bucket %q in non-zonal e2e test run (--zonal=false)", o.BucketName()) + } + } + + return +} + func WriteToObject(ctx context.Context, client *storage.Client, object, content string, precondition storage.Conditions) error { bucket, object := setup.GetBucketAndObjectBasedOnTypeOfMount(object) @@ -132,7 +160,10 @@ func WriteToObject(ctx context.Context, client *storage.Client, object, content } // Upload an object with storage.Writer. - wc := o.NewWriter(ctx) + wc, err := NewWriter(ctx, o, client) + if err != nil { + return fmt.Errorf("Failed to open writer for object %q: %w", o.ObjectName(), err) + } if _, err := io.WriteString(wc, content); err != nil { return fmt.Errorf("io.WriteSTring: %w", err) } @@ -279,7 +310,10 @@ func StatObject(ctx context.Context, client *storage.Client, object string) (*st func UploadGcsObject(ctx context.Context, client *storage.Client, localPath, bucketName, objectName string, uploadGzipEncoded bool) error { // Create a writer to upload the object. obj := client.Bucket(bucketName).Object(objectName) - w := obj.NewWriter(ctx) + w, err := NewWriter(ctx, obj, client) + if err != nil { + return fmt.Errorf("Failed to open writer for GCS object gs://%s/%s: %w", bucketName, objectName, err) + } defer func() { if err := w.Close(); err != nil { log.Printf("Failed to close GCS object gs://%s/%s: %v", bucketName, objectName, err) From 0030fed159c640f95a08b31b3a3b70f92c791cf0 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 13 Feb 2025 11:31:13 +0530 Subject: [PATCH 0222/1298] Fix performance degradation due to metrics (#2978) * Fix metrics performance The gcs/read_bytes_count metric is single-handedly responsible for perf degradation due when metrics are enabled. With this change, we are now using an atomic Int64 which efficiently computes the metric. The Int64Counter that uses OTel for metric computation has now been replaced with Int64ObservableCounter that uses the aforementioned atomic Int64 to report the same metric. * Remove error check --- common/noop_metrics.go | 2 +- common/oc_metrics.go | 4 ++-- common/otel_metrics.go | 21 ++++++++++++++++----- common/telemetry.go | 2 +- internal/monitor/bucket.go | 4 +--- 5 files changed, 21 insertions(+), 12 deletions(-) diff --git a/common/noop_metrics.go b/common/noop_metrics.go index fd2e7dc319..b31fab8186 100644 --- a/common/noop_metrics.go +++ b/common/noop_metrics.go @@ -23,7 +23,7 @@ func NewNoopMetrics() MetricHandle { type noopMetrics struct{} -func (*noopMetrics) GCSReadBytesCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) GCSReadBytesCount(_ context.Context, _ int64) {} func (*noopMetrics) GCSReaderCount(_ context.Context, _ int64, _ []MetricAttr) {} func (*noopMetrics) GCSRequestCount(_ context.Context, _ int64, _ []MetricAttr) {} func (*noopMetrics) GCSRequestLatency(_ context.Context, value float64, _ []MetricAttr) {} diff --git a/common/oc_metrics.go b/common/oc_metrics.go index 2bcf633615..f99274cf71 100644 --- a/common/oc_metrics.go +++ b/common/oc_metrics.go @@ -91,8 +91,8 @@ func attrsToAddOption(attrs []MetricAttr) []metric.AddOption { return otelOptions } -func (o *ocMetrics) GCSReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { - recordOCMetric(ctx, o.gcsReadBytesCount, inc, attrs, "GCS read bytes count") +func (o *ocMetrics) GCSReadBytesCount(ctx context.Context, inc int64) { + recordOCMetric(ctx, o.gcsReadBytesCount, inc, nil, "GCS read bytes count") } func (o *ocMetrics) GCSReaderCount(ctx context.Context, inc int64, attrs []MetricAttr) { diff --git a/common/otel_metrics.go b/common/otel_metrics.go index 623be51a6a..39e2368c3d 100644 --- a/common/otel_metrics.go +++ b/common/otel_metrics.go @@ -17,6 +17,7 @@ package common import ( "context" "errors" + "sync/atomic" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" @@ -35,7 +36,7 @@ type otelMetrics struct { fsOpsLatency metric.Float64Histogram gcsReadCount metric.Int64Counter - gcsReadBytesCount metric.Int64Counter + gcsReadBytesCountInt *atomic.Int64 gcsReaderCount metric.Int64Counter gcsRequestCount metric.Int64Counter gcsRequestLatency metric.Float64Histogram @@ -46,8 +47,8 @@ type otelMetrics struct { fileCacheReadLatency metric.Float64Histogram } -func (o *otelMetrics) GCSReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { - o.gcsReadBytesCount.Add(ctx, inc, attrsToAddOption(attrs)...) +func (o *otelMetrics) GCSReadBytesCount(_ context.Context, inc int64) { + o.gcsReadBytesCountInt.Add(inc) } func (o *otelMetrics) GCSReaderCount(ctx context.Context, inc int64, attrs []MetricAttr) { @@ -101,10 +102,19 @@ func NewOTelMetrics() (MetricHandle, error) { fsOpsErrorCount, err3 := fsOpsMeter.Int64Counter("fs/ops_error_count", metric.WithDescription("The cumulative number of errors generated by file system operations")) gcsReadCount, err4 := gcsMeter.Int64Counter("gcs/read_count", metric.WithDescription("Specifies the number of gcs reads made along with type - Sequential/Random")) + gcsDownloadBytesCount, err5 := gcsMeter.Int64Counter("gcs/download_bytes_count", metric.WithDescription("The cumulative number of bytes downloaded from GCS along with type - Sequential/Random"), metric.WithUnit("By")) - gcsReadBytesCount, err6 := gcsMeter.Int64Counter("gcs/read_bytes_count", metric.WithDescription("The cumulative number of bytes read from GCS objects."), metric.WithUnit("By")) + + var gcsReadBytesCountInt atomic.Int64 + _, err6 := gcsMeter.Int64ObservableCounter("gcs/read_bytes_count", + metric.WithDescription("The cumulative number of bytes read from GCS objects."), + metric.WithUnit("By"), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + obsrv.Observe(gcsReadBytesCountInt.Load()) + return nil + })) gcsReaderCount, err7 := gcsMeter.Int64Counter("gcs/reader_count", metric.WithDescription("The cumulative number of GCS object readers opened or closed.")) gcsRequestCount, err8 := gcsMeter.Int64Counter("gcs/request_count", metric.WithDescription("The cumulative number of GCS requests processed.")) gcsRequestLatency, err9 := gcsMeter.Float64Histogram("gcs/request_latencies", metric.WithDescription("The cumulative distribution of the GCS request latencies."), metric.WithUnit("ms")) @@ -122,12 +132,13 @@ func NewOTelMetrics() (MetricHandle, error) { if err := errors.Join(err1, err2, err3, err4, err5, err6, err7, err8, err9, err10, err11, err12); err != nil { return nil, err } + return &otelMetrics{ fsOpsCount: fsOpsCount, fsOpsErrorCount: fsOpsErrorCount, fsOpsLatency: fsOpsLatency, gcsReadCount: gcsReadCount, - gcsReadBytesCount: gcsReadBytesCount, + gcsReadBytesCountInt: &gcsReadBytesCountInt, gcsReaderCount: gcsReaderCount, gcsRequestCount: gcsRequestCount, gcsRequestLatency: gcsRequestLatency, diff --git a/common/telemetry.go b/common/telemetry.go index ac42554dcc..e7e678f8b6 100644 --- a/common/telemetry.go +++ b/common/telemetry.go @@ -52,7 +52,7 @@ func (a *MetricAttr) String() string { } type GCSMetricHandle interface { - GCSReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) + GCSReadBytesCount(ctx context.Context, inc int64) GCSReaderCount(ctx context.Context, inc int64, attrs []MetricAttr) GCSRequestCount(ctx context.Context, inc int64, attrs []MetricAttr) GCSRequestLatency(ctx context.Context, value float64, attrs []MetricAttr) diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index ad695ff848..78964bd28f 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -231,9 +231,7 @@ type monitoringReadCloser struct { func (mrc *monitoringReadCloser) Read(p []byte) (n int, err error) { n, err = mrc.wrapped.Read(p) - if err == nil || err == io.EOF { - mrc.metricHandle.GCSReadBytesCount(mrc.ctx, int64(n), nil) - } + mrc.metricHandle.GCSReadBytesCount(mrc.ctx, int64(n)) return } From 6a294948cf02ddeccc5e46341b36078b919ac97d Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 13 Feb 2025 15:19:29 +0530 Subject: [PATCH 0223/1298] Rename data member (#2996) Rename a data member to make it explicit that it's atomic i.e. concurrent-safe. --- common/otel_metrics.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/common/otel_metrics.go b/common/otel_metrics.go index 39e2368c3d..6ce4acc2c9 100644 --- a/common/otel_metrics.go +++ b/common/otel_metrics.go @@ -35,12 +35,12 @@ type otelMetrics struct { fsOpsErrorCount metric.Int64Counter fsOpsLatency metric.Float64Histogram - gcsReadCount metric.Int64Counter - gcsReadBytesCountInt *atomic.Int64 - gcsReaderCount metric.Int64Counter - gcsRequestCount metric.Int64Counter - gcsRequestLatency metric.Float64Histogram - gcsDownloadBytesCount metric.Int64Counter + gcsReadCount metric.Int64Counter + gcsReadBytesCountAtomic *atomic.Int64 + gcsReaderCount metric.Int64Counter + gcsRequestCount metric.Int64Counter + gcsRequestLatency metric.Float64Histogram + gcsDownloadBytesCount metric.Int64Counter fileCacheReadCount metric.Int64Counter fileCacheReadBytesCount metric.Int64Counter @@ -48,7 +48,7 @@ type otelMetrics struct { } func (o *otelMetrics) GCSReadBytesCount(_ context.Context, inc int64) { - o.gcsReadBytesCountInt.Add(inc) + o.gcsReadBytesCountAtomic.Add(inc) } func (o *otelMetrics) GCSReaderCount(ctx context.Context, inc int64, attrs []MetricAttr) { @@ -107,12 +107,12 @@ func NewOTelMetrics() (MetricHandle, error) { metric.WithDescription("The cumulative number of bytes downloaded from GCS along with type - Sequential/Random"), metric.WithUnit("By")) - var gcsReadBytesCountInt atomic.Int64 + var gcsReadBytesCountAtomic atomic.Int64 _, err6 := gcsMeter.Int64ObservableCounter("gcs/read_bytes_count", metric.WithDescription("The cumulative number of bytes read from GCS objects."), metric.WithUnit("By"), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - obsrv.Observe(gcsReadBytesCountInt.Load()) + obsrv.Observe(gcsReadBytesCountAtomic.Load()) return nil })) gcsReaderCount, err7 := gcsMeter.Int64Counter("gcs/reader_count", metric.WithDescription("The cumulative number of GCS object readers opened or closed.")) @@ -138,7 +138,7 @@ func NewOTelMetrics() (MetricHandle, error) { fsOpsErrorCount: fsOpsErrorCount, fsOpsLatency: fsOpsLatency, gcsReadCount: gcsReadCount, - gcsReadBytesCountInt: &gcsReadBytesCountInt, + gcsReadBytesCountAtomic: &gcsReadBytesCountAtomic, gcsReaderCount: gcsReaderCount, gcsRequestCount: gcsRequestCount, gcsRequestLatency: gcsRequestLatency, From 68fa680d2ae080e25ef92579cf1b4da999f07d83 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Thu, 13 Feb 2025 12:00:17 +0000 Subject: [PATCH 0224/1298] Added new tests for streaming writes flow (#2937) * Fixing merge # Conflicts: # tools/integration_tests/streaming_writes/read_file_test.go # tools/integration_tests/write_large_files/write_large_files_test.go * fixing ci * Code review comments * Add tests for read and symlink. * start running common local file test from streaming writes package. * test running common file test from streaming package. * test running common file test from streaming package. * fixing lint --------- Co-authored-by: Mohit Yadav --- .../local_file/edit_file_test.go | 68 ++++++++++++ .../streaming_writes/read_file_test.go | 37 +++++++ .../util/operations/file_operations.go | 28 +++-- .../concurrent_write_to_same_file_test.go | 100 ++++++++++++++++++ .../write_large_files_test.go | 7 +- 5 files changed, 228 insertions(+), 12 deletions(-) create mode 100644 tools/integration_tests/local_file/edit_file_test.go create mode 100644 tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go diff --git a/tools/integration_tests/local_file/edit_file_test.go b/tools/integration_tests/local_file/edit_file_test.go new file mode 100644 index 0000000000..4edc7ce017 --- /dev/null +++ b/tools/integration_tests/local_file/edit_file_test.go @@ -0,0 +1,68 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package local_file + +import ( + "os" + "path" + + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/require" +) + +func (t *CommonLocalFileTestSuite) TestEditsToNewlyCreatedFile() { + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) + // Write some contents to file sequentially. + for i := 0; i < 3; i++ { + operations.WriteWithoutClose(fh, FileContents, t.T()) + } + // Close the file and validate that the file is created on GCS. + expectedContent := FileContents + FileContents + FileContents + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, expectedContent, t.T()) + + // Perform edit + fhNew := operations.OpenFile(path.Join(testDirPath, FileName1), t.T()) + newContent := "newContent" + _, err := fhNew.WriteAt([]byte(newContent), 0) + + require.Nil(t.T(), err) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fhNew, testDirName, FileName1, newContent+FileContents+FileContents, t.T()) +} + +func (t *CommonLocalFileTestSuite) TestAppendsToNewlyCreatedFile() { + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) + // Write some contents to file sequentially. + for i := 0; i < 3; i++ { + operations.WriteWithoutClose(fh, FileContents, t.T()) + } + // Close the file and validate that the file is created on GCS. + expectedContent := FileContents + FileContents + FileContents + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, expectedContent, t.T()) + + // Append to the file. + fhNew, err := os.OpenFile(path.Join(testDirPath, FileName1), os.O_RDWR|os.O_APPEND, operations.FilePermission_0777) + require.Nil(t.T(), err) + appendedContent := "appendedContent" + _, err = fhNew.Write([]byte(appendedContent)) + + require.Nil(t.T(), err) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fhNew, testDirName, FileName1, expectedContent+appendedContent, t.T()) +} diff --git a/tools/integration_tests/streaming_writes/read_file_test.go b/tools/integration_tests/streaming_writes/read_file_test.go index 406a55e5f6..175ca3e004 100644 --- a/tools/integration_tests/streaming_writes/read_file_test.go +++ b/tools/integration_tests/streaming_writes/read_file_test.go @@ -15,8 +15,12 @@ package streaming_writes import ( + "path" + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func (t *defaultMountCommonTest) TestReadLocalFileFails() { @@ -32,3 +36,36 @@ func (t *defaultMountCommonTest) TestReadLocalFileFails() { // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents, t.T()) } + +func (t *defaultMountCommonTest) TestReadBeforeFileIsFlushed() { + testContent := "testContent" + // Write data to file. + operations.WriteAt(testContent, 0, t.f1, t.T()) + + // Try to read the file. + _, err := t.f1.Seek(0, 0) + require.NoError(t.T(), err) + buf := make([]byte, 10) + _, err = t.f1.Read(buf) + + require.Error(t.T(), err, "input/output error") + // Validate if correct content is uploaded to GCS after read error. + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, testContent, t.T()) +} + +func (t *defaultMountCommonTest) TestReadAfterFlush() { + testContent := "testContent" + // Write data to file and flush. + operations.WriteAt(testContent, 0, t.f1, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, testContent, t.T()) + + // Perform read and validate the contents. + var err error + t.f1, err = operations.OpenFileAsReadonly(path.Join(testDirPath, t.fileName)) + require.NoError(t.T(), err) + buf := make([]byte, len(testContent)) + _, err = t.f1.Read(buf) + + require.NoError(t.T(), err) + require.Equal(t.T(), string(buf), testContent) +} diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 8ae7a3eb30..e8731aca27 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -230,6 +230,10 @@ func ReadFileSequentially(filePath string, chunkSize int64) (content []byte, err // Write data of chunkSize in file at given offset. func WriteChunkOfRandomBytesToFile(file *os.File, chunkSize int, offset int64) error { + return WriteChunkOfRandomBytesToFiles([]*os.File{file}, chunkSize, offset) +} + +func WriteChunkOfRandomBytesToFiles(files []*os.File, chunkSize int, offset int64) error { // Generate random data of chunk size. chunk := make([]byte, chunkSize) _, err := rand.Read(chunk) @@ -237,19 +241,21 @@ func WriteChunkOfRandomBytesToFile(file *os.File, chunkSize int, offset int64) e return fmt.Errorf("error while generating random string: %v", err) } - // Write data in the file. - n, err := file.WriteAt(chunk, offset) - if err != nil { - return fmt.Errorf("error in writing randomly in file: %v", err) - } + for _, file := range files { + // Write data in the file. + n, err := file.WriteAt(chunk, offset) + if err != nil { + return fmt.Errorf("error in writing randomly in file: %s, %v", file.Name(), err) + } - if n != chunkSize { - return fmt.Errorf("incorrect number of bytes written in the file actual %d, expected %d", n, chunkSize) - } + if n != chunkSize { + return fmt.Errorf("incorrect number of bytes written in the file %s actual %d, expected %d", file.Name(), n, chunkSize) + } - err = file.Sync() - if err != nil { - return fmt.Errorf("error in syncing file: %v", err) + err = file.Sync() + if err != nil { + return fmt.Errorf("error in syncing file: %v", err) + } } return nil diff --git a/tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go b/tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go new file mode 100644 index 0000000000..07ecb2090f --- /dev/null +++ b/tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go @@ -0,0 +1,100 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Provides integration tests for write large files sequentially and randomly. + +package write_large_files + +import ( + "os" + "path" + "syscall" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "golang.org/x/sync/errgroup" +) + +func TestWriteToSameFileConcurrently(t *testing.T) { + seqWriteDir := setup.SetupTestDirectory(DirForSeqWrite) + mountedFilePath := path.Join(seqWriteDir, "50mb"+setup.GenerateRandomString(5)+".txt") + localFilePath := path.Join(TmpDir, "50mbLocal"+setup.GenerateRandomString(5)+".txt") + localFile := operations.CreateFile(localFilePath, setup.FilePermission_0600, t) + + // Clean up. + defer operations.RemoveDir(seqWriteDir) + defer operations.RemoveFile(localFilePath) + + var eG errgroup.Group + concurrentWriterCount := 5 + chunkSize := 50 * OneMiB / concurrentWriterCount + + // We will have x numbers of concurrent threads trying to write from the same file. + // Every thread will start at offset = thread_index * (fileSize/thread_count). + for i := 0; i < concurrentWriterCount; i++ { + offset := i * chunkSize + + eG.Go(func() error { + return writeToFileSequentially(localFilePath, mountedFilePath, offset, offset+chunkSize, t) + }) + } + + // Wait on threads to end. + err := eG.Wait() + if err != nil { + t.Errorf("writing failed") + } + + // Close the local file since the below method will open the file again. + operations.CloseFile(localFile) + + identical, err := operations.AreFilesIdentical(mountedFilePath, localFilePath) + if !identical { + t.Fatalf("Comparision failed: %v", err) + } +} + +func writeToFileSequentially(localFilePath string, mountedFilePath string, startOffset int, endOffset int, t *testing.T) (err error) { + mountedFile, err := os.OpenFile(mountedFilePath, os.O_RDWR|syscall.O_DIRECT|os.O_CREATE, setup.FilePermission_0600) + if err != nil { + t.Fatalf("Error in opening file: %v", err) + } + + localFile, err := os.OpenFile(localFilePath, os.O_RDWR|syscall.O_DIRECT|os.O_CREATE, setup.FilePermission_0600) + if err != nil { + t.Fatalf("Error in opening file: %v", err) + } + + filesToWrite := []*os.File{localFile, mountedFile} + + // Closing file at the end. + defer operations.CloseFile(mountedFile) + defer operations.CloseFile(localFile) + + var chunkSize = 5 * OneMiB + for startOffset < endOffset { + if (endOffset - startOffset) < chunkSize { + chunkSize = endOffset - startOffset + } + + err := operations.WriteChunkOfRandomBytesToFiles(filesToWrite, chunkSize, int64(startOffset)) + if err != nil { + t.Fatalf("Error in writing chunk: %v", err) + } + + startOffset = startOffset + chunkSize + } + return +} diff --git a/tools/integration_tests/write_large_files/write_large_files_test.go b/tools/integration_tests/write_large_files/write_large_files_test.go index 26003f7540..2d88ee1291 100644 --- a/tools/integration_tests/write_large_files/write_large_files_test.go +++ b/tools/integration_tests/write_large_files/write_large_files_test.go @@ -54,7 +54,12 @@ func compareFileFromGCSBucketAndMntDir(gcsFile, mntDirFile, localFilePathToDownl func TestMain(m *testing.M) { setup.ParseSetUpFlags() - flags := [][]string{{"--enable-streaming-writes=true"}} + // write-global-max-blocks=2 is for checking multiple file writes in parallel. + // concurrent_write_files_test.go- we are writing 3 files in parallel. + // with this config, we are giving 2 blocks to 2 files and 1 block to other file. + flags := [][]string{ + {"--enable-streaming-writes=false"}, + {"--enable-streaming-writes=true", "--write-max-blocks-per-file=2", "--write-global-max-blocks=2"}} setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() From 3bea0453138557f9a712f2a5ef60a9d8aaa4704f Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 14 Feb 2025 15:16:35 +0530 Subject: [PATCH 0225/1298] Reducing the number of metric updates (#2993) * Reducing the number of metric updates * Wait for the entire buffer to be filled up before updating the metrics. This helps reduce the number of updates to the gcs/read_bytes_count metric by a factor of 1000 there by improving performance and reducing CPU and memory usage. --- internal/monitor/bucket.go | 2 + internal/monitor/full_read_closer.go | 47 +++++++++++ internal/monitor/full_read_closer_test.go | 97 +++++++++++++++++++++++ 3 files changed, 146 insertions(+) create mode 100644 internal/monitor/full_read_closer.go create mode 100644 internal/monitor/full_read_closer_test.go diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index 78964bd28f..2ae5cff2e1 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -63,7 +63,9 @@ func setupReader(ctx context.Context, mb *monitoringBucket, req *gcs.ReadObjectR startTime := time.Now() rc, err := mb.wrapped.NewReaderWithReadHandle(ctx, req) + if err == nil { + rc = newGCSFullReadCloser(rc) rc = newMonitoringReadCloser(ctx, req.Name, rc, mb.metricHandle) } diff --git a/internal/monitor/full_read_closer.go b/internal/monitor/full_read_closer.go new file mode 100644 index 0000000000..16e36e0abf --- /dev/null +++ b/internal/monitor/full_read_closer.go @@ -0,0 +1,47 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package monitor + +import ( + "io" + + storagev2 "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" +) + +// gcsFullReadCloser wraps a gcs.StorageReader and ensures that the Read call reads the entire response up to the buffer size even if the wrapped read returns data in smaller chunks. +type gcsFullReadCloser struct { + wrapped gcs.StorageReader +} + +func newGCSFullReadCloser(reader gcs.StorageReader) gcs.StorageReader { + return gcsFullReadCloser{wrapped: reader} +} + +// Read reads exactly len(buf) bytes from the wrapped StorageReader into buf. +// 1. the number of bytes copied and an ErrUnexpectedEOF if response size < buffer size +// 2. EOF only if no bytes were read. +// 3. n == len(buf) if and only if err == nil. +func (frc gcsFullReadCloser) Read(buf []byte) (n int, err error) { + return io.ReadFull(frc.wrapped, buf) +} + +func (frc gcsFullReadCloser) ReadHandle() (rh storagev2.ReadHandle) { + return frc.wrapped.ReadHandle() +} + +func (frc gcsFullReadCloser) Close() (err error) { + return frc.wrapped.Close() +} diff --git a/internal/monitor/full_read_closer_test.go b/internal/monitor/full_read_closer_test.go new file mode 100644 index 0000000000..48f7c8c80a --- /dev/null +++ b/internal/monitor/full_read_closer_test.go @@ -0,0 +1,97 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package monitor + +import ( + "bytes" + "io" + "testing" + + storagev2 "cloud.google.com/go/storage" + "github.com/stretchr/testify/assert" +) + +// twoBytesStorageReader reads at most 2 bytes from the buffer in one go. +type twoBytesStorageReader struct { + buf *bytes.Buffer +} + +func (psr twoBytesStorageReader) ReadHandle() (rh storagev2.ReadHandle) { + return nil +} + +func (psr twoBytesStorageReader) Close() (err error) { + return nil +} + +func (psr twoBytesStorageReader) Read(b []byte) (n int, err error) { + maxBytes := 2 + bufLen := min(len(b), maxBytes) + temp := make([]byte, bufLen) + n, err = psr.buf.Read(temp) + copy(b, temp) + return n, err +} + +func TestFullReaderCloser(t *testing.T) { + t.Parallel() + tests := []struct { + name string + bufSize int + data []byte + expectedData []byte + expectedErr error + }{ + { + name: "large_buffer", + data: []byte("0123"), + bufSize: 5, + expectedData: []byte("0123"), + expectedErr: io.ErrUnexpectedEOF, + }, + { + name: "small_buffer", + data: []byte("0123"), + bufSize: 2, + expectedData: []byte("01"), + expectedErr: nil, + }, + { + name: "equal_buffer", + data: []byte("0123"), + bufSize: 4, + expectedData: []byte("0123"), + expectedErr: nil, + }, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + storageReader := twoBytesStorageReader{ + buf: new(bytes.Buffer), + } + storageReader.buf.Write(tc.data) + fullReadCloser := newGCSFullReadCloser(storageReader) + buffer := make([]byte, tc.bufSize) + + n, err := fullReadCloser.Read(buffer) + + assert.Equal(t, len(tc.expectedData), n) + assert.Equal(t, tc.expectedErr, err) + assert.Equal(t, tc.expectedData[:n], buffer[:n]) + }) + } +} From 6e6fabe167dabed678e4d4936d8da99b5feb7d14 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:35:26 +0530 Subject: [PATCH 0226/1298] Empty commit (#3000) From d5c689ca83dbd68dbc2cc3d17f1a96a67584d68e Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Mon, 17 Feb 2025 10:09:06 +0530 Subject: [PATCH 0227/1298] Add Troubleshooting guide for No space left on Device As part of onduty bug b/395736039 , adding this troubleshooting steps to help. --- docs/troubleshooting.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 1d7389c392..580297d237 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -145,3 +145,10 @@ If it's running on GKE, the issue could be caused by an Out-of-Memory (OOM) erro **Solution:** This happens when the mounting bucket contains an object with suffix `/\n` like, `gs://gcs-bkt/a/\n` You need to find such objects and replace them with any other valid gcs object names. - [How](https://github.com/GoogleCloudPlatform/gcsfuse/discussions/2894)? +### OSError [ErrNo 28] No space left on device + +The Writes in GCSFuse are staged locally before they are uploaded to GCS buckets. It takes up the space of the files that are being uploaded concurrently and deleted locally once they are uploaded. During this time, since the disk is used, it may get used up. + +The path can be configured by using the mount flag [--temp-dir](https://cloud.google.com/storage/docs/cloud-storage-fuse/cli-options) to a path which has the disk space if available. By default, it takes the temp directory of the machine. + +Alternatively, from [GCSFuse version 2.9.1](https://github.com/GoogleCloudPlatform/gcsfuse/releases/tag/v2.9.1) onwards, writes can be configured with streaming writes feature ( which doesnt involve staging the file locally ) with the help of --enable-streaming-writes flag From ecde726624b2be11d3f4ecfaaf4e6c9865c28914 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Mon, 17 Feb 2025 10:12:33 +0530 Subject: [PATCH 0228/1298] Nit changes. --- docs/troubleshooting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 580297d237..f0766ef99c 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -147,8 +147,8 @@ You need to find such objects and replace them with any other valid gcs object n ### OSError [ErrNo 28] No space left on device -The Writes in GCSFuse are staged locally before they are uploaded to GCS buckets. It takes up the space of the files that are being uploaded concurrently and deleted locally once they are uploaded. During this time, since the disk is used, it may get used up. +The Writes in GCSFuse are staged locally before they are uploaded to GCS buckets. It takes up the space of the files that are being uploaded concurrently and deleted locally once they are uploaded. During this time, since the disk is used, this error may come up. -The path can be configured by using the mount flag [--temp-dir](https://cloud.google.com/storage/docs/cloud-storage-fuse/cli-options) to a path which has the disk space if available. By default, it takes the temp directory of the machine. +The path can be configured by using the mount flag [--temp-dir](https://cloud.google.com/storage/docs/cloud-storage-fuse/cli-options) to a path which has the disk space if available. By default, it takes the temp directory of the machine. (sometimes may be limited depending on path set on the machine ). Alternatively, from [GCSFuse version 2.9.1](https://github.com/GoogleCloudPlatform/gcsfuse/releases/tag/v2.9.1) onwards, writes can be configured with streaming writes feature ( which doesnt involve staging the file locally ) with the help of --enable-streaming-writes flag From b52c826b6b65d9301482dd4fc9c998f924554204 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 22 Jan 2025 10:33:53 +0000 Subject: [PATCH 0229/1298] Add sample test for failure. --- .../streaming_writes/upload_failure_test.go | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tools/integration_tests/streaming_writes/upload_failure_test.go diff --git a/tools/integration_tests/streaming_writes/upload_failure_test.go b/tools/integration_tests/streaming_writes/upload_failure_test.go new file mode 100644 index 0000000000..03415087d5 --- /dev/null +++ b/tools/integration_tests/streaming_writes/upload_failure_test.go @@ -0,0 +1,42 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes + +import ( + "testing" + + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +func TestStreamingWritesObjectWriterThrottled(t *testing.T) { + flags := []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"} + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) + defer setup.UnmountGCSFuse(rootDir) + testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + data, err := operations.GenerateRandomData(2 * 1024 * 1024) + if err != nil { + t.Fatalf("Error in generating data: %v", err) + } + + // Write data to file. + operations.WriteAt(string(data[:]), 0, fh, t) + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, + FileName1, string(data[:]), t) +} From 995c1a0a4a339c54c9e69f8de22cbb5b0f41d5fb Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Thu, 23 Jan 2025 17:20:45 +0000 Subject: [PATCH 0230/1298] Add failure scenario buffered writes integration tests for local files. --- .../emulator_tests/emulator_tests.sh | 17 +- .../second_chunk_upload_returns412.yaml | 8 + .../local_file_failure_test.go | 303 ++++++++++++++++++ .../streaming_writes_failure_test.go | 56 ++++ .../emulator_tests/util/test_helper.go | 5 +- .../write_stall_test.go} | 2 +- .../writes_stall_on_sync_test.go} | 8 +- .../streaming_writes/upload_failure_test.go | 42 --- .../util/client/storage_client.go | 2 + .../util/operations/file_operations.go | 12 + tools/integration_tests/util/setup/setup.go | 6 + 11 files changed, 409 insertions(+), 52 deletions(-) create mode 100644 tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml create mode 100644 tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go create mode 100644 tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go rename tools/integration_tests/emulator_tests/{emulator_test.go => write_stall/write_stall_test.go} (98%) rename tools/integration_tests/emulator_tests/{write_stall_test.go => write_stall/writes_stall_on_sync_test.go} (95%) delete mode 100644 tools/integration_tests/streaming_writes/upload_failure_test.go diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index 70a7e37b14..126a5c8544 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -59,7 +59,7 @@ if [ -f /etc/debian_version ]; then $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin + sudo apt-get install -y docker-ce docker-ce-cli containerd.io # TODO(mohitkyadav): These dependencies do not work on Cloudtop docker-buildx-plugin docker-compose-plugin sudo apt-get install -y lsof # RHEL/CentOS based machine. elif [ -f /etc/redhat-release ]; then @@ -88,6 +88,14 @@ DOCKER_NETWORK="--net=host" # Get the docker image for the testbench sudo docker pull $DOCKER_IMAGE +# Remove the docker container if it's already running. +CONTAINER_ID=$(sudo docker ps -aqf "name=$CONTAINER_NAME") +if [[ -n "$CONTAINER_ID" ]]; then + echo "Container with ID:[$CONTAINER_ID] is already running with name:[$CONTAINER_NAME]" + echo "Stoping container...." + sudo docker stop $CONTAINER_ID +fi + # Start the testbench sudo docker run --name $CONTAINER_NAME --rm -d $DOCKER_NETWORK $DOCKER_IMAGE echo "Running the Cloud Storage testbench: $STORAGE_EMULATOR_HOST" @@ -112,5 +120,8 @@ curl -X POST --data-binary @test.json \ "$STORAGE_EMULATOR_HOST/storage/v1/b?project=test-project" rm test.json -# Run specific test suite -go test ./tools/integration_tests/emulator_tests/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE +# Run Write Stall Tests. +go test ./tools/integration_tests/emulator_tests/write_stall/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE + +# Run Streaming Writes Failure Tests. +go test ./tools/integration_tests/emulator_tests/streaming_writes_failure/... -p 1 -short --integrationTest -v --testbucket=test-bucket --testOnCustomEndpoint=http://localhost:8020 -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml b/tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml new file mode 100644 index 0000000000..8464699da6 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml @@ -0,0 +1,8 @@ +targetHost: http://localhost:9000 +retryConfig: +- method: JsonCreate + retryInstruction: "return-412" + retryCount: 1 + # Skip count of three is required as first call is used to create the testDir on the GCS and then + # second and third call are used in creating resumable upload session uri and first chunk upload. + skipCount: 3 diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go new file mode 100644 index 0000000000..09f2a7c1c7 --- /dev/null +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go @@ -0,0 +1,303 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes_failure + +import ( + "context" + "log" + "os" + "testing" + + "cloud.google.com/go/storage" + emulator_tests "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/emulator_tests/util" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type defaultFailureTestSuite struct { + suite.Suite + configPath string + flags []string + testDirPath string + filePath string + fh1 *os.File + storageClient *storage.Client + closeStorageClient func() error + ctx context.Context +} + +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// +func (t *defaultFailureTestSuite) SetupSuite() { + t.configPath = "../proxy_server/configs/second_chunk_upload_returns412.yaml" + t.flags = []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1", "--custom-endpoint=" + proxyEndpoint} + log.Printf("Running tests with flags: %v", t.flags) +} + +func (t *defaultFailureTestSuite) SetupTest() { + // Start proxy server for each test to ensure the config is initialized per test. + emulator_tests.StartProxyServer(t.configPath) + // Create storage client before running tests. + t.ctx = context.Background() + t.closeStorageClient = client.CreateStorageClientWithCancel(&t.ctx, &t.storageClient) + setup.MountGCSFuseWithGivenMountFunc(t.flags, mountFunc) + // Setup test directory for testing. + t.testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + t.filePath, t.fh1 = CreateLocalFileInTestDir(t.ctx, t.storageClient, t.testDirPath, FileName1, t.T()) +} + +func (t *defaultFailureTestSuite) TearDownTest() { + // CleanUp MntDir before unmounting GCSFuse. + setup.CleanUpDir(rootDir) + setup.UnmountGCSFuse(rootDir) + err := t.closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + assert.NoError(t.T(), emulator_tests.KillProxyServerProcess(port)) +} + +func (t *defaultFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFailure() { + // Generate 5 MB random data. + data, err := operations.GenerateRandomData(5 * operations.MiB) + if err != nil { + t.T().Fatalf("Error in generating data: %v", err) + } + // Write first 2 MB (say A,B) block to file succeeds. + // Fuse:[B] -> Go-SDK:[A]-> GCS[] + _, err = t.fh1.WriteAt(data[:2*operations.MiB], 0) + assert.NoError(t.T(), err) + // Write again 2MB (C, D) may or may not fail based on the status of block B upload but it ensures the block B + // is uploaded after the write and error is propagated. + // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] + _, err = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + + // Write 5th 1MB results in errors. + _, err = t.fh1.WriteAt(data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + + assert.Error(t.T(), err) + // Opening new file handle succeeds. + fh2 := operations.OpenFile(t.filePath, t.T()) + // Writes with fh2 also fails. + _, err = fh2.WriteAt(data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + assert.Error(t.T(), err) + // Closing first file handle. + operations.CloseFileShouldThrowError(t.fh1, t.T()) + // Creating new file with same name will fail unless all file handles are closed. + // TODO(mohitkyadav): Uncomment when b/395029462 is fixed. + // _, err = os.OpenFile(filePath, os.O_CREATE, FilePerms) + // assert.Error(t.T(), err) + // Close last file handle. + operations.CloseFileShouldThrowError(fh2, t.T()) + // Verify that Object is not found on GCS. + ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) + // Opening new file handle and writing to file succeeds. + fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) + _, err = fh3.WriteAt(data, 0) + assert.NoError(t.T(), err) + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh3, testDirName, FileName1, string(data), t.T()) +} + +func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecondChunkUploadFailure() { + // Generate 5 MB random data. + data, err := operations.GenerateRandomData(5 * operations.MiB) + if err != nil { + t.T().Fatalf("Error in generating data: %v", err) + } + // Write first 2 MB (say A,B) block to file succeeds. + // Fuse:[B] -> Go-SDK:[A]-> GCS[] + _, err = t.fh1.WriteAt(data[:2*operations.MiB], 0) + assert.NoError(t.T(), err) + // Write again 2MB (C, D) may or may not fail based on the status of block B upload but it ensures the block B + // is uploaded after the write and error is propagated. + // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] + _, err = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + + // Write 5th 1MB results in errors as it sees the error propagated via B upload failure. + _, err = t.fh1.WriteAt(data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + + assert.Error(t.T(), err) + // Truncate to smaller size fails. + err = t.fh1.Truncate(1 * operations.MiB) + assert.Error(t.T(), err) + // Opening new file handle succeeds. + fh2 := operations.OpenFile(t.filePath, t.T()) + // Write still fails. + _, err = fh2.WriteAt(data[3*operations.MiB:4*operations.MiB], 3*operations.MiB) + assert.Error(t.T(), err) + // Closing all file handles to reinitialize bwh. + operations.CloseFileShouldThrowError(fh2, t.T()) + operations.CloseFileShouldThrowError(t.fh1, t.T()) + // Verify that Object is not found on GCS. + ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) + // Opening new file handle and writing to file succeeds. + fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) + _, err = fh3.WriteAt(data, 0) + assert.NoError(t.T(), err) + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh3, testDirName, FileName1, string(data), t.T()) +} + +func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSecondChunkUploadFailure() { + // Generate 5 MB random data. + data, err := operations.GenerateRandomData(5 * operations.MiB) + if err != nil { + t.T().Fatalf("Error in generating data: %v", err) + } + // Write first 2 MB (say A,B) block to file succeeds. + // Fuse:[B] -> Go-SDK:[A]-> GCS[] + _, err = t.fh1.WriteAt(data[:2*operations.MiB], 0) + assert.NoError(t.T(), err) + // Write again 2MB (C, D) may succeed based on the status of block B. + // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] + _, err = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + + // Write 5th 1MB results in errors as it sees the error propagated via B upload failure. + _, err = t.fh1.WriteAt(data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + + assert.Error(t.T(), err) + // Opening new file handle succeeds. + fh2 := operations.OpenFile(t.filePath, t.T()) + // Truncate to bigger size succeeds. + err = fh2.Truncate(5 * operations.MiB) + assert.NoError(t.T(), err) + // Closing all file handles to reinitialize bwh. + operations.CloseFileShouldThrowError(fh2, t.T()) + operations.CloseFileShouldThrowError(t.fh1, t.T()) + // Verify that Object is not found on GCS. + ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) + // Opening new file handle and writing to file succeeds. + fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) + _, err = fh3.WriteAt(data, 0) + assert.NoError(t.T(), err) + // Truncate to bigger succeeds. + err = fh3.Truncate(6 * operations.MiB) + assert.NoError(t.T(), err) + // Close and validate object content found on GCS. + emptyBytes := make([]byte, operations.MiB) + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh3, testDirName, FileName1, string(data)+string(emptyBytes), t.T()) +} + +func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploadFailure() { + // Generate 5 MB random data. + data, err := operations.GenerateRandomData(6 * operations.MiB) + if err != nil { + t.T().Fatalf("Error in generating data: %v", err) + } + // Write first 2 MB (say A,B) block to file succeeds. + // Fuse:[B] -> Go-SDK:[A]-> GCS[] + _, err = t.fh1.WriteAt(data[:2*operations.MiB], 0) + assert.NoError(t.T(), err) + // Sync file succeeds as the block B is only passed to Go-SDK. + // Fuse:[] -> Go-SDK:[B]-> GCS[A] + operations.SyncFile(t.fh1, t.T()) + + // Write next 1 MB block C may succeed based on the status of block B. + // Fuse:[C] -> Go-SDK:[B]-> GCS[A] + _, err = t.fh1.WriteAt(data[2*operations.MiB:3*operations.MiB], 2*operations.MiB) + + // Sync now reports failure from B block upload. + // Fuse:[] -> Go-SDK:[C]-> GCS[A, B -> upload fails] + operations.SyncFileShouldThrowError(t.fh1, t.T()) + // Opening new file handle fails. + _, err = os.OpenFile(t.filePath, os.O_RDWR, FilePerms) + assert.Error(t.T(), err) + // Close file handle to reinitialize bwh. + operations.CloseFileShouldThrowError(t.fh1, t.T()) + // Verify that Object is not found on GCS. + ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) + // Opening new file handle and writing to file succeeds. + fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) + _, err = fh3.WriteAt(data, 0) + assert.NoError(t.T(), err) + // Sync succeeds after bwh reinitialization. + err = fh3.Sync() + assert.NoError(t.T(), err) + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh3, testDirName, FileName1, string(data), t.T()) +} + +func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUploadFailure() { + // Generate 5 MB random data. + data, err := operations.GenerateRandomData(6 * operations.MiB) + if err != nil { + t.T().Fatalf("Error in generating data: %v", err) + } + // Write first 2 MB (say A,B) block to file succeeds. + // Fuse:[B] -> Go-SDK:[A]-> GCS[] + _, err = t.fh1.WriteAt(data[:2*operations.MiB], 0) + assert.NoError(t.T(), err) + + // Close fails as it sees error from B block upload. + err = t.fh1.Close() + + assert.NotNil(t.T(), err) + // Opening new file handle fails. + _, err = os.OpenFile(t.filePath, os.O_RDWR, FilePerms) + assert.Error(t.T(), err) + // Close file handle to reinitialize bwh. + operations.CloseFileShouldThrowError(t.fh1, t.T()) + // Verify that Object is not found on GCS. + ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) + // Opening new file handle and writing to file succeeds. + fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) + _, err = fh3.WriteAt(data, 0) + assert.NoError(t.T(), err) + // Close succeeds after bwh reinitialization. + operations.CloseFileShouldNotThrowError(fh3, t.T()) + // Validate object content found on GCS. + ValidateObjectContentsFromGCS(t.ctx, t.storageClient, testDirName, FileName1, string(data), t.T()) +} + +func (t *defaultFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() { + // Generate 1 MB random data. + data, err := operations.GenerateRandomData(operations.MiB) + if err != nil { + t.T().Fatalf("Error in generating data: %v", err) + } + _, err = t.fh1.WriteAt(data, 0) + assert.NoError(t.T(), err) + + // Close fails as it sees error on the finalize. + err = t.fh1.Close() + + assert.NotNil(t.T(), err) + // Verify that Object is not found on GCS. + ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) + // Opening new file handle and writing to file succeeds. + fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) + _, err = fh3.WriteAt(data, 0) + assert.NoError(t.T(), err) + // Close succeeds after bwh reinitialization. + operations.CloseFileShouldNotThrowError(fh3, t.T()) + // Validate object content found on GCS. + ValidateObjectContentsFromGCS(t.ctx, t.storageClient, testDirName, FileName1, string(data), t.T()) +} + +func TestUploadFailureTestSuite(t *testing.T) { + suite.Run(t, new(defaultFailureTestSuite)) +} diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go new file mode 100644 index 0000000000..8be711bf51 --- /dev/null +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go @@ -0,0 +1,56 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes_failure + +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +const ( + testDirName = "StreamingWritesFailureTest" + port = 8020 +) + +var ( + proxyEndpoint = fmt.Sprintf("http://localhost:%d/storage/v1/b?project=test-project", port) + mountFunc func([]string) error + // root directory is the directory to be unmounted. + rootDir string +) + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + // To run mountedDirectory tests, we need both testBucket and mountedDirectory + // flags to be set, as operations tests validates content from the bucket. + if setup.MountedDirectory() != "" { + + } + + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + rootDir = setup.MntDir() + log.Printf("Test log: %s\n", setup.LogFile()) + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() + os.Exit(successCode) +} diff --git a/tools/integration_tests/emulator_tests/util/test_helper.go b/tools/integration_tests/emulator_tests/util/test_helper.go index 88ed30aaeb..92a60f6f96 100644 --- a/tools/integration_tests/emulator_tests/util/test_helper.go +++ b/tools/integration_tests/emulator_tests/util/test_helper.go @@ -35,7 +35,7 @@ import ( // It launches the proxy server with the specified configuration and port, logs its output to a file. func StartProxyServer(configPath string) { // Start the proxy in the background - cmd := exec.Command("go", "run", "./proxy_server/.", "--config-path="+configPath) + cmd := exec.Command("go", "run", "../proxy_server/.", "--config-path="+configPath) logFileForProxyServer, err := os.Create(path.Join(os.Getenv("KOKORO_ARTIFACTS_DIR"), "proxy-"+setup.GenerateRandomString(5))) if err != nil { log.Fatal("Error in creating log file for proxy server.") @@ -64,7 +64,8 @@ func KillProxyServerProcess(port int) error { lines := strings.Split(string(output), "\n") for _, line := range lines[1:] { fields := strings.Fields(line) - if len(fields) > 1 { + // Kill only the process directly listening on the port. + if len(fields) > 1 && strings.Contains(line, "LISTEN") { pidStr := fields[1] pid, err := strconv.Atoi(pidStr) if err != nil { diff --git a/tools/integration_tests/emulator_tests/emulator_test.go b/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go similarity index 98% rename from tools/integration_tests/emulator_tests/emulator_test.go rename to tools/integration_tests/emulator_tests/write_stall/write_stall_test.go index 06a4cde1ff..a020587eb5 100644 --- a/tools/integration_tests/emulator_tests/emulator_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package emulator_tests +package write_stall import ( "fmt" diff --git a/tools/integration_tests/emulator_tests/write_stall_test.go b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go similarity index 95% rename from tools/integration_tests/emulator_tests/write_stall_test.go rename to tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go index 0df0ca3f08..d31fc19da5 100644 --- a/tools/integration_tests/emulator_tests/write_stall_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package emulator_tests +package write_stall import ( "fmt" @@ -41,7 +41,7 @@ type chunkTransferTimeoutInfinity struct { } func (s *chunkTransferTimeoutInfinity) Setup(t *testing.T) { - configPath := "./proxy_server/configs/write_stall_40s.yaml" + configPath := "../proxy_server/configs/write_stall_40s.yaml" emulator_tests.StartProxyServer(configPath) setup.MountGCSFuseWithGivenMountFunc(s.flags, mountFunc) } @@ -103,14 +103,14 @@ func TestChunkTransferTimeout(t *testing.T) { }{ { name: "SingleStall", - configPath: "./proxy_server/configs/write_stall_40s.yaml", + configPath: "../proxy_server/configs/write_stall_40s.yaml", expectedTimeout: func(chunkTransferTimeoutSecs int) time.Duration { return time.Duration(chunkTransferTimeoutSecs) * time.Second }, }, { name: "MultipleStalls", - configPath: "./proxy_server/configs/write_stall_twice_40s.yaml", // 2 stalls + configPath: "../proxy_server/configs/write_stall_twice_40s.yaml", // 2 stalls // Expect total time to be greater than the timeout multiplied by the number of stalls (2 in this case). expectedTimeout: func(chunkTransferTimeoutSecs int) time.Duration { return time.Duration(chunkTransferTimeoutSecs*2) * time.Second diff --git a/tools/integration_tests/streaming_writes/upload_failure_test.go b/tools/integration_tests/streaming_writes/upload_failure_test.go deleted file mode 100644 index 03415087d5..0000000000 --- a/tools/integration_tests/streaming_writes/upload_failure_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package streaming_writes - -import ( - "testing" - - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" -) - -func TestStreamingWritesObjectWriterThrottled(t *testing.T) { - flags := []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"} - setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) - defer setup.UnmountGCSFuse(rootDir) - testDirPath = setup.SetupTestDirectory(testDirName) - // Create a local file. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) - data, err := operations.GenerateRandomData(2 * 1024 * 1024) - if err != nil { - t.Fatalf("Error in generating data: %v", err) - } - - // Write data to file. - operations.WriteAt(string(data[:]), 0, fh, t) - // Close the file and validate that the file is created on GCS. - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, string(data[:]), t) -} diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 5ef70ae5b4..2674d26af4 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -50,6 +50,8 @@ func CreateStorageClient(ctx context.Context) (client *storage.Client, err error return nil, fmt.Errorf("unable to fetch token-source for TPC: %w", err) } client, err = storage.NewClient(ctx, option.WithEndpoint("storage.apis-tpczero.goog:443"), option.WithTokenSource(ts)) + } else if setup.TestOnCustomEndpoint() != "" { + client, err = storage.NewClient(ctx, option.WithEndpoint(setup.TestOnCustomEndpoint())) } else { if setup.IsZonalBucketRun() { client, err = storage.NewGRPCClient(ctx, experimental.WithGRPCBidiReads()) diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index e8731aca27..eff5685bf2 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -579,6 +579,12 @@ func CloseFileShouldNotThrowError(file *os.File, t *testing.T) { } } +func CloseFileShouldThrowError(file *os.File, t *testing.T) { + if err := file.Close(); err == nil { + t.Fatalf("file.Close() for file %s should throw an error: %v", file.Name(), err) + } +} + func SyncFile(fh *os.File, t *testing.T) { err := fh.Sync() @@ -588,6 +594,12 @@ func SyncFile(fh *os.File, t *testing.T) { } } +func SyncFileShouldThrowError(file *os.File, t *testing.T) { + if err := file.Sync(); err == nil { + t.Fatalf("file.Close() for file %s should throw an error: %v", file.Name(), err) + } +} + func CreateFileWithContent(filePath string, filePerms os.FileMode, content string, t testing.TB) { fh := CreateFile(filePath, filePerms, t) diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 1cbb4ce39b..b6cb7dee68 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -43,6 +43,7 @@ var mountedDirectory = flag.String("mountedDirectory", "", "The GCSFuse mounted var integrationTest = flag.Bool("integrationTest", false, "Run tests only when the flag value is true.") var testInstalledPackage = flag.Bool("testInstalledPackage", false, "[Optional] Run tests on the package pre-installed on the host machine. By default, integration tests build a new package to run the tests.") var testOnTPCEndPoint = flag.Bool("testOnTPCEndPoint", false, "Run tests on TPC endpoint only when the flag value is true.") +var testOnCustomEndpoint = flag.String("testOnCustomEndpoint", "", "Run tests on custom endpoint only when the flag value is set. Required for tests requiring proxy server.") var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) const ( @@ -97,6 +98,10 @@ func TestOnTPCEndPoint() bool { return *testOnTPCEndPoint } +func TestOnCustomEndpoint() string { + return *testOnCustomEndpoint +} + func MountedDirectory() string { return *mountedDirectory } @@ -342,6 +347,7 @@ func SetUpTestDirForTestBucketFlag() { log.Printf("setUpTestDir: %v\n", err) os.Exit(1) } + } func SetUpLogDirForTestDirTests(logDirName string) (logDir string) { From 9ec3dce23697898dcda21b162aaaf23d838c15b3 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 12 Feb 2025 10:21:14 +0000 Subject: [PATCH 0231/1298] Add log for mounted directory tests. --- .../streaming_writes_failure_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go index 8be711bf51..90927155c5 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go @@ -39,10 +39,9 @@ var ( func TestMain(m *testing.M) { setup.ParseSetUpFlags() - // To run mountedDirectory tests, we need both testBucket and mountedDirectory - // flags to be set, as operations tests validates content from the bucket. if setup.MountedDirectory() != "" { - + log.Printf("These tests will not run with mounted directory.") + return } // Set up test directory. From 7d3a4ee55f27e8a09d50b4e9420c3d173f8164a7 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Wed, 12 Feb 2025 10:30:16 +0000 Subject: [PATCH 0232/1298] fix ineffective assignments --- .../local_file_failure_test.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go index 09f2a7c1c7..d83ac18307 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go @@ -93,7 +93,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFai // Write again 2MB (C, D) may or may not fail based on the status of block B upload but it ensures the block B // is uploaded after the write and error is propagated. // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] - _, err = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + _, _ = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) // Write 5th 1MB results in errors. _, err = t.fh1.WriteAt(data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) @@ -132,10 +132,9 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecon // Fuse:[B] -> Go-SDK:[A]-> GCS[] _, err = t.fh1.WriteAt(data[:2*operations.MiB], 0) assert.NoError(t.T(), err) - // Write again 2MB (C, D) may or may not fail based on the status of block B upload but it ensures the block B - // is uploaded after the write and error is propagated. + // Write again 2MB (C, D) may succeed based on the status of block B but write ensure the error is propagated. // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] - _, err = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + _, _ = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) // Write 5th 1MB results in errors as it sees the error propagated via B upload failure. _, err = t.fh1.WriteAt(data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) @@ -174,7 +173,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSec assert.NoError(t.T(), err) // Write again 2MB (C, D) may succeed based on the status of block B. // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] - _, err = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + _, _ = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) // Write 5th 1MB results in errors as it sees the error propagated via B upload failure. _, err = t.fh1.WriteAt(data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) @@ -218,7 +217,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploa // Write next 1 MB block C may succeed based on the status of block B. // Fuse:[C] -> Go-SDK:[B]-> GCS[A] - _, err = t.fh1.WriteAt(data[2*operations.MiB:3*operations.MiB], 2*operations.MiB) + _, _ = t.fh1.WriteAt(data[2*operations.MiB:3*operations.MiB], 2*operations.MiB) // Sync now reports failure from B block upload. // Fuse:[] -> Go-SDK:[C]-> GCS[A, B -> upload fails] From cfc2b9793d3787b4786c242912acb92e043c82ee Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Thu, 13 Feb 2025 18:36:49 +0000 Subject: [PATCH 0233/1298] fix comments and refactor code. --- .../local_file_failure_test.go | 199 ++++++++---------- 1 file changed, 83 insertions(+), 116 deletions(-) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go index d83ac18307..345aedac6e 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go @@ -42,17 +42,25 @@ type defaultFailureTestSuite struct { testDirPath string filePath string fh1 *os.File - storageClient *storage.Client + storageClient *storage.Client // Storage Client based on proxy server. closeStorageClient func() error ctx context.Context + data []byte } // ////////////////////////////////////////////////////////////////////// -// Tests +// Helpers // ////////////////////////////////////////////////////////////////////// + func (t *defaultFailureTestSuite) SetupSuite() { t.configPath = "../proxy_server/configs/second_chunk_upload_returns412.yaml" t.flags = []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1", "--custom-endpoint=" + proxyEndpoint} + // Generate 5 MB random data. + data, err := operations.GenerateRandomData(5 * operations.MiB) + t.data = data + if err != nil { + t.T().Fatalf("Error in generating data: %v", err) + } log.Printf("Running tests with flags: %v", t.flags) } @@ -80,103 +88,95 @@ func (t *defaultFailureTestSuite) TearDownTest() { assert.NoError(t.T(), emulator_tests.KillProxyServerProcess(port)) } +func (t *defaultFailureTestSuite) WritingWithNewFileHandleAlsoFails(data []byte, off int64) { + // Opening a new file handle succeeds. + fh := operations.OpenFile(t.filePath, t.T()) + // Writes with this file handle fails. + _, err := fh.WriteAt(data, off) + assert.Error(t.T(), err) + // Closing the file handle returns error. + operations.CloseFileShouldThrowError(fh, t.T()) +} + +func (t *defaultFailureTestSuite) WritingAfterBwhReinitializationSucceeds() { + // Opening new file handle and writing to file succeeds. + fh := operations.CreateFile(t.filePath, FilePerms, t.T()) + _, err := fh.WriteAt(t.data, 0) + assert.NoError(t.T(), err) + // Sync succeeds. + err = fh.Sync() + assert.NoError(t.T(), err) + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh, testDirName, FileName1, string(t.data), t.T()) +} + +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// + func (t *defaultFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFailure() { - // Generate 5 MB random data. - data, err := operations.GenerateRandomData(5 * operations.MiB) - if err != nil { - t.T().Fatalf("Error in generating data: %v", err) - } - // Write first 2 MB (say A,B) block to file succeeds. - // Fuse:[B] -> Go-SDK:[A]-> GCS[] - _, err = t.fh1.WriteAt(data[:2*operations.MiB], 0) + // Write first 2 MB (say A,B) block to file succeeds but upload of block B will result in error. + // Fuse:[B] -> Go-SDK:[A] -> GCS[] + _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) // Write again 2MB (C, D) may or may not fail based on the status of block B upload but it ensures the block B - // is uploaded after the write and error is propagated. + // upload attempt is done and the error is propagated. // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] - _, _ = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) - // Write 5th 1MB results in errors. - _, err = t.fh1.WriteAt(data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + // Write 5th 1MB results in errors as it sees the error propagated from block B upload failure. + _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) assert.Error(t.T(), err) - // Opening new file handle succeeds. - fh2 := operations.OpenFile(t.filePath, t.T()) - // Writes with fh2 also fails. - _, err = fh2.WriteAt(data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) - assert.Error(t.T(), err) - // Closing first file handle. + // Writing from new file handles also fails. + t.WritingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) - // Creating new file with same name will fail unless all file handles are closed. - // TODO(mohitkyadav): Uncomment when b/395029462 is fixed. - // _, err = os.OpenFile(filePath, os.O_CREATE, FilePerms) - // assert.Error(t.T(), err) - // Close last file handle. - operations.CloseFileShouldThrowError(fh2, t.T()) // Verify that Object is not found on GCS. ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) - // Opening new file handle and writing to file succeeds. - fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) - _, err = fh3.WriteAt(data, 0) - assert.NoError(t.T(), err) - // Close and validate object content found on GCS. - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh3, testDirName, FileName1, string(data), t.T()) + // Writing after bwh reinitialization succeeds. + t.WritingAfterBwhReinitializationSucceeds() } func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecondChunkUploadFailure() { - // Generate 5 MB random data. - data, err := operations.GenerateRandomData(5 * operations.MiB) - if err != nil { - t.T().Fatalf("Error in generating data: %v", err) - } - // Write first 2 MB (say A,B) block to file succeeds. - // Fuse:[B] -> Go-SDK:[A]-> GCS[] - _, err = t.fh1.WriteAt(data[:2*operations.MiB], 0) + // Write first 2 MB (say A,B) block to file succeeds but upload of block B will result in error. + // Fuse:[B] -> Go-SDK:[A] -> GCS[] + _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) - // Write again 2MB (C, D) may succeed based on the status of block B but write ensure the error is propagated. + // Write again 2MB (C, D) may or may not fail based on the status of block B upload but it ensures the block B + // upload attempt is done and the error is propagated. // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] - _, _ = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) - // Write 5th 1MB results in errors as it sees the error propagated via B upload failure. - _, err = t.fh1.WriteAt(data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + // Write 5th 1MB results in errors as it sees the error propagated from block B upload failure. + _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) assert.Error(t.T(), err) // Truncate to smaller size fails. err = t.fh1.Truncate(1 * operations.MiB) assert.Error(t.T(), err) - // Opening new file handle succeeds. - fh2 := operations.OpenFile(t.filePath, t.T()) - // Write still fails. - _, err = fh2.WriteAt(data[3*operations.MiB:4*operations.MiB], 3*operations.MiB) - assert.Error(t.T(), err) - // Closing all file handles to reinitialize bwh. - operations.CloseFileShouldThrowError(fh2, t.T()) + // Writing from new file handles also fails. + t.WritingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) // Verify that Object is not found on GCS. ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) - // Opening new file handle and writing to file succeeds. - fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) - _, err = fh3.WriteAt(data, 0) - assert.NoError(t.T(), err) - // Close and validate object content found on GCS. - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh3, testDirName, FileName1, string(data), t.T()) + // Writing after bwh reinitialization succeeds. + t.WritingAfterBwhReinitializationSucceeds() } func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSecondChunkUploadFailure() { - // Generate 5 MB random data. - data, err := operations.GenerateRandomData(5 * operations.MiB) - if err != nil { - t.T().Fatalf("Error in generating data: %v", err) - } - // Write first 2 MB (say A,B) block to file succeeds. - // Fuse:[B] -> Go-SDK:[A]-> GCS[] - _, err = t.fh1.WriteAt(data[:2*operations.MiB], 0) + // Write first 2 MB (say A,B) block to file succeeds but upload of block B will result in error. + // Fuse:[B] -> Go-SDK:[A] -> GCS[] + _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) - // Write again 2MB (C, D) may succeed based on the status of block B. + // Write again 2MB (C, D) may or may not fail based on the status of block B upload but it ensures the block B + // upload attempt is done and the error is propagated. // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] - _, _ = t.fh1.WriteAt(data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) // Write 5th 1MB results in errors as it sees the error propagated via B upload failure. - _, err = t.fh1.WriteAt(data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) assert.Error(t.T(), err) // Opening new file handle succeeds. @@ -191,33 +191,28 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSec ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) - _, err = fh3.WriteAt(data, 0) + _, err = fh3.WriteAt(t.data, 0) assert.NoError(t.T(), err) // Truncate to bigger succeeds. err = fh3.Truncate(6 * operations.MiB) assert.NoError(t.T(), err) // Close and validate object content found on GCS. emptyBytes := make([]byte, operations.MiB) - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh3, testDirName, FileName1, string(data)+string(emptyBytes), t.T()) + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh3, testDirName, FileName1, string(t.data)+string(emptyBytes), t.T()) } func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploadFailure() { - // Generate 5 MB random data. - data, err := operations.GenerateRandomData(6 * operations.MiB) - if err != nil { - t.T().Fatalf("Error in generating data: %v", err) - } - // Write first 2 MB (say A,B) block to file succeeds. - // Fuse:[B] -> Go-SDK:[A]-> GCS[] - _, err = t.fh1.WriteAt(data[:2*operations.MiB], 0) + // Write first 2 MB (say A,B) block to file succeeds but upload of block B will result in error. + // Fuse:[B] -> Go-SDK:[A] -> GCS[] + _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) - // Sync file succeeds as the block B is only passed to Go-SDK. + // Sync file succeeds as the block B is only passed to Go-SDK for upload. // Fuse:[] -> Go-SDK:[B]-> GCS[A] operations.SyncFile(t.fh1, t.T()) // Write next 1 MB block C may succeed based on the status of block B. // Fuse:[C] -> Go-SDK:[B]-> GCS[A] - _, _ = t.fh1.WriteAt(data[2*operations.MiB:3*operations.MiB], 2*operations.MiB) + _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:3*operations.MiB], 2*operations.MiB) // Sync now reports failure from B block upload. // Fuse:[] -> Go-SDK:[C]-> GCS[A, B -> upload fails] @@ -230,25 +225,13 @@ func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploa // Verify that Object is not found on GCS. ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. - fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) - _, err = fh3.WriteAt(data, 0) - assert.NoError(t.T(), err) - // Sync succeeds after bwh reinitialization. - err = fh3.Sync() - assert.NoError(t.T(), err) - // Close and validate object content found on GCS. - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh3, testDirName, FileName1, string(data), t.T()) + t.WritingAfterBwhReinitializationSucceeds() } func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUploadFailure() { - // Generate 5 MB random data. - data, err := operations.GenerateRandomData(6 * operations.MiB) - if err != nil { - t.T().Fatalf("Error in generating data: %v", err) - } - // Write first 2 MB (say A,B) block to file succeeds. - // Fuse:[B] -> Go-SDK:[A]-> GCS[] - _, err = t.fh1.WriteAt(data[:2*operations.MiB], 0) + // Write first 2 MB (say A,B) block to file succeeds but upload of block B will result in error. + // Fuse:[B] -> Go-SDK:[A] -> GCS[] + _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) // Close fails as it sees error from B block upload. @@ -263,22 +246,12 @@ func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUplo // Verify that Object is not found on GCS. ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. - fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) - _, err = fh3.WriteAt(data, 0) - assert.NoError(t.T(), err) - // Close succeeds after bwh reinitialization. - operations.CloseFileShouldNotThrowError(fh3, t.T()) - // Validate object content found on GCS. - ValidateObjectContentsFromGCS(t.ctx, t.storageClient, testDirName, FileName1, string(data), t.T()) + t.WritingAfterBwhReinitializationSucceeds() } func (t *defaultFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() { - // Generate 1 MB random data. - data, err := operations.GenerateRandomData(operations.MiB) - if err != nil { - t.T().Fatalf("Error in generating data: %v", err) - } - _, err = t.fh1.WriteAt(data, 0) + // Write 1 MB data to file succeeds and upload of block will also succeed. + _, err := t.fh1.WriteAt(t.data[:operations.MiB], 0) assert.NoError(t.T(), err) // Close fails as it sees error on the finalize. @@ -288,13 +261,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() // Verify that Object is not found on GCS. ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. - fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) - _, err = fh3.WriteAt(data, 0) - assert.NoError(t.T(), err) - // Close succeeds after bwh reinitialization. - operations.CloseFileShouldNotThrowError(fh3, t.T()) - // Validate object content found on GCS. - ValidateObjectContentsFromGCS(t.ctx, t.storageClient, testDirName, FileName1, string(data), t.T()) + t.WritingAfterBwhReinitializationSucceeds() } func TestUploadFailureTestSuite(t *testing.T) { From 57560c2d0a249268636fbd6e7a56461af93255f2 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Fri, 14 Feb 2025 09:25:34 +0000 Subject: [PATCH 0234/1298] fix tests --- .../emulator_tests/emulator_tests.sh | 2 +- .../local_file_failure_test.go | 48 +++++++++---------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index 126a5c8544..1ae5244fb7 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -121,7 +121,7 @@ curl -X POST --data-binary @test.json \ rm test.json # Run Write Stall Tests. -go test ./tools/integration_tests/emulator_tests/write_stall/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE +# go test ./tools/integration_tests/emulator_tests/write_stall/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE # Run Streaming Writes Failure Tests. go test ./tools/integration_tests/emulator_tests/streaming_writes_failure/... -p 1 -short --integrationTest -v --testbucket=test-bucket --testOnCustomEndpoint=http://localhost:8020 -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go index 345aedac6e..52f6ae856c 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go @@ -99,6 +99,8 @@ func (t *defaultFailureTestSuite) WritingWithNewFileHandleAlsoFails(data []byte, } func (t *defaultFailureTestSuite) WritingAfterBwhReinitializationSucceeds() { + // Verify that Object is not found on GCS. + ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. fh := operations.CreateFile(t.filePath, FilePerms, t.T()) _, err := fh.WriteAt(t.data, 0) @@ -106,8 +108,6 @@ func (t *defaultFailureTestSuite) WritingAfterBwhReinitializationSucceeds() { // Sync succeeds. err = fh.Sync() assert.NoError(t.T(), err) - // Close and validate object content found on GCS. - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh, testDirName, FileName1, string(t.data), t.T()) } // ////////////////////////////////////////////////////////////////////// @@ -115,7 +115,7 @@ func (t *defaultFailureTestSuite) WritingAfterBwhReinitializationSucceeds() { // ////////////////////////////////////////////////////////////////////// func (t *defaultFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFailure() { - // Write first 2 MB (say A,B) block to file succeeds but upload of block B will result in error. + // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) @@ -132,14 +132,14 @@ func (t *defaultFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFai t.WritingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) - // Verify that Object is not found on GCS. - ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) - // Writing after bwh reinitialization succeeds. + // Opening new file handle and writing to file succeeds. t.WritingAfterBwhReinitializationSucceeds() + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecondChunkUploadFailure() { - // Write first 2 MB (say A,B) block to file succeeds but upload of block B will result in error. + // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) @@ -159,14 +159,14 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecon t.WritingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) - // Verify that Object is not found on GCS. - ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) - // Writing after bwh reinitialization succeeds. + // Opening new file handle and writing to file succeeds. t.WritingAfterBwhReinitializationSucceeds() + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSecondChunkUploadFailure() { - // Write first 2 MB (say A,B) block to file succeeds but upload of block B will result in error. + // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) @@ -187,14 +187,10 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSec // Closing all file handles to reinitialize bwh. operations.CloseFileShouldThrowError(fh2, t.T()) operations.CloseFileShouldThrowError(t.fh1, t.T()) - // Verify that Object is not found on GCS. - ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. + t.WritingAfterBwhReinitializationSucceeds() + // Opening new file handle and truncate to bigger size succeeds. fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) - _, err = fh3.WriteAt(t.data, 0) - assert.NoError(t.T(), err) - // Truncate to bigger succeeds. - err = fh3.Truncate(6 * operations.MiB) assert.NoError(t.T(), err) // Close and validate object content found on GCS. emptyBytes := make([]byte, operations.MiB) @@ -202,7 +198,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSec } func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploadFailure() { - // Write first 2 MB (say A,B) block to file succeeds but upload of block B will result in error. + // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) @@ -222,14 +218,14 @@ func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploa assert.Error(t.T(), err) // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) - // Verify that Object is not found on GCS. - ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. t.WritingAfterBwhReinitializationSucceeds() + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUploadFailure() { - // Write first 2 MB (say A,B) block to file succeeds but upload of block B will result in error. + // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) @@ -243,14 +239,14 @@ func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUplo assert.Error(t.T(), err) // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) - // Verify that Object is not found on GCS. - ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. t.WritingAfterBwhReinitializationSucceeds() + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } func (t *defaultFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() { - // Write 1 MB data to file succeeds and upload of block will also succeed. + // Write 1 MB data to file succeeds and async upload of block will also succeed. _, err := t.fh1.WriteAt(t.data[:operations.MiB], 0) assert.NoError(t.T(), err) @@ -258,10 +254,10 @@ func (t *defaultFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() err = t.fh1.Close() assert.NotNil(t.T(), err) - // Verify that Object is not found on GCS. - ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. t.WritingAfterBwhReinitializationSucceeds() + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } func TestUploadFailureTestSuite(t *testing.T) { From f04fda3b01aee869e0af7b23046f19a488334414 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Fri, 14 Feb 2025 09:34:23 +0000 Subject: [PATCH 0235/1298] fix tests --- tools/integration_tests/emulator_tests/emulator_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index 1ae5244fb7..126a5c8544 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -121,7 +121,7 @@ curl -X POST --data-binary @test.json \ rm test.json # Run Write Stall Tests. -# go test ./tools/integration_tests/emulator_tests/write_stall/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE +go test ./tools/integration_tests/emulator_tests/write_stall/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE # Run Streaming Writes Failure Tests. go test ./tools/integration_tests/emulator_tests/streaming_writes_failure/... -p 1 -short --integrationTest -v --testbucket=test-bucket --testOnCustomEndpoint=http://localhost:8020 -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE From 9239377a7224818f1dd165e0177c1e4336850655 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 17 Feb 2025 09:29:07 +0000 Subject: [PATCH 0236/1298] fix comments --- .../emulator_tests/emulator_tests.sh | 2 +- .../local_file_failure_test.go | 35 +++++++++---------- 2 files changed, 17 insertions(+), 20 deletions(-) diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index 126a5c8544..ff3240c1e6 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -59,7 +59,7 @@ if [ -f /etc/debian_version ]; then $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io # TODO(mohitkyadav): These dependencies do not work on Cloudtop docker-buildx-plugin docker-compose-plugin + sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo apt-get install -y lsof # RHEL/CentOS based machine. elif [ -f /etc/redhat-release ]; then diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go index 52f6ae856c..6c5fdc2bb3 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go @@ -88,7 +88,7 @@ func (t *defaultFailureTestSuite) TearDownTest() { assert.NoError(t.T(), emulator_tests.KillProxyServerProcess(port)) } -func (t *defaultFailureTestSuite) WritingWithNewFileHandleAlsoFails(data []byte, off int64) { +func (t *defaultFailureTestSuite) writingWithNewFileHandleAlsoFails(data []byte, off int64) { // Opening a new file handle succeeds. fh := operations.OpenFile(t.filePath, t.T()) // Writes with this file handle fails. @@ -98,7 +98,7 @@ func (t *defaultFailureTestSuite) WritingWithNewFileHandleAlsoFails(data []byte, operations.CloseFileShouldThrowError(fh, t.T()) } -func (t *defaultFailureTestSuite) WritingAfterBwhReinitializationSucceeds() { +func (t *defaultFailureTestSuite) writingAfterBwhReinitializationSucceeds() { // Verify that Object is not found on GCS. ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. @@ -119,21 +119,20 @@ func (t *defaultFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFai // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) - // Write again 2MB (C, D) may or may not fail based on the status of block B upload but it ensures the block B - // upload attempt is done and the error is propagated. + // Write again 2MB (C, D) will trigger B upload. // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) - // Write 5th 1MB results in errors as it sees the error propagated from block B upload failure. + // Write 5th 1MB results in errors. _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) assert.Error(t.T(), err) // Writing from new file handles also fails. - t.WritingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + t.writingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) // Opening new file handle and writing to file succeeds. - t.WritingAfterBwhReinitializationSucceeds() + t.writingAfterBwhReinitializationSucceeds() // Close and validate object content found on GCS. CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } @@ -143,12 +142,11 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecon // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) - // Write again 2MB (C, D) may or may not fail based on the status of block B upload but it ensures the block B - // upload attempt is done and the error is propagated. + // Write again 2MB (C, D) will trigger B upload. // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) - // Write 5th 1MB results in errors as it sees the error propagated from block B upload failure. + // Write 5th 1MB results in errors. _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) assert.Error(t.T(), err) @@ -156,11 +154,11 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecon err = t.fh1.Truncate(1 * operations.MiB) assert.Error(t.T(), err) // Writing from new file handles also fails. - t.WritingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + t.writingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) // Opening new file handle and writing to file succeeds. - t.WritingAfterBwhReinitializationSucceeds() + t.writingAfterBwhReinitializationSucceeds() // Close and validate object content found on GCS. CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } @@ -170,12 +168,11 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSec // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) assert.NoError(t.T(), err) - // Write again 2MB (C, D) may or may not fail based on the status of block B upload but it ensures the block B - // upload attempt is done and the error is propagated. + // Write again 2MB (C, D) will trigger B upload. // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) - // Write 5th 1MB results in errors as it sees the error propagated via B upload failure. + // Write 5th 1MB results in errors. _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) assert.Error(t.T(), err) @@ -188,7 +185,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSec operations.CloseFileShouldThrowError(fh2, t.T()) operations.CloseFileShouldThrowError(t.fh1, t.T()) // Opening new file handle and writing to file succeeds. - t.WritingAfterBwhReinitializationSucceeds() + t.writingAfterBwhReinitializationSucceeds() // Opening new file handle and truncate to bigger size succeeds. fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) assert.NoError(t.T(), err) @@ -219,7 +216,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploa // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) // Opening new file handle and writing to file succeeds. - t.WritingAfterBwhReinitializationSucceeds() + t.writingAfterBwhReinitializationSucceeds() // Close and validate object content found on GCS. CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } @@ -240,7 +237,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUplo // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) // Opening new file handle and writing to file succeeds. - t.WritingAfterBwhReinitializationSucceeds() + t.writingAfterBwhReinitializationSucceeds() // Close and validate object content found on GCS. CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } @@ -255,7 +252,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() assert.NotNil(t.T(), err) // Opening new file handle and writing to file succeeds. - t.WritingAfterBwhReinitializationSucceeds() + t.writingAfterBwhReinitializationSucceeds() // Close and validate object content found on GCS. CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } From 838fb2462b7c6fdef024d9920a4fbf9892a13a70 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 17 Feb 2025 10:44:27 +0000 Subject: [PATCH 0237/1298] fix typo --- .../local_file_failure_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go index 6c5fdc2bb3..360b801e70 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go @@ -102,11 +102,11 @@ func (t *defaultFailureTestSuite) writingAfterBwhReinitializationSucceeds() { // Verify that Object is not found on GCS. ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. - fh := operations.CreateFile(t.filePath, FilePerms, t.T()) - _, err := fh.WriteAt(t.data, 0) + t.fh1 = operations.CreateFile(t.filePath, FilePerms, t.T()) + _, err := t.fh1.WriteAt(t.data, 0) assert.NoError(t.T(), err) // Sync succeeds. - err = fh.Sync() + err = t.fh1.Sync() assert.NoError(t.T(), err) } @@ -186,12 +186,12 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSec operations.CloseFileShouldThrowError(t.fh1, t.T()) // Opening new file handle and writing to file succeeds. t.writingAfterBwhReinitializationSucceeds() - // Opening new file handle and truncate to bigger size succeeds. - fh3 := operations.CreateFile(t.filePath, FilePerms, t.T()) + // Truncate to bigger size succeeds. + t.fh1.Truncate(6 * operations.MiB) assert.NoError(t.T(), err) // Close and validate object content found on GCS. emptyBytes := make([]byte, operations.MiB) - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, fh3, testDirName, FileName1, string(t.data)+string(emptyBytes), t.T()) + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data)+string(emptyBytes), t.T()) } func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploadFailure() { From 9acb4376549e23d29a9e5515643bacfa56939440 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Mon, 17 Feb 2025 10:47:10 +0000 Subject: [PATCH 0238/1298] fix typo --- .../streaming_writes_failure/local_file_failure_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go index 360b801e70..a70bbe7bf3 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go @@ -187,7 +187,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSec // Opening new file handle and writing to file succeeds. t.writingAfterBwhReinitializationSucceeds() // Truncate to bigger size succeeds. - t.fh1.Truncate(6 * operations.MiB) + err = t.fh1.Truncate(6 * operations.MiB) assert.NoError(t.T(), err) // Close and validate object content found on GCS. emptyBytes := make([]byte, operations.MiB) From 88022c1812ce759d4a2c019c81053bdc07d12f1f Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Tue, 18 Feb 2025 05:35:39 +0000 Subject: [PATCH 0239/1298] fix tests --- .../streaming_writes_failure/local_file_failure_test.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go index a70bbe7bf3..87d4c8e2b1 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go @@ -210,9 +210,6 @@ func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploa // Sync now reports failure from B block upload. // Fuse:[] -> Go-SDK:[C]-> GCS[A, B -> upload fails] operations.SyncFileShouldThrowError(t.fh1, t.T()) - // Opening new file handle fails. - _, err = os.OpenFile(t.filePath, os.O_RDWR, FilePerms) - assert.Error(t.T(), err) // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) // Opening new file handle and writing to file succeeds. @@ -231,9 +228,6 @@ func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUplo err = t.fh1.Close() assert.NotNil(t.T(), err) - // Opening new file handle fails. - _, err = os.OpenFile(t.filePath, os.O_RDWR, FilePerms) - assert.Error(t.T(), err) // Close file handle to reinitialize bwh. operations.CloseFileShouldThrowError(t.fh1, t.T()) // Opening new file handle and writing to file succeeds. From 67d19907fce36e32fa5a6a2c001e4638a4af119d Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Tue, 18 Feb 2025 09:11:47 +0000 Subject: [PATCH 0240/1298] fix review comments. --- .../local_file_failure_test.go | 40 ++++++++----------- .../util/operations/file_operations.go | 6 ++- tools/integration_tests/util/setup/setup.go | 1 - 3 files changed, 21 insertions(+), 26 deletions(-) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go index 87d4c8e2b1..4ec2cbc3a3 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go @@ -28,6 +28,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -58,9 +59,7 @@ func (t *defaultFailureTestSuite) SetupSuite() { // Generate 5 MB random data. data, err := operations.GenerateRandomData(5 * operations.MiB) t.data = data - if err != nil { - t.T().Fatalf("Error in generating data: %v", err) - } + require.NoError(t.T(), err) log.Printf("Running tests with flags: %v", t.flags) } @@ -81,24 +80,23 @@ func (t *defaultFailureTestSuite) TearDownTest() { // CleanUp MntDir before unmounting GCSFuse. setup.CleanUpDir(rootDir) setup.UnmountGCSFuse(rootDir) - err := t.closeStorageClient() - if err != nil { - log.Fatalf("closeStorageClient failed: %v", err) - } + assert.NoError(t.T(), t.closeStorageClient()) assert.NoError(t.T(), emulator_tests.KillProxyServerProcess(port)) } func (t *defaultFailureTestSuite) writingWithNewFileHandleAlsoFails(data []byte, off int64) { + t.T().Helper() // Opening a new file handle succeeds. fh := operations.OpenFile(t.filePath, t.T()) // Writes with this file handle fails. _, err := fh.WriteAt(data, off) assert.Error(t.T(), err) // Closing the file handle returns error. - operations.CloseFileShouldThrowError(fh, t.T()) + operations.CloseFileShouldThrowError(t.T(), fh) } func (t *defaultFailureTestSuite) writingAfterBwhReinitializationSucceeds() { + t.T().Helper() // Verify that Object is not found on GCS. ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) // Opening new file handle and writing to file succeeds. @@ -126,11 +124,10 @@ func (t *defaultFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFai // Write 5th 1MB results in errors. _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) - assert.Error(t.T(), err) - // Writing from new file handles also fails. + require.Error(t.T(), err) t.writingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) // Close file handle to reinitialize bwh. - operations.CloseFileShouldThrowError(t.fh1, t.T()) + operations.CloseFileShouldThrowError(t.T(), t.fh1) // Opening new file handle and writing to file succeeds. t.writingAfterBwhReinitializationSucceeds() // Close and validate object content found on GCS. @@ -149,14 +146,13 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecon // Write 5th 1MB results in errors. _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) - assert.Error(t.T(), err) + require.Error(t.T(), err) // Truncate to smaller size fails. err = t.fh1.Truncate(1 * operations.MiB) assert.Error(t.T(), err) - // Writing from new file handles also fails. t.writingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) // Close file handle to reinitialize bwh. - operations.CloseFileShouldThrowError(t.fh1, t.T()) + operations.CloseFileShouldThrowError(t.T(), t.fh1) // Opening new file handle and writing to file succeeds. t.writingAfterBwhReinitializationSucceeds() // Close and validate object content found on GCS. @@ -175,15 +171,15 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSec // Write 5th 1MB results in errors. _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) - assert.Error(t.T(), err) + require.Error(t.T(), err) // Opening new file handle succeeds. fh2 := operations.OpenFile(t.filePath, t.T()) // Truncate to bigger size succeeds. err = fh2.Truncate(5 * operations.MiB) assert.NoError(t.T(), err) // Closing all file handles to reinitialize bwh. - operations.CloseFileShouldThrowError(fh2, t.T()) - operations.CloseFileShouldThrowError(t.fh1, t.T()) + operations.CloseFileShouldThrowError(t.T(), fh2) + operations.CloseFileShouldThrowError(t.T(), t.fh1) // Opening new file handle and writing to file succeeds. t.writingAfterBwhReinitializationSucceeds() // Truncate to bigger size succeeds. @@ -209,9 +205,9 @@ func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploa // Sync now reports failure from B block upload. // Fuse:[] -> Go-SDK:[C]-> GCS[A, B -> upload fails] - operations.SyncFileShouldThrowError(t.fh1, t.T()) + operations.SyncFileShouldThrowError(t.T(), t.fh1) // Close file handle to reinitialize bwh. - operations.CloseFileShouldThrowError(t.fh1, t.T()) + operations.CloseFileShouldThrowError(t.T(), t.fh1) // Opening new file handle and writing to file succeeds. t.writingAfterBwhReinitializationSucceeds() // Close and validate object content found on GCS. @@ -227,9 +223,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUplo // Close fails as it sees error from B block upload. err = t.fh1.Close() - assert.NotNil(t.T(), err) - // Close file handle to reinitialize bwh. - operations.CloseFileShouldThrowError(t.fh1, t.T()) + require.Error(t.T(), err) // Opening new file handle and writing to file succeeds. t.writingAfterBwhReinitializationSucceeds() // Close and validate object content found on GCS. @@ -244,7 +238,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() // Close fails as it sees error on the finalize. err = t.fh1.Close() - assert.NotNil(t.T(), err) + require.Error(t.T(), err) // Opening new file handle and writing to file succeeds. t.writingAfterBwhReinitializationSucceeds() // Close and validate object content found on GCS. diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index eff5685bf2..9ee43684f0 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -579,7 +579,8 @@ func CloseFileShouldNotThrowError(file *os.File, t *testing.T) { } } -func CloseFileShouldThrowError(file *os.File, t *testing.T) { +func CloseFileShouldThrowError(t *testing.T, file *os.File) { + t.Helper() if err := file.Close(); err == nil { t.Fatalf("file.Close() for file %s should throw an error: %v", file.Name(), err) } @@ -594,7 +595,8 @@ func SyncFile(fh *os.File, t *testing.T) { } } -func SyncFileShouldThrowError(file *os.File, t *testing.T) { +func SyncFileShouldThrowError(t *testing.T, file *os.File) { + t.Helper() if err := file.Sync(); err == nil { t.Fatalf("file.Close() for file %s should throw an error: %v", file.Name(), err) } diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index b6cb7dee68..841bdf927a 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -347,7 +347,6 @@ func SetUpTestDirForTestBucketFlag() { log.Printf("setUpTestDir: %v\n", err) os.Exit(1) } - } func SetUpLogDirForTestDirTests(logDirName string) (logDir string) { From 8987d2059aced08dd9b0fbe2298a3d6724310073 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 18 Feb 2025 16:04:26 +0530 Subject: [PATCH 0241/1298] do not do force stat for local files during flush (#2989) * do not do force stat for local files during flush * header fix * review comment * review comment --- internal/fs/inode/file.go | 20 +-- internal/fs/inode/file_mock_bucket_test.go | 153 +++++++++++++++++++++ 2 files changed, 164 insertions(+), 9 deletions(-) create mode 100644 internal/fs/inode/file_mock_bucket_test.go diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 221379f29d..02be0df694 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -781,10 +781,14 @@ func (f *FileInode) Sync(ctx context.Context) (gcsSynced bool, err error) { // object, syncs the content and updates the inode state. // // LOCKS_REQUIRED(f.mu) -func (f *FileInode) syncUsingContent(ctx context.Context) (err error) { - latestGcsObj, err := f.fetchLatestGcsObject(ctx) - if err != nil { - return +func (f *FileInode) syncUsingContent(ctx context.Context) error { + var latestGcsObj *gcs.Object + if !f.local { + var err error + latestGcsObj, err = f.fetchLatestGcsObject(ctx) + if err != nil { + return err + } } // Write out the contents if they are dirty. @@ -794,21 +798,19 @@ func (f *FileInode) syncUsingContent(ctx context.Context) (err error) { var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { - err = &gcsfuse_errors.FileClobberedError{ + return &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("SyncObject: %w", err), } - return } // Propagate other errors. if err != nil { - err = fmt.Errorf("SyncObject: %w", err) - return + return fmt.Errorf("SyncObject: %w", err) } minObj := storageutil.ConvertObjToMinObject(newObj) // If we wrote out a new object, we need to update our state. f.updateInodeStateAfterSync(minObj) - return + return nil } // Flush writes out contents to GCS. If this fails due to the generation diff --git a/internal/fs/inode/file_mock_bucket_test.go b/internal/fs/inode/file_mock_bucket_test.go new file mode 100644 index 0000000000..deb83a3bc8 --- /dev/null +++ b/internal/fs/inode/file_mock_bucket_test.go @@ -0,0 +1,153 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package inode + +import ( + "context" + "math" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + storagemock "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/mock" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/jacobsa/fuse/fuseops" + "github.com/jacobsa/syncutil" + "github.com/jacobsa/timeutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "golang.org/x/sync/semaphore" +) + +type FileMockBucketTest struct { + suite.Suite + ctx context.Context + bucket *storagemock.TestifyMockBucket + clock timeutil.SimulatedClock + backingObj *gcs.MinObject + in *FileInode +} + +func TestFileMockBucketTestSuite(t *testing.T) { + suite.Run(t, new(FileMockBucketTest)) +} + +func (t *FileMockBucketTest) SetupTest() { + // Enabling invariant check for all tests. + syncutil.EnableInvariantChecking() + t.ctx = context.Background() + t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)) + t.bucket = new(storagemock.TestifyMockBucket) + t.bucket.On("BucketType").Return(gcs.BucketType{Hierarchical: false, Zonal: false}) + + // Create the inode. + t.createLockedInode(fileName, localFile) +} + +func (t *FileMockBucketTest) TearDownTest() { + t.in.Unlock() +} + +func (t *FileMockBucketTest) createLockedInode(fileName string, fileType string) { + if fileType != emptyGCSFile && fileType != localFile { + t.T().Errorf("fileType should be either local or empty") + } + + name := NewFileName( + NewRootName(""), + fileName, + ) + syncerBucket := gcsx.NewSyncerBucket( + 1, // Append threshold + ChunkTransferTimeoutSecs, + ".gcsfuse_tmp/", + t.bucket) + + isLocal := false + if fileType == localFile { + t.backingObj = nil + isLocal = true + } + + if fileType == emptyGCSFile { + object, err := storageutil.CreateObject( + t.ctx, + t.bucket, + fileName, + []byte{}) + t.backingObj = storageutil.ConvertObjToMinObject(object) + + assert.Nil(t.T(), err) + } + + t.in = NewFileInode( + fileInodeID, + name, + t.backingObj, + fuseops.InodeAttributes{ + Uid: uid, + Gid: gid, + Mode: fileMode, + }, + &syncerBucket, + false, // localFileCache + contentcache.New("", &t.clock), + &t.clock, + isLocal, + &cfg.Config{}, + semaphore.NewWeighted(math.MaxInt64)) + + // Create write handler for the local inode created above. + err := t.in.CreateBufferedOrTempWriter(t.ctx) + assert.Nil(t.T(), err) + + t.in.Lock() +} + +func (t *FileMockBucketTest) TestFlushLocalFileDoesNotForceFetchObjectFromGCS() { + assert.True(t.T(), t.in.IsLocal()) + // Expect only CreateObject call on bucket. + t.bucket.On("CreateObject", t.ctx, mock.AnythingOfType("*gcs.CreateObjectRequest")). + Return(&gcs.Object{Name: fileName}, nil) + + err := t.in.Flush(t.ctx) + + require.NoError(t.T(), err) + t.bucket.AssertExpectations(t.T()) +} + +func (t *FileMockBucketTest) TestFlushSyncedFileForceFetchObjectFromGCS() { + // Expect a CreateObject call because createLockedInode creates a synced file + // inode (backed by GCS object). + t.bucket.On("CreateObject", t.ctx, mock.AnythingOfType("*gcs.CreateObjectRequest")). + Return(&gcs.Object{Name: fileName}, nil) + t.createLockedInode(fileName, emptyGCSFile) + assert.False(t.T(), t.in.IsLocal()) + // Expect both StatObject and CreateObject call on bucket. + t.bucket.On("StatObject", t.ctx, mock.AnythingOfType("*gcs.StatObjectRequest")). + Return(&gcs.MinObject{Name: fileName}, &gcs.ExtendedObjectAttributes{}, nil) + t.bucket.On("CreateObject", t.ctx, mock.AnythingOfType("*gcs.CreateObjectRequest")). + Return(&gcs.Object{Name: fileName}, nil) + + err := t.in.Flush(t.ctx) + + require.NoError(t.T(), err) + t.bucket.AssertExpectations(t.T()) +} From 7d5a904183ab20ca388edddaf7b0124d4a8cd918 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Tue, 18 Feb 2025 16:18:41 +0530 Subject: [PATCH 0242/1298] Update docs/troubleshooting.md Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> --- docs/troubleshooting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index f0766ef99c..aca1e3e7e8 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -149,6 +149,6 @@ You need to find such objects and replace them with any other valid gcs object n The Writes in GCSFuse are staged locally before they are uploaded to GCS buckets. It takes up the space of the files that are being uploaded concurrently and deleted locally once they are uploaded. During this time, since the disk is used, this error may come up. -The path can be configured by using the mount flag [--temp-dir](https://cloud.google.com/storage/docs/cloud-storage-fuse/cli-options) to a path which has the disk space if available. By default, it takes the temp directory of the machine. (sometimes may be limited depending on path set on the machine ). +The path can be configured by using the mount flag [--temp-dir](https://cloud.google.com/storage/docs/cloud-storage-fuse/cli-options) to a path which has the disk space if available. By default, it takes the `/tmp` directory of the machine. (sometimes may be limited depending on the machine ). Alternatively, from [GCSFuse version 2.9.1](https://github.com/GoogleCloudPlatform/gcsfuse/releases/tag/v2.9.1) onwards, writes can be configured with streaming writes feature ( which doesnt involve staging the file locally ) with the help of --enable-streaming-writes flag From 7fd8cede799578278c2113c9a5fd35c2f6b72236 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Tue, 18 Feb 2025 16:18:48 +0530 Subject: [PATCH 0243/1298] Update docs/troubleshooting.md Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> --- docs/troubleshooting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index aca1e3e7e8..1f87d70975 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -151,4 +151,4 @@ The Writes in GCSFuse are staged locally before they are uploaded to GCS buckets The path can be configured by using the mount flag [--temp-dir](https://cloud.google.com/storage/docs/cloud-storage-fuse/cli-options) to a path which has the disk space if available. By default, it takes the `/tmp` directory of the machine. (sometimes may be limited depending on the machine ). -Alternatively, from [GCSFuse version 2.9.1](https://github.com/GoogleCloudPlatform/gcsfuse/releases/tag/v2.9.1) onwards, writes can be configured with streaming writes feature ( which doesnt involve staging the file locally ) with the help of --enable-streaming-writes flag +Alternatively, from [GCSFuse version 2.9.1](https://github.com/GoogleCloudPlatform/gcsfuse/releases/tag/v2.9.1) onwards, writes can be configured with streaming writes feature ( which doesnt involve staging the file locally ) with the help of `--enable-streaming-writes` flag From 5aa324171b9cdad6d9ffa323e21837024136b475 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Tue, 18 Feb 2025 16:19:13 +0530 Subject: [PATCH 0244/1298] Update docs/troubleshooting.md Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> --- docs/troubleshooting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 1f87d70975..e2830e9267 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -147,7 +147,7 @@ You need to find such objects and replace them with any other valid gcs object n ### OSError [ErrNo 28] No space left on device -The Writes in GCSFuse are staged locally before they are uploaded to GCS buckets. It takes up the space of the files that are being uploaded concurrently and deleted locally once they are uploaded. During this time, since the disk is used, this error may come up. +The Writes in GCSFuse are staged locally before they are uploaded to GCS buckets. It takes up disk space equivalent to the size of the files that are being uploaded concurrently and deleted locally once they are uploaded. During this time, since the disk is used, this error may come up. The path can be configured by using the mount flag [--temp-dir](https://cloud.google.com/storage/docs/cloud-storage-fuse/cli-options) to a path which has the disk space if available. By default, it takes the `/tmp` directory of the machine. (sometimes may be limited depending on the machine ). From 20ea0ce84821e20d0947d62c53885f9c42c2ac5e Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 18 Feb 2025 21:15:15 +0530 Subject: [PATCH 0245/1298] Revert "Add Streaming Writes failure scenario tests for local files." --- .../write_stall_test.go => emulator_test.go} | 2 +- .../emulator_tests/emulator_tests.sh | 15 +- .../second_chunk_upload_returns412.yaml | 8 - .../local_file_failure_test.go | 250 ------------------ .../streaming_writes_failure_test.go | 55 ---- .../emulator_tests/util/test_helper.go | 5 +- ...ll_on_sync_test.go => write_stall_test.go} | 8 +- .../util/client/storage_client.go | 2 - .../util/operations/file_operations.go | 14 - tools/integration_tests/util/setup/setup.go | 5 - 10 files changed, 9 insertions(+), 355 deletions(-) rename tools/integration_tests/emulator_tests/{write_stall/write_stall_test.go => emulator_test.go} (98%) delete mode 100644 tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml delete mode 100644 tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go delete mode 100644 tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go rename tools/integration_tests/emulator_tests/{write_stall/writes_stall_on_sync_test.go => write_stall_test.go} (95%) diff --git a/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go b/tools/integration_tests/emulator_tests/emulator_test.go similarity index 98% rename from tools/integration_tests/emulator_tests/write_stall/write_stall_test.go rename to tools/integration_tests/emulator_tests/emulator_test.go index a020587eb5..06a4cde1ff 100644 --- a/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go +++ b/tools/integration_tests/emulator_tests/emulator_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package write_stall +package emulator_tests import ( "fmt" diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index ff3240c1e6..70a7e37b14 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -88,14 +88,6 @@ DOCKER_NETWORK="--net=host" # Get the docker image for the testbench sudo docker pull $DOCKER_IMAGE -# Remove the docker container if it's already running. -CONTAINER_ID=$(sudo docker ps -aqf "name=$CONTAINER_NAME") -if [[ -n "$CONTAINER_ID" ]]; then - echo "Container with ID:[$CONTAINER_ID] is already running with name:[$CONTAINER_NAME]" - echo "Stoping container...." - sudo docker stop $CONTAINER_ID -fi - # Start the testbench sudo docker run --name $CONTAINER_NAME --rm -d $DOCKER_NETWORK $DOCKER_IMAGE echo "Running the Cloud Storage testbench: $STORAGE_EMULATOR_HOST" @@ -120,8 +112,5 @@ curl -X POST --data-binary @test.json \ "$STORAGE_EMULATOR_HOST/storage/v1/b?project=test-project" rm test.json -# Run Write Stall Tests. -go test ./tools/integration_tests/emulator_tests/write_stall/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE - -# Run Streaming Writes Failure Tests. -go test ./tools/integration_tests/emulator_tests/streaming_writes_failure/... -p 1 -short --integrationTest -v --testbucket=test-bucket --testOnCustomEndpoint=http://localhost:8020 -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE +# Run specific test suite +go test ./tools/integration_tests/emulator_tests/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml b/tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml deleted file mode 100644 index 8464699da6..0000000000 --- a/tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml +++ /dev/null @@ -1,8 +0,0 @@ -targetHost: http://localhost:9000 -retryConfig: -- method: JsonCreate - retryInstruction: "return-412" - retryCount: 1 - # Skip count of three is required as first call is used to create the testDir on the GCS and then - # second and third call are used in creating resumable upload session uri and first chunk upload. - skipCount: 3 diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go deleted file mode 100644 index 4ec2cbc3a3..0000000000 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package streaming_writes_failure - -import ( - "context" - "log" - "os" - "testing" - - "cloud.google.com/go/storage" - emulator_tests "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/emulator_tests/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" -) - -// ////////////////////////////////////////////////////////////////////// -// Boilerplate -// ////////////////////////////////////////////////////////////////////// - -type defaultFailureTestSuite struct { - suite.Suite - configPath string - flags []string - testDirPath string - filePath string - fh1 *os.File - storageClient *storage.Client // Storage Client based on proxy server. - closeStorageClient func() error - ctx context.Context - data []byte -} - -// ////////////////////////////////////////////////////////////////////// -// Helpers -// ////////////////////////////////////////////////////////////////////// - -func (t *defaultFailureTestSuite) SetupSuite() { - t.configPath = "../proxy_server/configs/second_chunk_upload_returns412.yaml" - t.flags = []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1", "--custom-endpoint=" + proxyEndpoint} - // Generate 5 MB random data. - data, err := operations.GenerateRandomData(5 * operations.MiB) - t.data = data - require.NoError(t.T(), err) - log.Printf("Running tests with flags: %v", t.flags) -} - -func (t *defaultFailureTestSuite) SetupTest() { - // Start proxy server for each test to ensure the config is initialized per test. - emulator_tests.StartProxyServer(t.configPath) - // Create storage client before running tests. - t.ctx = context.Background() - t.closeStorageClient = client.CreateStorageClientWithCancel(&t.ctx, &t.storageClient) - setup.MountGCSFuseWithGivenMountFunc(t.flags, mountFunc) - // Setup test directory for testing. - t.testDirPath = setup.SetupTestDirectory(testDirName) - // Create a local file. - t.filePath, t.fh1 = CreateLocalFileInTestDir(t.ctx, t.storageClient, t.testDirPath, FileName1, t.T()) -} - -func (t *defaultFailureTestSuite) TearDownTest() { - // CleanUp MntDir before unmounting GCSFuse. - setup.CleanUpDir(rootDir) - setup.UnmountGCSFuse(rootDir) - assert.NoError(t.T(), t.closeStorageClient()) - assert.NoError(t.T(), emulator_tests.KillProxyServerProcess(port)) -} - -func (t *defaultFailureTestSuite) writingWithNewFileHandleAlsoFails(data []byte, off int64) { - t.T().Helper() - // Opening a new file handle succeeds. - fh := operations.OpenFile(t.filePath, t.T()) - // Writes with this file handle fails. - _, err := fh.WriteAt(data, off) - assert.Error(t.T(), err) - // Closing the file handle returns error. - operations.CloseFileShouldThrowError(t.T(), fh) -} - -func (t *defaultFailureTestSuite) writingAfterBwhReinitializationSucceeds() { - t.T().Helper() - // Verify that Object is not found on GCS. - ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) - // Opening new file handle and writing to file succeeds. - t.fh1 = operations.CreateFile(t.filePath, FilePerms, t.T()) - _, err := t.fh1.WriteAt(t.data, 0) - assert.NoError(t.T(), err) - // Sync succeeds. - err = t.fh1.Sync() - assert.NoError(t.T(), err) -} - -// ////////////////////////////////////////////////////////////////////// -// Tests -// ////////////////////////////////////////////////////////////////////// - -func (t *defaultFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFailure() { - // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. - // Fuse:[B] -> Go-SDK:[A] -> GCS[] - _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) - assert.NoError(t.T(), err) - // Write again 2MB (C, D) will trigger B upload. - // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] - _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) - - // Write 5th 1MB results in errors. - _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) - - require.Error(t.T(), err) - t.writingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) - // Close file handle to reinitialize bwh. - operations.CloseFileShouldThrowError(t.T(), t.fh1) - // Opening new file handle and writing to file succeeds. - t.writingAfterBwhReinitializationSucceeds() - // Close and validate object content found on GCS. - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) -} - -func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecondChunkUploadFailure() { - // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. - // Fuse:[B] -> Go-SDK:[A] -> GCS[] - _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) - assert.NoError(t.T(), err) - // Write again 2MB (C, D) will trigger B upload. - // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] - _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) - - // Write 5th 1MB results in errors. - _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) - - require.Error(t.T(), err) - // Truncate to smaller size fails. - err = t.fh1.Truncate(1 * operations.MiB) - assert.Error(t.T(), err) - t.writingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) - // Close file handle to reinitialize bwh. - operations.CloseFileShouldThrowError(t.T(), t.fh1) - // Opening new file handle and writing to file succeeds. - t.writingAfterBwhReinitializationSucceeds() - // Close and validate object content found on GCS. - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) -} - -func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSecondChunkUploadFailure() { - // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. - // Fuse:[B] -> Go-SDK:[A] -> GCS[] - _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) - assert.NoError(t.T(), err) - // Write again 2MB (C, D) will trigger B upload. - // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] - _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) - - // Write 5th 1MB results in errors. - _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) - - require.Error(t.T(), err) - // Opening new file handle succeeds. - fh2 := operations.OpenFile(t.filePath, t.T()) - // Truncate to bigger size succeeds. - err = fh2.Truncate(5 * operations.MiB) - assert.NoError(t.T(), err) - // Closing all file handles to reinitialize bwh. - operations.CloseFileShouldThrowError(t.T(), fh2) - operations.CloseFileShouldThrowError(t.T(), t.fh1) - // Opening new file handle and writing to file succeeds. - t.writingAfterBwhReinitializationSucceeds() - // Truncate to bigger size succeeds. - err = t.fh1.Truncate(6 * operations.MiB) - assert.NoError(t.T(), err) - // Close and validate object content found on GCS. - emptyBytes := make([]byte, operations.MiB) - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data)+string(emptyBytes), t.T()) -} - -func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploadFailure() { - // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. - // Fuse:[B] -> Go-SDK:[A] -> GCS[] - _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) - assert.NoError(t.T(), err) - // Sync file succeeds as the block B is only passed to Go-SDK for upload. - // Fuse:[] -> Go-SDK:[B]-> GCS[A] - operations.SyncFile(t.fh1, t.T()) - - // Write next 1 MB block C may succeed based on the status of block B. - // Fuse:[C] -> Go-SDK:[B]-> GCS[A] - _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:3*operations.MiB], 2*operations.MiB) - - // Sync now reports failure from B block upload. - // Fuse:[] -> Go-SDK:[C]-> GCS[A, B -> upload fails] - operations.SyncFileShouldThrowError(t.T(), t.fh1) - // Close file handle to reinitialize bwh. - operations.CloseFileShouldThrowError(t.T(), t.fh1) - // Opening new file handle and writing to file succeeds. - t.writingAfterBwhReinitializationSucceeds() - // Close and validate object content found on GCS. - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) -} - -func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUploadFailure() { - // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. - // Fuse:[B] -> Go-SDK:[A] -> GCS[] - _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) - assert.NoError(t.T(), err) - - // Close fails as it sees error from B block upload. - err = t.fh1.Close() - - require.Error(t.T(), err) - // Opening new file handle and writing to file succeeds. - t.writingAfterBwhReinitializationSucceeds() - // Close and validate object content found on GCS. - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) -} - -func (t *defaultFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() { - // Write 1 MB data to file succeeds and async upload of block will also succeed. - _, err := t.fh1.WriteAt(t.data[:operations.MiB], 0) - assert.NoError(t.T(), err) - - // Close fails as it sees error on the finalize. - err = t.fh1.Close() - - require.Error(t.T(), err) - // Opening new file handle and writing to file succeeds. - t.writingAfterBwhReinitializationSucceeds() - // Close and validate object content found on GCS. - CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) -} - -func TestUploadFailureTestSuite(t *testing.T) { - suite.Run(t, new(defaultFailureTestSuite)) -} diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go deleted file mode 100644 index 90927155c5..0000000000 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package streaming_writes_failure - -import ( - "fmt" - "log" - "os" - "testing" - - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" -) - -const ( - testDirName = "StreamingWritesFailureTest" - port = 8020 -) - -var ( - proxyEndpoint = fmt.Sprintf("http://localhost:%d/storage/v1/b?project=test-project", port) - mountFunc func([]string) error - // root directory is the directory to be unmounted. - rootDir string -) - -func TestMain(m *testing.M) { - setup.ParseSetUpFlags() - - if setup.MountedDirectory() != "" { - log.Printf("These tests will not run with mounted directory.") - return - } - - // Set up test directory. - setup.SetUpTestDirForTestBucketFlag() - rootDir = setup.MntDir() - log.Printf("Test log: %s\n", setup.LogFile()) - log.Println("Running static mounting tests...") - mountFunc = static_mounting.MountGcsfuseWithStaticMounting - successCode := m.Run() - os.Exit(successCode) -} diff --git a/tools/integration_tests/emulator_tests/util/test_helper.go b/tools/integration_tests/emulator_tests/util/test_helper.go index 92a60f6f96..88ed30aaeb 100644 --- a/tools/integration_tests/emulator_tests/util/test_helper.go +++ b/tools/integration_tests/emulator_tests/util/test_helper.go @@ -35,7 +35,7 @@ import ( // It launches the proxy server with the specified configuration and port, logs its output to a file. func StartProxyServer(configPath string) { // Start the proxy in the background - cmd := exec.Command("go", "run", "../proxy_server/.", "--config-path="+configPath) + cmd := exec.Command("go", "run", "./proxy_server/.", "--config-path="+configPath) logFileForProxyServer, err := os.Create(path.Join(os.Getenv("KOKORO_ARTIFACTS_DIR"), "proxy-"+setup.GenerateRandomString(5))) if err != nil { log.Fatal("Error in creating log file for proxy server.") @@ -64,8 +64,7 @@ func KillProxyServerProcess(port int) error { lines := strings.Split(string(output), "\n") for _, line := range lines[1:] { fields := strings.Fields(line) - // Kill only the process directly listening on the port. - if len(fields) > 1 && strings.Contains(line, "LISTEN") { + if len(fields) > 1 { pidStr := fields[1] pid, err := strconv.Atoi(pidStr) if err != nil { diff --git a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go b/tools/integration_tests/emulator_tests/write_stall_test.go similarity index 95% rename from tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go rename to tools/integration_tests/emulator_tests/write_stall_test.go index d31fc19da5..0df0ca3f08 100644 --- a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go +++ b/tools/integration_tests/emulator_tests/write_stall_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package write_stall +package emulator_tests import ( "fmt" @@ -41,7 +41,7 @@ type chunkTransferTimeoutInfinity struct { } func (s *chunkTransferTimeoutInfinity) Setup(t *testing.T) { - configPath := "../proxy_server/configs/write_stall_40s.yaml" + configPath := "./proxy_server/configs/write_stall_40s.yaml" emulator_tests.StartProxyServer(configPath) setup.MountGCSFuseWithGivenMountFunc(s.flags, mountFunc) } @@ -103,14 +103,14 @@ func TestChunkTransferTimeout(t *testing.T) { }{ { name: "SingleStall", - configPath: "../proxy_server/configs/write_stall_40s.yaml", + configPath: "./proxy_server/configs/write_stall_40s.yaml", expectedTimeout: func(chunkTransferTimeoutSecs int) time.Duration { return time.Duration(chunkTransferTimeoutSecs) * time.Second }, }, { name: "MultipleStalls", - configPath: "../proxy_server/configs/write_stall_twice_40s.yaml", // 2 stalls + configPath: "./proxy_server/configs/write_stall_twice_40s.yaml", // 2 stalls // Expect total time to be greater than the timeout multiplied by the number of stalls (2 in this case). expectedTimeout: func(chunkTransferTimeoutSecs int) time.Duration { return time.Duration(chunkTransferTimeoutSecs*2) * time.Second diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 2674d26af4..5ef70ae5b4 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -50,8 +50,6 @@ func CreateStorageClient(ctx context.Context) (client *storage.Client, err error return nil, fmt.Errorf("unable to fetch token-source for TPC: %w", err) } client, err = storage.NewClient(ctx, option.WithEndpoint("storage.apis-tpczero.goog:443"), option.WithTokenSource(ts)) - } else if setup.TestOnCustomEndpoint() != "" { - client, err = storage.NewClient(ctx, option.WithEndpoint(setup.TestOnCustomEndpoint())) } else { if setup.IsZonalBucketRun() { client, err = storage.NewGRPCClient(ctx, experimental.WithGRPCBidiReads()) diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 9ee43684f0..e8731aca27 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -579,13 +579,6 @@ func CloseFileShouldNotThrowError(file *os.File, t *testing.T) { } } -func CloseFileShouldThrowError(t *testing.T, file *os.File) { - t.Helper() - if err := file.Close(); err == nil { - t.Fatalf("file.Close() for file %s should throw an error: %v", file.Name(), err) - } -} - func SyncFile(fh *os.File, t *testing.T) { err := fh.Sync() @@ -595,13 +588,6 @@ func SyncFile(fh *os.File, t *testing.T) { } } -func SyncFileShouldThrowError(t *testing.T, file *os.File) { - t.Helper() - if err := file.Sync(); err == nil { - t.Fatalf("file.Close() for file %s should throw an error: %v", file.Name(), err) - } -} - func CreateFileWithContent(filePath string, filePerms os.FileMode, content string, t testing.TB) { fh := CreateFile(filePath, filePerms, t) diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 841bdf927a..1cbb4ce39b 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -43,7 +43,6 @@ var mountedDirectory = flag.String("mountedDirectory", "", "The GCSFuse mounted var integrationTest = flag.Bool("integrationTest", false, "Run tests only when the flag value is true.") var testInstalledPackage = flag.Bool("testInstalledPackage", false, "[Optional] Run tests on the package pre-installed on the host machine. By default, integration tests build a new package to run the tests.") var testOnTPCEndPoint = flag.Bool("testOnTPCEndPoint", false, "Run tests on TPC endpoint only when the flag value is true.") -var testOnCustomEndpoint = flag.String("testOnCustomEndpoint", "", "Run tests on custom endpoint only when the flag value is set. Required for tests requiring proxy server.") var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) const ( @@ -98,10 +97,6 @@ func TestOnTPCEndPoint() bool { return *testOnTPCEndPoint } -func TestOnCustomEndpoint() string { - return *testOnCustomEndpoint -} - func MountedDirectory() string { return *mountedDirectory } From 9ee0b0e6e718f1bab7d9003f35d629db8159d064 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 18 Feb 2025 21:34:19 +0530 Subject: [PATCH 0246/1298] [Refactoring] Common method to wrap error as Precondition or NotFound (#2999) * [Refactoring] To keep a common method to wrap under GCSFuse precondition-notfound error * minor refactoring * minor renaming * better renaming * review comments * fixing linux tests * review comments * Making the error handling same for all bucket interface methods * review comments * updating todo --- internal/storage/bucket_handle.go | 259 ++++++++++--------------- internal/storage/bucket_handle_test.go | 61 +----- internal/storage/gcs/errors.go | 48 ++++- internal/storage/gcs/errors_test.go | 150 ++++++++++++++ 4 files changed, 298 insertions(+), 220 deletions(-) create mode 100644 internal/storage/gcs/errors_test.go diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index f06f0faaa3..d77c1bee24 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -21,23 +21,17 @@ package storage import ( "context" - "errors" "fmt" "io" - "net/http" "time" "cloud.google.com/go/storage" "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googleapis/gax-go/v2" - "github.com/googleapis/gax-go/v2/apierror" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "google.golang.org/api/googleapi" "google.golang.org/api/iterator" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" ) const FullFolderPathHNS = "projects/_/buckets/%s/folders/%s" @@ -68,7 +62,12 @@ func (bh *bucketHandle) NewReader( func (bh *bucketHandle) NewReaderWithReadHandle( ctx context.Context, - req *gcs.ReadObjectRequest) (gcs.StorageReader, error) { + req *gcs.ReadObjectRequest) (reader gcs.StorageReader, err error) { + + defer func() { + err = gcs.GetGCSError(err) + }() + // Initialising the starting offset and the length to be read by the reader. start := int64(0) length := int64(-1) @@ -100,16 +99,15 @@ func (bh *bucketHandle) NewReaderWithReadHandle( } // NewRangeReader creates a "storage.Reader" object which is also io.ReadCloser since it contains both Read() and Close() methods present in io.ReadCloser interface. - r, err := obj.NewRangeReader(ctx, start, length) - - if err == storage.ErrObjectNotExist { - err = &gcs.NotFoundError{Err: storage.ErrObjectNotExist} - } - - return r, err + reader, err = obj.NewRangeReader(ctx, start, length) + return } -func (bh *bucketHandle) DeleteObject(ctx context.Context, req *gcs.DeleteObjectRequest) error { +func (bh *bucketHandle) DeleteObject(ctx context.Context, req *gcs.DeleteObjectRequest) (err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + obj := bh.bucket.Object(req.Name) // Switching to the requested generation of the object. By default, generation @@ -123,38 +121,23 @@ func (bh *bucketHandle) DeleteObject(ctx context.Context, req *gcs.DeleteObjectR obj = obj.If(storage.Conditions{MetagenerationMatch: *req.MetaGenerationPrecondition}) } - err := obj.Delete(ctx) - // If storage object does not exist, httpclient is returning ErrObjectNotExist error instead of googleapi error - // https://github.com/GoogleCloudPlatform/gcsfuse/blob/7ad451c6f2ead7992e030503e5b66c555b2ebf71/vendor/cloud.google.com/go/storage/http_client.go#L399 + err = obj.Delete(ctx) if err != nil { - switch ee := err.(type) { - case *googleapi.Error: - if ee.Code == http.StatusPreconditionFailed { - err = &gcs.PreconditionError{Err: ee} - } - default: - if err == storage.ErrObjectNotExist { - err = &gcs.NotFoundError{Err: storage.ErrObjectNotExist} - } else { - err = fmt.Errorf("error in deleting object: %w", err) - } - } + err = fmt.Errorf("error in deleting object: %w", err) } - return err - + return } func (bh *bucketHandle) StatObject(ctx context.Context, req *gcs.StatObjectRequest) (m *gcs.MinObject, e *gcs.ExtendedObjectAttributes, err error) { + + defer func() { + err = gcs.GetGCSError(err) + }() + var attrs *storage.ObjectAttrs // Retrieving object attrs through Go Storage Client. attrs, err = bh.bucket.Object(req.Name).Attrs(ctx) - - // If error is of type storage.ErrObjectNotExist - if err == storage.ErrObjectNotExist { - err = &gcs.NotFoundError{Err: err} // Special case error that object not found in the bucket. - return - } if err != nil { err = fmt.Errorf("error in fetching object attributes: %w", err) return @@ -202,6 +185,10 @@ func (bh *bucketHandle) getObjectHandleWithPreconditionsSet(req *gcs.CreateObjec } func (bh *bucketHandle) CreateObject(ctx context.Context, req *gcs.CreateObjectRequest) (o *gcs.Object, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + obj := bh.getObjectHandleWithPreconditionsSet(req) // Creating a NewWriter with requested attributes, using Go Storage Client. @@ -224,24 +211,6 @@ func (bh *bucketHandle) CreateObject(ctx context.Context, req *gcs.CreateObjectR // We can't use defer to close the writer, because we need to close the // writer successfully before calling Attrs() method of writer. if err = wc.Close(); err != nil { - // This checks if the error returned from the RPC call is a gRPC status error. - // If it is, and the error code is `codes.FailedPrecondition`, it means the - // operation failed due to a precondition not being met.The generic gRPC error - // is converted into a more specific `gcs.PreconditionError`. This allows handling - // of precondition failures differently. - if rpcErr, ok := status.FromError(err); ok { - if code := rpcErr.Code(); code == codes.FailedPrecondition { - err = &gcs.PreconditionError{Err: err} - return - } - } - var gErr *googleapi.Error - if errors.As(err, &gErr) { - if gErr.Code == http.StatusPreconditionFailed { - err = &gcs.PreconditionError{Err: err} - return - } - } err = fmt.Errorf("error in closing writer : %w", err) return } @@ -251,6 +220,7 @@ func (bh *bucketHandle) CreateObject(ctx context.Context, req *gcs.CreateObjectR o = storageutil.ObjectAttrsToBucketObject(attrs) return } + func (bh *bucketHandle) CreateObjectChunkWriter(ctx context.Context, req *gcs.CreateObjectRequest, chunkSize int, callBack func(bytesUploadedSoFar int64)) (gcs.Writer, error) { obj := bh.getObjectHandleWithPreconditionsSet(req) @@ -270,14 +240,11 @@ func (bh *bucketHandle) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cr } func (bh *bucketHandle) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + if err = w.Close(); err != nil { - var gErr *googleapi.Error - if errors.As(err, &gErr) { - if gErr.Code == http.StatusPreconditionFailed { - err = &gcs.PreconditionError{Err: err} - return - } - } err = fmt.Errorf("error in closing writer : %w", err) return } @@ -289,6 +256,10 @@ func (bh *bucketHandle) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gc } func (bh *bucketHandle) CopyObject(ctx context.Context, req *gcs.CopyObjectRequest) (o *gcs.Object, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + srcObj := bh.bucket.Object(req.SrcName) dstObj := bh.bucket.Object(req.DstName) @@ -305,17 +276,7 @@ func (bh *bucketHandle) CopyObject(ctx context.Context, req *gcs.CopyObjectReque objAttrs, err := dstObj.CopierFrom(srcObj).Run(ctx) if err != nil { - switch ee := err.(type) { - case *googleapi.Error: - if ee.Code == http.StatusPreconditionFailed { - err = &gcs.PreconditionError{Err: ee} - } - if ee.Code == http.StatusNotFound { - err = &gcs.NotFoundError{Err: storage.ErrObjectNotExist} - } - default: - err = fmt.Errorf("error in copying object: %w", err) - } + err = fmt.Errorf("error in copying object: %w", err) return } // Converting objAttrs to type *Object @@ -341,6 +302,10 @@ func getProjectionValue(req gcs.Projection) storage.Projection { } func (bh *bucketHandle) ListObjects(ctx context.Context, req *gcs.ListObjectsRequest) (listing *gcs.Listing, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + // Converting *ListObjectsRequest to type *storage.Query as expected by the Go Storage Client. query := &storage.Query{ Delimiter: req.Delimiter, @@ -405,6 +370,10 @@ func (bh *bucketHandle) ListObjects(ctx context.Context, req *gcs.ListObjectsReq } func (bh *bucketHandle) UpdateObject(ctx context.Context, req *gcs.UpdateObjectRequest) (o *gcs.Object, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + obj := bh.bucket.Object(req.Name) if req.Generation != 0 { @@ -444,31 +413,21 @@ func (bh *bucketHandle) UpdateObject(ctx context.Context, req *gcs.UpdateObjectR attrs, err := obj.Update(ctx, updateQuery) - if err == nil { - // Converting objAttrs to type *Object - o = storageutil.ObjectAttrsToBucketObject(attrs) + if err != nil { + err = fmt.Errorf("error in updating object: %w", err) return } - // If storage object does not exist, httpclient is returning ErrObjectNotExist error instead of googleapi error - // https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/vendor/cloud.google.com/go/storage/http_client.go#L516 - switch ee := err.(type) { - case *googleapi.Error: - if ee.Code == http.StatusPreconditionFailed { - err = &gcs.PreconditionError{Err: ee} - } - default: - if err == storage.ErrObjectNotExist { - err = &gcs.NotFoundError{Err: storage.ErrObjectNotExist} - } else { - err = fmt.Errorf("error in updating object: %w", err) - } - } - + // Converting objAttrs to type *Object + o = storageutil.ObjectAttrsToBucketObject(attrs) return } func (bh *bucketHandle) ComposeObjects(ctx context.Context, req *gcs.ComposeObjectsRequest) (o *gcs.Object, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + dstObj := bh.bucket.Object(req.DstName) dstObjConds := storage.Conditions{} @@ -507,53 +466,32 @@ func (bh *bucketHandle) ComposeObjects(ctx context.Context, req *gcs.ComposeObje // Composing Source Objects to Destination Object using Composer created through Go Storage Client. attrs, err := dstObj.ComposerFrom(srcObjList...).Run(ctx) if err != nil { - switch ee := err.(type) { - case *googleapi.Error: - if ee.Code == http.StatusPreconditionFailed { - err = &gcs.PreconditionError{Err: ee} - } - if ee.Code == http.StatusNotFound { - err = &gcs.NotFoundError{Err: storage.ErrObjectNotExist} - } - default: - err = fmt.Errorf("error in composing object: %w", err) - } + err = fmt.Errorf("error in composing object: %w", err) return } // Converting attrs to type *Object. o = storageutil.ObjectAttrsToBucketObject(attrs) - return } func (bh *bucketHandle) DeleteFolder(ctx context.Context, folderName string) (err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + var callOptions []gax.CallOption err = bh.controlClient.DeleteFolder(ctx, &controlpb.DeleteFolderRequest{ Name: fmt.Sprintf(FullFolderPathHNS, bh.bucketName, folderName), }, callOptions...) - - return err -} - -func isPreconditionFailed(err error) (bool, error) { - var gapiErr *googleapi.Error - if errors.As(err, &gapiErr) && gapiErr.Code == http.StatusPreconditionFailed { - return true, &gcs.PreconditionError{Err: gapiErr} - } - - var apiErr *apierror.APIError - if errors.As(err, &apiErr) && apiErr.GRPCStatus().Code() == codes.FailedPrecondition { - return true, &gcs.PreconditionError{Err: apiErr} - } - - return false, nil + return } -func (bh *bucketHandle) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (*gcs.Object, error) { - var o *gcs.Object - var err error +func (bh *bucketHandle) MoveObject(ctx context.Context, req *gcs.MoveObjectRequest) (o *gcs.Object, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() obj := bh.bucket.Object(req.SrcName) @@ -573,25 +511,21 @@ func (bh *bucketHandle) MoveObject(ctx context.Context, req *gcs.MoveObjectReque } attrs, err := obj.Move(ctx, dstMoveObject) - if err == nil { - // Converting objAttrs to type *Object - o = storageutil.ObjectAttrsToBucketObject(attrs) - return o, nil - } - - // If storage object does not exist, httpclient is returning ErrObjectNotExist error instead of googleapi error - // https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/vendor/cloud.google.com/go/storage/http_client.go#L516 - if ok, preCondErr := isPreconditionFailed(err); ok { - err = preCondErr - } else if errors.Is(err, storage.ErrObjectNotExist) { - err = &gcs.NotFoundError{Err: storage.ErrObjectNotExist} - } else { + if err != nil { err = fmt.Errorf("error in moving object: %w", err) + return } - return nil, err + + // Converting objAttrs to type *Object + o = storageutil.ObjectAttrsToBucketObject(attrs) + return } func (bh *bucketHandle) RenameFolder(ctx context.Context, folderName string, destinationFolderId string) (folder *gcs.Folder, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + var controlFolder *controlpb.Folder req := &controlpb.RenameFolderRequest{ Name: fmt.Sprintf(FullFolderPathHNS, bh.bucketName, folderName), @@ -599,15 +533,15 @@ func (bh *bucketHandle) RenameFolder(ctx context.Context, folderName string, des } resp, err := bh.controlClient.RenameFolder(ctx, req) if err != nil { - return nil, err + err = fmt.Errorf("error in renaming folder: %w", err) + return } // Wait blocks until the long-running operation is completed, // returning the response and any errors encountered. controlFolder, err = resp.Wait(ctx) folder = gcs.GCSFolder(bh.bucketName, controlFolder) - - return folder, err + return } // TODO: Consider adding this method to the bucket interface if additional @@ -623,29 +557,31 @@ func (bh *bucketHandle) getStorageLayout() (*controlpb.StorageLayout, error) { return stoargeLayout, err } -func (bh *bucketHandle) GetFolder(ctx context.Context, folderName string) (*gcs.Folder, error) { - var callOptions []gax.CallOption +func (bh *bucketHandle) GetFolder(ctx context.Context, folderName string) (folder *gcs.Folder, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() - clientFolder, err := bh.controlClient.GetFolder(ctx, &controlpb.GetFolderRequest{ + var callOptions []gax.CallOption + var clientFolder *controlpb.Folder + clientFolder, err = bh.controlClient.GetFolder(ctx, &controlpb.GetFolderRequest{ Name: fmt.Sprintf(FullFolderPathHNS, bh.bucketName, folderName), }, callOptions...) if err != nil { err = fmt.Errorf("error getting metadata for folder: %s, %w", folderName, err) - var gcsAPIErr *apierror.APIError - if errors.As(err, &gcsAPIErr) { - if "NotFound" == gcsAPIErr.GRPCStatus().Code().String() { - return nil, &gcs.NotFoundError{Err: err} - } - } - return nil, err + return } - folderResponse := gcs.GCSFolder(bh.bucketName, clientFolder) - return folderResponse, err + folder = gcs.GCSFolder(bh.bucketName, clientFolder) + return } -func (bh *bucketHandle) CreateFolder(ctx context.Context, folderName string) (*gcs.Folder, error) { +func (bh *bucketHandle) CreateFolder(ctx context.Context, folderName string) (folder *gcs.Folder, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + req := &controlpb.CreateFolderRequest{ Parent: fmt.Sprintf(FullBucketPathHNS, bh.bucketName), FolderId: folderName, @@ -654,16 +590,20 @@ func (bh *bucketHandle) CreateFolder(ctx context.Context, folderName string) (*g clientFolder, err := bh.controlClient.CreateFolder(ctx, req) if err != nil { - return nil, err + err = fmt.Errorf("error in creating folder: %w", err) + return } - folder := gcs.GCSFolder(bh.bucketName, clientFolder) - - return folder, nil + folder = gcs.GCSFolder(bh.bucketName, clientFolder) + return } func (bh *bucketHandle) NewMultiRangeDownloader( - ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (gcs.MultiRangeDownloader, error) { + ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (mrd gcs.MultiRangeDownloader, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + obj := bh.bucket.Object(req.Name) // Switching to the requested generation of object. @@ -675,7 +615,8 @@ func (bh *bucketHandle) NewMultiRangeDownloader( obj = obj.ReadCompressed(true) } - return obj.NewMultiRangeDownloader(ctx) + mrd, err = obj.NewMultiRangeDownloader(ctx) + return } func isStorageConditionsNotEmpty(conditions storage.Conditions) bool { diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index e82664b9a2..0134e5782e 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -18,7 +18,6 @@ import ( "context" "errors" "fmt" - "net/http" "reflect" "strings" "testing" @@ -27,14 +26,12 @@ import ( "cloud.google.com/go/storage" control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" - "github.com/googleapis/gax-go/v2/apierror" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "google.golang.org/api/googleapi" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -316,7 +313,7 @@ func (testSuite *BucketHandleTest) TestDeleteObjectMethodWithMissingObject() { MetaGenerationPrecondition: nil, }) - assert.Equal(testSuite.T(), "gcs.NotFoundError: storage: object doesn't exist", err.Error()) + assert.NotNil(testSuite.T(), err) } func (testSuite *BucketHandleTest) TestDeleteObjectMethodWithMissingGeneration() { @@ -1633,59 +1630,3 @@ func (testSuite *BucketHandleTest) TestCreateFolderWithGivenName() { assert.NoError(testSuite.T(), err) assert.Equal(testSuite.T(), gcs.GCSFolder(TestBucketName, &mockFolder), folder) } - -func TestIsPreconditionFailed(t *testing.T) { - preCondApiError, _ := apierror.FromError(status.New(codes.FailedPrecondition, "Precondition error").Err()) - notFoundApiError, _ := apierror.FromError(status.New(codes.NotFound, "Not Found error").Err()) - - tests := []struct { - name string - err error - expectPreCond bool - }{ - { - name: "googleapi.Error with PreconditionFailed", - err: &googleapi.Error{Code: http.StatusPreconditionFailed}, - expectPreCond: true, - }, - { - name: "googleapi.Error with other code", - err: &googleapi.Error{Code: http.StatusNotFound}, - expectPreCond: false, - }, - { - name: "apierror.APIError with FailedPrecondition", - err: preCondApiError, - expectPreCond: true, - }, - { - name: "apierror.APIError with other code", - err: notFoundApiError, - expectPreCond: false, - }, - { - name: "nil error", - err: nil, - expectPreCond: false, - }, - { - name: "generic error", - err: errors.New("generic error"), - expectPreCond: false, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - isPreCond, err := isPreconditionFailed(tt.err) - - assert.Equal(t, tt.expectPreCond, isPreCond) - if tt.expectPreCond { - var preCondErr *gcs.PreconditionError - assert.ErrorAs(t, err, &preCondErr) - } else { - assert.NoError(t, err) - } - }) - } -} diff --git a/internal/storage/gcs/errors.go b/internal/storage/gcs/errors.go index ccc4941581..9457e074ff 100644 --- a/internal/storage/gcs/errors.go +++ b/internal/storage/gcs/errors.go @@ -14,7 +14,16 @@ package gcs -import "fmt" +import ( + "errors" + "fmt" + "net/http" + + "cloud.google.com/go/storage" + "google.golang.org/api/googleapi" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) // A *NotFoundError value is an error that indicates an object name or a // particular generation for that name were not found. @@ -35,3 +44,40 @@ type PreconditionError struct { func (pe *PreconditionError) Error() string { return fmt.Sprintf("gcs.PreconditionError: %v", pe.Err) } + +// GetGCSError converts an error returned by go-sdk into gcsfuse specific common gcs error. +func GetGCSError(err error) error { + if err == nil { + return nil + } + + // Http client error. + var gErr *googleapi.Error + if errors.As(err, &gErr) { + switch gErr.Code { + case http.StatusNotFound: + return &NotFoundError{Err: err} + case http.StatusPreconditionFailed: + return &PreconditionError{Err: err} + } + } + + // RPC error (all gRPC client including control client). + if rpcErr, ok := status.FromError(err); ok { + switch rpcErr.Code() { + case codes.NotFound: + return &NotFoundError{Err: err} + case codes.FailedPrecondition: + return &PreconditionError{Err: err} + } + } + + // If storage object doesn't exist, go-sdk returns as ErrObjectNotExist. + // Important to note: currently go-sdk doesn't format/convert error coming from the control-client. + // Ref: http://shortn/_CY9Jyqf2wF + if errors.Is(err, storage.ErrObjectNotExist) { + return &NotFoundError{Err: err} + } + + return err +} diff --git a/internal/storage/gcs/errors_test.go b/internal/storage/gcs/errors_test.go new file mode 100644 index 0000000000..4ed1e8d022 --- /dev/null +++ b/internal/storage/gcs/errors_test.go @@ -0,0 +1,150 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcs + +import ( + "fmt" + "net/http" + "testing" + + "cloud.google.com/go/storage" + "github.com/googleapis/gax-go/v2/apierror" + "github.com/stretchr/testify/assert" + "google.golang.org/api/googleapi" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + "errors" +) + +func TestGetGCSError(t *testing.T) { + preconditionAPIErr, ok := apierror.FromError(status.Error(codes.FailedPrecondition, codes.FailedPrecondition.String())) + assert.True(t, ok) + + notFoundAPIErr, ok := apierror.FromError(status.Error(codes.NotFound, codes.NotFound.String())) + assert.True(t, ok) + + otherAPIErr, ok := apierror.FromError(status.Error(codes.Internal, codes.Internal.String())) + assert.True(t, ok) + + // TODO: to directly create the error for "wrapped_grpc_status_NotFound" sub-test, when the following issue is resolved. + // Ref: https://github.com/grpc/grpc-go/issues/8102 + grpcNotFoundErr := status.Error(codes.NotFound, "not found") + assert.True(t, ok) + + testCases := []struct { + name string + inputErr error + expectedErr error + }{ + { + name: "nil_error", + inputErr: nil, + expectedErr: nil, + }, + { + name: "googleapi.Error_NotFound", + inputErr: &googleapi.Error{Code: http.StatusNotFound}, + expectedErr: &NotFoundError{Err: &googleapi.Error{Code: http.StatusNotFound}}, + }, + { + name: "googleapi.Error_PreconditionFailed", + inputErr: &googleapi.Error{Code: http.StatusPreconditionFailed}, + expectedErr: &PreconditionError{Err: &googleapi.Error{Code: http.StatusPreconditionFailed}}, + }, + { + name: "googleapi.Error_other_code", + inputErr: &googleapi.Error{Code: http.StatusBadRequest}, + expectedErr: &googleapi.Error{Code: http.StatusBadRequest}, + }, + { + name: "wrapped_googleapi.Error_NotFound", + inputErr: fmt.Errorf("wrapped: %w", &googleapi.Error{Code: http.StatusNotFound}), + expectedErr: &NotFoundError{Err: fmt.Errorf("wrapped: %w", &googleapi.Error{Code: http.StatusNotFound})}, + }, + { + name: "grpc_status_NotFound", + inputErr: status.Error(codes.NotFound, "not found"), + expectedErr: &NotFoundError{Err: status.Error(codes.NotFound, "not found")}, + }, + { + name: "grpc_status_FailedPrecondition", + inputErr: status.Error(codes.FailedPrecondition, "failed precondition"), + expectedErr: &PreconditionError{Err: status.Error(codes.FailedPrecondition, "failed precondition")}, + }, + { + name: "grpc_status_other_code", + inputErr: status.Error(codes.Internal, "internal error"), + expectedErr: status.Error(codes.Internal, "internal error"), + }, + { + name: "other_error", + inputErr: errors.New("some error"), + expectedErr: errors.New("some error"), + }, + { + name: "wrapped_grpc_status_NotFound", + inputErr: fmt.Errorf("wrapped: %w", grpcNotFoundErr), + expectedErr: &NotFoundError{Err: fmt.Errorf("wrapped: %w", grpcNotFoundErr)}, + }, + { + name: "GCS_Precondition_error", + inputErr: &PreconditionError{Err: errors.New("precondition error")}, + expectedErr: &PreconditionError{Err: errors.New("precondition error")}, + }, + { + name: "GCS_NotFound_error", + inputErr: &NotFoundError{Err: errors.New("not found error")}, + expectedErr: &NotFoundError{Err: errors.New("not found error")}, + }, + { + name: "wrapped_GCS_Precondition_error", + inputErr: fmt.Errorf("wrapped: %w", &PreconditionError{Err: errors.New("precondition error")}), + expectedErr: fmt.Errorf("wrapped: %w", &PreconditionError{Err: errors.New("precondition error")}), + }, + { + name: "storage_object_not_exist", + inputErr: storage.ErrObjectNotExist, + expectedErr: &NotFoundError{Err: storage.ErrObjectNotExist}, + }, + { + name: "precondition_apierror", + inputErr: preconditionAPIErr, + expectedErr: &PreconditionError{Err: preconditionAPIErr}, + }, + { + name: "notfound_apierror", + inputErr: notFoundAPIErr, + expectedErr: &NotFoundError{Err: notFoundAPIErr}, + }, + { + name: "other_apierror", + inputErr: otherAPIErr, + expectedErr: otherAPIErr, + }, + { + name: "wrapped_precondition_apierror", + inputErr: fmt.Errorf("wrapped: %w", preconditionAPIErr), + expectedErr: &PreconditionError{Err: fmt.Errorf("wrapped: %w", preconditionAPIErr)}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + got := GetGCSError(tc.inputErr) + assert.Equal(t, tc.expectedErr, got) + }) + } +} From 4ee239aecaa60d79bdac1a8b7aae854b57e25a76 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 18 Feb 2025 21:45:38 +0530 Subject: [PATCH 0247/1298] Revert "Revert "Add Streaming Writes failure scenario tests for local files."" --- .../emulator_tests/emulator_tests.sh | 15 +- .../second_chunk_upload_returns412.yaml | 8 + .../local_file_failure_test.go | 250 ++++++++++++++++++ .../streaming_writes_failure_test.go | 55 ++++ .../emulator_tests/util/test_helper.go | 5 +- .../write_stall_test.go} | 2 +- .../writes_stall_on_sync_test.go} | 8 +- .../util/client/storage_client.go | 2 + .../util/operations/file_operations.go | 14 + tools/integration_tests/util/setup/setup.go | 5 + 10 files changed, 355 insertions(+), 9 deletions(-) create mode 100644 tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml create mode 100644 tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go create mode 100644 tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go rename tools/integration_tests/emulator_tests/{emulator_test.go => write_stall/write_stall_test.go} (98%) rename tools/integration_tests/emulator_tests/{write_stall_test.go => write_stall/writes_stall_on_sync_test.go} (95%) diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index 70a7e37b14..ff3240c1e6 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -88,6 +88,14 @@ DOCKER_NETWORK="--net=host" # Get the docker image for the testbench sudo docker pull $DOCKER_IMAGE +# Remove the docker container if it's already running. +CONTAINER_ID=$(sudo docker ps -aqf "name=$CONTAINER_NAME") +if [[ -n "$CONTAINER_ID" ]]; then + echo "Container with ID:[$CONTAINER_ID] is already running with name:[$CONTAINER_NAME]" + echo "Stoping container...." + sudo docker stop $CONTAINER_ID +fi + # Start the testbench sudo docker run --name $CONTAINER_NAME --rm -d $DOCKER_NETWORK $DOCKER_IMAGE echo "Running the Cloud Storage testbench: $STORAGE_EMULATOR_HOST" @@ -112,5 +120,8 @@ curl -X POST --data-binary @test.json \ "$STORAGE_EMULATOR_HOST/storage/v1/b?project=test-project" rm test.json -# Run specific test suite -go test ./tools/integration_tests/emulator_tests/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE +# Run Write Stall Tests. +go test ./tools/integration_tests/emulator_tests/write_stall/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE + +# Run Streaming Writes Failure Tests. +go test ./tools/integration_tests/emulator_tests/streaming_writes_failure/... -p 1 -short --integrationTest -v --testbucket=test-bucket --testOnCustomEndpoint=http://localhost:8020 -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml b/tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml new file mode 100644 index 0000000000..8464699da6 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml @@ -0,0 +1,8 @@ +targetHost: http://localhost:9000 +retryConfig: +- method: JsonCreate + retryInstruction: "return-412" + retryCount: 1 + # Skip count of three is required as first call is used to create the testDir on the GCS and then + # second and third call are used in creating resumable upload session uri and first chunk upload. + skipCount: 3 diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go new file mode 100644 index 0000000000..4ec2cbc3a3 --- /dev/null +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go @@ -0,0 +1,250 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes_failure + +import ( + "context" + "log" + "os" + "testing" + + "cloud.google.com/go/storage" + emulator_tests "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/emulator_tests/util" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type defaultFailureTestSuite struct { + suite.Suite + configPath string + flags []string + testDirPath string + filePath string + fh1 *os.File + storageClient *storage.Client // Storage Client based on proxy server. + closeStorageClient func() error + ctx context.Context + data []byte +} + +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// + +func (t *defaultFailureTestSuite) SetupSuite() { + t.configPath = "../proxy_server/configs/second_chunk_upload_returns412.yaml" + t.flags = []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1", "--custom-endpoint=" + proxyEndpoint} + // Generate 5 MB random data. + data, err := operations.GenerateRandomData(5 * operations.MiB) + t.data = data + require.NoError(t.T(), err) + log.Printf("Running tests with flags: %v", t.flags) +} + +func (t *defaultFailureTestSuite) SetupTest() { + // Start proxy server for each test to ensure the config is initialized per test. + emulator_tests.StartProxyServer(t.configPath) + // Create storage client before running tests. + t.ctx = context.Background() + t.closeStorageClient = client.CreateStorageClientWithCancel(&t.ctx, &t.storageClient) + setup.MountGCSFuseWithGivenMountFunc(t.flags, mountFunc) + // Setup test directory for testing. + t.testDirPath = setup.SetupTestDirectory(testDirName) + // Create a local file. + t.filePath, t.fh1 = CreateLocalFileInTestDir(t.ctx, t.storageClient, t.testDirPath, FileName1, t.T()) +} + +func (t *defaultFailureTestSuite) TearDownTest() { + // CleanUp MntDir before unmounting GCSFuse. + setup.CleanUpDir(rootDir) + setup.UnmountGCSFuse(rootDir) + assert.NoError(t.T(), t.closeStorageClient()) + assert.NoError(t.T(), emulator_tests.KillProxyServerProcess(port)) +} + +func (t *defaultFailureTestSuite) writingWithNewFileHandleAlsoFails(data []byte, off int64) { + t.T().Helper() + // Opening a new file handle succeeds. + fh := operations.OpenFile(t.filePath, t.T()) + // Writes with this file handle fails. + _, err := fh.WriteAt(data, off) + assert.Error(t.T(), err) + // Closing the file handle returns error. + operations.CloseFileShouldThrowError(t.T(), fh) +} + +func (t *defaultFailureTestSuite) writingAfterBwhReinitializationSucceeds() { + t.T().Helper() + // Verify that Object is not found on GCS. + ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) + // Opening new file handle and writing to file succeeds. + t.fh1 = operations.CreateFile(t.filePath, FilePerms, t.T()) + _, err := t.fh1.WriteAt(t.data, 0) + assert.NoError(t.T(), err) + // Sync succeeds. + err = t.fh1.Sync() + assert.NoError(t.T(), err) +} + +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// + +func (t *defaultFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFailure() { + // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. + // Fuse:[B] -> Go-SDK:[A] -> GCS[] + _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) + assert.NoError(t.T(), err) + // Write again 2MB (C, D) will trigger B upload. + // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] + _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + + // Write 5th 1MB results in errors. + _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + + require.Error(t.T(), err) + t.writingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + // Close file handle to reinitialize bwh. + operations.CloseFileShouldThrowError(t.T(), t.fh1) + // Opening new file handle and writing to file succeeds. + t.writingAfterBwhReinitializationSucceeds() + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) +} + +func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecondChunkUploadFailure() { + // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. + // Fuse:[B] -> Go-SDK:[A] -> GCS[] + _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) + assert.NoError(t.T(), err) + // Write again 2MB (C, D) will trigger B upload. + // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] + _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + + // Write 5th 1MB results in errors. + _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + + require.Error(t.T(), err) + // Truncate to smaller size fails. + err = t.fh1.Truncate(1 * operations.MiB) + assert.Error(t.T(), err) + t.writingWithNewFileHandleAlsoFails(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + // Close file handle to reinitialize bwh. + operations.CloseFileShouldThrowError(t.T(), t.fh1) + // Opening new file handle and writing to file succeeds. + t.writingAfterBwhReinitializationSucceeds() + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) +} + +func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSecondChunkUploadFailure() { + // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. + // Fuse:[B] -> Go-SDK:[A] -> GCS[] + _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) + assert.NoError(t.T(), err) + // Write again 2MB (C, D) will trigger B upload. + // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] + _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + + // Write 5th 1MB results in errors. + _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + + require.Error(t.T(), err) + // Opening new file handle succeeds. + fh2 := operations.OpenFile(t.filePath, t.T()) + // Truncate to bigger size succeeds. + err = fh2.Truncate(5 * operations.MiB) + assert.NoError(t.T(), err) + // Closing all file handles to reinitialize bwh. + operations.CloseFileShouldThrowError(t.T(), fh2) + operations.CloseFileShouldThrowError(t.T(), t.fh1) + // Opening new file handle and writing to file succeeds. + t.writingAfterBwhReinitializationSucceeds() + // Truncate to bigger size succeeds. + err = t.fh1.Truncate(6 * operations.MiB) + assert.NoError(t.T(), err) + // Close and validate object content found on GCS. + emptyBytes := make([]byte, operations.MiB) + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data)+string(emptyBytes), t.T()) +} + +func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploadFailure() { + // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. + // Fuse:[B] -> Go-SDK:[A] -> GCS[] + _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) + assert.NoError(t.T(), err) + // Sync file succeeds as the block B is only passed to Go-SDK for upload. + // Fuse:[] -> Go-SDK:[B]-> GCS[A] + operations.SyncFile(t.fh1, t.T()) + + // Write next 1 MB block C may succeed based on the status of block B. + // Fuse:[C] -> Go-SDK:[B]-> GCS[A] + _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:3*operations.MiB], 2*operations.MiB) + + // Sync now reports failure from B block upload. + // Fuse:[] -> Go-SDK:[C]-> GCS[A, B -> upload fails] + operations.SyncFileShouldThrowError(t.T(), t.fh1) + // Close file handle to reinitialize bwh. + operations.CloseFileShouldThrowError(t.T(), t.fh1) + // Opening new file handle and writing to file succeeds. + t.writingAfterBwhReinitializationSucceeds() + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) +} + +func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUploadFailure() { + // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. + // Fuse:[B] -> Go-SDK:[A] -> GCS[] + _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) + assert.NoError(t.T(), err) + + // Close fails as it sees error from B block upload. + err = t.fh1.Close() + + require.Error(t.T(), err) + // Opening new file handle and writing to file succeeds. + t.writingAfterBwhReinitializationSucceeds() + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) +} + +func (t *defaultFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() { + // Write 1 MB data to file succeeds and async upload of block will also succeed. + _, err := t.fh1.WriteAt(t.data[:operations.MiB], 0) + assert.NoError(t.T(), err) + + // Close fails as it sees error on the finalize. + err = t.fh1.Close() + + require.Error(t.T(), err) + // Opening new file handle and writing to file succeeds. + t.writingAfterBwhReinitializationSucceeds() + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) +} + +func TestUploadFailureTestSuite(t *testing.T) { + suite.Run(t, new(defaultFailureTestSuite)) +} diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go new file mode 100644 index 0000000000..90927155c5 --- /dev/null +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go @@ -0,0 +1,55 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes_failure + +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +const ( + testDirName = "StreamingWritesFailureTest" + port = 8020 +) + +var ( + proxyEndpoint = fmt.Sprintf("http://localhost:%d/storage/v1/b?project=test-project", port) + mountFunc func([]string) error + // root directory is the directory to be unmounted. + rootDir string +) + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + if setup.MountedDirectory() != "" { + log.Printf("These tests will not run with mounted directory.") + return + } + + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + rootDir = setup.MntDir() + log.Printf("Test log: %s\n", setup.LogFile()) + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() + os.Exit(successCode) +} diff --git a/tools/integration_tests/emulator_tests/util/test_helper.go b/tools/integration_tests/emulator_tests/util/test_helper.go index 88ed30aaeb..92a60f6f96 100644 --- a/tools/integration_tests/emulator_tests/util/test_helper.go +++ b/tools/integration_tests/emulator_tests/util/test_helper.go @@ -35,7 +35,7 @@ import ( // It launches the proxy server with the specified configuration and port, logs its output to a file. func StartProxyServer(configPath string) { // Start the proxy in the background - cmd := exec.Command("go", "run", "./proxy_server/.", "--config-path="+configPath) + cmd := exec.Command("go", "run", "../proxy_server/.", "--config-path="+configPath) logFileForProxyServer, err := os.Create(path.Join(os.Getenv("KOKORO_ARTIFACTS_DIR"), "proxy-"+setup.GenerateRandomString(5))) if err != nil { log.Fatal("Error in creating log file for proxy server.") @@ -64,7 +64,8 @@ func KillProxyServerProcess(port int) error { lines := strings.Split(string(output), "\n") for _, line := range lines[1:] { fields := strings.Fields(line) - if len(fields) > 1 { + // Kill only the process directly listening on the port. + if len(fields) > 1 && strings.Contains(line, "LISTEN") { pidStr := fields[1] pid, err := strconv.Atoi(pidStr) if err != nil { diff --git a/tools/integration_tests/emulator_tests/emulator_test.go b/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go similarity index 98% rename from tools/integration_tests/emulator_tests/emulator_test.go rename to tools/integration_tests/emulator_tests/write_stall/write_stall_test.go index 06a4cde1ff..a020587eb5 100644 --- a/tools/integration_tests/emulator_tests/emulator_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package emulator_tests +package write_stall import ( "fmt" diff --git a/tools/integration_tests/emulator_tests/write_stall_test.go b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go similarity index 95% rename from tools/integration_tests/emulator_tests/write_stall_test.go rename to tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go index 0df0ca3f08..d31fc19da5 100644 --- a/tools/integration_tests/emulator_tests/write_stall_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package emulator_tests +package write_stall import ( "fmt" @@ -41,7 +41,7 @@ type chunkTransferTimeoutInfinity struct { } func (s *chunkTransferTimeoutInfinity) Setup(t *testing.T) { - configPath := "./proxy_server/configs/write_stall_40s.yaml" + configPath := "../proxy_server/configs/write_stall_40s.yaml" emulator_tests.StartProxyServer(configPath) setup.MountGCSFuseWithGivenMountFunc(s.flags, mountFunc) } @@ -103,14 +103,14 @@ func TestChunkTransferTimeout(t *testing.T) { }{ { name: "SingleStall", - configPath: "./proxy_server/configs/write_stall_40s.yaml", + configPath: "../proxy_server/configs/write_stall_40s.yaml", expectedTimeout: func(chunkTransferTimeoutSecs int) time.Duration { return time.Duration(chunkTransferTimeoutSecs) * time.Second }, }, { name: "MultipleStalls", - configPath: "./proxy_server/configs/write_stall_twice_40s.yaml", // 2 stalls + configPath: "../proxy_server/configs/write_stall_twice_40s.yaml", // 2 stalls // Expect total time to be greater than the timeout multiplied by the number of stalls (2 in this case). expectedTimeout: func(chunkTransferTimeoutSecs int) time.Duration { return time.Duration(chunkTransferTimeoutSecs*2) * time.Second diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 5ef70ae5b4..2674d26af4 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -50,6 +50,8 @@ func CreateStorageClient(ctx context.Context) (client *storage.Client, err error return nil, fmt.Errorf("unable to fetch token-source for TPC: %w", err) } client, err = storage.NewClient(ctx, option.WithEndpoint("storage.apis-tpczero.goog:443"), option.WithTokenSource(ts)) + } else if setup.TestOnCustomEndpoint() != "" { + client, err = storage.NewClient(ctx, option.WithEndpoint(setup.TestOnCustomEndpoint())) } else { if setup.IsZonalBucketRun() { client, err = storage.NewGRPCClient(ctx, experimental.WithGRPCBidiReads()) diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index e8731aca27..9ee43684f0 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -579,6 +579,13 @@ func CloseFileShouldNotThrowError(file *os.File, t *testing.T) { } } +func CloseFileShouldThrowError(t *testing.T, file *os.File) { + t.Helper() + if err := file.Close(); err == nil { + t.Fatalf("file.Close() for file %s should throw an error: %v", file.Name(), err) + } +} + func SyncFile(fh *os.File, t *testing.T) { err := fh.Sync() @@ -588,6 +595,13 @@ func SyncFile(fh *os.File, t *testing.T) { } } +func SyncFileShouldThrowError(t *testing.T, file *os.File) { + t.Helper() + if err := file.Sync(); err == nil { + t.Fatalf("file.Close() for file %s should throw an error: %v", file.Name(), err) + } +} + func CreateFileWithContent(filePath string, filePerms os.FileMode, content string, t testing.TB) { fh := CreateFile(filePath, filePerms, t) diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 1cbb4ce39b..841bdf927a 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -43,6 +43,7 @@ var mountedDirectory = flag.String("mountedDirectory", "", "The GCSFuse mounted var integrationTest = flag.Bool("integrationTest", false, "Run tests only when the flag value is true.") var testInstalledPackage = flag.Bool("testInstalledPackage", false, "[Optional] Run tests on the package pre-installed on the host machine. By default, integration tests build a new package to run the tests.") var testOnTPCEndPoint = flag.Bool("testOnTPCEndPoint", false, "Run tests on TPC endpoint only when the flag value is true.") +var testOnCustomEndpoint = flag.String("testOnCustomEndpoint", "", "Run tests on custom endpoint only when the flag value is set. Required for tests requiring proxy server.") var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) const ( @@ -97,6 +98,10 @@ func TestOnTPCEndPoint() bool { return *testOnTPCEndPoint } +func TestOnCustomEndpoint() string { + return *testOnCustomEndpoint +} + func MountedDirectory() string { return *mountedDirectory } From 1824ef77092b999ddd2db99bcb55be7fb883009e Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 19 Feb 2025 11:05:24 +0530 Subject: [PATCH 0248/1298] Upgrading fuse for better fuse logging (#3009) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 50bd5f30b6..5c6a68af30 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.1 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec - github.com/jacobsa/fuse v0.0.0-20240607092844-7285af0d05b0 + github.com/jacobsa/fuse v0.0.0-20240626143436-8a36813dc074 github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 diff --git a/go.sum b/go.sum index 7aa8315aff..e46c19e4ae 100644 --- a/go.sum +++ b/go.sum @@ -245,8 +245,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= -github.com/jacobsa/fuse v0.0.0-20240607092844-7285af0d05b0 h1:IWVMQZZvWN+9FeRwWnZAINYNrsr3yyCWI2BcddQBDvk= -github.com/jacobsa/fuse v0.0.0-20240607092844-7285af0d05b0/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= +github.com/jacobsa/fuse v0.0.0-20240626143436-8a36813dc074 h1:rrmTkL654m7vQTYzi9NpEzAO7t0to5f1/jgkvSorVs8= +github.com/jacobsa/fuse v0.0.0-20240626143436-8a36813dc074/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= From 3b4023a99d16fc978c0aaf075bd96f1193581eb6 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 19 Feb 2025 13:51:36 +0530 Subject: [PATCH 0249/1298] turn flag on (#2995) --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 7aee16a980..c45049670b 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -317,7 +317,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.BoolP("enable-atomic-rename-object", "", false, "Enables support for atomic rename object operation on HNS bucket.") + flagSet.BoolP("enable-atomic-rename-object", "", true, "Enables support for atomic rename object operation on HNS bucket.") if err := flagSet.MarkHidden("enable-atomic-rename-object"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 7805fc8c6a..6750327f22 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -75,7 +75,7 @@ flag-name: "enable-atomic-rename-object" type: "bool" usage: "Enables support for atomic rename object operation on HNS bucket." - default: false + default: true hide-flag: true - config-path: "enable-hns" From 33e7a953a6e1f30e848158e31f7212e99228fe5d Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 19 Feb 2025 20:46:12 +0530 Subject: [PATCH 0250/1298] Add integration tests for read stall retries (#2997) * Added Read Stall Tests. * fixed lint * Updated read_stall_test * updated retryInstruction * changed start time * set skipcount to 1 * set skipCount to 2 * updated config file * added SetupSDuite and TearDownSuite * Added flag read-stall-initial-req-timeout * resolved comments * rebased * added read_stall tests to emulator_tests.sh * updated timeout * changed config path * Running all the tests from single command * reduced stall time --- .../emulator_tests/emulator_tests.sh | 7 +- .../proxy_server/configs/read_stall_5s.yaml | 6 ++ .../proxy_server/configs/write_stall_40s.yaml | 2 +- .../read_stall/read_stall_test.go | 97 +++++++++++++++++++ .../emulator_tests/read_stall/setup_test.go | 54 +++++++++++ .../emulator_tests/util/test_helper.go | 25 +++++ 6 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 tools/integration_tests/emulator_tests/proxy_server/configs/read_stall_5s.yaml create mode 100644 tools/integration_tests/emulator_tests/read_stall/read_stall_test.go create mode 100644 tools/integration_tests/emulator_tests/read_stall/setup_test.go diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index ff3240c1e6..ef53c83327 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -120,8 +120,5 @@ curl -X POST --data-binary @test.json \ "$STORAGE_EMULATOR_HOST/storage/v1/b?project=test-project" rm test.json -# Run Write Stall Tests. -go test ./tools/integration_tests/emulator_tests/write_stall/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE - -# Run Streaming Writes Failure Tests. -go test ./tools/integration_tests/emulator_tests/streaming_writes_failure/... -p 1 -short --integrationTest -v --testbucket=test-bucket --testOnCustomEndpoint=http://localhost:8020 -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE +# Run all the tests. +go test ./tools/integration_tests/emulator_tests/... -p 1 --integrationTest -v --testbucket=test-bucket --testOnCustomEndpoint=http://localhost:8020 -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/read_stall_5s.yaml b/tools/integration_tests/emulator_tests/proxy_server/configs/read_stall_5s.yaml new file mode 100644 index 0000000000..16c1445272 --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/configs/read_stall_5s.yaml @@ -0,0 +1,6 @@ +targetHost: http://localhost:9000 +retryConfig: +- method: XmlRead + retryInstruction: "stall-for-5s-after-0K" + retryCount: 1 + skipCount: 0 diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_40s.yaml b/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_40s.yaml index 89dc3ade0e..500ef70028 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_40s.yaml +++ b/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_40s.yaml @@ -5,6 +5,6 @@ retryConfig: retryCount: 1 # To add forced error scenarios for resumable uploads, we need to define skipCount two. # This is because the first POST request creates the file in our tests, and the second POST request only initiates - # the resumable upload request. Subsequent requests actually upload the data, and it's + # the resumable upload request. Subsequent requests actually upload the data, and it's # these requests we want to stall for testing. skipCount: 2 diff --git a/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go b/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go new file mode 100644 index 0000000000..702dca8557 --- /dev/null +++ b/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go @@ -0,0 +1,97 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_stall + +import ( + "fmt" + "log" + "path" + "testing" + "time" + + "github.com/stretchr/testify/suite" + + emulator_tests "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/emulator_tests/util" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" +) + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +const ( + fileSize = 5 * 1024 * 1024 + forcedStallTime = 5 * time.Second + minReqTimeout = 1500 * time.Millisecond +) + +type readStall struct { + flags []string + suite.Suite +} + +func (r *readStall) SetupSuite() { + configPath := "../proxy_server/configs/read_stall_5s.yaml" + emulator_tests.StartProxyServer(configPath) + setup.MountGCSFuseWithGivenMountFunc(r.flags, mountFunc) +} + +func (r *readStall) SetupTest() { + testDirPath = setup.SetupTestDirectory(r.T().Name()) +} + +func (r *readStall) TearDownSuite() { + setup.UnmountGCSFuse(rootDir) + assert.NoError(r.T(), emulator_tests.KillProxyServerProcess(port)) +} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +// TestReadFirstByteStallInducedShouldCompleteInLessThanStallTime verifies that reading the first byte +// of a file completes in less time than the configured stall time, even when a read stall is induced. +// It creates a file, reads the first byte, and asserts that the elapsed time is less than the expected stall duration. +func (r *readStall) TestReadFirstByteStallInducedShouldCompleteInLessThanStallTime() { + filePath := path.Join(testDirPath, "file.txt") + operations.CreateFileOfSize(fileSize, filePath, r.T()) + + elapsedTime, err := emulator_tests.ReadFirstByte(r.T(), filePath) + + assert.NoError(r.T(), err) + assert.Greater(r.T(), elapsedTime, minReqTimeout) + assert.Less(r.T(), elapsedTime, forcedStallTime) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestReadStall(t *testing.T) { + ts := &readStall{} + // Define flag set to run the tests. + flagsSet := [][]string{ + {"--custom-endpoint=" + proxyEndpoint, "--enable-read-stall-retry=true", "--read-stall-min-req-timeout=" + fmt.Sprintf("%dms", minReqTimeout.Milliseconds()), "--read-stall-initial-req-timeout=" + fmt.Sprintf("%dms", minReqTimeout.Milliseconds())}, + } + + // Run tests. + for _, flags := range flagsSet { + ts.flags = flags + log.Printf("Running tests with flags: %s", ts.flags) + suite.Run(t, ts) + } +} diff --git a/tools/integration_tests/emulator_tests/read_stall/setup_test.go b/tools/integration_tests/emulator_tests/read_stall/setup_test.go new file mode 100644 index 0000000000..be7c3389cb --- /dev/null +++ b/tools/integration_tests/emulator_tests/read_stall/setup_test.go @@ -0,0 +1,54 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_stall + +import ( + "fmt" + "log" + "os" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +const port = 8020 + +var ( + testDirPath string + mountFunc func([]string) error + // root directory is the directory to be unmounted. + rootDir string + proxyEndpoint = fmt.Sprintf("http://localhost:%d/storage/v1/b?project=test-project", port) +) + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + if setup.MountedDirectory() != "" { + log.Printf("These tests will not run with mounted directory..") + return + } + + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + + rootDir = setup.MntDir() + + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() + os.Exit(successCode) +} diff --git a/tools/integration_tests/emulator_tests/util/test_helper.go b/tools/integration_tests/emulator_tests/util/test_helper.go index 92a60f6f96..330ca4354c 100644 --- a/tools/integration_tests/emulator_tests/util/test_helper.go +++ b/tools/integration_tests/emulator_tests/util/test_helper.go @@ -25,8 +25,10 @@ import ( "strconv" "strings" "syscall" + "testing" "time" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) @@ -133,6 +135,29 @@ func WriteFileAndSync(filePath string, fileSize int) (time.Duration, error) { return endTime.Sub(startTime), nil } +// ReadFirstByte reads the first byte of a file and returns the time taken. +func ReadFirstByte(t *testing.T, filePath string) (time.Duration, error) { + t.Helper() + + file, err := operations.OpenFileAsReadonly(filePath) + if err != nil { + return 0, err + } + defer file.Close() + + buffer := make([]byte, 1) + + startTime := time.Now() + _, err = file.Read(buffer) + if err != nil && err != io.EOF { + return 0, err + } + + endTime := time.Now() + + return endTime.Sub(startTime), nil +} + func GetChunkTransferTimeoutFromFlags(flags []string) (int, error) { timeout := 10 // Default value for _, flag := range flags { From 3961773dec9c4cecb806c0e22e905aeb639e0038 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Thu, 20 Feb 2025 09:25:56 +0530 Subject: [PATCH 0251/1298] Fixing the missing error handling in RenameFolder (#3014) * Fixing the missing error handling in RenameFolder * fixing format --- internal/storage/bucket_handle.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index d77c1bee24..8bef26bd93 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -540,6 +540,11 @@ func (bh *bucketHandle) RenameFolder(ctx context.Context, folderName string, des // Wait blocks until the long-running operation is completed, // returning the response and any errors encountered. controlFolder, err = resp.Wait(ctx) + if err != nil { + err = fmt.Errorf("error in getting result from renaming folder response: %w", err) + return + } + folder = gcs.GCSFolder(bh.bucketName, controlFolder) return } From d5cd3dffe6d459db7cfc69c4084fb9944aa586c6 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Tue, 18 Feb 2025 08:35:49 +0000 Subject: [PATCH 0252/1298] Add request, respone and elapsed time logging in proxy server --- .../emulator_tests/proxy_server/main.go | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tools/integration_tests/emulator_tests/proxy_server/main.go b/tools/integration_tests/emulator_tests/proxy_server/main.go index 6ba0e14bdd..e4810e1286 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/main.go +++ b/tools/integration_tests/emulator_tests/proxy_server/main.go @@ -47,6 +47,18 @@ type ProxyHandler struct { http.Handler } +// logRequestAndType is used for logging the request on proxy server. +// More fields can be added or removed as per requirement for debugging purpose. +func logRequestAndType(req *http.Request, r RequestType) { + // Print empty lines to separate each request in log. + log.Println("") + log.Println("") + log.Printf("RequestType: %s\n", r) + log.Printf("URL: %s\n", req.URL.String()) + log.Printf("Content-Length: %s\n", req.Header.Get("Content-Length")) + log.Printf("Content-Range: %s\n", req.Header.Get("Content-Range")) +} + // AddRetryID creates mock error behavior on the target host for specific request types. // It retrieves the corresponding operation from the operation manager based on the provided RequestTypeAndInstruction. // If a matching operation is found, it creates a retry test with the target host and instruction, @@ -55,6 +67,12 @@ type ProxyHandler struct { // This function is used to simulate error scenarios for testing retry mechanisms. func AddRetryID(req *http.Request, r RequestTypeAndInstruction) error { plantOp := gOpManager.retrieveOperation(r.RequestType) + if *fDebug { + logRequestAndType(req, r.RequestType) + if plantOp != "" { + log.Println("Planting operation: ", plantOp) + } + } if plantOp != "" { testID, err := CreateRetryTest(gConfig.TargetHost, map[string][]string{r.Instruction: {plantOp}}) if err != nil { @@ -92,7 +110,9 @@ func (ph ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Send the request to the target server client := &http.Client{} + start := time.Now() resp, err := client.Do(req) + elapsed := time.Since(start) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return @@ -129,6 +149,10 @@ func (ph ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err != nil { log.Printf("Error in coping response body: %v", err) } + if *fDebug { + log.Printf("Respnse Status: %d\n", resp.StatusCode) + log.Printf("Elapsed Time: %.3fs\n", elapsed.Seconds()) + } } // ProxyServer represents a simple proxy server over GCS storage based API endpoint. From da15df6bed81b54774905136891441b66b0226bc Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 20 Feb 2025 15:08:30 +0530 Subject: [PATCH 0253/1298] fix log parsing in cache tests (#3016) * fix log parsing in cache tests * update log timestamp --- .../json_parser/read_logs/json_read_log_parser.go | 2 +- .../json_parser/read_logs/json_read_log_parser_test.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go index d0c852ddde..23dfecfdcd 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go @@ -47,7 +47,7 @@ func filterAndParseLogLine(logLine string, // Parse the logs based on type. switch { // TODO: change this to regex matching instead of strings.Contains. - case strings.Contains(logMessage, "ReadFile"): + case strings.Contains(logMessage, "<- ReadFile"): if err := parseReadFileLog(timestampSeconds, timestampNanos, tokenizedLogs, structuredLogs); err != nil { return fmt.Errorf("parseReadFileLog failed: %v", err) } diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go index d96f7b4a39..2d828c6c33 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go @@ -70,7 +70,8 @@ func TestParseLogFileSuccessful(t *testing.T) { reader: bytes.NewReader([]byte(`{"timestamp": {"seconds": 1704458059, "nanos": 975956234}, "severity": "TRACE", "message": "fuse_debug: Op 0x00000182 connection.go:415] <- ReadFile (inode 6, PID 2382526, handle 29, offset 0, 4096 bytes)"} {"timestamp": {"seconds": 1704458060, "nanos": 976093794}, "severity": "TRACE", "message": "f41c82a2-c891 <- FileCache(redacted:/smallfile.txt, offset: 0, size: 4096 handle: 29)"} {"timestamp": {"seconds": 1704458061, "nanos": 269924363}, "severity": "TRACE", "message": "Job:0xc000aa65b0 (redacted:/smallfile.txt) downloaded till 6 offset."} -{"timestamp": {"seconds": 1704458061, "nanos": 270075223}, "severity": "TRACE", "message": "f41c82a2-c891 -> OK (isSeq: true, hit: false) (293.935998ms)"}`), +{"timestamp": {"seconds": 1704458061, "nanos": 270075223}, "severity": "TRACE", "message": "f41c82a2-c891 -> OK (isSeq: true, hit: false) (293.935998ms)"} +{"timestamp": {"seconds": 1704458061, "nanos": 975956234}, "severity":"TRACE","message":"fuse_debug: Op 0x00000182 connection.go:497] -> ReadFile ()"}`), ), expected: map[int64]*read_logs.StructuredReadLogEntry{ handleId: { @@ -119,9 +120,9 @@ func TestParseLogFileSuccessful(t *testing.T) { }, { name: "Test file cache logs with no parsable logs", - reader: bytes.NewReader([]byte(`{"timestamp": {"seconds": 1704458059, "nanos": 975956234}, "severity":"TRACE","message":"fuse_debug: Op 0x00000182 connection.go:497] -> OK ()"} + reader: bytes.NewReader([]byte(`{"timestamp": {"seconds": 1704458059, "nanos": 975956234}, "severity":"TRACE","message":"fuse_debug: Op 0x00000182 connection.go:497] -> ReadFile ()"} {"timestamp": {"seconds": 1704458059, "nanos": 975956234}, "severity":"TRACE","message":"fuse_debug: Op 0x00000184 connection.go:415] <- FlushFile (inode 6, PID 2382526)"} -{"timestamp": {"seconds": 1704458059, "nanos": 975956234}, "severity":"TRACE","message":"fuse_debug: Op 0x00000184 connection.go:497] -> OK ()"}`), +{"timestamp": {"seconds": 1704458059, "nanos": 975956234}, "severity":"TRACE","message":"fuse_debug: Op 0x00000184 connection.go:497] -> FlushFile ()"}`), ), expected: make(map[int64]*read_logs.StructuredReadLogEntry), }, From 25a95c12e4b499d6e117a6c86e98a1264ab472ea Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 20 Feb 2025 17:10:54 +0530 Subject: [PATCH 0254/1298] Improve logging of default values in --help (#3019) * improve logging of default values in `gcsfuse --help` --- cfg/config.go | 20 ++++++++++---------- cfg/params.yaml | 21 ++++++++++----------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index c45049670b..f908c475e4 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -253,15 +253,15 @@ type WriteConfig struct { func BuildFlagSet(flagSet *pflag.FlagSet) error { - flagSet.BoolP("anonymous-access", "", false, "Authentication is enabled by default. This flag disables authentication") + flagSet.BoolP("anonymous-access", "", false, "This flag disables authentication.") flagSet.StringP("app-name", "", "", "The application name of this mount.") - flagSet.StringP("billing-project", "", "", "Project to use for billing when accessing a bucket enabled with \"Requester Pays\". (The default is none)") + flagSet.StringP("billing-project", "", "", "Project to use for billing when accessing a bucket enabled with \"Requester Pays\".") flagSet.StringP("cache-dir", "", "", "Enables file-caching. Specifies the directory to use for file-cache.") - flagSet.IntP("chunk-transfer-timeout-secs", "", 10, "We send larger file uploads in 16 MiB chunks. This flag controls the duration that the HTTP client will wait for a response after making a request to upload a chunk. The default value of 10s indicates that the client will wait 10 seconds for upload completion; otherwise, it cancels the request and retries for that chunk till chunkRetryDeadline(32s). 0 means no timeout.") + flagSet.IntP("chunk-transfer-timeout-secs", "", 10, "We send larger file uploads in 16 MiB chunks. This flag controls the duration that the HTTP client will wait for a response after making a request to upload a chunk. As an example, a value of 10 indicates that the client will wait 10 seconds for upload completion; otherwise, it cancels the request and retries for that chunk till chunkRetryDeadline(32s). 0 means no timeout.") if err := flagSet.MarkHidden("chunk-transfer-timeout-secs"); err != nil { return err @@ -429,15 +429,15 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.DurationP("http-client-timeout", "", 0*time.Nanosecond, "The time duration that http client will wait to get response from the server. The default value 0 indicates no timeout.") + flagSet.DurationP("http-client-timeout", "", 0*time.Nanosecond, "The time duration that http client will wait to get response from the server. A value of 0 indicates no timeout.") - flagSet.BoolP("ignore-interrupts", "", true, "Instructs gcsfuse to ignore system interrupt signals (like SIGINT, triggered by Ctrl+C). This prevents those signals from immediately terminating gcsfuse inflight operations. (default: true)") + flagSet.BoolP("ignore-interrupts", "", true, "Instructs gcsfuse to ignore system interrupt signals (like SIGINT, triggered by Ctrl+C). This prevents those signals from immediately terminating gcsfuse inflight operations.") flagSet.BoolP("implicit-dirs", "", false, "Implicitly define directories based on content. See files and directories in docs/semantics for more information") flagSet.IntP("kernel-list-cache-ttl-secs", "", 0, "How long the directory listing (output of ls ) should be cached in the kernel page cache. If a particular directory cache entry is kept by kernel for longer than TTL, then it will be sent for invalidation by gcsfuse on next opendir (comes in the start, as part of next listing) call. 0 means no caching. Use -1 to cache for lifetime (no ttl). Negative value other than -1 will throw error.") - flagSet.StringP("key-file", "", "", "Absolute path to JSON key file for use with GCS. (The default is none, Google application default credentials used)") + flagSet.StringP("key-file", "", "", "Absolute path to JSON key file for use with GCS. If this flag is left unset, Google application default credentials are used.") flagSet.Float64P("limit-bytes-per-sec", "", -1, "Bandwidth limit for reading data, measured over a 30-second window. (use -1 for no limit)") @@ -447,7 +447,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.StringP("log-format", "", "json", "The format of the log file: 'text' or 'json'.") - flagSet.IntP("log-rotate-backup-file-count", "", 10, "The maximum number of backup log files to retain after they have been rotated. The default value is 10. When value is set to 0, all backup files are retained.") + flagSet.IntP("log-rotate-backup-file-count", "", 10, "The maximum number of backup log files to retain after they have been rotated. A value of 0 indicates all backup files are retained.") flagSet.BoolP("log-rotate-compress", "", true, "Controls whether the rotated log files should be compressed using gzip.") @@ -455,11 +455,11 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.StringP("log-severity", "", "info", "Specifies the logging severity expressed as one of [trace, debug, info, warning, error, off]") - flagSet.IntP("max-conns-per-host", "", 0, "The max number of TCP connections allowed per server. This is effective when client-protocol is set to 'http1'. The default value 0 indicates no limit on TCP connections (limited by the machine specifications).") + flagSet.IntP("max-conns-per-host", "", 0, "The max number of TCP connections allowed per server. This is effective when client-protocol is set to 'http1'. A value of 0 indicates no limit on TCP connections (limited by the machine specifications).") flagSet.IntP("max-idle-conns-per-host", "", 100, "The number of maximum idle connections allowed per server.") - flagSet.IntP("max-retry-attempts", "", 0, "It sets a limit on the number of times an operation will be retried if it fails, preventing endless retry loops. The default value 0 indicates no limit.") + flagSet.IntP("max-retry-attempts", "", 0, "It sets a limit on the number of times an operation will be retried if it fails, preventing endless retry loops. A value of 0 indicates no limit.") flagSet.DurationP("max-retry-duration", "", 0*time.Nanosecond, "This is currently unused.") @@ -531,7 +531,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.IntP("sequential-read-size-mb", "", 200, "File chunk size to read from GCS in one call. Need to specify the value in MB. ChunkSize less than 1MB is not supported") - flagSet.DurationP("stackdriver-export-interval", "", 0*time.Nanosecond, "Export metrics to stackdriver with this interval. The default value 0 indicates no exporting.") + flagSet.DurationP("stackdriver-export-interval", "", 0*time.Nanosecond, "Export metrics to stackdriver with this interval. A value of 0 indicates no exporting.") if err := flagSet.MarkDeprecated("stackdriver-export-interval", "Please use --cloud-metrics-export-interval-secs instead."); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 6750327f22..2c879c342d 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -186,7 +186,7 @@ usage: >- Instructs gcsfuse to ignore system interrupt signals (like SIGINT, triggered by Ctrl+C). This prevents those signals from immediately terminating gcsfuse - inflight operations. (default: true) + inflight operations. default: true - config-path: "file-system.kernel-list-cache-ttl-secs" @@ -240,13 +240,13 @@ - config-path: "gcs-auth.anonymous-access" flag-name: "anonymous-access" type: "bool" - usage: "Authentication is enabled by default. This flag disables authentication" + usage: "This flag disables authentication." default: false - config-path: "gcs-auth.key-file" flag-name: "key-file" type: "resolvedPath" - usage: "Absolute path to JSON key file for use with GCS. (The default is none, Google application default credentials used)" + usage: "Absolute path to JSON key file for use with GCS. If this flag is left unset, Google application default credentials are used." - config-path: "gcs-auth.reuse-token-from-url" flag-name: "reuse-token-from-url" @@ -265,7 +265,7 @@ type: "string" usage: >- Project to use for billing when accessing a bucket enabled with "Requester - Pays". (The default is none) + Pays". default: "" - config-path: "gcs-connection.client-protocol" @@ -312,7 +312,7 @@ type: "duration" usage: >- The time duration that http client will wait to get response from the - server. The default value 0 indicates no timeout. + server. A value of 0 indicates no timeout. default: "0s" - config-path: "gcs-connection.limit-bytes-per-sec" @@ -332,7 +332,7 @@ type: "int" usage: >- The max number of TCP connections allowed per server. This is effective when - client-protocol is set to 'http1'. The default value 0 indicates no limit on + client-protocol is set to 'http1'. A value of 0 indicates no limit on TCP connections (limited by the machine specifications). default: "0" @@ -354,7 +354,7 @@ usage: >- We send larger file uploads in 16 MiB chunks. This flag controls the duration that the HTTP client will wait for a response after making a request to upload a chunk. - The default value of 10s indicates that the client will wait 10 seconds for upload completion; + As an example, a value of 10 indicates that the client will wait 10 seconds for upload completion; otherwise, it cancels the request and retries for that chunk till chunkRetryDeadline(32s). 0 means no timeout. default: "10" hide-flag: true @@ -364,7 +364,7 @@ type: "int" usage: >- It sets a limit on the number of times an operation will be retried if it - fails, preventing endless retry loops. The default value 0 indicates no limit. + fails, preventing endless retry loops. A value of 0 indicates no limit. default: "0" - config-path: "gcs-retries.max-retry-sleep" @@ -465,8 +465,7 @@ type: "int" usage: >- The maximum number of backup log files to retain after they have been - rotated. The default value is 10. When value is set to 0, all backup files are - retained. + rotated. A value of 0 indicates all backup files are retained. default: "10" - config-path: "logging.log-rotate.compress" @@ -613,7 +612,7 @@ flag-name: "stackdriver-export-interval" type: "duration" usage: >- - Export metrics to stackdriver with this interval. The default value 0 + Export metrics to stackdriver with this interval. A value of 0 indicates no exporting. default: "0s" deprecated: true From 4a1a4afba6bc904da08790e11f1e368694561b13 Mon Sep 17 00:00:00 2001 From: Mohit Yadav Date: Tue, 18 Feb 2025 17:30:15 +0000 Subject: [PATCH 0255/1298] Add failure scenario integration tests for empty gcs file for buffered writes --- .../emulator_tests/emulator_tests.sh | 2 +- ..._gcs_file_2nd_chunk_upload_returns412.yaml | 9 +++ ...cal_file_2nd_chunk_upload_returns412.yaml} | 0 ...failure_test.go => common_failure_test.go} | 54 +++++++++-------- .../empty_gcs_file_failure_test.go | 58 +++++++++++++++++++ .../new_local_file_failure_test.go | 53 +++++++++++++++++ .../streaming_writes_failure_test.go | 7 ++- 7 files changed, 151 insertions(+), 32 deletions(-) create mode 100644 tools/integration_tests/emulator_tests/proxy_server/configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml rename tools/integration_tests/emulator_tests/proxy_server/configs/{second_chunk_upload_returns412.yaml => local_file_2nd_chunk_upload_returns412.yaml} (100%) rename tools/integration_tests/emulator_tests/streaming_writes_failure/{local_file_failure_test.go => common_failure_test.go} (84%) create mode 100644 tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go create mode 100644 tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index ef53c83327..f40d19d9f6 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -121,4 +121,4 @@ curl -X POST --data-binary @test.json \ rm test.json # Run all the tests. -go test ./tools/integration_tests/emulator_tests/... -p 1 --integrationTest -v --testbucket=test-bucket --testOnCustomEndpoint=http://localhost:8020 -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE +go test ./tools/integration_tests/emulator_tests/... -p 1 --integrationTest -v --testbucket=test-bucket --testOnCustomEndpoint=http://localhost:8020/storage/v1/ -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml b/tools/integration_tests/emulator_tests/proxy_server/configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml new file mode 100644 index 0000000000..5e9259474d --- /dev/null +++ b/tools/integration_tests/emulator_tests/proxy_server/configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml @@ -0,0 +1,9 @@ +targetHost: http://localhost:9000 +retryConfig: +- method: JsonCreate + retryInstruction: "return-412" + retryCount: 1 + # Skip count of four is required as first call is used to create the testDir on the GCS and + # second is used to create the empty GCS file and third, fourth call are used in creating + # resumable upload session uri and first chunk upload. + skipCount: 4 diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml b/tools/integration_tests/emulator_tests/proxy_server/configs/local_file_2nd_chunk_upload_returns412.yaml similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/configs/second_chunk_upload_returns412.yaml rename to tools/integration_tests/emulator_tests/proxy_server/configs/local_file_2nd_chunk_upload_returns412.yaml diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go similarity index 84% rename from tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go rename to tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go index 4ec2cbc3a3..f5cbdec7ca 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/local_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go @@ -18,11 +18,9 @@ import ( "context" "log" "os" - "testing" "cloud.google.com/go/storage" emulator_tests "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/emulator_tests/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" @@ -36,7 +34,7 @@ import ( // Boilerplate // ////////////////////////////////////////////////////////////////////// -type defaultFailureTestSuite struct { +type commonFailureTestSuite struct { suite.Suite configPath string flags []string @@ -47,44 +45,48 @@ type defaultFailureTestSuite struct { closeStorageClient func() error ctx context.Context data []byte + gcsObjectValidator +} + +type gcsObjectValidator interface { + // Validate file from GCS for empty gcs file and new local files. + validateGcsObject() } // ////////////////////////////////////////////////////////////////////// // Helpers // ////////////////////////////////////////////////////////////////////// -func (t *defaultFailureTestSuite) SetupSuite() { - t.configPath = "../proxy_server/configs/second_chunk_upload_returns412.yaml" +func (t *commonFailureTestSuite) SetupSuite() { t.flags = []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1", "--custom-endpoint=" + proxyEndpoint} // Generate 5 MB random data. - data, err := operations.GenerateRandomData(5 * operations.MiB) - t.data = data + var err error + t.data, err = operations.GenerateRandomData(5 * operations.MiB) require.NoError(t.T(), err) log.Printf("Running tests with flags: %v", t.flags) } -func (t *defaultFailureTestSuite) SetupTest() { +func (t *commonFailureTestSuite) setupTest() { + t.T().Helper() // Start proxy server for each test to ensure the config is initialized per test. emulator_tests.StartProxyServer(t.configPath) // Create storage client before running tests. t.ctx = context.Background() - t.closeStorageClient = client.CreateStorageClientWithCancel(&t.ctx, &t.storageClient) + t.closeStorageClient = CreateStorageClientWithCancel(&t.ctx, &t.storageClient) setup.MountGCSFuseWithGivenMountFunc(t.flags, mountFunc) + // Setup random testDirName. + testDirName = testDirNamePrefix + setup.GenerateRandomString(5) // Setup test directory for testing. t.testDirPath = setup.SetupTestDirectory(testDirName) - // Create a local file. - t.filePath, t.fh1 = CreateLocalFileInTestDir(t.ctx, t.storageClient, t.testDirPath, FileName1, t.T()) } -func (t *defaultFailureTestSuite) TearDownTest() { - // CleanUp MntDir before unmounting GCSFuse. - setup.CleanUpDir(rootDir) +func (t *commonFailureTestSuite) TearDownTest() { setup.UnmountGCSFuse(rootDir) assert.NoError(t.T(), t.closeStorageClient()) assert.NoError(t.T(), emulator_tests.KillProxyServerProcess(port)) } -func (t *defaultFailureTestSuite) writingWithNewFileHandleAlsoFails(data []byte, off int64) { +func (t *commonFailureTestSuite) writingWithNewFileHandleAlsoFails(data []byte, off int64) { t.T().Helper() // Opening a new file handle succeeds. fh := operations.OpenFile(t.filePath, t.T()) @@ -95,10 +97,10 @@ func (t *defaultFailureTestSuite) writingWithNewFileHandleAlsoFails(data []byte, operations.CloseFileShouldThrowError(t.T(), fh) } -func (t *defaultFailureTestSuite) writingAfterBwhReinitializationSucceeds() { +func (t *commonFailureTestSuite) writingAfterBwhReinitializationSucceeds() { t.T().Helper() - // Verify that Object is not found on GCS. - ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) + // Verify that expectation from GCS matches. + t.validateGcsObject() // Opening new file handle and writing to file succeeds. t.fh1 = operations.CreateFile(t.filePath, FilePerms, t.T()) _, err := t.fh1.WriteAt(t.data, 0) @@ -112,7 +114,7 @@ func (t *defaultFailureTestSuite) writingAfterBwhReinitializationSucceeds() { // Tests // ////////////////////////////////////////////////////////////////////// -func (t *defaultFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFailure() { +func (t *commonFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFailure() { // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) @@ -134,7 +136,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesFailsOnSecondChunkUploadFai CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } -func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecondChunkUploadFailure() { +func (t *commonFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecondChunkUploadFailure() { // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) @@ -159,7 +161,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateSmallerFailsOnSecon CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } -func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSecondChunkUploadFailure() { +func (t *commonFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSecondChunkUploadFailure() { // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) @@ -190,7 +192,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesTruncateBiggerSucceedsOnSec CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data)+string(emptyBytes), t.T()) } -func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploadFailure() { +func (t *commonFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploadFailure() { // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) @@ -214,7 +216,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesSyncFailsOnSecondChunkUploa CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } -func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUploadFailure() { +func (t *commonFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUploadFailure() { // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. // Fuse:[B] -> Go-SDK:[A] -> GCS[] _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) @@ -230,7 +232,7 @@ func (t *defaultFailureTestSuite) TestStreamingWritesCloseFailsOnSecondChunkUplo CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } -func (t *defaultFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() { +func (t *commonFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() { // Write 1 MB data to file succeeds and async upload of block will also succeed. _, err := t.fh1.WriteAt(t.data[:operations.MiB], 0) assert.NoError(t.T(), err) @@ -244,7 +246,3 @@ func (t *defaultFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() // Close and validate object content found on GCS. CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } - -func TestUploadFailureTestSuite(t *testing.T) { - suite.Run(t, new(defaultFailureTestSuite)) -} diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go new file mode 100644 index 0000000000..27d589f041 --- /dev/null +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go @@ -0,0 +1,58 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes_failure + +import ( + "path" + "testing" + + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type emptyGcsFileFailureTestSuite struct { + commonFailureTestSuite +} + +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// + +func (t *emptyGcsFileFailureTestSuite) SetupTest() { + t.configPath = "../proxy_server/configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml" + t.setupTest() + // Create an empty file on GCS. + CreateObjectInGCSTestDir(t.ctx, t.storageClient, testDirName, FileName1, "", t.T()) + ValidateObjectContentsFromGCS(t.ctx, t.storageClient, testDirName, FileName1, "", t.T()) + t.filePath = path.Join(t.testDirPath, FileName1) + t.fh1 = operations.OpenFile(t.filePath, t.T()) +} + +func (t *emptyGcsFileFailureTestSuite) validateGcsObject() { + // Validate empty gcs file is found on GCS. + ValidateObjectContentsFromGCS(t.ctx, t.storageClient, testDirName, FileName1, "", t.T()) +} + +// Executes all failure tests for empty gcs files. +func TestEmptyGcsFileFailureTestSuite(t *testing.T) { + s := new(emptyGcsFileFailureTestSuite) + s.gcsObjectValidator = s + suite.Run(t, s) +} diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go new file mode 100644 index 0000000000..34f65a0cc0 --- /dev/null +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go @@ -0,0 +1,53 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes_failure + +import ( + "testing" + + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type newLocalFileFailureTestSuite struct { + commonFailureTestSuite +} + +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// + +func (t *newLocalFileFailureTestSuite) SetupTest() { + t.configPath = "../proxy_server/configs/local_file_2nd_chunk_upload_returns412.yaml" + t.setupTest() + // Create local file. + t.filePath, t.fh1 = CreateLocalFileInTestDir(t.ctx, t.storageClient, t.testDirPath, FileName1, t.T()) +} + +func (t *newLocalFileFailureTestSuite) validateGcsObject() { + // Validate file not found error from GCS. + ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, FileName1, t.T()) +} + +// Executes all failure tests for new local files. +func TestNewLocalFileFailureTestSuite(t *testing.T) { + s := new(newLocalFileFailureTestSuite) + s.gcsObjectValidator = s + suite.Run(t, s) +} diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go index 90927155c5..ddad91c78b 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go @@ -25,15 +25,16 @@ import ( ) const ( - testDirName = "StreamingWritesFailureTest" - port = 8020 + testDirNamePrefix = "StreamingWritesFailureTest" + port = 8020 ) var ( proxyEndpoint = fmt.Sprintf("http://localhost:%d/storage/v1/b?project=test-project", port) mountFunc func([]string) error // root directory is the directory to be unmounted. - rootDir string + rootDir string + testDirName string ) func TestMain(m *testing.M) { From 32dd3b8a45eb94ca080b75e9d4e5a8fb72fb6d61 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 21 Feb 2025 08:10:40 +0530 Subject: [PATCH 0256/1298] Use different ports for Prometheus metrics tests (#3015) * Ports don't quickly become available after use. So, this can lead to bind errors when one tries to use the same port for multiple tests. Fix that by using different ports for different tests. * Use non-HNS bucket when tests are being run in non-HNS mode. --- .../integration_tests/monitoring/prom_test.go | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/tools/integration_tests/monitoring/prom_test.go b/tools/integration_tests/monitoring/prom_test.go index f0191078bb..b78e337371 100644 --- a/tools/integration_tests/monitoring/prom_test.go +++ b/tools/integration_tests/monitoring/prom_test.go @@ -36,12 +36,33 @@ import ( ) const ( - testBucket = "gcsfuse_monitoring_test_bucket" - portNonHNSRun = 9191 - portHNSRun = 9192 + testHNSBucket = "gcsfuse_monitoring_test_bucket" + testFlatBucket = "gcsfuse_monitoring_test_bucket_flat" ) -var prometheusPort = portNonHNSRun +var ( + portNonHNSRun = 9190 + portHNSRun = 10190 +) + +var prometheusPort int + +func setPrometheusPort(t *testing.T) { + if isHNSTestRun(t) { + prometheusPort = portHNSRun + portHNSRun++ + return + } + prometheusPort = portNonHNSRun + portNonHNSRun++ +} + +func getBucket(t *testing.T) string { + if isHNSTestRun(t) { + return testHNSBucket + } + return testFlatBucket +} func isPortOpen(port int) bool { c := exec.Command("lsof", "-t", fmt.Sprintf("-i:%d", port)) @@ -71,12 +92,6 @@ func isHNSTestRun(t *testing.T) bool { func (testSuite *PromTest) SetupSuite() { setup.IgnoreTestIfIntegrationTestFlagIsNotSet(testSuite.T()) - if isHNSTestRun(testSuite.T()) { - // sets different Prometheus ports for HNS and non-HNS presubmit runs. - // This ensures that there is no port contention if both HNS and non-HNS test runs are happening simultaneously. - prometheusPort = portHNSRun - } - err := setup.SetUpTestDir() require.NoErrorf(testSuite.T(), err, "error while building GCSFuse: %p", err) } @@ -86,9 +101,10 @@ func (testSuite *PromTest) SetupTest() { testSuite.gcsfusePath = setup.BinFile() testSuite.mountPoint, err = os.MkdirTemp("", "gcsfuse_monitoring_tests") require.NoError(testSuite.T(), err) + setPrometheusPort(testSuite.T()) setup.SetLogFile(fmt.Sprintf("%s%s.txt", "/tmp/gcsfuse_monitoring_test_", strings.ReplaceAll(testSuite.T().Name(), "/", "_"))) - err = testSuite.mount(testBucket) + err = testSuite.mount(getBucket(testSuite.T())) require.NoError(testSuite.T(), err) } From 9a7348087a631312f04e719fdd8e5e8b9cc4de06 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 21 Feb 2025 10:31:00 +0530 Subject: [PATCH 0257/1298] Run monitoring tests in installed_package mode (#3021) * Run monitoring tests in installed_package mode * The monitoring tests are currently skipped in installed_package mode of test runs. This will be changed and monitoring tests will be run in the installed package mode as well. * Don't clear the test directory post test run. This is to ensure that logs remain available and can be collected post run. --- tools/cd_scripts/e2e_test.sh | 1 + tools/integration_tests/monitoring/prom_test.go | 10 ---------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 417e43773b..ddf73ca960 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -142,6 +142,7 @@ git checkout $(sed -n 2p ~/details.txt) |& tee -a ~/logs.txt set +e # Test directory arrays TEST_DIR_PARALLEL=( + "monitoring" "local_file" "log_rotation" "mounting" diff --git a/tools/integration_tests/monitoring/prom_test.go b/tools/integration_tests/monitoring/prom_test.go index b78e337371..e634d771ed 100644 --- a/tools/integration_tests/monitoring/prom_test.go +++ b/tools/integration_tests/monitoring/prom_test.go @@ -118,10 +118,6 @@ func (testSuite *PromTest) TearDownTest() { assert.NoError(testSuite.T(), err) } -func (testSuite *PromTest) TearDownSuite() { - os.RemoveAll(setup.TestDir()) -} - func (testSuite *PromTest) mount(bucketName string) error { testSuite.T().Helper() if portAvailable := isPortOpen(prometheusPort); !portAvailable { @@ -258,15 +254,9 @@ func (testSuite *PromTest) TestReadMetrics() { } func TestPromOCSuite(t *testing.T) { - if setup.TestInstalledPackage() { - t.Skip("Skipping since testing on installed package") - } suite.Run(t, &PromTest{enableOTEL: false}) } func TestPromOTELSuite(t *testing.T) { - if setup.TestInstalledPackage() { - t.Skip("Skipping since testing on installed package") - } suite.Run(t, &PromTest{enableOTEL: true}) } From d699f24d424cbf52b0a2212c85f6502e34b33367 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Fri, 21 Feb 2025 15:16:31 +0530 Subject: [PATCH 0258/1298] fix write large file tests (#3013) --- .../log_content/file_upload_log_test.go | 5 +- .../util/operations/file_operations.go | 62 ++++++++-------- .../concurrent_write_files_test.go | 61 +++++----------- .../concurrent_write_to_same_file_test.go | 70 ++++++------------- .../random_write_large_file_test.go | 59 ++++++---------- .../seq_write_large_file_test.go | 37 ++++------ .../write_large_files_test.go | 21 ------ 7 files changed, 109 insertions(+), 206 deletions(-) diff --git a/tools/integration_tests/log_content/file_upload_log_test.go b/tools/integration_tests/log_content/file_upload_log_test.go index c70f870c70..f2cdf5fd83 100644 --- a/tools/integration_tests/log_content/file_upload_log_test.go +++ b/tools/integration_tests/log_content/file_upload_log_test.go @@ -44,10 +44,7 @@ func uploadFile(t *testing.T, dirNamePrefix string, fileSize int64) { filePath := path.Join(testDir, FileName) // Sequentially write the data in file. - err = operations.WriteFileSequentially(filePath, fileSize, fileSize) - if err != nil { - t.Fatalf("Error in writing file: %v", err) - } + operations.WriteFilesSequentially(t, []string{filePath}, fileSize, fileSize) } func extractRelevantLogsFromLogFile(t *testing.T, logFile string, logFileOffset int64) (logString string) { diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 9ee43684f0..ddf90f26f7 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -18,7 +18,6 @@ package operations import ( "bytes" "compress/gzip" - "crypto/rand" "fmt" "hash/crc32" "io" @@ -34,6 +33,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/net/context" ) @@ -171,6 +171,14 @@ func WriteFile(fileName string, content string) (err error) { return } +func CloseFiles(t *testing.T, files []*os.File) { + t.Helper() + for _, file := range files { + err := file.Close() + assert.NoError(t, err) + } +} + func CloseFile(file *os.File) { if err := file.Close(); err != nil { log.Fatalf("error in closing: %v", err) @@ -228,17 +236,11 @@ func ReadFileSequentially(filePath string, chunkSize int64) (content []byte, err return } -// Write data of chunkSize in file at given offset. -func WriteChunkOfRandomBytesToFile(file *os.File, chunkSize int, offset int64) error { - return WriteChunkOfRandomBytesToFiles([]*os.File{file}, chunkSize, offset) -} - func WriteChunkOfRandomBytesToFiles(files []*os.File, chunkSize int, offset int64) error { // Generate random data of chunk size. - chunk := make([]byte, chunkSize) - _, err := rand.Read(chunk) + chunk, err := GenerateRandomData(int64(chunkSize)) if err != nil { - return fmt.Errorf("error while generating random string: %v", err) + return fmt.Errorf("error in generating random data: %v", err) } for _, file := range files { @@ -261,28 +263,17 @@ func WriteChunkOfRandomBytesToFiles(files []*os.File, chunkSize int, offset int6 return nil } -func WriteFileSequentially(filePath string, fileSize int64, chunkSize int64) (err error) { - file, err := os.OpenFile(filePath, os.O_RDWR|syscall.O_DIRECT|os.O_CREATE, FilePermission_0600) - if err != nil { - log.Fatalf("Error in opening file: %v", err) - } - - // Closing file at the end. - defer CloseFile(file) +func WriteFilesSequentially(t *testing.T, filePaths []string, fileSize int64, chunkSize int64) { + t.Helper() + files := OpenFiles(t, filePaths) + defer CloseFiles(t, files) var offset int64 = 0 - for offset < fileSize { - // Get random chunkSize or remaining filesize data into chunk. - if (fileSize - offset) < chunkSize { - chunkSize = fileSize - offset - } - - err := WriteChunkOfRandomBytesToFile(file, int(chunkSize), offset) - if err != nil { - log.Fatalf("Error in writing chunk: %v", err) - } - + // Reduce chunk size to remaining file size in case chunk size is larger. + chunkSize = min(chunkSize, fileSize-offset) + err := WriteChunkOfRandomBytesToFiles(files, int(chunkSize), offset) + assert.NoError(t, err) offset = offset + chunkSize } return @@ -482,7 +473,20 @@ func CreateFile(filePath string, filePerms os.FileMode, t testing.TB) (f *os.Fil return } -func OpenFile(filePath string, t testing.TB) (f *os.File) { +func OpenFiles(t *testing.T, filePaths []string) []*os.File { + t.Helper() + var files []*os.File + + // Open all files. + for _, filePath := range filePaths { + file, err := os.OpenFile(filePath, os.O_RDWR|syscall.O_DIRECT|os.O_CREATE, FilePermission_0600) + require.NoError(t, err) + files = append(files, file) + } + return files +} + +func OpenFile(filePath string, t *testing.T) (f *os.File) { f, err := os.OpenFile(filePath, os.O_RDWR, FilePermission_0777) if err != nil { t.Fatalf("OpenFile(%s): %v", filePath, err) diff --git a/tools/integration_tests/write_large_files/concurrent_write_files_test.go b/tools/integration_tests/write_large_files/concurrent_write_files_test.go index aba12bed84..6867b89a63 100644 --- a/tools/integration_tests/write_large_files/concurrent_write_files_test.go +++ b/tools/integration_tests/write_large_files/concurrent_write_files_test.go @@ -15,14 +15,13 @@ package write_large_files import ( - "fmt" - "os" "path" - "syscall" "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" ) @@ -30,58 +29,34 @@ const ( DirForConcurrentWrite = "dirForConcurrentWrite" ) -var FileOne = "fileOne" + setup.GenerateRandomString(5) + ".txt" -var FileTwo = "fileTwo" + setup.GenerateRandomString(5) + ".txt" -var FileThree = "fileThree" + setup.GenerateRandomString(5) + ".txt" - -func writeFile(filePath string, fileSize int64) error { - f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|syscall.O_DIRECT, WritePermission_0200) - if err != nil { - return fmt.Errorf("Open file for write at start: %v", err) - } - - // Closing file at the end. - defer operations.CloseFile(f) - return operations.WriteChunkOfRandomBytesToFile(f, int(fileSize), 0) -} - -func validateFileContents(fileName string, mntFilePath string, t *testing.T) error { - filePathInGcsBucket := path.Join(DirForConcurrentWrite, fileName) - localFilePath := path.Join(TmpDir, fileName) - return compareFileFromGCSBucketAndMntDir(filePathInGcsBucket, mntFilePath, localFilePath, t) -} - -func TestMultipleFilesAtSameTime(t *testing.T) { - concurrentWriteDir := path.Join(setup.MntDir(), DirForConcurrentWrite) - setup.SetupTestDirectory(DirForConcurrentWrite) - - // Clean up. - defer operations.RemoveDir(concurrentWriteDir) - +func TestWriteMultipleFilesConcurrently(t *testing.T) { + concurrentWriteDir := setup.SetupTestDirectory(DirForConcurrentWrite) + var FileOne = "fileOne" + setup.GenerateRandomString(5) + ".txt" + var FileTwo = "fileTwo" + setup.GenerateRandomString(5) + ".txt" + var FileThree = "fileThree" + setup.GenerateRandomString(5) + ".txt" files := []string{FileOne, FileTwo, FileThree} - var eG errgroup.Group - // Concurrently write three files. + // Concurrently write three different files. for i := range files { // Copy the current value of i into a local variable to avoid data races. fileIndex := i // Thread to write the current file. eG.Go(func() error { - mntFilePath := path.Join(setup.MntDir(), DirForConcurrentWrite, files[fileIndex]) - err := writeFile(mntFilePath, FiveHundredMB) - if err != nil { - return fmt.Errorf("WriteError: %v", err) - } + mountedDirFilePath := path.Join(concurrentWriteDir, files[fileIndex]) + localFilePath := path.Join(TmpDir, setup.GenerateRandomString(5)) + t.Cleanup(func() { operations.RemoveFile(localFilePath) }) + + operations.WriteFilesSequentially(t, []string{localFilePath, mountedDirFilePath}, FiveHundredMB, ChunkSize) - return validateFileContents(files[fileIndex], mntFilePath, t) + identical, err := operations.AreFilesIdentical(mountedDirFilePath, localFilePath) + require.NoError(t, err) + assert.True(t, identical) + return nil }) } // Wait on threads to end. - err := eG.Wait() - if err != nil { - t.Fatalf("Error: %v", err) - } + _ = eG.Wait() } diff --git a/tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go b/tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go index 07ecb2090f..5c42b6917a 100644 --- a/tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go +++ b/tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go @@ -17,84 +17,56 @@ package write_large_files import ( - "os" "path" - "syscall" "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" ) func TestWriteToSameFileConcurrently(t *testing.T) { + // Setup Test directory and files to write to. seqWriteDir := setup.SetupTestDirectory(DirForSeqWrite) - mountedFilePath := path.Join(seqWriteDir, "50mb"+setup.GenerateRandomString(5)+".txt") - localFilePath := path.Join(TmpDir, "50mbLocal"+setup.GenerateRandomString(5)+".txt") - localFile := operations.CreateFile(localFilePath, setup.FilePermission_0600, t) - - // Clean up. - defer operations.RemoveDir(seqWriteDir) - defer operations.RemoveFile(localFilePath) - + mountedDirFilePath := path.Join(seqWriteDir, setup.GenerateRandomString(5)) + localFilePath := path.Join(TmpDir, setup.GenerateRandomString(5)) + t.Cleanup(func() { operations.RemoveFile(localFilePath) }) + // We will have x numbers of concurrent writers trying to write to the same file. + // Every thread will start at offset = writer_index * (fileSize/thread_count). var eG errgroup.Group concurrentWriterCount := 5 chunkSize := 50 * OneMiB / concurrentWriterCount - // We will have x numbers of concurrent threads trying to write from the same file. - // Every thread will start at offset = thread_index * (fileSize/thread_count). for i := 0; i < concurrentWriterCount; i++ { offset := i * chunkSize - eG.Go(func() error { - return writeToFileSequentially(localFilePath, mountedFilePath, offset, offset+chunkSize, t) + writeToFileSequentially(t, []string{localFilePath, mountedDirFilePath}, offset, offset+chunkSize) + return nil }) } // Wait on threads to end. err := eG.Wait() - if err != nil { - t.Errorf("writing failed") - } - - // Close the local file since the below method will open the file again. - operations.CloseFile(localFile) - - identical, err := operations.AreFilesIdentical(mountedFilePath, localFilePath) - if !identical { - t.Fatalf("Comparision failed: %v", err) - } + require.NoError(t, err) + identical, err := operations.AreFilesIdentical(mountedDirFilePath, localFilePath) + require.NoError(t, err) + assert.True(t, identical) } -func writeToFileSequentially(localFilePath string, mountedFilePath string, startOffset int, endOffset int, t *testing.T) (err error) { - mountedFile, err := os.OpenFile(mountedFilePath, os.O_RDWR|syscall.O_DIRECT|os.O_CREATE, setup.FilePermission_0600) - if err != nil { - t.Fatalf("Error in opening file: %v", err) - } - - localFile, err := os.OpenFile(localFilePath, os.O_RDWR|syscall.O_DIRECT|os.O_CREATE, setup.FilePermission_0600) - if err != nil { - t.Fatalf("Error in opening file: %v", err) - } - - filesToWrite := []*os.File{localFile, mountedFile} - - // Closing file at the end. - defer operations.CloseFile(mountedFile) - defer operations.CloseFile(localFile) +func writeToFileSequentially(t *testing.T, filePaths []string, startOffset int, endOffset int) { + t.Helper() + filesToWrite := operations.OpenFiles(t, filePaths) + defer operations.CloseFiles(t, filesToWrite) - var chunkSize = 5 * OneMiB + var chunkSize = OneMiB for startOffset < endOffset { - if (endOffset - startOffset) < chunkSize { - chunkSize = endOffset - startOffset - } + chunkSize = min(chunkSize, endOffset-startOffset) err := operations.WriteChunkOfRandomBytesToFiles(filesToWrite, chunkSize, int64(startOffset)) - if err != nil { - t.Fatalf("Error in writing chunk: %v", err) - } + require.NoError(t, err) startOffset = startOffset + chunkSize } - return } diff --git a/tools/integration_tests/write_large_files/random_write_large_file_test.go b/tools/integration_tests/write_large_files/random_write_large_file_test.go index 267b0d4314..2f20b88ec2 100644 --- a/tools/integration_tests/write_large_files/random_write_large_file_test.go +++ b/tools/integration_tests/write_large_files/random_write_large_file_test.go @@ -16,57 +16,42 @@ package write_large_files import ( rand2 "math/rand" - "os" "path" - "syscall" "testing" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( NumberOfRandomWriteCalls = 20 DirForRandomWrite = "dirForRandomWrite" - MaxWritableByteFromFile = 500 * OneMiB + MaxFileOffset = 500 * OneMiB ) -var FiveHundredMBFileForRandomWriteInLocalSystem string = "fiveHundredMBFileForRandomWriteInLocalSystem" + setup.GenerateRandomString(5) - func TestWriteLargeFileRandomly(t *testing.T) { - randomWriteDir := path.Join(setup.MntDir(), DirForRandomWrite) - setup.SetupTestDirectory(DirForRandomWrite) - filePath := path.Join(randomWriteDir, FiveHundredMBFile) - - // Clean up. - defer operations.RemoveDir(randomWriteDir) - - f, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|syscall.O_DIRECT, WritePermission_0200) - if err != nil { - t.Fatalf("Open file for write at start: %v", err) - } - - defer operations.CloseFile(f) + // Setup Test directory and files to write to. + randomWriteDir := setup.SetupTestDirectory(DirForRandomWrite) + mountedDirFilePath := path.Join(randomWriteDir, FiveHundredMBFile) + localFilePath := path.Join(TmpDir, setup.GenerateRandomString(5)) + t.Cleanup(func() { operations.RemoveFile(localFilePath) }) + // Open local file and mounted directory file for writing. + filesToWrite := operations.OpenFiles(t, []string{localFilePath, mountedDirFilePath}) for i := 0; i < NumberOfRandomWriteCalls; i++ { - offset := rand2.Int63n(MaxWritableByteFromFile) - - // Generate chunk with random data. - chunkSize := ChunkSize - if offset+ChunkSize > MaxWritableByteFromFile { - chunkSize = int(MaxWritableByteFromFile - offset) - } - - err := operations.WriteChunkOfRandomBytesToFile(f, chunkSize, offset) - if err != nil { - t.Fatalf("Error: %v", err) - } - - filePathInGcsBucket := path.Join(DirForRandomWrite, FiveHundredMBFile) - localFilePath := path.Join(TmpDir, FiveHundredMBFileForRandomWriteInLocalSystem) - err = compareFileFromGCSBucketAndMntDir(filePathInGcsBucket, filePath, localFilePath, t) - if err != nil { - t.Fatalf("Error: %v", err) - } + offset := rand2.Int63n(MaxFileOffset - ChunkSize) + // Aligning to 4KiB page boundaries is required for O_DIRECT writes to local files. + offset = offset - offset%(4*util.KiB) + // Write chunk of random data to both local and mounted directory file. + err := operations.WriteChunkOfRandomBytesToFiles(filesToWrite, ChunkSize, offset) + require.NoError(t, err) } + + operations.CloseFiles(t, filesToWrite) + identical, err := operations.AreFilesIdentical(mountedDirFilePath, localFilePath) + require.NoError(t, err) + assert.True(t, identical) } diff --git a/tools/integration_tests/write_large_files/seq_write_large_file_test.go b/tools/integration_tests/write_large_files/seq_write_large_file_test.go index 3e0c07efb1..5163af5099 100644 --- a/tools/integration_tests/write_large_files/seq_write_large_file_test.go +++ b/tools/integration_tests/write_large_files/seq_write_large_file_test.go @@ -20,6 +20,8 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( @@ -28,29 +30,18 @@ const ( DirForSeqWrite = "dirForSeqWrite" ) -var FiveHundredMBFile string = "fiveHundredMBFile" + setup.GenerateRandomString(5) + ".txt" -var FiveHundredMBFileForSeqWriteInLocalSystem string = "fiveHundredMBFileForSeqWriteInLocalSystem" + setup.GenerateRandomString(5) +var FiveHundredMBFile = "fiveHundredMBFile" + setup.GenerateRandomString(5) + ".txt" func TestWriteLargeFileSequentially(t *testing.T) { - seqWriteDir := path.Join(setup.MntDir(), DirForSeqWrite) - setup.SetupTestDirectory(DirForSeqWrite) - filePath := path.Join(seqWriteDir, FiveHundredMBFile) - - // Clean up. - defer operations.RemoveDir(seqWriteDir) - - // Sequentially write the data in file. - err := operations.WriteFileSequentially(filePath, FiveHundredMB, ChunkSize) - if err != nil { - t.Fatalf("Error in writing file: %v", err) - } - - // Download the file from a bucket in which we write the content and compare with - // the file content we wrote in mntDir. - filePathInGcsBucket := path.Join(DirForSeqWrite, FiveHundredMBFile) - localFilePath := path.Join(TmpDir, FiveHundredMBFileForSeqWriteInLocalSystem) - err = compareFileFromGCSBucketAndMntDir(filePathInGcsBucket, filePath, localFilePath, t) - if err != nil { - t.Fatalf("Error:%v", err) - } + seqWriteDir := setup.SetupTestDirectory(DirForSeqWrite) + mountedDirFilePath := path.Join(seqWriteDir, FiveHundredMBFile) + localFilePath := path.Join(TmpDir, setup.GenerateRandomString(5)) + t.Cleanup(func() { operations.RemoveFile(localFilePath) }) + + // Write sequentially to local and mounted directory file. + operations.WriteFilesSequentially(t, []string{localFilePath, mountedDirFilePath}, FiveHundredMB, ChunkSize) + + identical, err := operations.AreFilesIdentical(mountedDirFilePath, localFilePath) + require.NoError(t, err) + assert.True(t, identical) } diff --git a/tools/integration_tests/write_large_files/write_large_files_test.go b/tools/integration_tests/write_large_files/write_large_files_test.go index 2d88ee1291..a286c1b3bc 100644 --- a/tools/integration_tests/write_large_files/write_large_files_test.go +++ b/tools/integration_tests/write_large_files/write_large_files_test.go @@ -16,15 +16,11 @@ package write_large_files import ( - "fmt" "log" "os" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) @@ -34,23 +30,6 @@ const ( WritePermission_0200 = 0200 ) -func compareFileFromGCSBucketAndMntDir(gcsFile, mntDirFile, localFilePathToDownloadGcsFile string, t *testing.T) error { - err := client.DownloadObjectFromGCS(gcsFile, localFilePathToDownloadGcsFile, t) - if err != nil { - return fmt.Errorf("Error in downloading object: %v", err) - } - - // Remove file after testing. - defer operations.RemoveFile(localFilePathToDownloadGcsFile) - - identical, err := operations.AreFilesIdentical(mntDirFile, localFilePathToDownloadGcsFile) - if !identical { - return fmt.Errorf("Download of GCS object %s didn't match the Mounted local file (%s): %v", localFilePathToDownloadGcsFile, mntDirFile, err) - } - - return nil -} - func TestMain(m *testing.M) { setup.ParseSetUpFlags() From 073938cb5fc8f2aed98ef8f71678f967c7a1ccff Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 24 Feb 2025 09:25:06 +0530 Subject: [PATCH 0259/1298] Upgrade jacobsa fuse (#3023) * upgrade to latest of jacobsa/fuse * add implementations of SyncFS * empty commit * address a review comment --- go.mod | 2 +- go.sum | 4 ++-- internal/fs/fs.go | 6 ++++++ internal/fs/wrappers/error_mapping.go | 9 +++++++++ internal/fs/wrappers/monitoring.go | 4 ++++ internal/fs/wrappers/tracing.go | 4 ++++ internal/fs/wrappers/tracing_test.go | 4 ++++ 7 files changed, 30 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5c6a68af30..1ff4b5c9c6 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.1 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec - github.com/jacobsa/fuse v0.0.0-20240626143436-8a36813dc074 + github.com/jacobsa/fuse v0.0.0-20241025064006-8ccd61173b05 github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 diff --git a/go.sum b/go.sum index e46c19e4ae..ae88fbd8ad 100644 --- a/go.sum +++ b/go.sum @@ -245,8 +245,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= -github.com/jacobsa/fuse v0.0.0-20240626143436-8a36813dc074 h1:rrmTkL654m7vQTYzi9NpEzAO7t0to5f1/jgkvSorVs8= -github.com/jacobsa/fuse v0.0.0-20240626143436-8a36813dc074/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= +github.com/jacobsa/fuse v0.0.0-20241025064006-8ccd61173b05 h1:PDBoLGtxDPVzWurS8Vd5SFbafRRg2gSNVjLEaJlGTxw= +github.com/jacobsa/fuse v0.0.0-20241025064006-8ccd61173b05/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= diff --git a/internal/fs/fs.go b/internal/fs/fs.go index e0ed806ac6..d99f460dc7 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2719,3 +2719,9 @@ func (fs *fileSystem) ListXattr( op *fuseops.ListXattrOp) error { return syscall.ENOSYS } + +func (fs *fileSystem) SyncFS( + ctx context.Context, + op *fuseops.SyncFSOp) error { + return syscall.ENOSYS +} diff --git a/internal/fs/wrappers/error_mapping.go b/internal/fs/wrappers/error_mapping.go index 89d1c07d4a..d5535cbb3b 100644 --- a/internal/fs/wrappers/error_mapping.go +++ b/internal/fs/wrappers/error_mapping.go @@ -396,3 +396,12 @@ func (em *errorMapping) Fallocate( err := em.wrapped.Fallocate(ctx, op) return em.mapError("Fallocate", err) } + +func (em *errorMapping) SyncFS( + ctx context.Context, + op *fuseops.SyncFSOp) error { + defer em.handlePanic() + + err := em.wrapped.SyncFS(ctx, op) + return em.mapError("SyncFS", err) +} diff --git a/internal/fs/wrappers/monitoring.go b/internal/fs/wrappers/monitoring.go index aada6752a0..79b79f21e0 100644 --- a/internal/fs/wrappers/monitoring.go +++ b/internal/fs/wrappers/monitoring.go @@ -381,3 +381,7 @@ func (fs *monitoring) SetXattr(ctx context.Context, op *fuseops.SetXattrOp) erro func (fs *monitoring) Fallocate(ctx context.Context, op *fuseops.FallocateOp) error { return fs.invokeWrapped(ctx, "Fallocate", func(ctx context.Context) error { return fs.wrapped.Fallocate(ctx, op) }) } + +func (fs *monitoring) SyncFS(ctx context.Context, op *fuseops.SyncFSOp) error { + return fs.invokeWrapped(ctx, "SyncFS", func(ctx context.Context) error { return fs.wrapped.SyncFS(ctx, op) }) +} diff --git a/internal/fs/wrappers/tracing.go b/internal/fs/wrappers/tracing.go index c5ada7e519..73e169e97a 100644 --- a/internal/fs/wrappers/tracing.go +++ b/internal/fs/wrappers/tracing.go @@ -170,3 +170,7 @@ func (fs *tracing) SetXattr(ctx context.Context, op *fuseops.SetXattrOp) error { func (fs *tracing) Fallocate(ctx context.Context, op *fuseops.FallocateOp) error { return fs.invokeWrapped(ctx, "Fallocate", func(ctx context.Context) error { return fs.wrapped.Fallocate(ctx, op) }) } + +func (fs *tracing) SyncFS(ctx context.Context, op *fuseops.SyncFSOp) error { + return fs.invokeWrapped(ctx, "SyncFS", func(ctx context.Context) error { return fs.wrapped.SyncFS(ctx, op) }) +} diff --git a/internal/fs/wrappers/tracing_test.go b/internal/fs/wrappers/tracing_test.go index d40794ef9e..f98a259bf7 100644 --- a/internal/fs/wrappers/tracing_test.go +++ b/internal/fs/wrappers/tracing_test.go @@ -155,6 +155,10 @@ func (d dummyFS) Fallocate(_ context.Context, _ *fuseops.FallocateOp) error { return nil } +func (d dummyFS) SyncFS(_ context.Context, _ *fuseops.SyncFSOp) error { + return nil +} + func (d dummyFS) Destroy() {} func TestSpanCreation(t *testing.T) { From 186338e54be0b94390a29e8355baa18c48d11e37 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 25 Feb 2025 12:13:48 +0530 Subject: [PATCH 0260/1298] Add streaming writes failure test when fh open in readonly. (#3022) --- .../common_failure_test.go | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go index f5cbdec7ca..3b08e969fa 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go @@ -246,3 +246,27 @@ func (t *commonFailureTestSuite) TestStreamingWritesWhenFinalizeObjectFailure() // Close and validate object content found on GCS. CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } + +func (t *commonFailureTestSuite) TestStreamingWritesBwhResetsWhenFileHandlesAreOpenInReadMode() { + // Write first 2 MB (say A,B) block to file succeeds but async upload of block B will result in error. + // Fuse:[B] -> Go-SDK:[A] -> GCS[] + _, err := t.fh1.WriteAt(t.data[:2*operations.MiB], 0) + assert.NoError(t.T(), err) + // Write again 2MB (C, D) will trigger B upload. + // Fuse:[D] -> Go-SDK:[C] -> GCS[A, B -> upload fails] + _, _ = t.fh1.WriteAt(t.data[2*operations.MiB:4*operations.MiB], 2*operations.MiB) + + // Write 5th 1MB results in errors. + _, err = t.fh1.WriteAt(t.data[4*operations.MiB:5*operations.MiB], 4*operations.MiB) + + require.Error(t.T(), err) + fh2, err := operations.OpenFileAsReadonly(t.filePath) + assert.NoError(t.T(), err) + // Closing only file handle in write mode. + operations.CloseFileShouldThrowError(t.T(), t.fh1) + // Opening new file handle and writing to file succeeds when file handles in O_RDONLY mode are open. + t.writingAfterBwhReinitializationSucceeds() + operations.CloseFileShouldNotThrowError(fh2, t.T()) + // Close and validate object content found on GCS. + CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) +} From fbc9321cabf6e1ffb15704e93554339ea2a0dd5a Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 25 Feb 2025 14:10:35 +0530 Subject: [PATCH 0261/1298] Fix flaky UT TestMRDWrapperTestSuite/Test_Read (#3031) Increase read-timeout for MRD unit test ReadChunk. --- internal/gcsx/multi_range_downloader_wrapper_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/gcsx/multi_range_downloader_wrapper_test.go b/internal/gcsx/multi_range_downloader_wrapper_test.go index 3b5134feb6..10d021efec 100644 --- a/internal/gcsx/multi_range_downloader_wrapper_test.go +++ b/internal/gcsx/multi_range_downloader_wrapper_test.go @@ -151,7 +151,7 @@ func (t *mrdWrapperTest) Test_Read() { t.mrdWrapper.Wrapped = nil t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond)) - bytesRead, err := t.mrdWrapper.Read(context.Background(), buf, int64(tc.start), int64(tc.end), time.Millisecond, common.NewNoopMetrics()) + bytesRead, err := t.mrdWrapper.Read(context.Background(), buf, int64(tc.start), int64(tc.end), 10*time.Millisecond, common.NewNoopMetrics()) assert.NoError(t.T(), err) assert.Equal(t.T(), tc.end-tc.start, bytesRead) From 062a27bf95e294c1f2fb2f856b72ab8779f0087c Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 26 Feb 2025 11:18:05 +0530 Subject: [PATCH 0262/1298] Remove unused functions (#3032) --- internal/storage/bucket_handle_test.go | 10 ---------- internal/util/sizeof.go | 22 ---------------------- 2 files changed, 32 deletions(-) diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 0134e5782e..8fdf3c9e34 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -52,16 +52,6 @@ var ContentDisposition string = "ContentDisposition" // Hence, we are not writing tests for these flows. // https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/vendor/github.com/fsouza/fake-gcs-server/fakestorage/object.go#L515 -func objectsToObjectNames(objects []*gcs.Object) (objectNames []string) { - objectNames = make([]string, len(objects)) - for i, object := range objects { - if object != nil { - objectNames[i] = object.Name - } - } - return -} - func minObjectsToMinObjectNames(minObjects []*gcs.MinObject) (objectNames []string) { objectNames = make([]string, len(minObjects)) for i, object := range minObjects { diff --git a/internal/util/sizeof.go b/internal/util/sizeof.go index 9b4a885478..422d215be9 100644 --- a/internal/util/sizeof.go +++ b/internal/util/sizeof.go @@ -167,28 +167,6 @@ func contentSizeOfServerResponse(sr *googleapi.ServerResponse) (size int) { return } -func contentSizeOfObjectAccessControlProjectTeam(oacpt *storagev1.ObjectAccessControlProjectTeam) (size int) { - if oacpt == nil { - return - } - - // Account for string members. - for _, strPtr := range []*string{ - &oacpt.ProjectNumber, &oacpt.Team, - } { - size += contentSizeOfString(strPtr) - } - - // Account for string-array members. - for _, strArrayPtr := range []*[]string{ - &oacpt.ForceSendFields, &oacpt.NullFields, - } { - size += contentSizeOfArrayOfStrings(strArrayPtr) - } - - return -} - // NestedSizeOfGcsMinObject returns the full nested memory size // of the gcs.MinObject pointed by the passed pointer. // Improvement scope: This can be generalized to a general-struct From b29df75cc1bb5e726448d9644f96e40486beaf4e Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 26 Feb 2025 11:59:17 +0530 Subject: [PATCH 0263/1298] Fix flaky TestConcurrentReads_ReadIsTreatedNonSequentialAfterFileIsRemovedFromCache (#3024) * fix flaky test TestConcurrentReads_ReadIsTreatedNonSequentialAfterFileIsRemovedFromCache * close file after use * review comment * review comment * review comments --- .../cache_file_for_range_read_false_test.go | 77 ++++++++++++------- .../read_cache/helpers_test.go | 4 +- .../util/operations/file_operations.go | 23 ++++++ 3 files changed, 73 insertions(+), 31 deletions(-) diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go index 06d0d5daa0..28c16d095a 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go @@ -17,10 +17,11 @@ package read_cache import ( "context" "log" + "os" "path" "strings" - "sync" "testing" + "time" "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -61,11 +62,20 @@ func (s *cacheFileForRangeReadFalseTest) Teardown(t *testing.T) { // Helpers //////////////////////////////////////////////////////////////////////// -func readFileAsync(t *testing.T, wg *sync.WaitGroup, testFileName string, expectedOutcome **Expected) { - go func() { - defer wg.Done() - *expectedOutcome = readFileAndGetExpectedOutcome(testDirPath, testFileName, true, zeroOffset, t) - }() +func readFileBetweenOffset(t *testing.T, file *os.File, startOffset, endOffSet int64) *Expected { + t.Helper() + expected := &Expected{ + StartTimeStampSeconds: time.Now().Unix(), + BucketName: setup.TestBucket(), + ObjectName: path.Join(testDirName, path.Base(file.Name())), + } + if setup.DynamicBucketMounted() != "" { + expected.BucketName = setup.DynamicBucketMounted() + } + + expected.content = operations.ReadFileBetweenOffset(t, file, startOffset, endOffSet, chunkSizeToRead) + expected.EndTimeStampSeconds = time.Now().Unix() + return expected } //////////////////////////////////////////////////////////////////////// @@ -86,39 +96,48 @@ func (s *cacheFileForRangeReadFalseTest) TestRangeReadsWithCacheMiss(t *testing. validateFileIsNotCached(testFileName, t) } -func (s *cacheFileForRangeReadFalseTest) TestConcurrentReads_ReadIsTreatedNonSequentialAfterFileIsRemovedFromCache(t *testing.T) { +func (s *cacheFileForRangeReadFalseTest) TestReadIsTreatedNonSequentialAfterFileIsRemovedFromCache(t *testing.T) { var testFileNames [2]string - var expectedOutcome [2]*Expected + var expectedOutcome [4]*Expected testFileNames[0] = setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSizeSameAsCacheCapacity, t) testFileNames[1] = setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSizeSameAsCacheCapacity, t) randomReadChunkCount := fileSizeSameAsCacheCapacity / chunkSizeToRead - - var wg sync.WaitGroup - for i := 0; i < 2; i++ { - wg.Add(1) - readFileAsync(t, &wg, testFileNames[i], &expectedOutcome[i]) - } - wg.Wait() - + readTillChunk := randomReadChunkCount / 2 + fh1 := operations.OpenFile(path.Join(testDirPath, testFileNames[0]), t) + defer operations.CloseFile(fh1) + fh2 := operations.OpenFile(path.Join(testDirPath, testFileNames[1]), t) + defer operations.CloseFile(fh2) + + // Use file handle 1 to read file 1 partially. + expectedOutcome[0] = readFileBetweenOffset(t, fh1, 0, int64(readTillChunk*chunkSizeToRead)) + // Use file handle 2 to read file 2 partially. This will evict file 1 from + // cache due to cache capacity constraints. + expectedOutcome[1] = readFileBetweenOffset(t, fh2, 0, int64(readTillChunk*chunkSizeToRead)) + // Read remaining file 1. File 2 remains cached. Cache eviction happens on + // cache handler creation, which is tied to the file handle. Since the handle + // isn't recreated, eviction doesn't occur. + expectedOutcome[2] = readFileBetweenOffset(t, fh1, int64(readTillChunk*chunkSizeToRead)+1, fileSizeSameAsCacheCapacity) + // Read remaining file 2. + expectedOutcome[3] = readFileBetweenOffset(t, fh2, int64(readTillChunk*chunkSizeToRead)+1, fileSizeSameAsCacheCapacity) + + // Merge the expected outcomes. + expectedOutcome[0].EndTimeStampSeconds = expectedOutcome[2].EndTimeStampSeconds + expectedOutcome[0].content = expectedOutcome[0].content + expectedOutcome[2].content + expectedOutcome[1].EndTimeStampSeconds = expectedOutcome[3].EndTimeStampSeconds + expectedOutcome[1].content = expectedOutcome[1].content + expectedOutcome[3].content + // Parse the logs and validate with expected outcome. structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) require.Equal(t, 2, len(structuredReadLogs)) - // Goroutine execution order isn't guaranteed. - // If the object name in expected outcome doesn't align with the logs, swap - // the expected outcome objects and file names at positions 0 and 1. - if expectedOutcome[0].ObjectName != structuredReadLogs[0].ObjectName { - expectedOutcome[0], expectedOutcome[1] = expectedOutcome[1], expectedOutcome[0] - testFileNames[0], testFileNames[1] = testFileNames[1], testFileNames[0] - } validate(expectedOutcome[0], structuredReadLogs[0], true, false, randomReadChunkCount, t) validate(expectedOutcome[1], structuredReadLogs[1], true, false, randomReadChunkCount, t) - // Validate last chunk was considered non-sequential and cache hit false for first read. - assert.False(t, structuredReadLogs[0].Chunks[randomReadChunkCount-1].IsSequential) - assert.False(t, structuredReadLogs[0].Chunks[randomReadChunkCount-1].CacheHit) - // Validate last chunk was considered sequential and cache hit true for second read. - assert.True(t, structuredReadLogs[1].Chunks[randomReadChunkCount-1].IsSequential) + // Validate after cache eviction, read was considered non-sequential and cache hit false for first file. + assert.False(t, structuredReadLogs[0].Chunks[readTillChunk+1].IsSequential) + assert.False(t, structuredReadLogs[0].Chunks[readTillChunk+1].CacheHit) + // Validate for 2nd file read was considered sequential because of no cache eviction. + assert.True(t, structuredReadLogs[1].Chunks[readTillChunk+1].IsSequential) if !s.isParallelDownloadsEnabled { // When parallel downloads are enabled, we can't concretely say that the read will be cache Hit. - assert.True(t, structuredReadLogs[1].Chunks[randomReadChunkCount-1].CacheHit) + assert.True(t, structuredReadLogs[1].Chunks[readTillChunk+1].CacheHit) } validateFileIsNotCached(testFileNames[0], t) diff --git a/tools/integration_tests/read_cache/helpers_test.go b/tools/integration_tests/read_cache/helpers_test.go index de875adc3e..227a78af60 100644 --- a/tools/integration_tests/read_cache/helpers_test.go +++ b/tools/integration_tests/read_cache/helpers_test.go @@ -73,8 +73,8 @@ func readFileAndGetExpectedOutcome(testDirPath, fileName string, readFullFile bo return expected } -func validate(expected *Expected, logEntry *read_logs.StructuredReadLogEntry, - isSeq, cacheHit bool, chunkCount int, t *testing.T) { +func validate(expected *Expected, logEntry *read_logs.StructuredReadLogEntry, isSeq, cacheHit bool, chunkCount int, t *testing.T) { + t.Helper() if logEntry.StartTimeSeconds < expected.StartTimeStampSeconds { t.Errorf("start time in logs %d less than actual start time %d.", logEntry.StartTimeSeconds, expected.StartTimeStampSeconds) } diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index ddf90f26f7..99095b5d55 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -316,6 +316,29 @@ func ReadChunkFromFile(filePath string, chunkSize int64, offset int64, flag int) return } +func ReadFileBetweenOffset(t *testing.T, file *os.File, startOffset, endOffset, chunkSize int64) string { + t.Helper() + chunk := make([]byte, chunkSize) + var readData []byte + + for startOffset < endOffset { + readSize := min(chunkSize, endOffset-startOffset) + + n, err := file.ReadAt(chunk[:readSize], startOffset) + if err == io.EOF { + readData = append(readData, chunk[:n]...) + break + } else if err != nil { + t.Errorf("Failed to read file chunk at offset %d: %v", startOffset, err) + return "" + } + readData = append(readData, chunk[:n]...) + startOffset += int64(n) + } + + return string(readData) +} + // Returns the stats of a file. // Fails if the passed input is a directory. func StatFile(file string) (*fs.FileInfo, error) { From 93923eb5aacf0c8ab08bdf5b0e2f36d769157835 Mon Sep 17 00:00:00 2001 From: Ankita Luthra Date: Wed, 26 Feb 2025 12:05:18 +0530 Subject: [PATCH 0264/1298] Marks negative stat cache flag public (#3034) * marks negative stat cache flag public * marks negative stat cache flag public --- cfg/config.go | 4 ---- cfg/params.yaml | 1 - 2 files changed, 5 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index f908c475e4..abecc6426a 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -471,10 +471,6 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.IntP("metadata-cache-negative-ttl-secs", "", 5, "The negative-ttl-secs value in seconds to be used for expiring negative entries in metadata-cache. It can be set to -1 for no-ttl, 0 for no cache and > 0 for ttl-controlled negative entries in metadata-cache. Any value set below -1 will throw an error.") - if err := flagSet.MarkHidden("metadata-cache-negative-ttl-secs"); err != nil { - return err - } - flagSet.IntP("metadata-cache-ttl-secs", "", 60, "The ttl value in seconds to be used for expiring items in metadata-cache. It can be set to -1 for no-ttl, 0 for no cache and > 0 for ttl-controlled metadata-cache. Any value set below -1 will throw an error.") flagSet.StringSliceP("o", "", []string{}, "Additional system-specific mount options. Multiple options can be passed as comma separated. For readonly, use --o ro") diff --git a/cfg/params.yaml b/cfg/params.yaml index 2c879c342d..1f28bfe284 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -563,7 +563,6 @@ can be set to -1 for no-ttl, 0 for no cache and > 0 for ttl-controlled negative entries in metadata-cache. Any value set below -1 will throw an error. default: "5" - hide-flag: true - config-path: "metadata-cache.stat-cache-max-size-mb" flag-name: "stat-cache-max-size-mb" From f9af3ba53d0e1b746b3819e051b574b22b6bead3 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 27 Feb 2025 11:21:40 +0530 Subject: [PATCH 0265/1298] Use random port for proxy server and run emulator test packages in parallel (#3028) --- .../emulator_tests/emulator_tests.sh | 4 +- .../emulator_tests/proxy_server/main.go | 50 ++++-- .../read_stall/read_stall_test.go | 14 +- .../emulator_tests/read_stall/setup_test.go | 6 +- .../common_failure_test.go | 11 +- .../streaming_writes_failure_test.go | 5 +- .../emulator_tests/util/test_helper.go | 159 ++++++++++++------ .../write_stall/write_stall_test.go | 6 +- .../write_stall/writes_stall_on_sync_test.go | 24 ++- .../util/client/storage_client.go | 2 - tools/integration_tests/util/setup/setup.go | 18 +- 11 files changed, 199 insertions(+), 100 deletions(-) diff --git a/tools/integration_tests/emulator_tests/emulator_tests.sh b/tools/integration_tests/emulator_tests/emulator_tests.sh index f40d19d9f6..84bc7f37ba 100755 --- a/tools/integration_tests/emulator_tests/emulator_tests.sh +++ b/tools/integration_tests/emulator_tests/emulator_tests.sh @@ -120,5 +120,5 @@ curl -X POST --data-binary @test.json \ "$STORAGE_EMULATOR_HOST/storage/v1/b?project=test-project" rm test.json -# Run all the tests. -go test ./tools/integration_tests/emulator_tests/... -p 1 --integrationTest -v --testbucket=test-bucket --testOnCustomEndpoint=http://localhost:8020/storage/v1/ -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE +# Run all emulator test packages in parallel. +go test ./tools/integration_tests/emulator_tests/... --integrationTest -v --testbucket=test-bucket -timeout 10m --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE diff --git a/tools/integration_tests/emulator_tests/proxy_server/main.go b/tools/integration_tests/emulator_tests/proxy_server/main.go index e4810e1286..be98aa843f 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/main.go +++ b/tools/integration_tests/emulator_tests/proxy_server/main.go @@ -20,29 +20,31 @@ import ( "fmt" "io" "log" + "net" "net/http" "net/url" "os" "os/signal" + "strconv" "syscall" "time" ) +const PortAndProxyProcessIdInfoLogFormat = "Listening Proxy Server On Port [%s] with Process ID [%d]" + var ( // Flag to accept config-file path. fConfigPath = flag.String("config-path", "configs/config.yaml", "Path to the file") // Flag to turn on fDebug logs. - // TODO: We can add support for specifying a log path for fDebug logs in a future update. - fDebug = flag.Bool("debug", false, "Enable proxy server fDebug logs.") + fDebug = flag.Bool("debug", false, "Enable proxy server fDebug logs.") + fLogFilePath = flag.String("log-file", "", "Path to the log file") // Initialized before the server gets started. gConfig *Config gOpManager *OperationManager + // Port number assigned to listener. + gPort string ) -// Host address of the proxy server. -// TODO: Allow this value to be configured via a command-line flag. -const Port = "8020" - type ProxyHandler struct { http.Handler } @@ -130,7 +132,7 @@ func (ph ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - u.Host = "localhost:" + Port + u.Host = "localhost:" + gPort resp.Header.Set("Location", u.String()) } @@ -157,40 +159,46 @@ func (ph ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { // ProxyServer represents a simple proxy server over GCS storage based API endpoint. type ProxyServer struct { - port string server *http.Server shutdown chan os.Signal } // NewProxyServer creates a new ProxyServer instance -func NewProxyServer(port string) *ProxyServer { +func NewProxyServer() *ProxyServer { return &ProxyServer{ - port: port, shutdown: make(chan os.Signal, 1), } } // Start starts the proxy server. func (ps *ProxyServer) Start() { + // Create a listener on random available port. + listener, err := net.Listen("tcp", ":0") + if err != nil { + log.Fatalf("Error on listening: %v", err) + } + gPort = strconv.Itoa(listener.Addr().(*net.TCPAddr).Port) + // Log port number and proxy process Id for the proxy server. + log.Printf(PortAndProxyProcessIdInfoLogFormat, gPort, os.Getpid()) ps.server = &http.Server{ - Addr: ":" + ps.port, + Addr: ":" + gPort, Handler: ProxyHandler{}, } // Start the server in a new goroutine go func() { - log.Printf("Proxy server started on port %s\n", ps.port) - if err := ps.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + if err := ps.server.Serve(listener); err != nil && err != http.ErrServerClosed { log.Fatalf("Server error: %v", err) } }() // Handle graceful shutdown signal.Notify(ps.shutdown, syscall.SIGINT, syscall.SIGTERM) + // Blocks until one of the Signal is recieved. <-ps.shutdown log.Println("Shutting down proxy server...") - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := ps.server.Shutdown(ctx); err != nil { @@ -211,12 +219,24 @@ func main() { os.Exit(1) } + if *fLogFilePath == "" { + log.Println("No log file path for proxy server provided.") + os.Exit(1) + } + logFile, err := os.OpenFile(*fLogFilePath, os.O_WRONLY|os.O_APPEND, 0666) + if err != nil { + log.Printf("Error opening log file: %v\n", err) + os.Exit(1) + } + defer logFile.Close() + log.SetOutput(logFile) + if *fDebug { log.Printf("%+v\n", gConfig) } gOpManager = NewOperationManager(*gConfig) - ps := NewProxyServer(Port) + ps := NewProxyServer() ps.Start() } diff --git a/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go b/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go index 702dca8557..02fa529364 100644 --- a/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go +++ b/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go @@ -21,6 +21,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" emulator_tests "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/emulator_tests/util" @@ -40,13 +41,18 @@ const ( ) type readStall struct { - flags []string + port int + proxyProcessId int + flags []string suite.Suite } func (r *readStall) SetupSuite() { configPath := "../proxy_server/configs/read_stall_5s.yaml" - emulator_tests.StartProxyServer(configPath) + var err error + r.port, r.proxyProcessId, err = emulator_tests.StartProxyServer(configPath, setup.CreateProxyServerLogFile(r.T())) + require.NoError(r.T(), err) + setup.AppendProxyEndpointToFlagSet(&r.flags, r.port) setup.MountGCSFuseWithGivenMountFunc(r.flags, mountFunc) } @@ -56,7 +62,7 @@ func (r *readStall) SetupTest() { func (r *readStall) TearDownSuite() { setup.UnmountGCSFuse(rootDir) - assert.NoError(r.T(), emulator_tests.KillProxyServerProcess(port)) + assert.NoError(r.T(), emulator_tests.KillProxyServerProcess(r.proxyProcessId)) } //////////////////////////////////////////////////////////////////////// @@ -85,7 +91,7 @@ func TestReadStall(t *testing.T) { ts := &readStall{} // Define flag set to run the tests. flagsSet := [][]string{ - {"--custom-endpoint=" + proxyEndpoint, "--enable-read-stall-retry=true", "--read-stall-min-req-timeout=" + fmt.Sprintf("%dms", minReqTimeout.Milliseconds()), "--read-stall-initial-req-timeout=" + fmt.Sprintf("%dms", minReqTimeout.Milliseconds())}, + {"--enable-read-stall-retry=true", "--read-stall-min-req-timeout=" + fmt.Sprintf("%dms", minReqTimeout.Milliseconds()), "--read-stall-initial-req-timeout=" + fmt.Sprintf("%dms", minReqTimeout.Milliseconds())}, } // Run tests. diff --git a/tools/integration_tests/emulator_tests/read_stall/setup_test.go b/tools/integration_tests/emulator_tests/read_stall/setup_test.go index be7c3389cb..d1858e16bd 100644 --- a/tools/integration_tests/emulator_tests/read_stall/setup_test.go +++ b/tools/integration_tests/emulator_tests/read_stall/setup_test.go @@ -15,7 +15,6 @@ package read_stall import ( - "fmt" "log" "os" "testing" @@ -24,14 +23,11 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -const port = 8020 - var ( testDirPath string mountFunc func([]string) error // root directory is the directory to be unmounted. - rootDir string - proxyEndpoint = fmt.Sprintf("http://localhost:%d/storage/v1/b?project=test-project", port) + rootDir string ) func TestMain(m *testing.M) { diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go index 3b08e969fa..2c1c752616 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go @@ -45,6 +45,8 @@ type commonFailureTestSuite struct { closeStorageClient func() error ctx context.Context data []byte + port int + proxyProcessId int gcsObjectValidator } @@ -58,7 +60,7 @@ type gcsObjectValidator interface { // ////////////////////////////////////////////////////////////////////// func (t *commonFailureTestSuite) SetupSuite() { - t.flags = []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1", "--custom-endpoint=" + proxyEndpoint} + t.flags = []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1"} // Generate 5 MB random data. var err error t.data, err = operations.GenerateRandomData(5 * operations.MiB) @@ -69,7 +71,10 @@ func (t *commonFailureTestSuite) SetupSuite() { func (t *commonFailureTestSuite) setupTest() { t.T().Helper() // Start proxy server for each test to ensure the config is initialized per test. - emulator_tests.StartProxyServer(t.configPath) + var err error + t.port, t.proxyProcessId, err = emulator_tests.StartProxyServer(t.configPath, setup.CreateProxyServerLogFile(t.T())) + require.NoError(t.T(), err) + setup.AppendProxyEndpointToFlagSet(&t.flags, t.port) // Create storage client before running tests. t.ctx = context.Background() t.closeStorageClient = CreateStorageClientWithCancel(&t.ctx, &t.storageClient) @@ -83,7 +88,7 @@ func (t *commonFailureTestSuite) setupTest() { func (t *commonFailureTestSuite) TearDownTest() { setup.UnmountGCSFuse(rootDir) assert.NoError(t.T(), t.closeStorageClient()) - assert.NoError(t.T(), emulator_tests.KillProxyServerProcess(port)) + assert.NoError(t.T(), emulator_tests.KillProxyServerProcess(t.proxyProcessId)) } func (t *commonFailureTestSuite) writingWithNewFileHandleAlsoFails(data []byte, off int64) { diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go index ddad91c78b..e3294b9a95 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go @@ -15,7 +15,6 @@ package streaming_writes_failure import ( - "fmt" "log" "os" "testing" @@ -26,12 +25,10 @@ import ( const ( testDirNamePrefix = "StreamingWritesFailureTest" - port = 8020 ) var ( - proxyEndpoint = fmt.Sprintf("http://localhost:%d/storage/v1/b?project=test-project", port) - mountFunc func([]string) error + mountFunc func([]string) error // root directory is the directory to be unmounted. rootDir string testDirName string diff --git a/tools/integration_tests/emulator_tests/util/test_helper.go b/tools/integration_tests/emulator_tests/util/test_helper.go index 330ca4354c..ae4de8db0e 100644 --- a/tools/integration_tests/emulator_tests/util/test_helper.go +++ b/tools/integration_tests/emulator_tests/util/test_helper.go @@ -16,12 +16,13 @@ package emulator_tests import ( "crypto/rand" + "errors" "fmt" "io" "log" "os" "os/exec" - "path" + "regexp" "strconv" "strings" "syscall" @@ -29,65 +30,71 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -// StartProxyServer starts a proxy server as a background process and handles its lifecycle. +const PortAndProxyProcessIdInfoRegex = `Listening Proxy Server On Port \[(\d+)\] with Process ID \[(\d+)\]` + +// StartProxyServer starts the proxy server in the background and returns the port and process ID it is listening on. +// +// It takes the config path and log file path as input. The proxy server is starts in background using +// the go run command. The output of the proxy server is redirected to the log file. It also sets the +// STORAGE_EMULATOR_HOST to `localhost:port` so that any new storage client will use this endpoint by +// default. If any error occurs it fails. // -// It launches the proxy server with the specified configuration and port, logs its output to a file. -func StartProxyServer(configPath string) { - // Start the proxy in the background - cmd := exec.Command("go", "run", "../proxy_server/.", "--config-path="+configPath) - logFileForProxyServer, err := os.Create(path.Join(os.Getenv("KOKORO_ARTIFACTS_DIR"), "proxy-"+setup.GenerateRandomString(5))) +// Parameters: +// - configPath: Path for config for Proxy Server. +// - logFilePath: Path for log file for Proxy Server Logs. +// +// Returns: +// - int: Port number that the proxy server is listening on. +// - int: Proxy Server Process ID. +// - error: An error if any error occurs in setting up Proxy Server. +func StartProxyServer(configPath, logFilePath string) (int, int, error) { + cmd := exec.Command("go", "run", "../proxy_server/.", "--config-path="+configPath, "--log-file="+logFilePath) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + err := cmd.Start() if err != nil { - log.Fatal("Error in creating log file for proxy server.") + return -1, -1, fmt.Errorf("error executing proxy server cmd: %v", err) } - log.Printf("Proxy server logs are generated with specific filename %s: ", logFileForProxyServer.Name()) - cmd.Stdout = logFileForProxyServer - cmd.Stderr = logFileForProxyServer - err = cmd.Start() + log.Printf("Proxy Server log file: %s", logFilePath) + port, proxyProcessId, err := getPortAndProcessInfoFromLogFile(logFilePath) if err != nil { - log.Fatal(err) + return -1, -1, fmt.Errorf("error getting port information from log file: %v", err) } + // Set STORAGE_EMULATOR_HOST to current proxy server for the test. This ensures + // any storage client created during this test will call this proxy endpoint. + // More details: https://cloud.google.com/go/docs/reference/cloud.google.com/go/storage/latest#hdr-Creating_a_Client + err = os.Setenv("STORAGE_EMULATOR_HOST", fmt.Sprintf("localhost:%d", port)) + if err != nil { + return -1, -1, fmt.Errorf("error setting STORAGE_EMULATOR_HOST environment variable: %v", err) + } + return port, proxyProcessId, nil } -// KillProxyServerProcess kills all processes listening on the specified port. +// KillProxyServerProcess kills given Proxy Server Process ID. +// It also unsets the STORAGE_EMULATOR_HOST environment variable for proxy server. +// +// Parameters: +// - proxyProcessId: PID of Proxy Server. // -// It uses the `lsof` command to identify the processes and sends SIGINT to each of them. -func KillProxyServerProcess(port int) error { - // Use lsof to find processes listening on the specified port - cmd := exec.Command("lsof", "-i", fmt.Sprintf(":%d", port)) - output, err := cmd.Output() +// Returns: +// - error: if any error occurs in unsetting env or killing proxy Server. +func KillProxyServerProcess(proxyProcessId int) error { + // Unset STORAGE_EMULATOR_HOST environment set in StartProxyServer. + err := os.Unsetenv("STORAGE_EMULATOR_HOST") if err != nil { - return fmt.Errorf("error running lsof: %w", err) + return fmt.Errorf("error unsetting STORAGE_EMULATOR_HOST environment variable: %v", err) } - - // Parse the lsof output to get the process IDs - lines := strings.Split(string(output), "\n") - for _, line := range lines[1:] { - fields := strings.Fields(line) - // Kill only the process directly listening on the port. - if len(fields) > 1 && strings.Contains(line, "LISTEN") { - pidStr := fields[1] - pid, err := strconv.Atoi(pidStr) - if err != nil { - log.Println("Error parsing process ID:", err) - continue - } - - // Send SIGINT to the process - process, err := os.FindProcess(pid) - if err != nil { - log.Println("Error finding process:", err) - continue - } - err = process.Signal(syscall.SIGINT) - if err != nil { - log.Println("Error sending SIGINT to process:", err) - } - } + // Find Proxy Server Process. + process, err := os.FindProcess(proxyProcessId) + if err != nil { + return fmt.Errorf("error finding the proxy server process with PID %d: %v", proxyProcessId, err) + } + // Send SIGINT to the Process. + err = process.Signal(syscall.SIGINT) + if err != nil { + return fmt.Errorf("error sending SIGINT to the proxy server process with PID %d: %v", proxyProcessId, err) } - return nil } @@ -173,3 +180,61 @@ func GetChunkTransferTimeoutFromFlags(flags []string) (int, error) { } return timeout, nil } + +// GetPortFromLogFile polls a log file for a specific log message containing the port number. +// It returns the port number if found, or an error if the port is not found within the specified duration. +// +// Parameters: +// - logFilePath: The path to the log file to monitor. +// +// Returns: +// - Port, proxy process ID , or an error if any error occurs. +func getPortAndProcessInfoFromLogFile(logFilePath string) (int, int, error) { + logPollingDuration := 3 * time.Minute // Duration to poll logs. + logPollingFrequency := 3 * time.Second // Frequency to poll logs. + + // Regular expression to extract the port number and Process ID from the log file. + re := regexp.MustCompile(PortAndProxyProcessIdInfoRegex) + + // Calculate the timeout time. + timeout := time.After(logPollingDuration) + + // Create a ticker to poll the log file at the specified frequency. + ticker := time.NewTicker(logPollingFrequency) + defer ticker.Stop() + + // Poll the log file until the timeout or the port is found. + for { + select { + case <-timeout: + // Timeout occurred, return an error. + return -1, -1, errors.New("timeout: port number not found in log file") + case <-ticker.C: + // Read the log file. + content, err := operations.ReadFile(logFilePath) + if err != nil { + // Log file could not be opened, return an error. + return -1, -1, fmt.Errorf("failed to read the log file: %w", err) + } + + // Attempt to extract the port number from the log line. + match := re.FindStringSubmatch(string(content)) + if len(match) == 3 { + // Port number and PID found, parse it and return. + portStr, proxyProcessIdStr := match[1], match[2] + + port, err := strconv.Atoi(portStr) + if err != nil { + // Port number could not be parsed, return an error. + return -1, -1, fmt.Errorf("failed to parse port number: %w", err) + } + proxyProcessId, err := strconv.Atoi(proxyProcessIdStr) + if err != nil { + // Process ID could not be parsed, return an error. + return -1, -1, fmt.Errorf("failed to parse process ID: %w", err) + } + return port, proxyProcessId, nil + } + } + } +} diff --git a/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go b/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go index a020587eb5..74dde554d2 100644 --- a/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go @@ -15,7 +15,6 @@ package write_stall import ( - "fmt" "log" "os" "testing" @@ -24,14 +23,11 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -const port = 8020 - var ( testDirPath string mountFunc func([]string) error // root directory is the directory to be unmounted. - rootDir string - proxyEndpoint = fmt.Sprintf("http://localhost:%d/storage/v1/b?project=test-project", port) + rootDir string ) func TestMain(m *testing.M) { diff --git a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go index d31fc19da5..0e18c472fe 100644 --- a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go @@ -25,6 +25,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) //////////////////////////////////////////////////////////////////////// @@ -37,18 +38,23 @@ const ( ) type chunkTransferTimeoutInfinity struct { - flags []string + port int + proxyProcessId int + flags []string } func (s *chunkTransferTimeoutInfinity) Setup(t *testing.T) { configPath := "../proxy_server/configs/write_stall_40s.yaml" - emulator_tests.StartProxyServer(configPath) + var err error + s.port, s.proxyProcessId, err = emulator_tests.StartProxyServer(configPath, setup.CreateProxyServerLogFile(t)) + require.NoError(t, err) + setup.AppendProxyEndpointToFlagSet(&s.flags, s.port) setup.MountGCSFuseWithGivenMountFunc(s.flags, mountFunc) } func (s *chunkTransferTimeoutInfinity) Teardown(t *testing.T) { setup.UnmountGCSFuse(rootDir) - assert.NoError(t, emulator_tests.KillProxyServerProcess(port)) + assert.NoError(t, emulator_tests.KillProxyServerProcess(s.proxyProcessId)) } //////////////////////////////////////////////////////////////////////// @@ -79,7 +85,7 @@ func TestChunkTransferTimeoutInfinity(t *testing.T) { ts := &chunkTransferTimeoutInfinity{} // Define flag set to run the tests. flagsSet := [][]string{ - {"--custom-endpoint=" + proxyEndpoint, "--chunk-transfer-timeout-secs=0"}, + {"--chunk-transfer-timeout-secs=0"}, } // Run tests. @@ -92,8 +98,8 @@ func TestChunkTransferTimeoutInfinity(t *testing.T) { func TestChunkTransferTimeout(t *testing.T) { flagSets := [][]string{ - {"--custom-endpoint=" + proxyEndpoint}, - {"--custom-endpoint=" + proxyEndpoint, "--chunk-transfer-timeout-secs=5"}, + {}, + {"--chunk-transfer-timeout-secs=5"}, } stallScenarios := []struct { @@ -127,12 +133,14 @@ func TestChunkTransferTimeout(t *testing.T) { t.Run(fmt.Sprintf("Flags_%v", flags), func(t *testing.T) { for _, scenario := range stallScenarios { t.Run(scenario.name, func(t *testing.T) { - emulator_tests.StartProxyServer(scenario.configPath) + port, proxyProcessId, err := emulator_tests.StartProxyServer(scenario.configPath, setup.CreateProxyServerLogFile(t)) + require.NoError(t, err) + setup.AppendProxyEndpointToFlagSet(&flags, port) setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) defer func() { // Defer unmount and killing the server. setup.UnmountGCSFuse(rootDir) - assert.NoError(t, emulator_tests.KillProxyServerProcess(port)) + assert.NoError(t, emulator_tests.KillProxyServerProcess(proxyProcessId)) }() testDir := scenario.name + setup.GenerateRandomString(3) diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 2674d26af4..5ef70ae5b4 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -50,8 +50,6 @@ func CreateStorageClient(ctx context.Context) (client *storage.Client, err error return nil, fmt.Errorf("unable to fetch token-source for TPC: %w", err) } client, err = storage.NewClient(ctx, option.WithEndpoint("storage.apis-tpczero.goog:443"), option.WithTokenSource(ts)) - } else if setup.TestOnCustomEndpoint() != "" { - client, err = storage.NewClient(ctx, option.WithEndpoint(setup.TestOnCustomEndpoint())) } else { if setup.IsZonalBucketRun() { client, err = storage.NewGRPCClient(ctx, experimental.WithGRPCBidiReads()) diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 841bdf927a..a6c7436641 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -43,7 +43,6 @@ var mountedDirectory = flag.String("mountedDirectory", "", "The GCSFuse mounted var integrationTest = flag.Bool("integrationTest", false, "Run tests only when the flag value is true.") var testInstalledPackage = flag.Bool("testInstalledPackage", false, "[Optional] Run tests on the package pre-installed on the host machine. By default, integration tests build a new package to run the tests.") var testOnTPCEndPoint = flag.Bool("testOnTPCEndPoint", false, "Run tests on TPC endpoint only when the flag value is true.") -var testOnCustomEndpoint = flag.String("testOnCustomEndpoint", "", "Run tests on custom endpoint only when the flag value is set. Required for tests requiring proxy server.") var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) const ( @@ -98,10 +97,6 @@ func TestOnTPCEndPoint() bool { return *testOnTPCEndPoint } -func TestOnCustomEndpoint() string { - return *testOnCustomEndpoint -} - func MountedDirectory() string { return *mountedDirectory } @@ -576,3 +571,16 @@ func CreateFileOnDiskAndCopyToMntDir(t *testing.T, filePathInLocalDisk string, f t.Errorf("Error in copying file:%v", err) } } + +func CreateProxyServerLogFile(t *testing.T) string { + proxyServerLogFile := path.Join(TestDir(), "proxy-server-log-"+GenerateRandomString(5)) + _, err := os.Create(proxyServerLogFile) + if err != nil { + t.Fatalf("Error in creating log file for proxy server: %v", err) + } + return proxyServerLogFile +} + +func AppendProxyEndpointToFlagSet(flagSet *[]string, port int) { + *flagSet = append(*flagSet, "--custom-endpoint="+fmt.Sprintf("http://localhost:%d/storage/v1/", port)) +} From 45109c0e54a261afb7a24f7f6e9e715e1d942746 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Fri, 28 Feb 2025 10:33:10 +0530 Subject: [PATCH 0266/1298] fix test on arm64 (#3042) --- .../cache_file_for_range_read_false_test.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go index 28c16d095a..7c48e8f687 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go @@ -130,14 +130,17 @@ func (s *cacheFileForRangeReadFalseTest) TestReadIsTreatedNonSequentialAfterFile require.Equal(t, 2, len(structuredReadLogs)) validate(expectedOutcome[0], structuredReadLogs[0], true, false, randomReadChunkCount, t) validate(expectedOutcome[1], structuredReadLogs[1], true, false, randomReadChunkCount, t) - // Validate after cache eviction, read was considered non-sequential and cache hit false for first file. - assert.False(t, structuredReadLogs[0].Chunks[readTillChunk+1].IsSequential) - assert.False(t, structuredReadLogs[0].Chunks[readTillChunk+1].CacheHit) + // Validate after cache eviction, read was considered non-sequential and cache + // hit false for first file. + // Checking for the last chunk, not readTillChunk+1, due to potential kernel + // over-reads on some architectures. + assert.False(t, structuredReadLogs[0].Chunks[randomReadChunkCount-1].IsSequential) + assert.False(t, structuredReadLogs[0].Chunks[randomReadChunkCount-1].CacheHit) // Validate for 2nd file read was considered sequential because of no cache eviction. - assert.True(t, structuredReadLogs[1].Chunks[readTillChunk+1].IsSequential) + assert.True(t, structuredReadLogs[1].Chunks[randomReadChunkCount-1].IsSequential) if !s.isParallelDownloadsEnabled { // When parallel downloads are enabled, we can't concretely say that the read will be cache Hit. - assert.True(t, structuredReadLogs[1].Chunks[readTillChunk+1].CacheHit) + assert.True(t, structuredReadLogs[1].Chunks[randomReadChunkCount-1].CacheHit) } validateFileIsNotCached(testFileNames[0], t) From e4364fb014f1e5a8b81809f71ec8ffdd253161de Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 28 Feb 2025 12:12:38 +0530 Subject: [PATCH 0267/1298] upgrade fuse module (#3043) --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1ff4b5c9c6..b56c471cd1 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.1 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec - github.com/jacobsa/fuse v0.0.0-20241025064006-8ccd61173b05 + github.com/jacobsa/fuse v0.0.0-20250227120101-009e36f010a3 github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 diff --git a/go.sum b/go.sum index ae88fbd8ad..75f3b7904f 100644 --- a/go.sum +++ b/go.sum @@ -247,6 +247,8 @@ github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtE github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= github.com/jacobsa/fuse v0.0.0-20241025064006-8ccd61173b05 h1:PDBoLGtxDPVzWurS8Vd5SFbafRRg2gSNVjLEaJlGTxw= github.com/jacobsa/fuse v0.0.0-20241025064006-8ccd61173b05/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= +github.com/jacobsa/fuse v0.0.0-20250227120101-009e36f010a3 h1:v7S0NR1fovnc7mkbZ+cvV8WkJbtIWU8kfOZzdaEzABY= +github.com/jacobsa/fuse v0.0.0-20250227120101-009e36f010a3/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= From 260e33cf56777fb6902e80f113607694ed714368 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 28 Feb 2025 12:14:18 +0530 Subject: [PATCH 0268/1298] Upgrade to golang 1.24.0 (#3006) * basic changes for golang 1.24.0 * upgrade golang version in github workflows --- .github/workflows/ci.yml | 6 +++--- .github/workflows/flake-detector.yml | 2 +- Dockerfile | 2 +- go.mod | 2 +- perfmetrics/scripts/ml_tests/pytorch/run_model.sh | 2 +- .../ml_tests/tf/resnet/setup_scripts/setup_container.sh | 4 ++-- perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh | 4 ++-- perfmetrics/scripts/read_cache/setup.sh | 2 +- tools/cd_scripts/e2e_test.sh | 2 +- tools/containerize_gcsfuse_docker/Dockerfile | 2 +- tools/integration_tests/run_e2e_tests.sh | 4 ++-- tools/package_gcsfuse_docker/Dockerfile | 2 +- 12 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f881d285c..cdeb47827e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: "1.23" + go-version: "1.24" - name: CodeGen run: go generate ./... - name: Formatting diff @@ -26,7 +26,7 @@ jobs: linux-tests: strategy: matrix: - go: [ 1.23.x ] + go: [ 1.24.x ] runs-on: ubuntu-latest timeout-minutes: 15 @@ -62,7 +62,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v4 with: - go-version: "1.23" + go-version: "1.24" - name: checkout code uses: actions/checkout@v4 - name: golangci-lint diff --git a/.github/workflows/flake-detector.yml b/.github/workflows/flake-detector.yml index 110c261194..ebcb1cf69a 100644 --- a/.github/workflows/flake-detector.yml +++ b/.github/workflows/flake-detector.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.23.x + go-version: 1.24.x cache: false - name: Install fuse run: sudo apt-get update && sudo apt-get install -y fuse3 libfuse-dev diff --git a/Dockerfile b/Dockerfile index fb6d0961b8..f210a2a523 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ # Mount the gcsfuse to /mnt/gcs: # > docker run --privileged --device /fuse -v /mnt/gcs:/gcs:rw,rshared gcsfuse -FROM golang:1.23.5-alpine AS builder +FROM golang:1.24.0-alpine AS builder RUN apk add git diff --git a/go.mod b/go.mod index b56c471cd1..fca4b9cce6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/googlecloudplatform/gcsfuse/v2 -go 1.23.5 +go 1.24.0 require ( cloud.google.com/go/compute/metadata v0.6.0 diff --git a/perfmetrics/scripts/ml_tests/pytorch/run_model.sh b/perfmetrics/scripts/ml_tests/pytorch/run_model.sh index 1cd3c2b8d4..312eb4a8e0 100755 --- a/perfmetrics/scripts/ml_tests/pytorch/run_model.sh +++ b/perfmetrics/scripts/ml_tests/pytorch/run_model.sh @@ -20,7 +20,7 @@ NUM_EPOCHS=80 TEST_BUCKET="gcsfuse-ml-data" # Install golang -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.5.linux-amd64.tar.gz -q +wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-amd64.tar.gz -q rm -rf /usr/local/go && tar -C /usr/local -xzf go_tar.tar.gz export PATH=$PATH:/usr/local/go/bin diff --git a/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh b/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh index 0c8c7cfbac..7c25249587 100755 --- a/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh +++ b/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh @@ -13,13 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Installs go1.23.5 on the container, builds gcsfuse using log_rotation file +# Installs go1.24.0 on the container, builds gcsfuse using log_rotation file # and installs tf-models-official v2.13.2, makes update to include clear_kernel_cache # and epochs functionality, and runs the model # Install go lang BUCKET_TYPE=$1 -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.5.linux-amd64.tar.gz -q +wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-amd64.tar.gz -q sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local export PATH=$PATH:/usr/local/go/bin diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index b28b652c6d..28f2186ac9 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -43,8 +43,8 @@ set -e sudo apt-get update echo Installing git sudo apt-get install git -echo Installing go-lang 1.23.5 -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.5.linux-amd64.tar.gz -q +echo Installing go-lang 1.24.0 +wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-amd64.tar.gz -q sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local export PATH=$PATH:/usr/local/go/bin export CGO_ENABLED=0 diff --git a/perfmetrics/scripts/read_cache/setup.sh b/perfmetrics/scripts/read_cache/setup.sh index 8392620075..8c59b95430 100755 --- a/perfmetrics/scripts/read_cache/setup.sh +++ b/perfmetrics/scripts/read_cache/setup.sh @@ -64,7 +64,7 @@ sed -i 's/define \+FIO_IO_U_PLAT_GROUP_NR \+\([0-9]\+\)/define FIO_IO_U_PLAT_GRO cd - # Install and validate go. -version=1.23.5 +version=1.24.0 wget -O go_tar.tar.gz https://go.dev/dl/go${version}.linux-amd64.tar.gz -q sudo rm -rf /usr/local/go tar -xzf go_tar.tar.gz && sudo mv go /usr/local diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index ddf73ca960..c9874b8885 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -112,7 +112,7 @@ else fi # install go -wget -O go_tar.tar.gz https://go.dev/dl/go1.23.5.linux-${architecture}.tar.gz +wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-${architecture}.tar.gz sudo tar -C /usr/local -xzf go_tar.tar.gz export PATH=${PATH}:/usr/local/go/bin #Write gcsfuse and go version to log file diff --git a/tools/containerize_gcsfuse_docker/Dockerfile b/tools/containerize_gcsfuse_docker/Dockerfile index 95c4f99c4b..3f20048176 100644 --- a/tools/containerize_gcsfuse_docker/Dockerfile +++ b/tools/containerize_gcsfuse_docker/Dockerfile @@ -34,7 +34,7 @@ ARG OS_VERSION ARG OS_NAME # Image with gcsfuse installed and its package (.deb) -FROM golang:1.23.5 as gcsfuse-package +FROM golang:1.24.0 as gcsfuse-package RUN apt-get update -qq && apt-get install -y ruby ruby-dev rubygems build-essential rpm fuse && gem install --no-document bundler diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 54952ddab6..88ce991cc2 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -127,8 +127,8 @@ function upgrade_gcloud_version() { function install_packages() { # e.g. architecture=arm64 or amd64 architecture=$(dpkg --print-architecture) - echo "Installing go-lang 1.23.5..." - wget -O go_tar.tar.gz https://go.dev/dl/go1.23.5.linux-${architecture}.tar.gz -q + echo "Installing go-lang 1.24.0..." + wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-${architecture}.tar.gz -q sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local export PATH=$PATH:/usr/local/go/bin sudo apt-get install -y python3 diff --git a/tools/package_gcsfuse_docker/Dockerfile b/tools/package_gcsfuse_docker/Dockerfile index 83435483bc..d5d877e176 100644 --- a/tools/package_gcsfuse_docker/Dockerfile +++ b/tools/package_gcsfuse_docker/Dockerfile @@ -17,7 +17,7 @@ # Copy the gcsfuse packages to the host: # > docker run -it -v /tmp:/output gcsfuse-release cp -r /packages /output -FROM golang:1.23.5 as builder +FROM golang:1.24.0 as builder RUN apt-get update -qq && apt-get install -y ruby ruby-dev rubygems build-essential rpm && gem install --no-document bundler -v "2.4.12" From 94c2614f373f6ebdaad332018cc6b84926216f29 Mon Sep 17 00:00:00 2001 From: Ankita Luthra Date: Fri, 28 Feb 2025 15:31:54 +0530 Subject: [PATCH 0269/1298] Parallel downloads default on with experimental flag (#3029) * Keeps 'WaitForDownload' true by default * Updates file cache default chunk size to 200 MB * increase the frequency of updating marker while parallel down for read cache * marker only gets updated in download range * fixes lint * returns error with read handle if any error occurs while copy * updates config test for default DownloadChunkSizeMb * fixes config test for new parallel download default values * fixes config test for new parallel download default values * removes parallel download cache check and correspnding unit test * keeps parallel download and chunk size OFF for early merge in mainline * adds new experiment parallel download default ON * adds IsExperimentalParallelDownloadsDefaultOn method to get value for experimental parallel download flag * wait for download when experimental parallel down default On is enabled * moves range map updates behind experimental flag * fixes cache handle tests * retriggering kokoro build * removes unwanted comment --- cfg/config.go | 12 +++ cfg/params.yaml | 7 ++ cfg/validate_test.go | 1 + internal/cache/file/cache_handle.go | 2 +- internal/cache/file/downloader/job.go | 7 ++ internal/cache/file/downloader/job_test.go | 16 ++++ .../file/downloader/parallel_downloads_job.go | 75 +++++++++++++++---- .../downloader/parallel_downloads_job_test.go | 8 +- 8 files changed, 109 insertions(+), 19 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index abecc6426a..03f02e31fe 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -84,6 +84,8 @@ type FileCacheConfig struct { EnableParallelDownloads bool `yaml:"enable-parallel-downloads"` + ExperimentalParallelDownloadsDefaultOn bool `yaml:"experimental-parallel-downloads-default-on"` + MaxParallelDownloads int64 `yaml:"max-parallel-downloads"` MaxSizeMb int64 `yaml:"max-size-mb"` @@ -405,6 +407,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("file-cache-enable-parallel-downloads", "", false, "Enable parallel downloads.") + flagSet.BoolP("file-cache-experimental-parallel-downloads-default-on", "", false, "Enable parallel downloads by default on experimental basis.") + + if err := flagSet.MarkHidden("file-cache-experimental-parallel-downloads-default-on"); err != nil { + return err + } + flagSet.IntP("file-cache-max-parallel-downloads", "", DefaultMaxParallelDownloads(), "Sets an uber limit of number of concurrent file download requests that are made across all files.") flagSet.IntP("file-cache-max-size-mb", "", -1, "Maximum size of the file-cache in MiBs") @@ -716,6 +724,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("file-cache.experimental-parallel-downloads-default-on", flagSet.Lookup("file-cache-experimental-parallel-downloads-default-on")); err != nil { + return err + } + if err := v.BindPFlag("file-cache.max-parallel-downloads", flagSet.Lookup("file-cache-max-parallel-downloads")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 1f28bfe284..4aa77263a9 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -117,6 +117,13 @@ usage: "Enable parallel downloads." default: false +- config-path: "file-cache.experimental-parallel-downloads-default-on" + flag-name: "file-cache-experimental-parallel-downloads-default-on" + type: "bool" + usage: "Enable parallel downloads by default on experimental basis." + default: false + hide-flag: true + - config-path: "file-cache.max-parallel-downloads" flag-name: "file-cache-max-parallel-downloads" type: "int" diff --git a/cfg/validate_test.go b/cfg/validate_test.go index 4a9fc4d780..74877189e0 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -365,6 +365,7 @@ func TestValidateConfig_ErrorScenarios(t *testing.T) { }, }, { + //TODO: Remove this test as check is also removed when parallel download is default ON name: "parallel_download_config_without_file_cache_enabled", config: &Config{ Logging: LoggingConfig{LogRotate: validLogRotateConfig()}, diff --git a/internal/cache/file/cache_handle.go b/internal/cache/file/cache_handle.go index 35229ef9aa..a188f69ea9 100644 --- a/internal/cache/file/cache_handle.go +++ b/internal/cache/file/cache_handle.go @@ -185,7 +185,7 @@ func (fch *CacheHandle) Read(ctx context.Context, bucket gcs.Bucket, object *gcs fch.prevOffset = offset - if fch.fileDownloadJob.IsParallelDownloadsEnabled() { + if fch.fileDownloadJob.IsParallelDownloadsEnabled() && !fch.fileDownloadJob.IsExperimentalParallelDownloadsDefaultOn() { waitForDownload = false } diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index 77bf3298d7..9841e2dc37 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -583,3 +583,10 @@ func (job *Job) IsParallelDownloadsEnabled() bool { } return false } + +func (job *Job) IsExperimentalParallelDownloadsDefaultOn() bool { + if job.fileCacheConfig != nil && job.fileCacheConfig.ExperimentalParallelDownloadsDefaultOn { + return true + } + return false +} diff --git a/internal/cache/file/downloader/job_test.go b/internal/cache/file/downloader/job_test.go index 9b6a6b4b5a..ca0ed1fc20 100644 --- a/internal/cache/file/downloader/job_test.go +++ b/internal/cache/file/downloader/job_test.go @@ -1023,6 +1023,22 @@ func (dt *downloaderTest) Test_When_Parallel_Download_Is_Disabled() { AssertFalse(result) } +func (dt *downloaderTest) Test_When_Experimental_Default_Parallel_Download_On() { + //Arrange - initJobTest is being called in setup of downloader.go + dt.job.fileCacheConfig.ExperimentalParallelDownloadsDefaultOn = true + + result := dt.job.IsExperimentalParallelDownloadsDefaultOn() + + AssertTrue(result) +} + +func (dt *downloaderTest) Test_When_Experimental_Default_Parallel_Download_Off() { + + result := dt.job.IsExperimentalParallelDownloadsDefaultOn() + + AssertFalse(result) +} + func (dt *downloaderTest) Test_createCacheFile_WhenNonParallelDownloads() { //Arrange - initJobTest is being called in setup of downloader.go dt.job.fileCacheConfig.EnableParallelDownloads = false diff --git a/internal/cache/file/downloader/parallel_downloads_job.go b/internal/cache/file/downloader/parallel_downloads_job.go index 45459e1474..47014bf85f 100644 --- a/internal/cache/file/downloader/parallel_downloads_job.go +++ b/internal/cache/file/downloader/parallel_downloads_job.go @@ -34,7 +34,7 @@ import ( // GCS into given destination writer. // // This function doesn't take locks and can be executed parallely. -func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, end int64, readHandle []byte) ([]byte, error) { +func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, end int64, readHandle []byte, rangeMap map[int64]int64) ([]byte, error) { newReader, err := job.bucket.NewReaderWithReadHandle( ctx, &gcs.ReadObjectRequest{ @@ -66,15 +66,60 @@ func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, e // Use standard copy function if O_DIRECT is disabled and memory aligned // buffer otherwise. if !job.fileCacheConfig.EnableODirect { - _, err = io.CopyN(dstWriter, newReader, end-start) + if job.IsExperimentalParallelDownloadsDefaultOn() { + for start < end { + writeSize := min(end-start, ReadChunkSize) + _, err = io.CopyN(dstWriter, newReader, writeSize) + if err != nil { + err = fmt.Errorf("downloadRange: error at the time of copying content to cache file %w", err) + return newReader.ReadHandle(), err + } + + err = job.updateRangeMap(rangeMap, start, start+writeSize) + if err != nil { + // should return existing read handle with error + return newReader.ReadHandle(), err + } + + start = start + writeSize + } + } else { + _, err = io.CopyN(dstWriter, newReader, end-start) + } } else { - _, err = cacheutil.CopyUsingMemoryAlignedBuffer(ctx, newReader, dstWriter, end-start, - job.fileCacheConfig.WriteBufferSize) - // If context is canceled while reading/writing in CopyUsingMemoryAlignedBuffer - // then it returns error different from context cancelled (invalid argument), - // and we need to report that error as context cancelled. - if !errors.Is(err, context.Canceled) && errors.Is(ctx.Err(), context.Canceled) { - err = errors.Join(err, ctx.Err()) + if job.IsExperimentalParallelDownloadsDefaultOn() { + for start < end { + writeSize := min(end-start, ReadChunkSize) + _, err = cacheutil.CopyUsingMemoryAlignedBuffer(ctx, newReader, dstWriter, writeSize, + job.fileCacheConfig.WriteBufferSize) + // If context is canceled while reading/writing in CopyUsingMemoryAlignedBuffer + // then it returns error different from context cancelled (invalid argument), + // and we need to report that error as context cancelled. + if !errors.Is(err, context.Canceled) && errors.Is(ctx.Err(), context.Canceled) { + err = errors.Join(err, ctx.Err()) + return newReader.ReadHandle(), err + } + if err != nil { + err = fmt.Errorf("downloadRange: error at the time of copying content to cache file %w", err) + return newReader.ReadHandle(), err + } + + err = job.updateRangeMap(rangeMap, start, start+writeSize) + if err != nil { + // should return existing read handle with error + return newReader.ReadHandle(), err + } + start = start + writeSize + } + } else { + _, err = cacheutil.CopyUsingMemoryAlignedBuffer(ctx, newReader, dstWriter, end-start, + job.fileCacheConfig.WriteBufferSize) + // If context is canceled while reading/writing in CopyUsingMemoryAlignedBuffer + // then it returns error different from context cancelled (invalid argument), + // and we need to report that error as context cancelled. + if !errors.Is(err, context.Canceled) && errors.Is(ctx.Err(), context.Canceled) { + err = errors.Join(err, ctx.Err()) + } } } @@ -148,15 +193,17 @@ func (job *Job) downloadOffsets(ctx context.Context, goroutineIndex int64, cache return nil } - offsetWriter := io.NewOffsetWriter(cacheFile, int64(objectRange.Start)) - readHandle, err = job.downloadRange(ctx, offsetWriter, objectRange.Start, objectRange.End, readHandle) + offsetWriter := io.NewOffsetWriter(cacheFile, objectRange.Start) + readHandle, err = job.downloadRange(ctx, offsetWriter, objectRange.Start, objectRange.End, readHandle, rangeMap) if err != nil { return err } - err = job.updateRangeMap(rangeMap, objectRange.Start, objectRange.End) - if err != nil { - return err + if !job.IsExperimentalParallelDownloadsDefaultOn() { + err = job.updateRangeMap(rangeMap, objectRange.Start, objectRange.End) + if err != nil { + return err + } } } } diff --git a/internal/cache/file/downloader/parallel_downloads_job_test.go b/internal/cache/file/downloader/parallel_downloads_job_test.go index c0ca8fffa8..be83201018 100644 --- a/internal/cache/file/downloader/parallel_downloads_job_test.go +++ b/internal/cache/file/downloader/parallel_downloads_job_test.go @@ -81,28 +81,28 @@ func (dt *parallelDownloaderTest) Test_downloadRange() { // Download end 1MiB of object start, end := int64(9*util.MiB), int64(10*util.MiB) offsetWriter := io.NewOffsetWriter(file, start) - _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil) + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, make(map[int64]int64)) AssertEq(nil, err) verifyContentAtOffset(file, start, end) // Download start 4MiB of object start, end = int64(0*util.MiB), int64(4*util.MiB) offsetWriter = io.NewOffsetWriter(file, start) - _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil) + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, make(map[int64]int64)) AssertEq(nil, err) verifyContentAtOffset(file, start, end) // Download middle 1B of object start, end = int64(5*util.MiB), int64(5*util.MiB+1) offsetWriter = io.NewOffsetWriter(file, start) - _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil) + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, make(map[int64]int64)) AssertEq(nil, err) verifyContentAtOffset(file, start, end) // Download 0B of object start, end = int64(5*util.MiB), int64(5*util.MiB) offsetWriter = io.NewOffsetWriter(file, start) - _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil) + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, make(map[int64]int64)) AssertEq(nil, err) verifyContentAtOffset(file, start, end) } From 3b5e2165163a304c7ce73bb1d9da77ec325ed434 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Fri, 28 Feb 2025 16:39:26 +0530 Subject: [PATCH 0270/1298] run stale_handle, streaming_writes tests with and without GRPC flag. (#3033) --- .../stale_handle/setup_test.go | 6 ++---- .../streaming_writes/default_mount_test.go | 1 - .../streaming_writes/streaming_writes_test.go | 19 ++++++++++++++++++- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/tools/integration_tests/stale_handle/setup_test.go b/tools/integration_tests/stale_handle/setup_test.go index b0b46bbe72..9b6199d6a8 100644 --- a/tools/integration_tests/stale_handle/setup_test.go +++ b/tools/integration_tests/stale_handle/setup_test.go @@ -59,10 +59,8 @@ func TestMain(m *testing.M) { flagsSet := [][]string{ {"--metadata-cache-ttl-secs=0", "--precondition-errors=true"}, } - - if !testing.Short() { - setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc") - } + // Run all tests for GRPC. + setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc", "") successCode := static_mounting.RunTests(flagsSet, m) diff --git a/tools/integration_tests/streaming_writes/default_mount_test.go b/tools/integration_tests/streaming_writes/default_mount_test.go index 3ddb32838f..a28df27e0d 100644 --- a/tools/integration_tests/streaming_writes/default_mount_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_test.go @@ -36,7 +36,6 @@ func (t *defaultMountCommonTest) SetupSuite() { SetStorageClient(storageClient) SetTestDirName(testDirName) - flags := []string{"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"} setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) testDirPath = setup.SetupTestDirectory(testDirName) } diff --git a/tools/integration_tests/streaming_writes/streaming_writes_test.go b/tools/integration_tests/streaming_writes/streaming_writes_test.go index aea4493b5a..2cfbd81915 100644 --- a/tools/integration_tests/streaming_writes/streaming_writes_test.go +++ b/tools/integration_tests/streaming_writes/streaming_writes_test.go @@ -31,6 +31,7 @@ const ( ) var ( + flags []string testDirPath string mountFunc func([]string) error // root directory is the directory to be unmounted. @@ -63,8 +64,24 @@ func TestMain(m *testing.M) { setup.SetUpTestDirForTestBucketFlag() rootDir = setup.MntDir() + // Define flag set to run the tests. + flagsSet := [][]string{ + {"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"}, + } + // Run all tests for GRPC. + setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc", "") + log.Println("Running static mounting tests...") mountFunc = static_mounting.MountGcsfuseWithStaticMounting - successCode := m.Run() + + var successCode int + for i := range flagsSet { + log.Printf("Running tests with flags: %v", flagsSet[i]) + flags = flagsSet[i] + successCode = m.Run() + if successCode != 0 { + break + } + } os.Exit(successCode) } From 83500613c998354882bf3770209924acd3c0c6ff Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 3 Mar 2025 10:51:11 +0530 Subject: [PATCH 0271/1298] Enable minimal test packages for zonal bucket in e2e test suite (#2994) * add dates in e2e bucket names * add logs for queueing/waiting/passing/failing on individual e2e test packages * add create_zonal_bucket function in e2e test script * always pass zonal=false for tpc tests * pass RUN_TESTS_FOR_ZONAL_BUCKET from callers of e2e test script * restrict location for zonal bucket runs * enable 1 test package for zonal bucket e2e run * address a review comment * also enable packages log_rotation and mount_timeout * disable package mounting for zonal-bucket run * address a review comment --- .../gcp_ubuntu/e2e_tests/build.sh | 5 +- .../gcp_ubuntu/e2e_tests/tpc_build.sh | 5 +- .../presubmit_test/pr_perf_test/build.sh | 7 +- tools/integration_tests/run_e2e_tests.sh | 144 ++++++++++++++++-- 4 files changed, 143 insertions(+), 18 deletions(-) diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh index f84b9e86e5..47cd63f86a 100755 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh @@ -26,6 +26,9 @@ readonly BUCKET_LOCATION=us-central1 # This flag, if set true, will indicate to the underlying script, to customize for a presubmit-run. readonly RUN_TESTS_WITH_PRESUBMIT_FLAG=false +# This flag, if set true, will indicate to underlying script to also run for zonal buckets. +readonly RUN_TESTS_FOR_ZONAL_BUCKET=true + cd "${KOKORO_ARTIFACTS_DIR}/github/gcsfuse" echo "Building and installing gcsfuse..." # Get the latest commitId of yesterday in the log file. Build gcsfuse and run @@ -37,4 +40,4 @@ git checkout $commitId echo "Running e2e tests on installed package...." # $1 argument is refering to value of testInstalledPackage -./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG +./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG ${RUN_TESTS_FOR_ZONAL_BUCKET} diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh index 01bd1dac22..9889dba41e 100755 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh @@ -27,6 +27,9 @@ readonly BUCKET_LOCATION="u-us-prp1" # This flag, if set true, will indicate to underlying script to customize for a presubmit run. readonly RUN_TESTS_WITH_PRESUBMIT_FLAG=false +# This flag, if set true, will indicate to underlying script to also run for zonal buckets. +readonly RUN_TESTS_FOR_ZONAL_BUCKET=false + cd "${KOKORO_ARTIFACTS_DIR}/github/gcsfuse" # Upgrade gcloud version @@ -60,7 +63,7 @@ gcloud config set project $PROJECT_ID set +e # $1 argument is refering to value of testInstalledPackage -./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG +./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG ${RUN_TESTS_FOR_ZONAL_BUCKET} exit_code=$? set -e diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index 28f2186ac9..c77eb98a4c 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -19,12 +19,15 @@ readonly EXECUTE_INTEGRATION_TEST_LABEL="execute-integration-tests" readonly EXECUTE_PACKAGE_BUILD_TEST_LABEL="execute-package-build-tests" readonly RUN_E2E_TESTS_ON_INSTALLED_PACKAGE=false readonly SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE=false -readonly BUCKET_LOCATION=us-west1 +readonly BUCKET_LOCATION=us-west4 readonly RUN_TEST_ON_TPC_ENDPOINT=false # This flag, if set true, will indicate to underlying script to customize for a presubmit run. readonly RUN_TESTS_WITH_PRESUBMIT_FLAG=true +# This flag, if set true, will indicate to underlying script to also run for zonal buckets. +readonly RUN_TESTS_FOR_ZONAL_BUCKET=true + curl https://api.github.com/repos/GoogleCloudPlatform/gcsfuse/pulls/$KOKORO_GITHUB_PULL_REQUEST_NUMBER >> pr.json perfTest=$(grep "$EXECUTE_PERF_TEST_LABEL" pr.json) integrationTests=$(grep "$EXECUTE_INTEGRATION_TEST_LABEL" pr.json) @@ -113,7 +116,7 @@ then echo "Running e2e tests...." # $1 argument is refering to value of testInstalledPackage. - ./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG + ./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG ${RUN_TESTS_FOR_ZONAL_BUCKET} fi # Execute package build tests. diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 88ce991cc2..3aa9caf05a 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -50,6 +50,13 @@ if [[ $# -ge 6 ]] ; then fi fi +if ${RUN_TESTS_WITH_ZONAL_BUCKET}; then + if [ "${BUCKET_LOCATION}" != "us-west4" ] && [ "${BUCKET_LOCATION}" != "us-central1" ]; then + >&2 echo "For enabling zonal bucket run, BUCKET_LOCATION should be one of: us-west4, us-central1; passed: ${BUCKET_LOCATION}" + exit 1 + fi +fi + if [ "$#" -lt 3 ] then echo "Incorrect number of arguments passed, please refer to the script and pass the three arguments required..." @@ -106,6 +113,43 @@ TEST_DIR_NON_PARALLEL=( "readonly_creds" ) +# Subset of TEST_DIR_PARALLEL, +# but only those tests which currently +# pass for zonal buckets. +TEST_DIR_PARALLEL_FOR_ZB=( + # "benchmarking" + # "concurrent_operations" + # "explicit_dir" + # "gzip" + # "implicit_dir" + # "interrupt" + # "kernel_list_cache" + # "list_large_dir" + # "local_file" + # "log_content" + "log_rotation" + # "monitoring" + "mount_timeout" + # "mounting" + # "negative_stat_cache" + # "operations" + # "read_cache" + # "read_large_files" + # "rename_dir_limit" + # "stale_handle" + # "streaming_writes" + # "write_large_files" +) + +# Subset of TEST_DIR_NON_PARALLEL, +# but only those tests which currently +# pass for zonal buckets. +TEST_DIR_NON_PARALLEL_FOR_ZB=( + # "readonly" + # "managed_folders" + # "readonly_creds" +) + # Create a temporary file to store the log file name. TEST_LOGS_FILE=$(mktemp) @@ -143,7 +187,7 @@ function create_bucket() { bucket_prefix=$1 local -r project_id="gcs-fuse-test-ml" # Generate bucket name with random string - bucket_name=$bucket_prefix$(tr -dc 'a-z0-9' < /dev/urandom | head -c $RANDOM_STRING_LENGTH) + bucket_name=${bucket_prefix}$(date +%Y%m%d-%H%M%S)"-"$(tr -dc 'a-z0-9' < /dev/urandom | head -c $RANDOM_STRING_LENGTH) # We are using gcloud alpha because gcloud storage is giving issues running on Kokoro gcloud alpha storage buckets create gs://$bucket_name --project=$project_id --location=$BUCKET_LOCATION --uniform-bucket-level-access echo $bucket_name @@ -154,15 +198,31 @@ function create_hns_bucket() { # Generate bucket name with random string. # Adding prefix `golang-grpc-test` to white list the bucket for grpc # so that we can run grpc related e2e tests. - bucket_name="golang-grpc-test-gcsfuse-e2e-tests-hns-"$(tr -dc 'a-z0-9' < /dev/urandom | head -c $RANDOM_STRING_LENGTH) + bucket_name="golang-grpc-test-gcsfuse-e2e-tests-hns-"$(date +%Y%m%d-%H%M%S)"-"$(tr -dc 'a-z0-9' < /dev/urandom | head -c $RANDOM_STRING_LENGTH) gcloud alpha storage buckets create gs://$bucket_name --project=$hns_project_id --location=$BUCKET_LOCATION --uniform-bucket-level-access --enable-hierarchical-namespace echo "$bucket_name" } +function create_zonal_bucket() { + local -r project_id="gcs-fuse-test-ml" + local -r region=${BUCKET_LOCATION} + local -r zone=${region}"-a" + + local -r hns_project_id="gcs-fuse-test" + # Generate bucket name with random string. + bucket_name="gcsfuse-e2e-tests-zb-"$(date +%Y%m%d-%H%M%S)"-"$(tr -dc 'a-z0-9' < /dev/urandom | head -c $RANDOM_STRING_LENGTH) + gcloud alpha storage buckets create gs://$bucket_name --project=$project_id --location=$region --placement=${zone} --default-storage-class=RAPID --uniform-bucket-level-access --enable-hierarchical-namespace + echo "${bucket_name}" +} + function run_non_parallel_tests() { local exit_code=0 local -n test_array=$1 local bucket_name_non_parallel=$2 + local zonal=false + if [ $# -ge 3 ] && [ "$3" = "true" ] ; then + zonal=true + fi for test_dir_np in "${test_array[@]}" do @@ -173,12 +233,14 @@ function run_non_parallel_tests() { echo $log_file >> $TEST_LOGS_FILE # Executing integration tests - GODEBUG=asyncpreemptoff=1 go test $test_path_non_parallel -p 1 $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${RUN_TESTS_WITH_ZONAL_BUCKET} --integrationTest -v --testbucket=$bucket_name_non_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 + echo "Running test package in non-parallel (with zonal=${zonal}): ${test_dir_np} ..." + GODEBUG=asyncpreemptoff=1 go test $test_path_non_parallel -p 1 $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${zonal} --integrationTest -v --testbucket=$bucket_name_non_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 exit_code_non_parallel=$? if [ $exit_code_non_parallel != 0 ]; then exit_code=$exit_code_non_parallel - echo "test fail in non parallel on package: " $test_dir_np + echo "test fail in non parallel on package (with zonal=${zonal}): " $test_dir_np fi + echo "Passed test package in non-parallel (with zonal=${zonal}): ${test_dir_np} ..." done return $exit_code } @@ -187,8 +249,12 @@ function run_parallel_tests() { local exit_code=0 local -n test_array=$1 local bucket_name_parallel=$2 + local zonal=false + if [ $# -ge 3 ] && [ "$3" = "true" ] ; then + zonal=true + fi local benchmark_flags="" - local pids=() + declare -A pids for test_dir_p in "${test_array[@]}" do @@ -204,21 +270,26 @@ function run_parallel_tests() { local log_file="/tmp/${test_dir_p}_${bucket_name_parallel}.log" echo $log_file >> $TEST_LOGS_FILE # Executing integration tests - GODEBUG=asyncpreemptoff=1 go test $test_path_parallel $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${RUN_TESTS_WITH_ZONAL_BUCKET} $benchmark_flags -p 1 --integrationTest -v --testbucket=$bucket_name_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & + echo "Queueing up test package in parallel (with zonal=${zonal}): ${test_dir_p} ..." + GODEBUG=asyncpreemptoff=1 go test $test_path_parallel $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${zonal} $benchmark_flags -p 1 --integrationTest -v --testbucket=$bucket_name_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & pid=$! # Store the PID of the background process - pids+=("$pid") # Optionally add the PID to an array for later - package_names[$pid]=$test_dir_p # Keep mapping between package name and PID to print failed package un case of failure. + echo "Queued up test package in parallel (with zonal=${zonal}): ${test_dir_p} with pid=${pid}" + pids[${test_dir_p}]=${pid} # Optionally add the PID to an array for later done # Wait for processes and collect exit codes - for pid in "${pids[@]}"; do + for package_name in "${!pids[@]}"; do + pid="${pids[${package_name}]}" + echo "Waiting on test package ${package_name} (with zonal=${zonal}) through pid=${pid} ..." + # What if the process for this test package completed long back and its PID got + # re-assigned to another process since then ? wait $pid exit_code_parallel=$? if [ $exit_code_parallel != 0 ]; then exit_code=$exit_code_parallel - package_name="${package_names[$pid]}" # Retrieve the package name - echo "test fail in parallel on package: " $package_name + echo "test fail in parallel on package (with zonal=${zonal}): " $package_name fi + echo "Passed test package in parallel (with zonal=${zonal}): ${package_name} ." done return $exit_code } @@ -240,11 +311,11 @@ function print_test_logs() { function run_e2e_tests_for_flat_bucket() { # Adding prefix `golang-grpc-test` to white list the bucket for grpc so that # we can run grpc related e2e tests. - bucketPrefix="golang-grpc-test-gcsfuse-non-parallel-e2e-tests-" + bucketPrefix="golang-grpc-test-gcsfuse-np-e2e-tests-" bucket_name_non_parallel=$(create_bucket $bucketPrefix) echo "Bucket name for non parallel tests: "$bucket_name_non_parallel - bucketPrefix="golang-grpc-test-gcsfuse-parallel-e2e-tests-" + bucketPrefix="golang-grpc-test-gcsfuse-p-e2e-tests-" bucket_name_parallel=$(create_bucket $bucketPrefix) echo "Bucket name for parallel tests: "$bucket_name_parallel @@ -301,12 +372,41 @@ function run_e2e_tests_for_hns_bucket(){ return 0 } +function run_e2e_tests_for_zonal_bucket(){ + zonal_bucket_name_parallel_group=$(create_zonal_bucket) + echo "Zonal Bucket Created for parallel tests: "$zonal_bucket_name_parallel_group + + zonal_bucket_name_non_parallel_group=$(create_zonal_bucket) + echo "Zonal Bucket Created for non-parallel tests: "$zonal_bucket_name_non_parallel_group + + echo "Running tests for ZONAL bucket" + run_parallel_tests TEST_DIR_PARALLEL_FOR_ZB "$zonal_bucket_name_parallel_group" true & + parallel_tests_zonal_group_pid=$! + run_non_parallel_tests TEST_DIR_NON_PARALLEL_FOR_ZB "$zonal_bucket_name_non_parallel_group" true & + non_parallel_tests_zonal_group_pid=$! + + # Wait for all tests to complete. + wait $parallel_tests_zonal_group_pid + parallel_tests_zonal_group_exit_code=$? + wait $non_parallel_tests_zonal_group_pid + non_parallel_tests_zonal_group_exit_code=$? + + zonal_buckets=("$zonal_bucket_name_parallel_group" "$zonal_bucket_name_non_parallel_group") + clean_up zonal_buckets + + if [ $parallel_tests_zonal_group_exit_code != 0 ] || [ $non_parallel_tests_zonal_group_exit_code != 0 ]; + then + return 1 + fi + return 0 +} + function run_e2e_tests_for_tpc() { # Clean bucket before testing. gcloud storage rm -r gs://gcsfuse-e2e-tests-tpc/** # Run Operations e2e tests in TPC to validate all the functionality. - GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... --testOnTPCEndPoint=$RUN_TEST_ON_TPC_ENDPOINT $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${RUN_TESTS_WITH_ZONAL_BUCKET} -p 1 --integrationTest -v --testbucket=gcsfuse-e2e-tests-tpc --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT + GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... --testOnTPCEndPoint=$RUN_TEST_ON_TPC_ENDPOINT $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=false -p 1 --integrationTest -v --testbucket=gcsfuse-e2e-tests-tpc --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT exit_code=$? set -e @@ -357,6 +457,12 @@ function main(){ fi #run integration tests + + if ${RUN_TESTS_WITH_ZONAL_BUCKET}; then + run_e2e_tests_for_zonal_bucket & + e2e_tests_zonal_bucket_pid=$! + fi + run_e2e_tests_for_hns_bucket & e2e_tests_hns_bucket_pid=$! @@ -375,6 +481,11 @@ function main(){ wait $e2e_tests_hns_bucket_pid e2e_tests_hns_bucket_status=$? + if ${RUN_TESTS_WITH_ZONAL_BUCKET}; then + wait $e2e_tests_zonal_bucket_pid + e2e_tests_zonal_bucket_status=$? + fi + set -e print_test_logs @@ -392,6 +503,11 @@ function main(){ exit_code=1 fi + if ${RUN_TESTS_WITH_ZONAL_BUCKET} && [ $e2e_tests_zonal_bucket_status != 0 ]; then + echo "The e2e tests for zonal bucket failed.." + exit_code=1 + fi + if [ $e2e_tests_emulator_status != 0 ]; then echo "The e2e tests for emulator failed.." From 627ab5e44ba4e62c90d8336e5d0989212181e1a1 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 3 Mar 2025 12:03:36 +0530 Subject: [PATCH 0272/1298] upgrade fuse module (#3045) --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index fca4b9cce6..0fbea80e24 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.1 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec - github.com/jacobsa/fuse v0.0.0-20250227120101-009e36f010a3 + github.com/jacobsa/fuse v0.0.0-20250228153609-219b4762b40e github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 @@ -44,7 +44,7 @@ require ( golang.org/x/net v0.34.0 golang.org/x/oauth2 v0.25.0 golang.org/x/sync v0.10.0 - golang.org/x/sys v0.29.0 + golang.org/x/sys v0.30.0 golang.org/x/text v0.21.0 golang.org/x/time v0.9.0 google.golang.org/api v0.217.0 diff --git a/go.sum b/go.sum index 75f3b7904f..addf615fd4 100644 --- a/go.sum +++ b/go.sum @@ -249,6 +249,8 @@ github.com/jacobsa/fuse v0.0.0-20241025064006-8ccd61173b05 h1:PDBoLGtxDPVzWurS8V github.com/jacobsa/fuse v0.0.0-20241025064006-8ccd61173b05/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= github.com/jacobsa/fuse v0.0.0-20250227120101-009e36f010a3 h1:v7S0NR1fovnc7mkbZ+cvV8WkJbtIWU8kfOZzdaEzABY= github.com/jacobsa/fuse v0.0.0-20250227120101-009e36f010a3/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= +github.com/jacobsa/fuse v0.0.0-20250228153609-219b4762b40e h1:1k0eO4yveJsWQDzjA7YbnqDfT631ld5wGfr9uPN15N0= +github.com/jacobsa/fuse v0.0.0-20250228153609-219b4762b40e/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= @@ -582,6 +584,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 4de31286777e3e6e5f0aad423251d9ba9d9292c5 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 4 Mar 2025 15:15:08 +0530 Subject: [PATCH 0273/1298] Change OpenCensus references to OTel in documentation (#3055) --- docs/metrics.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/metrics.md b/docs/metrics.md index 45975ef288..32d9c360d2 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -1,6 +1,6 @@ # GCSFuse Metrics -GCSFuse supports exporting custom metrics to Google cloud monitoring. -Metrics are collected using OpenCensus and exported via Stackdriver exporter. +GCSFuse supports exporting custom metrics to Google Cloud monitoring. +Metrics are collected using OpenTelemetry and exported via Cloud Monitoring exporter. As of today, GCSFuse exports following metrics related to filesystem and gcs calls. @@ -148,4 +148,4 @@ gcs_read_count{read_type="Sequential"} 5 to specify the target Prometheus metric endpoint under the `scrape_configs` section in the Prometheus configuration file. ## References: -* More details around adding custom metrics using OpenCensus can be found [here](https://cloud.google.com/monitoring/custom-metrics/open-census) +* More details around adding custom metrics using OpenTelemetry can be found [here](https://cloud.google.com/monitoring/custom-metrics/open-telemetry) From 688b6423d7e6c89ddb63f8993f1a9f5eacab94ce Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 4 Mar 2025 15:27:04 +0530 Subject: [PATCH 0274/1298] cleanup task: removing newReader method (replaced by newReaderWithReadHandle) (#3035) * replacing newReader with newReaderWithReadHandle * fixing comment desc * removing redundant tests * fixes in random reader test * update semantics for newReaderWithFileHandle * fixing test names in bucket_handle_test.go * correct semantics for newReaderWithReadHandle method * fixes for randomreadertest.go --- internal/cache/file/downloader/job.go | 2 +- internal/fs/inode/file.go | 2 +- internal/gcsx/integration_test.go | 2 +- internal/gcsx/prefix_bucket.go | 8 ----- internal/gcsx/prefix_bucket_test.go | 2 +- internal/gcsx/random_reader_test.go | 14 ++------ internal/monitor/bucket.go | 8 ----- internal/ratelimit/throttled_bucket.go | 8 ----- internal/storage/bucket_handle.go | 14 +++----- internal/storage/bucket_handle_test.go | 34 +++++++++---------- internal/storage/caching/fast_stat_bucket.go | 8 ----- internal/storage/debug_bucket.go | 7 ---- internal/storage/fake/bucket.go | 16 --------- internal/storage/fake/testing/bucket_tests.go | 24 ++++++------- internal/storage/gcs/bucket.go | 6 +--- internal/storage/mock/testify_mock_bucket.go | 6 ---- internal/storage/mock_bucket.go | 30 ---------------- internal/storage/storageutil/read_object.go | 2 +- internal/storage/testify_mock_bucket.go | 6 ---- 19 files changed, 43 insertions(+), 156 deletions(-) diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index 9841e2dc37..305e7ae48d 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -293,7 +293,7 @@ func (job *Job) updateStatusOffset(downloadedOffset int64) (err error) { } // downloadObjectToFile downloads the backing object from GCS into the given -// file and updates the file info cache. It uses gcs.Bucket's NewReader method +// file and updates the file info cache. It uses gcs.Bucket's NewReaderWithReadHandle method // to download the object. func (job *Job) downloadObjectToFile(cacheFile *os.File) (err error) { var newReader gcs.StorageReader diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 02be0df694..717d161f36 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -247,7 +247,7 @@ func (f *FileInode) clobbered(ctx context.Context, forceFetchFromGcs bool, inclu // Open a reader for the generation of object we care about. func (f *FileInode) openReader(ctx context.Context) (io.ReadCloser, error) { - rc, err := f.bucket.NewReader( + rc, err := f.bucket.NewReaderWithReadHandle( ctx, &gcs.ReadObjectRequest{ Name: f.src.Name, diff --git a/internal/gcsx/integration_test.go b/internal/gcsx/integration_test.go index 0df91e1937..9bc45521fe 100644 --- a/internal/gcsx/integration_test.go +++ b/internal/gcsx/integration_test.go @@ -104,7 +104,7 @@ func (t *IntegrationTest) TearDown() { func (t *IntegrationTest) create(o *gcs.Object) { // Set up a reader. - rc, err := t.bucket.NewReader( + rc, err := t.bucket.NewReaderWithReadHandle( t.ctx, &gcs.ReadObjectRequest{ Name: o.Name, diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index 0b3c8d6165..ea4bd5dae3 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -16,7 +16,6 @@ package gcsx import ( "errors" - "io" "strings" "unicode/utf8" @@ -67,13 +66,6 @@ func (b *prefixBucket) BucketType() gcs.BucketType { return b.wrapped.BucketType() } -func (b *prefixBucket) NewReader( - ctx context.Context, - req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { - rc, err = b.NewReaderWithReadHandle(ctx, req) - return -} - func (b *prefixBucket) NewReaderWithReadHandle( ctx context.Context, req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { diff --git a/internal/gcsx/prefix_bucket_test.go b/internal/gcsx/prefix_bucket_test.go index cc3d158451..f26e624136 100644 --- a/internal/gcsx/prefix_bucket_test.go +++ b/internal/gcsx/prefix_bucket_test.go @@ -80,7 +80,7 @@ func (t *PrefixBucketTest) Test_NewReader() { assert.Equal(t.T(), nil, err) // Read it through the prefix bucket. - rc, err := t.bucket.NewReader( + rc, err := t.bucket.NewReaderWithReadHandle( t.ctx, &gcs.ReadObjectRequest{ Name: suffix, diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index 39c4ab4fd5..e045ade8e7 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -200,12 +200,6 @@ func getReadCloser(content []byte) io.ReadCloser { return rc } -func (t *RandomReaderTest) mockNewReaderCallForTestBucket(start uint64, limit uint64, rd io.ReadCloser) { - ExpectCall(t.bucket, "NewReader")( - Any(), AllOf(rangeStartIs(start), rangeLimitIs(limit))). - WillRepeatedly(Return(rd, nil)) -} - func (t *RandomReaderTest) mockNewReaderWithHandleCallForTestBucket(start uint64, limit uint64, rd gcs.StorageReader) { ExpectCall(t.bucket, "NewReaderWithReadHandle")( Any(), AllOf(rangeStartIs(start), rangeLimitIs(limit))). @@ -741,8 +735,6 @@ func (t *RandomReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffsetLes t.object.Size = 20 * util.MiB objectSize := t.object.Size testContent := testutil.GenerateRandomBytes(int(objectSize)) - rc := getReadCloser(testContent) - t.mockNewReaderCallForTestBucket(0, objectSize, rc) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) @@ -870,7 +862,6 @@ func (t *RandomReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInvali // Second reader (rc2) is required, since first reader (rc) is completely read. // Reading again will return EOF. rc2 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderCallForTestBucket(0, objectSize, rc2) t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rc2) objectData, err = t.rr.ReadAt(buf, 0) ExpectEq(nil, err) @@ -908,9 +899,10 @@ func (t *RandomReaderTest) Test_ReadAt_IfCacheFileGetsDeleted() { filePath := util.GetDownloadPath(t.cacheDir, util.GetObjectPath(t.bucket.Name(), t.object.Name)) err = os.Remove(filePath) AssertEq(nil, err) - // Second reader (rc2) is required, since first reader (rc) is completely read. + // Second reader (rd2) is required, since first reader (rd) is completely read. // Reading again will return EOF. - t.mockNewReaderCallForTestBucket(0, objectSize, getReadCloser(testContent)) + rd2 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd2) _, err = t.rr.ReadAt(buf, 0) diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index 2ae5cff2e1..0bcb27caf7 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -17,7 +17,6 @@ package monitor import ( "context" "fmt" - "io" "time" storagev2 "cloud.google.com/go/storage" @@ -73,13 +72,6 @@ func setupReader(ctx context.Context, mb *monitoringBucket, req *gcs.ReadObjectR return rc, err } -func (mb *monitoringBucket) NewReader( - ctx context.Context, - req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { - rc, err = setupReader(ctx, mb, req, "NewReader") - return -} - func (mb *monitoringBucket) NewReaderWithReadHandle( ctx context.Context, req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { diff --git a/internal/ratelimit/throttled_bucket.go b/internal/ratelimit/throttled_bucket.go index 2a948aa36f..7905989b4c 100644 --- a/internal/ratelimit/throttled_bucket.go +++ b/internal/ratelimit/throttled_bucket.go @@ -54,14 +54,6 @@ func (b *throttledBucket) Name() string { func (b *throttledBucket) BucketType() gcs.BucketType { return b.wrapped.BucketType() } - -func (b *throttledBucket) NewReader( - ctx context.Context, - req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { - rc, err = b.NewReaderWithReadHandle(ctx, req) - return -} - func (b *throttledBucket) NewReaderWithReadHandle( ctx context.Context, req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 8bef26bd93..9fcf325672 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -53,13 +53,6 @@ func (bh *bucketHandle) BucketType() gcs.BucketType { return *bh.bucketType } -func (bh *bucketHandle) NewReader( - ctx context.Context, - req *gcs.ReadObjectRequest) (io.ReadCloser, error) { - rc, err := bh.NewReaderWithReadHandle(ctx, req) - return rc, err -} - func (bh *bucketHandle) NewReaderWithReadHandle( ctx context.Context, req *gcs.ReadObjectRequest) (reader gcs.StorageReader, err error) { @@ -71,8 +64,11 @@ func (bh *bucketHandle) NewReaderWithReadHandle( // Initialising the starting offset and the length to be read by the reader. start := int64(0) length := int64(-1) - // Following the semantics of NewReader method. Passing start, length as 0,-1 reads the entire file. - // https://github.com/GoogleCloudPlatform/gcsfuse/blob/34211af652dbaeb012b381a3daf3c94b95f65e00/vendor/cloud.google.com/go/storage/reader.go#L75 + // Following the semantics of NewRangeReader method. + // If length is negative, the object is read until the end. + // If offset is negative, the object is read abs(offset) bytes from the end, + // and length must also be negative to indicate all remaining bytes will be read. + // Ref: https://github.com/GoogleCloudPlatform/gcsfuse/blob/34211af652dbaeb012b381a3daf3c94b95f65e00/vendor/cloud.google.com/go/storage/reader.go#L80 if req.Range != nil { start = int64((*req.Range).Start) end := int64((*req.Range).Limit) diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 8fdf3c9e34..4156f29529 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -95,8 +95,8 @@ func (testSuite *BucketHandleTest) TearDownTest() { testSuite.fakeStorage.ShutDown() } -func (testSuite *BucketHandleTest) TestNewReaderMethodWithCompleteRead() { - rc, err := testSuite.bucketHandle.NewReader(context.Background(), +func (testSuite *BucketHandleTest) TestNewReaderWithReadHandleMethodWithCompleteRead() { + rc, err := testSuite.bucketHandle.NewReaderWithReadHandle(context.Background(), &gcs.ReadObjectRequest{ Name: TestObjectName, Range: &gcs.ByteRange{ @@ -113,11 +113,11 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithCompleteRead() { assert.Equal(testSuite.T(), ContentInTestObject, string(buf[:])) } -func (testSuite *BucketHandleTest) TestNewReaderMethodWithRangeRead() { +func (testSuite *BucketHandleTest) TestNewReaderWithReadHandleMethodWithRangeRead() { start := uint64(2) limit := uint64(8) - rc, err := testSuite.bucketHandle.NewReader(context.Background(), + rc, err := testSuite.bucketHandle.NewReaderWithReadHandle(context.Background(), &gcs.ReadObjectRequest{ Name: TestObjectName, Range: &gcs.ByteRange{ @@ -134,8 +134,8 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithRangeRead() { assert.Equal(testSuite.T(), ContentInTestObject[start:limit], string(buf[:])) } -func (testSuite *BucketHandleTest) TestNewReaderMethodWithNilRange() { - rc, err := testSuite.bucketHandle.NewReader(context.Background(), +func (testSuite *BucketHandleTest) TestNewReaderWithReadHandleMethodWithNilRange() { + rc, err := testSuite.bucketHandle.NewReaderWithReadHandle(context.Background(), &gcs.ReadObjectRequest{ Name: TestObjectName, Range: nil, @@ -149,10 +149,10 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithNilRange() { assert.Equal(testSuite.T(), ContentInTestObject, string(buf[:])) } -func (testSuite *BucketHandleTest) TestNewReaderMethodWithInValidObject() { +func (testSuite *BucketHandleTest) TestNewReaderWithReadHandleMethodWithInValidObject() { var notFoundErr *gcs.NotFoundError - rc, err := testSuite.bucketHandle.NewReader(context.Background(), + rc, err := testSuite.bucketHandle.NewReaderWithReadHandle(context.Background(), &gcs.ReadObjectRequest{ Name: missingObjectName, Range: &gcs.ByteRange{ @@ -166,8 +166,8 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithInValidObject() { assert.Nil(testSuite.T(), rc) } -func (testSuite *BucketHandleTest) TestNewReaderMethodWithValidGeneration() { - rc, err := testSuite.bucketHandle.NewReader(context.Background(), +func (testSuite *BucketHandleTest) TestNewReaderWithReadHandleMethodWithValidGeneration() { + rc, err := testSuite.bucketHandle.NewReaderWithReadHandle(context.Background(), &gcs.ReadObjectRequest{ Name: TestObjectName, Range: &gcs.ByteRange{ @@ -185,10 +185,10 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithValidGeneration() { assert.Equal(testSuite.T(), ContentInTestObject, string(buf[:])) } -func (testSuite *BucketHandleTest) TestNewReaderMethodWithInvalidGeneration() { +func (testSuite *BucketHandleTest) TestNewReaderWithReadHandleMethodWithInvalidGeneration() { var notFoundErr *gcs.NotFoundError - rc, err := testSuite.bucketHandle.NewReader(context.Background(), + rc, err := testSuite.bucketHandle.NewReaderWithReadHandle(context.Background(), &gcs.ReadObjectRequest{ Name: TestObjectName, Range: &gcs.ByteRange{ @@ -203,8 +203,8 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithInvalidGeneration() { assert.Nil(testSuite.T(), rc) } -func (testSuite *BucketHandleTest) TestNewReaderMethodWithCompressionEnabled() { - rc, err := testSuite.bucketHandle.NewReader(context.Background(), +func (testSuite *BucketHandleTest) TestNewReaderWithReadHandleMethodWithCompressionEnabled() { + rc, err := testSuite.bucketHandle.NewReaderWithReadHandle(context.Background(), &gcs.ReadObjectRequest{ Name: TestGzipObjectName, Range: &gcs.ByteRange{ @@ -222,8 +222,8 @@ func (testSuite *BucketHandleTest) TestNewReaderMethodWithCompressionEnabled() { assert.Equal(testSuite.T(), ContentInTestGzipObjectCompressed, string(buf)) } -func (testSuite *BucketHandleTest) TestNewReaderMethodWithCompressionDisabled() { - rc, err := testSuite.bucketHandle.NewReader(context.Background(), +func (testSuite *BucketHandleTest) TestNewReaderWithReadHandleMethodWithCompressionDisabled() { + rc, err := testSuite.bucketHandle.NewReaderWithReadHandle(context.Background(), &gcs.ReadObjectRequest{ Name: TestGzipObjectName, Range: &gcs.ByteRange{ @@ -931,7 +931,7 @@ func (testSuite *BucketHandleTest) TestUpdateObjectMethodWithMissingObject() { // Read content of an object and return func (testSuite *BucketHandleTest) readObjectContent(ctx context.Context, req *gcs.ReadObjectRequest) (buffer string) { - rc, err := testSuite.bucketHandle.NewReader(ctx, &gcs.ReadObjectRequest{ + rc, err := testSuite.bucketHandle.NewReaderWithReadHandle(ctx, &gcs.ReadObjectRequest{ Name: req.Name, Range: req.Range}) diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index 485af93239..078f3be2c9 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -16,7 +16,6 @@ package caching import ( "fmt" - "io" "strings" "sync" "time" @@ -211,13 +210,6 @@ func (b *fastStatBucket) BucketType() gcs.BucketType { return b.wrapped.BucketType() } -func (b *fastStatBucket) NewReader( - ctx context.Context, - req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { - rc, err = b.wrapped.NewReader(ctx, req) - return -} - func (b *fastStatBucket) NewReaderWithReadHandle( ctx context.Context, req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index 94bc5735b8..3a5b168229 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -156,13 +156,6 @@ func setupReader(ctx context.Context, b *debugBucket, req *gcs.ReadObjectRequest return rc, err } -func (b *debugBucket) NewReader( - ctx context.Context, - req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { - rc, err = setupReader(ctx, b, req, "Read") - return -} - func (b *debugBucket) NewReaderWithReadHandle( ctx context.Context, req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index dc2386e8a8..1134347286 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -658,22 +658,6 @@ func (b *bucket) ListObjects( return } -// LOCKS_EXCLUDED(b.mu) -func (b *bucket) NewReader( - ctx context.Context, - req *gcs.ReadObjectRequest) (rc io.ReadCloser, err error) { - b.mu.Lock() - defer b.mu.Unlock() - - r, _, err := b.newReaderLocked(req) - if err != nil { - return - } - - rc = io.NopCloser(r) - return -} - // LOCKS_EXCLUDED(b.mu) func (b *bucket) NewReaderWithReadHandle(ctx context.Context, req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { b.mu.Lock() diff --git a/internal/storage/fake/testing/bucket_tests.go b/internal/storage/fake/testing/bucket_tests.go index 55aa1ab834..a0c1837d69 100644 --- a/internal/storage/fake/testing/bucket_tests.go +++ b/internal/storage/fake/testing/bucket_tests.go @@ -271,7 +271,7 @@ func readMultiple( }() // Open a reader. - rc, err := bucket.NewReader(ctx, reqs[i]) + rc, err := bucket.NewReaderWithReadHandle(ctx, reqs[i]) if err != nil { err = fmt.Errorf("NewReader: %v", err) return @@ -449,7 +449,7 @@ func (t *bucketTest) readObject(objectName string) (contents string, err error) Name: objectName, } - reader, err := t.bucket.NewReader(t.ctx, req) + reader, err := t.bucket.NewReaderWithReadHandle(t.ctx, req) if err != nil { return } @@ -2751,7 +2751,7 @@ func (t *readTest) ObjectNameDoesntExist() { Name: "foobar", } - rc, err := t.bucket.NewReader(t.ctx, req) + rc, err := t.bucket.NewReaderWithReadHandle(t.ctx, req) if err == nil { defer rc.Close() _, err = rc.Read(make([]byte, 1)) @@ -2770,7 +2770,7 @@ func (t *readTest) EmptyObject() { Name: "foo", } - r, err := t.bucket.NewReader(t.ctx, req) + r, err := t.bucket.NewReaderWithReadHandle(t.ctx, req) AssertEq(nil, err) contents, err := io.ReadAll(r) @@ -2790,7 +2790,7 @@ func (t *readTest) NonEmptyObject() { Name: "foo", } - r, err := t.bucket.NewReader(t.ctx, req) + r, err := t.bucket.NewReaderWithReadHandle(t.ctx, req) AssertEq(nil, err) contents, err := io.ReadAll(r) @@ -2818,7 +2818,7 @@ func (t *readTest) ParticularGeneration_NeverExisted() { Generation: o.Generation + 1, } - rc, err := t.bucket.NewReader(t.ctx, req) + rc, err := t.bucket.NewReaderWithReadHandle(t.ctx, req) if err == nil { defer rc.Close() _, err = rc.Read(make([]byte, 1)) @@ -2854,7 +2854,7 @@ func (t *readTest) ParticularGeneration_HasBeenDeleted() { Generation: o.Generation, } - rc, err := t.bucket.NewReader(t.ctx, req) + rc, err := t.bucket.NewReaderWithReadHandle(t.ctx, req) if err == nil { defer rc.Close() _, err = rc.Read(make([]byte, 1)) @@ -2881,7 +2881,7 @@ func (t *readTest) ParticularGeneration_Exists() { Generation: o.Generation, } - r, err := t.bucket.NewReader(t.ctx, req) + r, err := t.bucket.NewReaderWithReadHandle(t.ctx, req) AssertEq(nil, err) contents, err := io.ReadAll(r) @@ -2920,7 +2920,7 @@ func (t *readTest) ParticularGeneration_ObjectHasBeenOverwritten() { Generation: o.Generation, } - rc, err := t.bucket.NewReader(t.ctx, req) + rc, err := t.bucket.NewReaderWithReadHandle(t.ctx, req) if err == nil { defer rc.Close() _, err = rc.Read(make([]byte, 1)) @@ -2932,7 +2932,7 @@ func (t *readTest) ParticularGeneration_ObjectHasBeenOverwritten() { // Reading by the new generation should work. req.Generation = o2.Generation - rc, err = t.bucket.NewReader(t.ctx, req) + rc, err = t.bucket.NewReaderWithReadHandle(t.ctx, req) AssertEq(nil, err) contents, err := io.ReadAll(rc) @@ -4069,7 +4069,7 @@ func (t *deleteTest) NoParticularGeneration_Successful() { Name: "a", } - rc, err := t.bucket.NewReader(t.ctx, req) + rc, err := t.bucket.NewReaderWithReadHandle(t.ctx, req) if err == nil { defer rc.Close() _, err = rc.Read(make([]byte, 1)) @@ -4854,7 +4854,7 @@ func (t *cancellationTest) ReadObject() { // Create a reader for the object using a cancellable context. ctx, cancel := context.WithCancel(t.ctx) - rc, err := t.bucket.NewReader( + rc, err := t.bucket.NewReaderWithReadHandle( ctx, &gcs.ReadObjectRequest{ Name: name, diff --git a/internal/storage/gcs/bucket.go b/internal/storage/gcs/bucket.go index 1714f5c5aa..512c45a72f 100644 --- a/internal/storage/gcs/bucket.go +++ b/internal/storage/gcs/bucket.go @@ -68,11 +68,7 @@ type Bucket interface { // // Official documentation: // https://cloud.google.com/storage/docs/json_api/v1/objects/get - NewReader( - ctx context.Context, - req *ReadObjectRequest) (io.ReadCloser, error) - - // Similar to NewReader. But establishes connection using the readHandle if not nil. + // Connection is established using the readHandle if not nil. // ReadHandle helps in reducing the latency by eleminating auth/metadata checks when a valid readHandle is passed. // ReadHandle is valid when its not nil, not expired and belongs to the same client. NewReaderWithReadHandle( diff --git a/internal/storage/mock/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go index 7816ad110c..7649fead3e 100644 --- a/internal/storage/mock/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -16,7 +16,6 @@ package mock import ( "context" - "io" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/stretchr/testify/mock" @@ -37,11 +36,6 @@ func (m *TestifyMockBucket) BucketType() gcs.BucketType { return args.Get(0).(gcs.BucketType) } -func (m *TestifyMockBucket) NewReader(ctx context.Context, req *gcs.ReadObjectRequest) (io.ReadCloser, error) { - args := m.Called(ctx, req) - return args.Get(0).(io.ReadCloser), args.Error(1) -} - func (m *TestifyMockBucket) NewReaderWithReadHandle(ctx context.Context, req *gcs.ReadObjectRequest) (gcs.StorageReader, error) { args := m.Called(ctx, req) if args.Get(0) == nil { diff --git a/internal/storage/mock_bucket.go b/internal/storage/mock_bucket.go index d36608dd74..add828a6a8 100644 --- a/internal/storage/mock_bucket.go +++ b/internal/storage/mock_bucket.go @@ -8,7 +8,6 @@ package storage import ( fmt "fmt" - io "io" runtime "runtime" unsafe "unsafe" @@ -342,35 +341,6 @@ func (m *mockBucket) BucketType() (o0 gcs.BucketType) { return } -func (m *mockBucket) NewReader(p0 context.Context, p1 *gcs.ReadObjectRequest) (o0 io.ReadCloser, o1 error) { - // Get a file name and line number for the caller. - _, file, line, _ := runtime.Caller(1) - - // Hand the call off to the controller, which does most of the work. - retVals := m.controller.HandleMethodCall( - m, - "NewReader", - file, - line, - []interface{}{p0, p1}) - - if len(retVals) != 2 { - panic(fmt.Sprintf("mockBucket.NewReader: invalid return values: %v", retVals)) - } - - // o0 io.ReadCloser - if retVals[0] != nil { - o0 = retVals[0].(io.ReadCloser) - } - - // o1 error - if retVals[1] != nil { - o1 = retVals[1].(error) - } - - return -} - func (m *mockBucket) NewReaderWithReadHandle(p0 context.Context, p1 *gcs.ReadObjectRequest) (o0 gcs.StorageReader, o1 error) { // Get a file name and line number for the caller. _, file, line, _ := runtime.Caller(1) diff --git a/internal/storage/storageutil/read_object.go b/internal/storage/storageutil/read_object.go index 7827df16e7..8b1b8f5fc7 100644 --- a/internal/storage/storageutil/read_object.go +++ b/internal/storage/storageutil/read_object.go @@ -33,7 +33,7 @@ func ReadObject( Name: name, } - rc, err := bucket.NewReader(ctx, req) + rc, err := bucket.NewReaderWithReadHandle(ctx, req) if err != nil { return } diff --git a/internal/storage/testify_mock_bucket.go b/internal/storage/testify_mock_bucket.go index 995515a667..b1e9757136 100644 --- a/internal/storage/testify_mock_bucket.go +++ b/internal/storage/testify_mock_bucket.go @@ -16,7 +16,6 @@ package storage import ( "context" - "io" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/stretchr/testify/mock" @@ -37,11 +36,6 @@ func (m *TestifyMockBucket) BucketType() gcs.BucketType { return args.Get(0).(gcs.BucketType) } -func (m *TestifyMockBucket) NewReader(ctx context.Context, req *gcs.ReadObjectRequest) (io.ReadCloser, error) { - args := m.Called(ctx, req) - return args.Get(0).(io.ReadCloser), args.Error(1) -} - func (m *TestifyMockBucket) NewReaderWithReadHandle(ctx context.Context, req *gcs.ReadObjectRequest) (gcs.StorageReader, error) { args := m.Called(ctx, req) if args.Get(0) == nil { From 589114f5e33e1361e55de8a926e44d9f05fcbf0c Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 4 Mar 2025 15:43:54 +0530 Subject: [PATCH 0275/1298] [testing-on-gke] Add support for setting numEpoch for each workload (#2717) This is for gke-testing scripts available in perfmetrics/scripts/testing_on_gke/examples/ . With this change, you can add a "numEpochs" attribute in each workload element in the workload configuration JSON file. --- .../examples/dlio/dlio_workload.py | 23 ++++++++ .../examples/dlio/dlio_workload_test.py | 39 +++++++++++++ .../examples/dlio/parse_logs.py | 8 +-- .../testing_on_gke/examples/dlio/run_tests.py | 56 +++++++++--------- .../templates/dlio-tester.yaml | 2 +- .../dlio/unet3d-loading-test/values.yaml | 1 + .../examples/fio/fio_workload.py | 25 +++++++- .../examples/fio/fio_workload_test.py | 39 +++++++++++++ .../loading-test/templates/fio-tester.yaml | 2 +- .../examples/fio/loading-test/values.yaml | 1 + .../testing_on_gke/examples/fio/parse_logs.py | 10 ++-- .../testing_on_gke/examples/fio/run_tests.py | 58 ++++++++++--------- .../testing_on_gke/examples/workloads.json | 2 + .../examples/workloads_test.json | 2 + 14 files changed, 202 insertions(+), 66 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py index 43805fd766..e5cce6af48 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py @@ -21,6 +21,9 @@ import json +DefaultNumEpochs = 4 + + def validateDlioWorkload(workload: dict, name: str): """Validates the given json workload object.""" for requiredWorkloadAttribute, expectedType in { @@ -41,6 +44,17 @@ def validateDlioWorkload(workload: dict, name: str): print(f"{name} has space in the value of '{requiredWorkloadAttribute}'") return False + if 'numEpochs' in workload: + if not type(workload['numEpochs']) is int: + print( + f"In {name}, the type of workload['numEpochs'] is of type" + f" {type(workload['numEpochs'])}, not {int}" + ) + return False + if int(workload['numEpochs']) < 0: + print(f"In {name}, the value of workload['numEpochs'] < 0, expected: >=0") + return False + if 'fioWorkload' in workload: print(f"{name} has 'fioWorkload' key in it, which is unexpected.") return False @@ -106,6 +120,8 @@ class DlioWorkload: "[:[:[...]]]:". For example, a legal value would be: "implicit-dirs,file_mode=777,file-cache:enable-parallel-downloads:true,metadata-cache:ttl-secs:true". + 7. numEpochs: Number of runs of the DLIO workload. Default is DefaultNumEpochs + if missing. """ def __init__( @@ -116,6 +132,7 @@ def __init__( bucket: str, batchSizes: list, gcsfuseMountOptions: str, + numEpochs: int = DefaultNumEpochs, ): self.scenario = scenario self.numFilesTrain = numFilesTrain @@ -123,6 +140,7 @@ def __init__( self.bucket = bucket self.batchSizes = set(batchSizes) self.gcsfuseMountOptions = gcsfuseMountOptions + self.numEpochs = numEpochs def ParseTestConfigForDlioWorkloads(testConfigFileName: str): @@ -154,6 +172,11 @@ def ParseTestConfigForDlioWorkloads(testConfigFileName: str): workload['bucket'], dlioWorkload['batchSizes'], workload['gcsfuseMountOptions'], + numEpochs=( + workload['numEpochs'] + if 'numEpochs' in workload + else DefaultNumEpochs + ), ) ) return dlioWorkloads diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload_test.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload_test.py index db80d994c2..47a60f8053 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload_test.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload_test.py @@ -184,6 +184,45 @@ def test_validate_dlio_workload_invalid_gcsfuseMountOptions_contains_space( ) ) + def test_validate_dlio_workload_invalid_unsupported_numEpochs( + self, + ): + workload = dict({ + "dlioWorkload": { + "numFilesTrain": 1000, + "recordLength": 10000, + "batchSizes": [100, 200], + }, + "bucket": "dummy-bucket", + "gcsfuseMountOptions": "implicit-dirs", + "numEpochs": False, + }) + self.assertFalse( + validateDlioWorkload( + workload, "invalid-dlio-workload-unsupported-numEpochs" + ) + ) + + def test_validate_dlio_workload_invalid_numEpochs_toolow( + self, + ): + workload = dict({ + "dlioWorkload": { + "numFilesTrain": 1000, + "recordLength": 10000, + "batchSizes": [100, 200], + }, + "bucket": "dummy-bucket", + "gcsfuseMountOptions": "implicit-dirs", + "numEpochs": -1, + }) + self.assertFalse( + validateDlioWorkload( + workload, + "invalid-dlio-workload-unsupported-numEpochs-too-low", + ) + ) + def test_validate_dlio_workload_invalid_missing_batchSizes(self): workload = dict({ "dlioWorkload": { diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py index 2f8d580016..7fea8e0086 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py @@ -95,10 +95,10 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: "num_files_train": str "batch_size": str "records": - "local-ssd": [record1, record2, record3, record4] - "gcsfuse-generic": [record1, record2, record3, record4] - "gcsfuse-file-cache": [record1, record2, record3, record4] - "gcsfuse-no-file-cache": [record1, record2, record3, record4] + "local-ssd": [record1, record2, record3, ...] + "gcsfuse-generic": [record1, record2, record3, ...] + "gcsfuse-file-cache": [record1, record2, record3, ...] + "gcsfuse-no-file-cache": [record1, record2, record3, ...] """ output = {} diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py index eff9cfa263..4f940730d5 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py @@ -55,33 +55,37 @@ def createHelmInstallCommands( resourceRequests = resourceLimits for dlioWorkload in dlioWorkloads: - for batchSize in dlioWorkload.batchSizes: - chartName, podName, outputDirPrefix = dlio_workload.DlioChartNamePodName( - dlioWorkload, instanceId, batchSize - ) - commands = [ - f'helm install {chartName} unet3d-loading-test', - f'--set bucketName={dlioWorkload.bucket}', - f'--set scenario={dlioWorkload.scenario}', - f'--set dlio.numFilesTrain={dlioWorkload.numFilesTrain}', - f'--set dlio.recordLength={dlioWorkload.recordLength}', - f'--set dlio.batchSize={batchSize}', - f'--set instanceId={instanceId}', - ( - '--set' - f' gcsfuse.mountOptions={escape_commas_in_string(dlioWorkload.gcsfuseMountOptions)}' - ), - f'--set nodeType={machineType}', - f'--set podName={podName}', - f'--set outputDirPrefix={outputDirPrefix}', - f"--set resourceLimits.cpu={resourceLimits['cpu']}", - f"--set resourceLimits.memory={resourceLimits['memory']}", - f"--set resourceRequests.cpu={resourceRequests['cpu']}", - f"--set resourceRequests.memory={resourceRequests['memory']}", - ] + if dlioWorkload.numEpochs > 0: + for batchSize in dlioWorkload.batchSizes: + chartName, podName, outputDirPrefix = ( + dlio_workload.DlioChartNamePodName( + dlioWorkload, instanceId, batchSize + ) + ) + commands = [ + f'helm install {chartName} unet3d-loading-test', + f'--set bucketName={dlioWorkload.bucket}', + f'--set scenario={dlioWorkload.scenario}', + f'--set dlio.numFilesTrain={dlioWorkload.numFilesTrain}', + f'--set dlio.recordLength={dlioWorkload.recordLength}', + f'--set dlio.batchSize={batchSize}', + f'--set instanceId={instanceId}', + ( + '--set' + f' gcsfuse.mountOptions={escape_commas_in_string(dlioWorkload.gcsfuseMountOptions)}' + ), + f'--set nodeType={machineType}', + f'--set podName={podName}', + f'--set outputDirPrefix={outputDirPrefix}', + f"--set resourceLimits.cpu={resourceLimits['cpu']}", + f"--set resourceLimits.memory={resourceLimits['memory']}", + f"--set resourceRequests.cpu={resourceRequests['cpu']}", + f"--set resourceRequests.memory={resourceRequests['memory']}", + f'--set numEpochs={dlioWorkload.numEpochs}', + ] - helm_command = ' '.join(commands) - helm_commands.append(helm_command) + helm_command = ' '.join(commands) + helm_commands.append(helm_command) return helm_commands diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml index 8a8258eded..32dbb606e7 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml @@ -88,7 +88,7 @@ spec: echo "Testing {{ .Values.scenario }}" mpirun -np 8 dlio_benchmark workload=unet3d_a100 \ ++workload.workflow.generate_data=True \ - ++workload.train.epochs=4 \ + ++workload.train.epochs={{ .Values.numEpochs }} \ ++workload.workflow.profiling=True \ ++workload.profiling.profiler=iostat \ ++workload.profiling.iostat_devices=[md0] \ diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml index 359185fcb9..69589d8da6 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml @@ -25,6 +25,7 @@ nodeType: n2-standard-96 instanceId: ldap-yyyymmdd-hhmmss podName: outputDirPrefix: +numEpochs: 4 resourceLimits: cpu: 0 diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py index 70e5721947..3cae5b61b3 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py @@ -21,6 +21,9 @@ import json +DefaultNumEpochs = 4 + + def validateFioWorkload(workload: dict, name: str): """Validates the given json workload object.""" for requiredWorkloadAttribute, expectedType in { @@ -41,6 +44,17 @@ def validateFioWorkload(workload: dict, name: str): print(f"{name} has space in the value of '{requiredWorkloadAttribute}'") return False + if 'numEpochs' in workload: + if not type(workload['numEpochs']) is int: + print( + f"In {name}, the type of workload['numEpochs'] is of type" + f" {type(workload['numEpochs'])}, not {int}" + ) + return False + if int(workload['numEpochs']) < 0: + print(f"In {name}, the value of workload['numEpochs'] < 0, expected: >=0") + return False + if 'dlioWorkload' in workload: print(f"{name} has 'dlioWorkload' key in it, which is unexpected.") return False @@ -117,6 +131,8 @@ class FioWorkload: "[:[:[...]]]:". For example, a legal value would be: "implicit-dirs,file_mode=777,file-cache:enable-parallel-downloads:true,metadata-cache:ttl-secs:true". + 9. numEpochs: Number of runs of the fio workload. Default is DefaultNumEpochs + if missing. """ def __init__( @@ -129,6 +145,7 @@ def __init__( bucket: str, readTypes: list, gcsfuseMountOptions: str, + numEpochs: int = DefaultNumEpochs, ): self.scenario = scenario self.fileSize = fileSize @@ -138,6 +155,7 @@ def __init__( self.bucket = bucket self.readTypes = set(readTypes) self.gcsfuseMountOptions = gcsfuseMountOptions + self.numEpochs = numEpochs def PPrint(self): print( @@ -145,7 +163,7 @@ def PPrint(self): f' blockSize:{self.blockSize}, filesPerThread:{self.filesPerThread},' f' numThreads:{self.numThreads}, bucket:{self.bucket},' f' readTypes:{self.readTypes}, gcsfuseMountOptions:' - f' {gcsfuseMountOptions}' + f' {gcsfuseMountOptions}, numEpochs: {self.numEpochs}' ) @@ -184,6 +202,11 @@ def ParseTestConfigForFioWorkloads(fioTestConfigFile: str): else ['read', 'randread'] ), workload['gcsfuseMountOptions'], + numEpochs=( + workload['numEpochs'] + if 'numEpochs' in workload + else DefaultNumEpochs + ), ) ) return fioWorkloads diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload_test.py b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload_test.py index 8e85e93b14..ad9b45fe61 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload_test.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload_test.py @@ -249,6 +249,45 @@ def test_validate_fio_workload_invalid_gcsfuseMountOptions_contains_space( ) ) + def test_validate_fio_workload_invalid_unsupported_numEpochs(self): + workload = dict({ + "fioWorkload": { + "fileSize": "1kb", + "filesPerThread": 2, + "blockSize": "1kb", + "numThreads": "1k", + }, + "bucket": "dummy-bucket", + "gcsfuseMountOptions": "implicit-dirs", + "numEpochs": False, + }) + self.assertFalse( + validateFioWorkload( + workload, "invalid-fio-workload-unsupported-numEpochs" + ) + ) + + def test_validate_fio_workload_invalid_numEpochsTooLow( + self, + ): + workload = dict({ + "fioWorkload": { + "fileSize": "1kb", + "filesPerThread": 2, + "blockSize": "1kb", + "numThreads": "1k", + }, + "bucket": "dummy-bucket", + "gcsfuseMountOptions": "implicit-dirs", + "numEpochs": -1, + }) + self.assertFalse( + validateFioWorkload( + workload, + "invalid-fio-workload-unsupported-numEpochs-too-low", + ) + ) + def test_validate_fio_workload_invalid_unsupported_readTypes_1(self): workload = dict({ "fioWorkload": { diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml index f536788be4..7009abb5a4 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml @@ -118,7 +118,7 @@ spec: {{ end }} echo "Setup default values..." - epoch=4 + epoch={{ .Values.numEpochs }} read_type={{ .Values.fio.readType }} pause_in_seconds=20 workload_dir=/data diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml index 6723168b88..e06c75581f 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml @@ -25,6 +25,7 @@ nodeType: n2-standard-96 instanceId: ldap-yyyymmdd-hhmmss podName: outputDirPrefix: +numEpochs: 4 resourceLimits: cpu: 0 diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py index 7d5b5f2074..80adb12c36 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py @@ -91,7 +91,7 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: from the downloaded fio output files, which are in the following format. <_LOCAL_LOGS_LOCATION>///----///epoch[N].json - where N=1-4 + where N=1-#epochs <_LOCAL_LOGS_LOCATION>///----///pod_name <_LOCAL_LOGS_LOCATION>///----/gcsfuse-generic//gcsfuse_mount_options @@ -100,10 +100,10 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: "mean_file_size": str "read_type": str "records": - "local-ssd": [record1, record2, record3, record4] - "gcsfuse-generic": [record1, record2, record3, record4] - "gcsfuse-file-cache": [record1, record2, record3, record4] - "gcsfuse-no-file-cache": [record1, record2, record3, record4] + "local-ssd": [record1, record2, record3, ...] + "gcsfuse-generic": [record1, record2, record3, ...] + "gcsfuse-file-cache": [record1, record2, record3, ...] + "gcsfuse-no-file-cache": [record1, record2, record3, ...] """ output = {} diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py b/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py index e6eceaa999..4fbf6b22c3 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py @@ -54,35 +54,37 @@ def createHelmInstallCommands( resourceRequests = resourceLimits for fioWorkload in fioWorkloads: - for readType in fioWorkload.readTypes: - chartName, podName, outputDirPrefix = fio_workload.FioChartNamePodName( - fioWorkload, instanceId, readType - ) - commands = [ - f'helm install {chartName} loading-test', - f'--set bucketName={fioWorkload.bucket}', - f'--set scenario={fioWorkload.scenario}', - f'--set fio.readType={readType}', - f'--set fio.fileSize={fioWorkload.fileSize}', - f'--set fio.blockSize={fioWorkload.blockSize}', - f'--set fio.filesPerThread={fioWorkload.filesPerThread}', - f'--set fio.numThreads={fioWorkload.numThreads}', - f'--set instanceId={instanceId}', - ( - '--set' - f' gcsfuse.mountOptions={escape_commas_in_string(fioWorkload.gcsfuseMountOptions)}' - ), - f'--set nodeType={machineType}', - f'--set podName={podName}', - f'--set outputDirPrefix={outputDirPrefix}', - f"--set resourceLimits.cpu={resourceLimits['cpu']}", - f"--set resourceLimits.memory={resourceLimits['memory']}", - f"--set resourceRequests.cpu={resourceRequests['cpu']}", - f"--set resourceRequests.memory={resourceRequests['memory']}", - ] + if fioWorkload.numEpochs > 0: + for readType in fioWorkload.readTypes: + chartName, podName, outputDirPrefix = fio_workload.FioChartNamePodName( + fioWorkload, instanceId, readType + ) + commands = [ + f'helm install {chartName} loading-test', + f'--set bucketName={fioWorkload.bucket}', + f'--set scenario={fioWorkload.scenario}', + f'--set fio.readType={readType}', + f'--set fio.fileSize={fioWorkload.fileSize}', + f'--set fio.blockSize={fioWorkload.blockSize}', + f'--set fio.filesPerThread={fioWorkload.filesPerThread}', + f'--set fio.numThreads={fioWorkload.numThreads}', + f'--set instanceId={instanceId}', + ( + '--set' + f' gcsfuse.mountOptions={escape_commas_in_string(fioWorkload.gcsfuseMountOptions)}' + ), + f'--set nodeType={machineType}', + f'--set podName={podName}', + f'--set outputDirPrefix={outputDirPrefix}', + f"--set resourceLimits.cpu={resourceLimits['cpu']}", + f"--set resourceLimits.memory={resourceLimits['memory']}", + f"--set resourceRequests.cpu={resourceRequests['cpu']}", + f"--set resourceRequests.memory={resourceRequests['memory']}", + f'--set numEpochs={fioWorkload.numEpochs}', + ] - helm_command = ' '.join(commands) - helm_commands.append(helm_command) + helm_command = ' '.join(commands) + helm_commands.append(helm_command) return helm_commands diff --git a/perfmetrics/scripts/testing_on_gke/examples/workloads.json b/perfmetrics/scripts/testing_on_gke/examples/workloads.json index ac1039d257..19e4f5df7e 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/workloads.json +++ b/perfmetrics/scripts/testing_on_gke/examples/workloads.json @@ -16,6 +16,7 @@ "readTypes": ["read","randread"] }, "gcsfuseMountOptions": "GCSFuse mount-options, in a compact stringified format, to be used for the test scenario gcsfuse-generic. The individual config/cli flag values should be separated by comma. Each cli flag should be of the form [=], while each config-file flag should be of form [:[:[...]]]:. For example, a legal value would be: implicit-dirs,file_mode=777,file-cache:enable-parallel-downloads:true,metadata-cache:ttl-secs:-1 .", + "numEpochs": "Optional integer value > 0, default = 4.", "bucket":"The bucket must have objects with name Workload.{i}/{j} for every i,j where i:0-{numThreads}-1, j:0-{filesPerThread}-1, and each of these objects must be of size {fileSize}. The buckets gke-* are all in us-central1, are owned by GKE team and are in their GCP project(s)." }, { @@ -127,6 +128,7 @@ "batchSizes": [800,128] }, "gcsfuseMountOptions": "implicit-dirs,metadata-cache:ttl-secs:-1,metadata-cache:type-cache-max-size-mb:-1,metadata-cache:stat-cache-max-size-mb:-1,file-cache:max-size-mb:-1,file-cache:cache-file-for-range-read:true", + "numEpochs": "Optional integer value > 0, default = 4.", "bucket":"The bucket must have objects with name 'train/', 'valid/', and train/img_{i}_of_{numFilesTrain}.npz for every i where i:0-{numFilesTrain}-1 and each train/img_{i}_of_{numFilesTrain}.npz must be of size {recordLength} bytes. The buckets gke-* are all in us-central1, are owned by GKE team and are in their GCP project(s)." }, { diff --git a/perfmetrics/scripts/testing_on_gke/examples/workloads_test.json b/perfmetrics/scripts/testing_on_gke/examples/workloads_test.json index 280bd073c2..28ed0ce4b4 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/workloads_test.json +++ b/perfmetrics/scripts/testing_on_gke/examples/workloads_test.json @@ -16,6 +16,7 @@ "readTypes": ["read","randread"] }, "gcsfuseMountOptions": "GCSFuse mount-options, in a compact stringified format, to be used for the test scenario gcsfuse-generic. The individual config/cli flag values should be separated by comma. Each cli flag should be of the form [=], while each config-file flag should be of form [:[:[...]]]:. For example, a legal value would be: implicit-dirs,file_mode=777,file-cache:enable-parallel-downloads:true,metadata-cache:ttl-secs:-1 .", + "numEpochs": "Optional integer value > 0, default = 4.", "bucket":"The bucket must have objects with name Workload.{i}/{j} for every i,j where i:0-{numThreads}-1, j:0-{filesPerThread}-1, and each of these objects must be of size {fileSize}. The buckets gke-* are all in us-central1, are owned by GKE team and are in their GCP project(s). For best performance, please ensure that the bucket is in the same google-cloud region and GCP project as that of the GKE cluster used for running this test configuration." }, { @@ -40,6 +41,7 @@ "batchSizes": [800,128] }, "gcsfuseMountOptions": "implicit-dirs,metadata-cache:ttl-secs:-1,metadata-cache:type-cache-max-size-mb:-1,metadata-cache:stat-cache-max-size-mb:-1,file-cache:max-size-mb:-1,file-cache:cache-file-for-range-read:true", + "numEpochs": "Optional integer value > 0, default = 4.", "bucket":"The bucket must have objects with name 'train/', 'valid/', and train/img_{i}_of_{numFilesTrain}.npz for every i where i:0-{numFilesTrain}-1 and each train/img_{i}_of_{numFilesTrain}.npz must be of size {recordLength} bytes. The buckets gke-* are all in us-central1, are owned by GKE team and are in their GCP project(s). For best performance, please ensure that the bucket is in the same google-cloud region and GCP project as that of the GKE cluster used for running this test configuration." }, { From 7b6cad140d83161db8ec1293435f8c629531f9d9 Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Tue, 4 Mar 2025 14:46:28 +0000 Subject: [PATCH 0276/1298] Adding UTs for negative cases in MRD (#3010) - Adding UTs for negative cases - Updated handling of EOF in file_handle - Updated FakeMRD code to return bytes read as the 2nd parameter in the callback. Next release of go-sdk will have the changes from their side in MRD as well --- internal/fs/handle/file.go | 4 +- .../gcsx/multi_range_downloader_wrapper.go | 4 +- .../multi_range_downloader_wrapper_test.go | 57 +++++++++++++++++++ internal/gcsx/random_reader_stretchr_test.go | 46 +++++++++------ .../fake/fake_multi_range_downloader.go | 30 +++++++--- 5 files changed, 113 insertions(+), 28 deletions(-) diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 027a1d117f..34e6d15b0d 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -15,6 +15,7 @@ package handle import ( + "errors" "fmt" "io" @@ -124,7 +125,8 @@ func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequen var objectData gcsx.ObjectData objectData, err = fh.reader.ReadAt(ctx, dst, offset) switch { - case err == io.EOF: + case errors.Is(err, io.EOF): + err = io.EOF return case err != nil: diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go index 632152dfd5..5f23fc5048 100644 --- a/internal/gcsx/multi_range_downloader_wrapper.go +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -213,11 +213,11 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []b requestId := uuid.New() logger.Tracef("%.13v <- MultiRangeDownloader::Add (%s, [%d, %d))", requestId, mrdWrapper.object.Name, startOffset, endOffset) start := time.Now() - mrdWrapper.Wrapped.Add(buffer, startOffset, endOffset-startOffset, func(offsetAddCallback int64, limit int64, e error) { + mrdWrapper.Wrapped.Add(buffer, startOffset, endOffset-startOffset, func(offsetAddCallback int64, bytesReadAddCallback int64, e error) { defer func() { mu.Lock() if done != nil { - done <- readResult{bytesRead: int(limit), err: e} + done <- readResult{bytesRead: int(bytesReadAddCallback), err: e} } mu.Unlock() }() diff --git a/internal/gcsx/multi_range_downloader_wrapper_test.go b/internal/gcsx/multi_range_downloader_wrapper_test.go index 10d021efec..bcf4555add 100644 --- a/internal/gcsx/multi_range_downloader_wrapper_test.go +++ b/internal/gcsx/multi_range_downloader_wrapper_test.go @@ -17,6 +17,7 @@ package gcsx import ( "context" "fmt" + "io" "sync" "testing" "time" @@ -143,6 +144,11 @@ func (t *mrdWrapperTest) Test_Read() { start: 10, end: 10 + int(t.object.Size)/2, }, + { + name: "ReadEmpty", + start: 10, + end: 10, + }, } for _, tc := range testCases { @@ -160,6 +166,57 @@ func (t *mrdWrapperTest) Test_Read() { } } +func (t *mrdWrapperTest) Test_Read_ErrorInCreatingMRD() { + t.mrdWrapper.Wrapped = nil + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("Error in creating MRD")).Once() + + bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, common.NewNoopMetrics()) + + assert.ErrorContains(t.T(), err, "MultiRangeDownloaderWrapper::Read: Error in creating MultiRangeDownloader") + assert.Equal(t.T(), 0, bytesRead) +} + +func (t *mrdWrapperTest) Test_Read_Timeout() { + t.mrdWrapper.Wrapped = nil + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, t.mrdTimeout+2*time.Millisecond), nil).Once() + + bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, common.NewNoopMetrics()) + + assert.ErrorContains(t.T(), err, "Timeout") + assert.Equal(t.T(), 0, bytesRead) +} + +func (t *mrdWrapperTest) Test_Read_ContextCancelled() { + t.mrdWrapper.Wrapped = nil + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond), nil).Once() + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + bytesRead, err := t.mrdWrapper.Read(ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, common.NewNoopMetrics()) + + assert.ErrorContains(t.T(), err, "Context Cancelled") + assert.Equal(t.T(), 0, bytesRead) +} + +func (t *mrdWrapperTest) Test_Read_EOF() { + t.mrdWrapper.Wrapped = nil + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleepAndDefaultError(t.object, t.objectData, time.Microsecond, io.EOF), nil).Once() + + _, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, common.NewNoopMetrics()) + + assert.ErrorIs(t.T(), err, io.EOF) +} + +func (t *mrdWrapperTest) Test_Read_Error() { + t.mrdWrapper.Wrapped = nil + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleepAndDefaultError(t.object, t.objectData, time.Microsecond, fmt.Errorf("Error")), nil).Once() + + bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, common.NewNoopMetrics()) + + assert.ErrorContains(t.T(), err, "Error in Add Call") + assert.Equal(t.T(), 0, bytesRead) +} + func (t *mrdWrapperTest) Test_NewMultiRangeDownloaderWrapper() { testCases := []struct { name string diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 98bedb7096..2c1a86269c 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -805,26 +805,40 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadChunk() { } } +func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_NilMRDWrapper() { + t.rr.wrapped.mrdWrapper = nil + + bytesRead, err := t.rr.wrapped.readFromMultiRangeReader(t.rr.ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), TestTimeoutForMultiRangeRead) + + assert.ErrorContains(t.T(), err, "readFromMultiRangeReader: Invalid MultiRangeDownloaderWrapper") + assert.Equal(t.T(), 0, bytesRead) +} + func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ValidateTimeout() { testCases := []struct { - name string - dataSize int - sleepTime time.Duration + name string + dataSize int + timeout time.Duration + sleepTime time.Duration + expectedErrKeyword string }{ { - name: "TimeoutPlusOneMilliSecond", - dataSize: 100, - sleepTime: TestTimeoutForMultiRangeRead + time.Millisecond, + name: "TimeoutPlusFiveMilliSecond", + dataSize: 100, + timeout: 5 * time.Millisecond, + sleepTime: 10 * time.Millisecond, + expectedErrKeyword: "Timeout", }, - // Ensure that this is always the last test { - name: "TimeoutValue", - dataSize: 100, - sleepTime: TestTimeoutForMultiRangeRead, + name: "TimeoutValue", + dataSize: 100, + timeout: 5 * time.Millisecond, + sleepTime: 5 * time.Millisecond, + expectedErrKeyword: "Timeout", }, } - for i, tc := range testCases { + for _, tc := range testCases { t.Run(tc.name, func() { t.rr.wrapped.reader = nil t.rr.wrapped.isMRDInUse = false @@ -833,19 +847,19 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ValidateTimeout fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper - t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, tc.sleepTime)).Times(1) - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, tc.sleepTime)).Once() + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Once() buf := make([]byte, tc.dataSize) - bytesRead, err := t.rr.wrapped.readFromMultiRangeReader(t.rr.ctx, buf, 0, int64(t.object.Size), TestTimeoutForMultiRangeRead) + bytesRead, err := t.rr.wrapped.readFromMultiRangeReader(t.rr.ctx, buf, 0, int64(t.object.Size), tc.timeout) - if i == len(testCases)-1 && bytesRead != 0 { + if tc.name == "TimeoutValue" && bytesRead != 0 { assert.NoError(t.T(), err) assert.Equal(t.T(), tc.dataSize, bytesRead) assert.Equal(t.T(), testContent[:tc.dataSize], buf[:bytesRead]) return } - assert.ErrorContains(t.T(), err, "Timeout") + assert.ErrorContains(t.T(), err, tc.expectedErrKeyword) }) } } diff --git a/internal/storage/fake/fake_multi_range_downloader.go b/internal/storage/fake/fake_multi_range_downloader.go index ec08c2210f..7deb177da0 100644 --- a/internal/storage/fake/fake_multi_range_downloader.go +++ b/internal/storage/fake/fake_multi_range_downloader.go @@ -27,10 +27,11 @@ import ( // This struct is an implementation of the gcs.MultiRangeDownloader interface. type fakeMultiRangeDownloader struct { gcs.MultiRangeDownloader - obj *fakeObject - wg sync.WaitGroup - err error - sleepTime time.Duration // Sleep time to simulate real-world. + obj *fakeObject + wg sync.WaitGroup + err error + defaultErr error + sleepTime time.Duration // Sleep time to simulate real-world. } func createFakeObject(obj *gcs.MinObject, data []byte) fakeObject { @@ -42,18 +43,29 @@ func createFakeObject(obj *gcs.MinObject, data []byte) fakeObject { } func NewFakeMultiRangeDownloader(obj *gcs.MinObject, data []byte) gcs.MultiRangeDownloader { - return NewFakeMultiRangeDownloaderWithSleep(obj, data, time.Millisecond) + return NewFakeMultiRangeDownloaderWithSleepAndDefaultError(obj, data, time.Millisecond, nil) } func NewFakeMultiRangeDownloaderWithSleep(obj *gcs.MinObject, data []byte, sleepTime time.Duration) gcs.MultiRangeDownloader { + return NewFakeMultiRangeDownloaderWithSleepAndDefaultError(obj, data, sleepTime, nil) +} + +func NewFakeMultiRangeDownloaderWithSleepAndDefaultError(obj *gcs.MinObject, data []byte, sleepTime time.Duration, err error) gcs.MultiRangeDownloader { fakeObj := createFakeObject(obj, data) return &fakeMultiRangeDownloader{ - obj: &fakeObj, - sleepTime: sleepTime, + obj: &fakeObj, + sleepTime: sleepTime, + defaultErr: err, } } func (fmrd *fakeMultiRangeDownloader) Add(output io.Writer, offset, length int64, callback func(int64, int64, error)) { + if fmrd.defaultErr != nil { + if callback != nil { + callback(offset, 0, fmrd.defaultErr) + } + return + } obj := fmrd.obj size := int64(len(obj.data)) var err error @@ -75,7 +87,7 @@ func (fmrd *fakeMultiRangeDownloader) Add(output io.Writer, offset, length int64 // If inputs aren't correct, fail immediately and return callback. fmrd.err = err if callback != nil { - callback(offset, length, err) + callback(offset, 0, err) } return } @@ -94,7 +106,7 @@ func (fmrd *fakeMultiRangeDownloader) Add(output io.Writer, offset, length int64 err = fmt.Errorf("failed to write %v bytes to writer through multi-range-downloader, bytes written = %v, error = %v", length, n, err) } if callback != nil { - callback(offset, length, err) + callback(offset, int64(n), err) } // Don't clear pre-existing error in downloader. if fmrd.err != nil { From c5088b47a32689e79167d414c529990152038996 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 4 Mar 2025 20:50:24 +0530 Subject: [PATCH 0277/1298] add JAX checkpoint tests to presubmit and periodic tests (#3052) * add checkpoint scripts * run tests in parallel * lint fix * review comment * install latest gcloud --- .../gcp_ubuntu/e2e_tests/checkpoint-tests.cfg | 22 ++++ .../checkpoint/Jax/emulated_checkpoints.py | 70 +++++++++++ .../ml_tests/checkpoint/Jax/requirements.txt | 43 +++++++ .../checkpoint/Jax/run_checkpoints.sh | 111 ++++++++++++++++++ .../presubmit_test/pr_perf_test/build.sh | 18 ++- .../presubmit_test/pr_perf_test/presubmit.cfg | 1 + 6 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/checkpoint-tests.cfg create mode 100755 perfmetrics/scripts/ml_tests/checkpoint/Jax/emulated_checkpoints.py create mode 100644 perfmetrics/scripts/ml_tests/checkpoint/Jax/requirements.txt create mode 100755 perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/checkpoint-tests.cfg b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/checkpoint-tests.cfg new file mode 100644 index 0000000000..2078b25121 --- /dev/null +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/checkpoint-tests.cfg @@ -0,0 +1,22 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +action { + define_artifacts { + regex: "gcsfuse_logs/*" + strip_prefix: "github/gcsfuse/perfmetrics/scripts" + } +} + +build_file: "gcsfuse/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh" diff --git a/perfmetrics/scripts/ml_tests/checkpoint/Jax/emulated_checkpoints.py b/perfmetrics/scripts/ml_tests/checkpoint/Jax/emulated_checkpoints.py new file mode 100755 index 0000000000..bb25aa0771 --- /dev/null +++ b/perfmetrics/scripts/ml_tests/checkpoint/Jax/emulated_checkpoints.py @@ -0,0 +1,70 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import jax +import jax.numpy as jnp +import optax +from flax import linen as nn +from flax.training import train_state +from flax.training import checkpoints +import argparse + +# Mock model definition. +class SimpleModel(nn.Module): + @nn.compact + def __call__(self, x): + features = [16384, 8192, 4096, 2048, 1024, 512, 256, 128, 1] + for feature in features: + x = nn.Dense(features=feature)(x) + x = nn.relu(x) + return x + +# Mock training step. +def train_step(state, batch): + def loss_fn(params): + preds = state.apply_fn(params, batch['x']) + loss = jnp.mean(jnp.square(preds - batch['y'])) + return loss + + grad_fn = jax.value_and_grad(loss_fn) + loss, grads = grad_fn(state.params) + state = state.apply_gradients(grads=grads) + return state, loss + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Train a simple model and save checkpoints.') + parser.add_argument('--checkpoint_dir', type=str, required=True, help='Directory to save checkpoints') + parser.add_argument('--num_train_steps', type=int, default=2000, help='Number of training steps') # Added argument for num_train_steps + args = parser.parse_args() + + # Sample data. + key = jax.random.PRNGKey(0) + x = jax.random.normal(key, (10, 5)) + y = jax.random.normal(key, (10, 1)) + + # Initialize model and optimizer. + model = SimpleModel() + params = model.init(key, x) + optimizer = optax.adam(learning_rate=0.01) + + # Create train state. + state = train_state.TrainState.create(apply_fn=model.apply, params=params, tx=optimizer) + + # Mock training step. + state, loss = train_step(state, {'x': x, 'y': y}) + + # Save checkpoint to local directory + for step in range(args.num_train_steps): + if step % 200 == 0: + checkpoints.save_checkpoint(args.checkpoint_dir, state, step, keep=100, prefix='checkpoint_') diff --git a/perfmetrics/scripts/ml_tests/checkpoint/Jax/requirements.txt b/perfmetrics/scripts/ml_tests/checkpoint/Jax/requirements.txt new file mode 100644 index 0000000000..e2a415ad3e --- /dev/null +++ b/perfmetrics/scripts/ml_tests/checkpoint/Jax/requirements.txt @@ -0,0 +1,43 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +absl-py==2.1.0 +chex==0.1.89 +etils==1.12.0 +flax==0.10.4 +fsspec==2025.2.0 +humanize==4.12.1 +importlib_resources==6.5.2 +jax==0.5.1 +jaxlib==0.5.1 +markdown-it-py==3.0.0 +mdurl==0.1.2 +ml_dtypes==0.5.1 +msgpack==1.1.0 +nest-asyncio==1.6.0 +numpy==2.2.3 +opt_einsum==3.4.0 +optax==0.2.4 +orbax-checkpoint==0.11.6 +protobuf==5.29.3 +Pygments==2.19.1 +PyYAML==6.0.2 +rich==13.9.4 +scipy==1.15.2 +simplejson==3.20.1 +tensorstore==0.1.72 +toolz==1.0.0 +treescope==0.1.9 +typing_extensions==4.12.2 +zipp==3.21.0 diff --git a/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh b/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh new file mode 100755 index 0000000000..9b14c78f51 --- /dev/null +++ b/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh @@ -0,0 +1,111 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Fail on any error. +set -e +set -x +echo "Running JAX checkpoint tests" + +sudo apt-get update +# Install Git. +echo "Installing git" +sudo apt-get install git +# Install Golang. +#wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-amd64.tar.gz -q +architecture=$(dpkg --print-architecture) +wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-${architecture}.tar.gz -q +sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go_tar.tar.gz +export PATH=$PATH:/usr/local/go/bin +# Install latest gcloud version for compatability with HNS bucket. +function upgrade_gcloud_version() { + sudo apt-get update + gcloud version + wget -O gcloud.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz -q + sudo tar xzf gcloud.tar.gz && sudo cp -r google-cloud-sdk /usr/local && sudo rm -r google-cloud-sdk + sudo /usr/local/google-cloud-sdk/install.sh + export PATH=/usr/local/google-cloud-sdk/bin:$PATH + echo 'export PATH=/usr/local/google-cloud-sdk/bin:$PATH' >> ~/.bashrc + gcloud version && rm gcloud.tar.gz + sudo /usr/local/google-cloud-sdk/bin/gcloud components update + sudo /usr/local/google-cloud-sdk/bin/gcloud components install alpha +} +upgrade_gcloud_version + +# Clone and build the gcsfuse master branch. +git clone https://github.com/GoogleCloudPlatform/gcsfuse.git +cd gcsfuse +CGO_ENABLED=0 go build . +cd - + +function mount_gcsfuse_and_run_test() { + # Function to mount GCSFuse. + # Input: + # $1: Bucket name + + local BUCKET_NAME="$1" + # Clean up bucket before run (ignoring the failure if there are no objects to delete). + gcloud alpha storage rm -r gs://${BUCKET_NAME}/** || true + # Create a directory for gcsfuse logs. + mkdir -p "${KOKORO_ARTIFACTS_DIR}/gcsfuse_logs" + local MOUNT_POINT="${HOME}/gcs/${BUCKET_NAME}" + mkdir -p "${MOUNT_POINT}" + + COMMON_FLAGS=(--log-severity=TRACE --enable-streaming-writes --log-file="${KOKORO_ARTIFACTS_DIR}"/gcsfuse_logs/"${BUCKET_NAME}".log) + if [[ $BUCKET_NAME == "jax-emulated-checkpoint-flat" ]] + then + go run . "${COMMON_FLAGS[@]}" --rename-dir-limit=100 "${BUCKET_NAME}" "${MOUNT_POINT}" + else + go run . "${COMMON_FLAGS[@]}" "${BUCKET_NAME}" "${MOUNT_POINT}" + fi + python3.11 ./perfmetrics/scripts/ml_tests/checkpoint/Jax/emulated_checkpoints.py --checkpoint_dir "${MOUNT_POINT}" +} + +# Enable python virtual environment. +# By default KOKORO VM installs python 3.8 which causes dependency issues. +# Following commands are to explicitly set up python 3.11. +sudo apt update +sudo apt install -y software-properties-common +sudo add-apt-repository -y ppa:deadsnakes/ppa +sudo apt update +sudo apt install -y python3.11 python3.11-dev python3.11-venv +# Install pip +curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py +python3.11 get-pip.py +rm get-pip.py +python3.11 -m venv .venv +source .venv/bin/activate +# Install JAX dependencies. +pip install -r ./perfmetrics/scripts/ml_tests/checkpoint/Jax/requirements.txt + +# Run tests in parallel on flat and hns bucket. +FLAT_BUCKET_NAME="jax-emulated-checkpoint-flat" +HNS_BUCKET_NAME="jax-emulated-checkpoint-hns" +mount_gcsfuse_and_run_test "${FLAT_BUCKET_NAME}" & +flat_pid=$! +mount_gcsfuse_and_run_test "${HNS_BUCKET_NAME}" & +hns_pid=$! + +# Wait for both processes to finish and check exit codes +wait "$flat_pid" +flat_status=$? +wait "$hns_pid" +hns_status=$? + +if [[ "$flat_status" -ne 0 ]] || [[ "$hns_status" -ne 0 ]]; then + echo "Checkpoint tests failed" + exit 1 +else + echo "Checkpoint tests completed successfully" +fi diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index c77eb98a4c..8413491e3a 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -13,10 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Running test only for when PR contains execute-perf-test or execute-integration-tests label +# Running test only for when PR contains execute-perf-test, +# execute-integration-tests or execute-checkpoint-test label. readonly EXECUTE_PERF_TEST_LABEL="execute-perf-test" readonly EXECUTE_INTEGRATION_TEST_LABEL="execute-integration-tests" readonly EXECUTE_PACKAGE_BUILD_TEST_LABEL="execute-package-build-tests" +readonly EXECUTE_CHECKPOINT_TEST_LABEL="execute-checkpoint-test" readonly RUN_E2E_TESTS_ON_INSTALLED_PACKAGE=false readonly SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE=false readonly BUCKET_LOCATION=us-west4 @@ -32,11 +34,13 @@ curl https://api.github.com/repos/GoogleCloudPlatform/gcsfuse/pulls/$KOKORO_GITH perfTest=$(grep "$EXECUTE_PERF_TEST_LABEL" pr.json) integrationTests=$(grep "$EXECUTE_INTEGRATION_TEST_LABEL" pr.json) packageBuildTests=$(grep "$EXECUTE_PACKAGE_BUILD_TEST_LABEL" pr.json) +checkpointTests=$(grep "$EXECUTE_CHECKPOINT_TEST_LABEL" pr.json) rm pr.json perfTestStr="$perfTest" integrationTestsStr="$integrationTests" packageBuildTestsStr="$packageBuildTests" -if [[ "$perfTestStr" != *"$EXECUTE_PERF_TEST_LABEL"* && "$integrationTestsStr" != *"$EXECUTE_INTEGRATION_TEST_LABEL"* && "$packageBuildTestsStr" != *"$EXECUTE_PACKAGE_BUILD_TEST_LABEL"* ]] +checkpointTestStr="$checkpointTests" +if [[ "$perfTestStr" != *"$EXECUTE_PERF_TEST_LABEL"* && "$integrationTestsStr" != *"$EXECUTE_INTEGRATION_TEST_LABEL"* && "$packageBuildTestsStr" != *"$EXECUTE_PACKAGE_BUILD_TEST_LABEL"* && "$checkpointTestStr" != *"$EXECUTE_CHECKPOINT_TEST_LABEL"* ]] then echo "No need to execute tests" exit 0 @@ -128,3 +132,13 @@ then echo "Running package build tests...." ./perfmetrics/scripts/build_and_install_gcsfuse.sh master fi + +# Execute JAX checkpoints tests. +if [[ "$checkpointTestStr" == *"$EXECUTE_CHECKPOINT_TEST_LABEL"* ]]; +then + echo checkout PR branch + git checkout pr/$KOKORO_GITHUB_PULL_REQUEST_NUMBER + + echo "Running checkpoint tests...." + ./perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh +fi diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/presubmit.cfg b/perfmetrics/scripts/presubmit_test/pr_perf_test/presubmit.cfg index 69e77006c7..2f24e53480 100644 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/presubmit.cfg +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/presubmit.cfg @@ -14,6 +14,7 @@ action { define_artifacts { regex: "gcsfuse-failed-integration-test-logs-*" + regex: "gcsfuse_logs/*" strip_prefix: "github/gcsfuse/perfmetrics/scripts" } } From f47c1d32b95ab188cccd5ca39aeac1eb1d6b935f Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 5 Mar 2025 10:33:52 +0530 Subject: [PATCH 0278/1298] Unhide prometheus-port flag (#3056) We plan to publish and document this flag. --- cfg/config.go | 4 ---- cfg/params.yaml | 1 - 2 files changed, 5 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 03f02e31fe..f556accd24 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -493,10 +493,6 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.IntP("prometheus-port", "", 0, "Expose Prometheus metrics endpoint on this port and a path of /metrics.") - if err := flagSet.MarkHidden("prometheus-port"); err != nil { - return err - } - flagSet.DurationP("read-stall-initial-req-timeout", "", 20000000000*time.Nanosecond, "Initial value of the read-request dynamic timeout.") if err := flagSet.MarkHidden("read-stall-initial-req-timeout"); err != nil { diff --git a/cfg/params.yaml b/cfg/params.yaml index 4aa77263a9..7bbcd09853 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -612,7 +612,6 @@ type: "int" usage: "Expose Prometheus metrics endpoint on this port and a path of /metrics." default: "0" - hide-flag: true - config-path: "metrics.stackdriver-export-interval" flag-name: "stackdriver-export-interval" From 02c772ee719c54abad5a6bc3164ab15f7f49c37a Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 5 Mar 2025 12:09:22 +0530 Subject: [PATCH 0279/1298] gcsfuse should be pre-cloned so just cd into correct path (#3058) --- .../scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh b/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh index 9b14c78f51..f307da6d18 100755 --- a/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh +++ b/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh @@ -43,11 +43,9 @@ function upgrade_gcloud_version() { } upgrade_gcloud_version -# Clone and build the gcsfuse master branch. -git clone https://github.com/GoogleCloudPlatform/gcsfuse.git -cd gcsfuse +# Build gcsfuse. +cd "${KOKORO_ARTIFACTS_DIR}/github/gcsfuse" CGO_ENABLED=0 go build . -cd - function mount_gcsfuse_and_run_test() { # Function to mount GCSFuse. From 93cdf3f2c52243c143a963103fb178caf6d448be Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Thu, 6 Mar 2025 14:09:25 +0530 Subject: [PATCH 0280/1298] Adding INFO log for successful gRPC connection (#3062) * add log for successful grpc connection * formatting fixes --- internal/storage/storage_handle.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index 067560dab1..d97737d681 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -334,6 +334,8 @@ func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, bi if sh.directPathDetector != nil { if err := sh.directPathDetector.isDirectPathPossible(ctx, bucketName); err != nil { logger.Warnf("Direct path connectivity unavailable for %s, reason: %v", bucketName, err) + } else { + logger.Infof("Successfully connected over gRPC DirectPath for %s", bucketName) } } } From e3dbb827a832d0debea75a8bec05f533bb2c39b3 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 6 Mar 2025 15:00:13 +0530 Subject: [PATCH 0281/1298] append bucket name with architecture in checkpoint tests (#3061) * append bucket name with architecture to avoid failures due to concurrent checkpoints on the same bucket * regex match for bucket name --- .../scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh b/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh index f307da6d18..3f6bf84458 100755 --- a/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh +++ b/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh @@ -61,8 +61,7 @@ function mount_gcsfuse_and_run_test() { mkdir -p "${MOUNT_POINT}" COMMON_FLAGS=(--log-severity=TRACE --enable-streaming-writes --log-file="${KOKORO_ARTIFACTS_DIR}"/gcsfuse_logs/"${BUCKET_NAME}".log) - if [[ $BUCKET_NAME == "jax-emulated-checkpoint-flat" ]] - then + if [[ "$BUCKET_NAME" =~ "flat" ]]; then go run . "${COMMON_FLAGS[@]}" --rename-dir-limit=100 "${BUCKET_NAME}" "${MOUNT_POINT}" else go run . "${COMMON_FLAGS[@]}" "${BUCKET_NAME}" "${MOUNT_POINT}" @@ -88,8 +87,8 @@ source .venv/bin/activate pip install -r ./perfmetrics/scripts/ml_tests/checkpoint/Jax/requirements.txt # Run tests in parallel on flat and hns bucket. -FLAT_BUCKET_NAME="jax-emulated-checkpoint-flat" -HNS_BUCKET_NAME="jax-emulated-checkpoint-hns" +FLAT_BUCKET_NAME="jax-emulated-checkpoint-flat-${architecture}" +HNS_BUCKET_NAME="jax-emulated-checkpoint-hns-${architecture}" mount_gcsfuse_and_run_test "${FLAT_BUCKET_NAME}" & flat_pid=$! mount_gcsfuse_and_run_test "${HNS_BUCKET_NAME}" & From 7f62375551ab20b0de613bb0ce8866808af7163e Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 10 Mar 2025 08:39:10 +0000 Subject: [PATCH 0282/1298] Separate zonal vs non-zonal bucket e2e test run (#3054) * Make running e2e test mutually exclusive between zb and non-zb * add zonal-bucket argument in run_tests_mounted_directory minor bug fix * Allow e2e build script to take argument for ZB * address a review comment --- .../gcp_ubuntu/e2e_tests/build.sh | 16 +- .../presubmit_test/pr_perf_test/build.sh | 4 +- tools/integration_tests/run_e2e_tests.sh | 91 ++++++------ .../run_tests_mounted_directory.sh | 140 ++++++++++-------- 4 files changed, 135 insertions(+), 116 deletions(-) diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh index 47cd63f86a..cf7948a15f 100755 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh @@ -26,8 +26,18 @@ readonly BUCKET_LOCATION=us-central1 # This flag, if set true, will indicate to the underlying script, to customize for a presubmit-run. readonly RUN_TESTS_WITH_PRESUBMIT_FLAG=false -# This flag, if set true, will indicate to underlying script to also run for zonal buckets. -readonly RUN_TESTS_FOR_ZONAL_BUCKET=true +# This flag, if set true, will indicate to underlying script(s) to run for zonal bucket(s) instead of non-zonal bucket(s). +ZONAL_BUCKET_ARG=false +if [ $# -gt 0 ]; then + if [ $1 = "true" ]; then + ZONAL_BUCKET_ARG=true + elif [ $1 != "false" ]; then + >&2 echo "$0: ZONAL_BUCKET_ARG (\$1) passed as $1 . Expected: true or false" + exit 1 + fi +elif test -n ${RUN_TESTS_FOR_ZONAL_BUCKET}; then + ZONAL_BUCKET_ARG=${RUN_TESTS_FOR_ZONAL_BUCKET} +fi cd "${KOKORO_ARTIFACTS_DIR}/github/gcsfuse" echo "Building and installing gcsfuse..." @@ -40,4 +50,4 @@ git checkout $commitId echo "Running e2e tests on installed package...." # $1 argument is refering to value of testInstalledPackage -./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG ${RUN_TESTS_FOR_ZONAL_BUCKET} +./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG ${ZONAL_BUCKET_ARG} diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index 8413491e3a..52aef8d781 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -27,8 +27,8 @@ readonly RUN_TEST_ON_TPC_ENDPOINT=false # This flag, if set true, will indicate to underlying script to customize for a presubmit run. readonly RUN_TESTS_WITH_PRESUBMIT_FLAG=true -# This flag, if set true, will indicate to underlying script to also run for zonal buckets. -readonly RUN_TESTS_FOR_ZONAL_BUCKET=true +# This flag, if set true, will indicate to underlying script to run for zonal bucket(s), and not for non-zonal bucket(s). +readonly RUN_TESTS_FOR_ZONAL_BUCKET=false curl https://api.github.com/repos/GoogleCloudPlatform/gcsfuse/pulls/$KOKORO_GITHUB_PULL_REQUEST_NUMBER >> pr.json perfTest=$(grep "$EXECUTE_PERF_TEST_LABEL" pr.json) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 3aa9caf05a..68b7df5e93 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -39,7 +39,9 @@ if [ $# -ge 5 ] ; then RUN_TESTS_WITH_PRESUBMIT_FLAG=$5 fi -# 6th parameter is set to enable/disable run for zonal bucket. +# 6th parameter is set to enable/disable run for zonal bucket(s). +# If it is set to true, then the run will be only on zonal bucket(s), +# otherwise the run will only on non-zonal bucket(s). RUN_TESTS_WITH_ZONAL_BUCKET=false if [[ $# -ge 6 ]] ; then if [[ "$6" == "true" ]]; then @@ -401,7 +403,7 @@ function run_e2e_tests_for_zonal_bucket(){ return 0 } -function run_e2e_tests_for_tpc() { +function run_e2e_tests_for_tpc_and_exit() { # Clean bucket before testing. gcloud storage rm -r gs://gcsfuse-e2e-tests-tpc/** @@ -451,68 +453,65 @@ function main(){ set +e - # Run tpc test and exit in case RUN_TEST_ON_TPC_ENDPOINT is true. - if [ $RUN_TEST_ON_TPC_ENDPOINT == true ]; then - run_e2e_tests_for_tpc - fi - #run integration tests + exit_code=0 if ${RUN_TESTS_WITH_ZONAL_BUCKET}; then run_e2e_tests_for_zonal_bucket & e2e_tests_zonal_bucket_pid=$! - fi - - run_e2e_tests_for_hns_bucket & - e2e_tests_hns_bucket_pid=$! + wait $e2e_tests_zonal_bucket_pid + e2e_tests_zonal_bucket_status=$? - run_e2e_tests_for_flat_bucket & - e2e_tests_flat_bucket_pid=$! + if [ $e2e_tests_zonal_bucket_status != 0 ]; then + echo "The e2e tests for zonal bucket failed.." + exit_code=1 + fi + else + # Run tpc test and exit in case RUN_TEST_ON_TPC_ENDPOINT is true. + if [ $RUN_TEST_ON_TPC_ENDPOINT == true ]; then + run_e2e_tests_for_tpc_and_exit + fi - run_e2e_tests_for_emulator & - e2e_tests_emulator_pid=$! + run_e2e_tests_for_hns_bucket & + e2e_tests_hns_bucket_pid=$! - wait $e2e_tests_emulator_pid - e2e_tests_emulator_status=$? + run_e2e_tests_for_flat_bucket & + e2e_tests_flat_bucket_pid=$! - wait $e2e_tests_flat_bucket_pid - e2e_tests_flat_bucket_status=$? + run_e2e_tests_for_emulator & + e2e_tests_emulator_pid=$! - wait $e2e_tests_hns_bucket_pid - e2e_tests_hns_bucket_status=$? + wait $e2e_tests_emulator_pid + e2e_tests_emulator_status=$? - if ${RUN_TESTS_WITH_ZONAL_BUCKET}; then - wait $e2e_tests_zonal_bucket_pid - e2e_tests_zonal_bucket_status=$? - fi + wait $e2e_tests_flat_bucket_pid + e2e_tests_flat_bucket_status=$? - set -e + wait $e2e_tests_hns_bucket_pid + e2e_tests_hns_bucket_status=$? - print_test_logs + if [ $e2e_tests_flat_bucket_status != 0 ]; + then + echo "The e2e tests for flat bucket failed.." + exit_code=1 + fi - exit_code=0 - if [ $e2e_tests_flat_bucket_status != 0 ]; - then - echo "The e2e tests for flat bucket failed.." - exit_code=1 - fi + if [ $e2e_tests_hns_bucket_status != 0 ]; + then + echo "The e2e tests for hns bucket failed.." + exit_code=1 + fi - if [ $e2e_tests_hns_bucket_status != 0 ]; - then - echo "The e2e tests for hns bucket failed.." - exit_code=1 + if [ $e2e_tests_emulator_status != 0 ]; + then + echo "The e2e tests for emulator failed.." + exit_code=1 + fi fi - if ${RUN_TESTS_WITH_ZONAL_BUCKET} && [ $e2e_tests_zonal_bucket_status != 0 ]; then - echo "The e2e tests for zonal bucket failed.." - exit_code=1 - fi + set -e - if [ $e2e_tests_emulator_status != 0 ]; - then - echo "The e2e tests for emulator failed.." - exit_code=1 - fi + print_test_logs exit $exit_code } diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index 97f59e558a..744000522b 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -23,75 +23,85 @@ TEST_BUCKET_NAME=$1 MOUNT_DIR=$2 export CGO_ENABLED=0 +ZONAL_BUCKET_ARG= +if [ $# -gt 2 ] ; then + if [ "$3" = "true" ]; then + ZONAL_BUCKET_ARG="--zonal=true" + else if [ "$3" != "false" ]; then + >&2 echo "Unexpected value of RUN_ZONAL_BUCKET: $3. Expected: true or false." + exit 1 + fi +fi + # package operations # Run test with static mounting. (flags: --implicit-dirs=true) gcsfuse --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs=true) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs=true -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with static mounting. (flags: --implicit-dirs=false) gcsfuse --implicit-dirs=false $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs=false) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs=false -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with static mounting. (flags: --experimental-enable-json-read) gcsfuse --experimental-enable-json-read $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with static mounting. (flags: --kernel-list-cache-ttl-secs=-1, --implicit-dirs=true) gcsfuse --kernel-list-cache-ttl-secs=-1 --implicit-dirs $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --experimental-enable-json-read, --implicit-dirs=true) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs=true,experimental_enable_json_read=true -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with static mounting. (flags: --implicit-dirs=true, --only-dir testDir) gcsfuse --only-dir testDir --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with persistent mounting. (flags: --implicit-dirs=true, --only-dir=testDir) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o only_dir=testDir,implicit_dirs=true -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with static mounting. (flags: --implicit-dirs=false, --only-dir testDir) gcsfuse --only-dir testDir --implicit-dirs=false $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with persistent mounting. (flags: --implicit-dirs=false, --only-dir=testDir) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o only_dir=testDir,implicit_dirs=false -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with only-dir mounting. (flags: --experimental-enable-json-read, --only-dir testDir) gcsfuse --experimental-enable-json-read --only-dir testDir $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with only-dir mounting. (flags: --kernel-list-cache-ttl-secs=-1, --implicit-dirs=true, --only-dir testDir) gcsfuse --kernel-list-cache-ttl-secs=-1 --implicit-dirs --only-dir testDir $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with persistent mounting. (flags: --experimental-enable-json-read, --implicit-dirs=true, --only-dir=testDir) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o only_dir=testDir,implicit_dirs=true,experimental_enable_json_read=true -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with config "create-empty-file: true". @@ -99,7 +109,7 @@ echo "write: create-empty-file: true " > /tmp/gcsfuse_config.yaml gcsfuse --config-file=/tmp/gcsfuse_config.yaml $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with config "file-cache: max-size-mb" static mounting. @@ -108,7 +118,7 @@ echo "file-cache: cache-dir: /tmp/cache-dir-operations-hns-false " > /tmp/gcsfuse_config.yaml gcsfuse --config-file=/tmp/gcsfuse_config.yaml $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with config "metadata-cache: ttl-secs: 0" static mounting. @@ -116,48 +126,48 @@ echo "metadata-cache: ttl-secs: 0 " > /tmp/gcsfuse_config.yaml gcsfuse --config-file=/tmp/gcsfuse_config.yaml $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # package readonly # Run tests with static mounting. (flags: --implicit-dirs=true,--o=ro) gcsfuse --o=ro --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs=true,--o=ro) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o ro,implicit_dirs=true -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with static mounting. (flags: --implicit-dirs=true, --file-mode=544, --dir-mode=544) gcsfuse --file-mode=544 --dir-mode=544 --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs=true, --file-mode=544, --dir-mode=544) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o file_mode=544,dir_mode=544,implicit_dirs=true -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with static mounting. (flags: --implicit-dirs=true, --o=ro, --only-dir testDir) gcsfuse --only-dir testDir --o=ro --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs=true,--o=ro,--only-dir=testDir) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o ro,only_dir=testDir,implicit_dirs=true -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with static mounting. (flags: --implicit-dirs=true, --file-mode=544, --dir-mode=544, --only-dir testDir) gcsfuse --only-dir testDir --file-mode=544 --dir-mode=544 --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs=true, --file-mode=544, --dir-mode=544, --only-dir=testDir) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o only_dir=testDir,file_mode=544,dir_mode=544,implicit_dirs=true -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with config "file-cache: max-size-mb" static mounting. @@ -166,102 +176,102 @@ echo "file-cache: cache-dir: /tmp/cache-dir-readonly-hns-false " > /tmp/gcsfuse_config.yaml gcsfuse --config-file /tmp/gcsfuse_config.yaml --only-dir testDir --file-mode=544 --dir-mode=544 --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readonly/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # package rename_dir_limit # Run tests with static mounting. (flags: --rename-dir-limit=3, --implicit-dirs) gcsfuse --rename-dir-limit=3 --implicit-dirs $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --rename-dir-limit=3, --implicit-dirs) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o rename_dir_limit=3,implicit_dirs -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with static mounting. (flags: --rename-dir-limit=3) gcsfuse --rename-dir-limit=3 $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --rename-dir-limit=3) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o rename_dir_limit=3 -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with static mounting. (flags: --rename-dir-limit=3, --implicit-dirs, --only-dir testDir) gcsfuse --only-dir testDir --rename-dir-limit=3 --implicit-dirs $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting . (flags: --rename-dir-limit=3, --implicit-dirs) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o only_dir=testDir,rename_dir_limit=3,implicit_dirs -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with static mounting. (flags: --rename-dir-limit=3, --only-dir testDir) gcsfuse --only-dir testDir --rename-dir-limit=3 $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting . (flags: --rename-dir-limit=3, --implicit-dirs, --only-dir=testDir) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o only_dir=testDir,rename_dir_limit=3,implicit_dirs -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/rename_dir_limit/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # package implicit_dir # Run tests with static mounting. (flags: --implicit-dirs) gcsfuse --implicit-dirs $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with static mounting. (flags: --implicit-dirs, --only-dir testDir) gcsfuse --only-dir testDir --implicit-dirs $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs,--only-dir=testDir) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o only_dir=testDir,implicit_dirs -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # package explicit_dir # Run tests with static mounting. (flags: --implicit-dirs=false) gcsfuse --implicit-dirs=false $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/explicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/explicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs=false) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs=false -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/explicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/explicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with static mounting. (flags: --implicit-dirs=false, --only-dir testDir) gcsfuse --only-dir testDir --implicit-dirs=false $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/explicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/explicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs=false, --only-dir=testDir) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o only_dir=testDir,implicit_dirs=false -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/explicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/explicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME/testDir ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # package list_large_dir # Run tests with static mounting. (flags: --implicit-dirs) gcsfuse --implicit-dirs --stat-cache-ttl=0 --kernel-list-cache-ttl-secs=-1 $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/list_large_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/list_large_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # package read_large_files # Run tests with static mounting. (flags: --implicit-dirs) gcsfuse --implicit-dirs $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/read_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/read_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with config "file-cache: max-size-mb, cache-file-for-range-read". @@ -271,7 +281,7 @@ echo "file-cache: cache-dir: /tmp/cache-dir-read-large-files-hns-false " > /tmp/gcsfuse_config.yaml gcsfuse --config-file /tmp/gcsfuse_config.yaml --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/read_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/read_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with config "file-cache: max-size-mb". @@ -281,30 +291,30 @@ echo "file-cache: cache-dir: /tmp/cache-dir-read-large-files-hns-false " > /tmp/gcsfuse_config.yaml gcsfuse --config-file /tmp/gcsfuse_config.yaml --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/read_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/read_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # package write_large_files # Run tests with static mounting. (flags: --implicit-dirs) gcsfuse --implicit-dirs $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/write_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/write_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # package gzip # Run tests with static mounting. (flags: --implicit-dirs) gcsfuse --implicit-dirs $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/gzip/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/gzip/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # package local_file # Run test with static mounting. (flags: --implicit-dirs=true) gcsfuse --implicit-dirs=true --rename-dir-limit=3 $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/local_file/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/local_file/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with static mounting. (flags: --implicit-dirs=false) gcsfuse --implicit-dirs=false --rename-dir-limit=3 $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/local_file/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/local_file/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run tests with log rotation config. @@ -320,7 +330,7 @@ echo "logging: compress: true " > /tmp/gcsfuse_config.yaml gcsfuse --config-file=/tmp/gcsfuse_config.yaml $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/log_rotation/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/log_rotation/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run read cache functional tests. @@ -492,12 +502,12 @@ echo "list: # Empty managed folders listing test. # Run test with static mounting (flags: --implicit-dirs) gcsfuse --implicit-dirs --config-file=/tmp/gcsfuse_config.yaml $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/managed_folders/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME -run TestEnableEmptyManagedFoldersTrue +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/managed_folders/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME -run TestEnableEmptyManagedFoldersTrue ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs -o config_file=/tmp/gcsfuse_config.yaml -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/managed_folders/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME -run TestEnableEmptyManagedFoldersTrue +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/managed_folders/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME -run TestEnableEmptyManagedFoldersTrue ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # For GRPC: running only core integration tests. @@ -505,45 +515,45 @@ sudo umount $MOUNT_DIR # Test packages: operations # Run test with static mounting. (flags: --client-protocol=grpc --implicit-dirs=true) gcsfuse --client-protocol=grpc --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --client-protocol=grpc --implicit-dirs=true) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs=true,client_protocol=grpc -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Test package: implicit_dir # Run tests with static mounting. (flags: --client-protocol=grpc --implicit-dirs=true) gcsfuse --implicit-dirs=true --client-protocol=grpc $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --client-protocol=grpc --implicit-dirs=true) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs=true,client_protocol=grpc -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/implicit_dir/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Test package: concurrent_operations # Run tests with static mounting. (flags: --kernel-list-cache-ttl-secs=-1 --implicit-dirs=true) gcsfuse --implicit-dirs=true --kernel-list-cache-ttl-secs=-1 $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/concurrent_operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/concurrent_operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --kernel-list-cache-ttl-secs=-1 --implicit-dirs=true) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs=true,kernel_list_cache_ttl_secs=-1 -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/concurrent_operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/concurrent_operations/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Test package: benchmarking # Run tests with static mounting. (flags: --implicit-dirs=true) gcsfuse --implicit-dirs=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/benchmarking/... --bench=. -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/benchmarking/... --bench=. -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --implicit-dirs=true) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o implicit_dirs=true -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/benchmarking/... --bench=. -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/benchmarking/... --bench=. -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Test package: kernel-list-cache @@ -598,21 +608,21 @@ done # Test package: stale_handle # Run tests with static mounting. (flags: --metadata-cache-ttl-secs=0 --precondition-errors=true) gcsfuse --metadata-cache-ttl-secs=0 --precondition-errors=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/stale_handle/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/stale_handle/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with persistent mounting. (flags: --metadata-cache-ttl-secs=0 --precondition-errors=true) mount.gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR -o metadata_cache_ttl_secs=0,precondition_errors=true -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/stale_handle/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/stale_handle/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Test package: streaming_writes # Run streaming_writes tests. gcsfuse --rename-dir-limit=3 --enable-streaming-writes=true --write-block-size-mb=1 --write-max-blocks-per-file=2 $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/streaming_writes/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/streaming_writes/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run write_large_files tests with streaming writes enabled. gcsfuse --enable-streaming-writes=true $TEST_BUCKET_NAME $MOUNT_DIR -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/write_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/write_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR From 2fd47c482a59af386f47636b9b9a4d87259b8b5a Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 10 Mar 2025 09:41:42 +0000 Subject: [PATCH 0283/1298] Implement github presubmit label execute-integration-tests-on-zb (#3059) * Implement label execute-integration-tests-on-zb execute-integration-tests-on-zb is a new flag similar to execute-integration-tests-on-zb for enabling running e2e presubmit tests on rapid buckets. * address a review comment * remove an unused variable --- .../presubmit_test/pr_perf_test/build.sh | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index 52aef8d781..7506857653 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -17,6 +17,7 @@ # execute-integration-tests or execute-checkpoint-test label. readonly EXECUTE_PERF_TEST_LABEL="execute-perf-test" readonly EXECUTE_INTEGRATION_TEST_LABEL="execute-integration-tests" +readonly EXECUTE_INTEGRATION_TEST_LABEL_ON_ZB="execute-integration-tests-on-zb" readonly EXECUTE_PACKAGE_BUILD_TEST_LABEL="execute-package-build-tests" readonly EXECUTE_CHECKPOINT_TEST_LABEL="execute-checkpoint-test" readonly RUN_E2E_TESTS_ON_INSTALLED_PACKAGE=false @@ -27,20 +28,19 @@ readonly RUN_TEST_ON_TPC_ENDPOINT=false # This flag, if set true, will indicate to underlying script to customize for a presubmit run. readonly RUN_TESTS_WITH_PRESUBMIT_FLAG=true -# This flag, if set true, will indicate to underlying script to run for zonal bucket(s), and not for non-zonal bucket(s). -readonly RUN_TESTS_FOR_ZONAL_BUCKET=false - curl https://api.github.com/repos/GoogleCloudPlatform/gcsfuse/pulls/$KOKORO_GITHUB_PULL_REQUEST_NUMBER >> pr.json perfTest=$(grep "$EXECUTE_PERF_TEST_LABEL" pr.json) -integrationTests=$(grep "$EXECUTE_INTEGRATION_TEST_LABEL" pr.json) +integrationTests=$(grep "\"$EXECUTE_INTEGRATION_TEST_LABEL\"" pr.json) +integrationTestsOnZB=$(grep "\"$EXECUTE_INTEGRATION_TEST_LABEL_ON_ZB\"" pr.json) packageBuildTests=$(grep "$EXECUTE_PACKAGE_BUILD_TEST_LABEL" pr.json) checkpointTests=$(grep "$EXECUTE_CHECKPOINT_TEST_LABEL" pr.json) rm pr.json perfTestStr="$perfTest" integrationTestsStr="$integrationTests" +integrationTestsOnZBStr="$integrationTestsOnZB" packageBuildTestsStr="$packageBuildTests" checkpointTestStr="$checkpointTests" -if [[ "$perfTestStr" != *"$EXECUTE_PERF_TEST_LABEL"* && "$integrationTestsStr" != *"$EXECUTE_INTEGRATION_TEST_LABEL"* && "$packageBuildTestsStr" != *"$EXECUTE_PACKAGE_BUILD_TEST_LABEL"* && "$checkpointTestStr" != *"$EXECUTE_CHECKPOINT_TEST_LABEL"* ]] +if [[ "$perfTestStr" != *"$EXECUTE_PERF_TEST_LABEL"* && "$integrationTestsStr" != *"$EXECUTE_INTEGRATION_TEST_LABEL"* && "$integrationTestsOnZBStr" != *"$EXECUTE_INTEGRATION_TEST_LABEL_ON_ZB"* && "$packageBuildTestsStr" != *"$EXECUTE_PACKAGE_BUILD_TEST_LABEL"* && "$checkpointTestStr" != *"$EXECUTE_CHECKPOINT_TEST_LABEL"* ]] then echo "No need to execute tests" exit 0 @@ -112,15 +112,26 @@ then python3 ./perfmetrics/scripts/presubmit/print_results.py fi -# Execute integration tests. -if [[ "$integrationTestsStr" == *"$EXECUTE_INTEGRATION_TEST_LABEL"* ]]; +# Execute integration tests on zonal bucket(s). +if test -n "${integrationTestsOnZBStr}" ; +then + echo checkout PR branch + git checkout pr/$KOKORO_GITHUB_PULL_REQUEST_NUMBER + + echo "Running e2e tests on zonal bucket(s) ..." + # $1 argument is refering to value of testInstalledPackage. + ./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG true +fi + +# Execute integration tests on non-zonal bucket(s). +if test -n "${integrationTestsStr}" ; then echo checkout PR branch git checkout pr/$KOKORO_GITHUB_PULL_REQUEST_NUMBER - echo "Running e2e tests...." + echo "Running e2e tests on non-zonal bucket(s) ..." # $1 argument is refering to value of testInstalledPackage. - ./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG ${RUN_TESTS_FOR_ZONAL_BUCKET} + ./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG false fi # Execute package build tests. From 7b78f15a1545cb221dbca42eca0ec11b0675ff9d Mon Sep 17 00:00:00 2001 From: Nitin Garg Date: Fri, 7 Mar 2025 04:21:53 +0000 Subject: [PATCH 0284/1298] Enable more test packages for zonal bucket e2e --- tools/integration_tests/run_e2e_tests.sh | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 68b7df5e93..d613b23775 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -119,37 +119,37 @@ TEST_DIR_NON_PARALLEL=( # but only those tests which currently # pass for zonal buckets. TEST_DIR_PARALLEL_FOR_ZB=( - # "benchmarking" + "benchmarking" # "concurrent_operations" # "explicit_dir" - # "gzip" + "gzip" # "implicit_dir" - # "interrupt" - # "kernel_list_cache" + "interrupt" + "kernel_list_cache" # "list_large_dir" - # "local_file" + "local_file" # "log_content" "log_rotation" - # "monitoring" + "monitoring" "mount_timeout" - # "mounting" - # "negative_stat_cache" + "mounting" + "negative_stat_cache" # "operations" - # "read_cache" - # "read_large_files" - # "rename_dir_limit" - # "stale_handle" + "read_cache" + "read_large_files" + "rename_dir_limit" + "stale_handle" # "streaming_writes" - # "write_large_files" + "write_large_files" ) # Subset of TEST_DIR_NON_PARALLEL, # but only those tests which currently # pass for zonal buckets. TEST_DIR_NON_PARALLEL_FOR_ZB=( - # "readonly" - # "managed_folders" - # "readonly_creds" + "readonly" + "managed_folders" + "readonly_creds" ) # Create a temporary file to store the log file name. From bb0fa779e7df380fca5120f77bd85ea168d198b3 Mon Sep 17 00:00:00 2001 From: Nitin Garg Date: Fri, 7 Mar 2025 06:02:34 +0000 Subject: [PATCH 0285/1298] Add new build config for e2e daily run on ZB --- .../e2e_tests/zonal/e2e-tests-master.cfg | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/zonal/e2e-tests-master.cfg diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/zonal/e2e-tests-master.cfg b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/zonal/e2e-tests-master.cfg new file mode 100644 index 0000000000..1866efc777 --- /dev/null +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/zonal/e2e-tests-master.cfg @@ -0,0 +1,28 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +action { + define_artifacts { + regex: "gcsfuse-failed-integration-test-logs-*" + strip_prefix: "github/gcsfuse/perfmetrics/scripts" + regex: "proxy*" + } +} + +env_vars { + key: "RUN_TESTS_WITH_ZONAL_BUCKET" + value: "true" +} + +build_file: "gcsfuse/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh" From 23fe8ae795e514feaa3262fdc6ce00de00df0ae5 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 10 Mar 2025 09:49:39 +0000 Subject: [PATCH 0286/1298] [testing-on-gke] Fixing minor bugs in script (#3067) * temp changes in workload * Revert "temp changes in workload" This reverts commit 81dcaf59d20dc1921e2c0151ca994415eb745392. * fix log of only_parse for -debug option * remove irrelevant gsheet logs/commands from run-gke-tests.sh --- .../testing_on_gke/examples/run-gke-tests.sh | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index f2b7f23f71..f04666eb57 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -303,8 +303,6 @@ function installDependencies() { echoerror "sudo addgroup docker && sudo usermod -aG docker $USER && newgrp docker" return 1 fi - # Install python modules for gsheet. - python3 -m pip install google-api-python-client } # Make sure you have access to the necessary GCP resources. The easiest way to enable it is to use @google.com as active auth. @@ -638,25 +636,25 @@ function waitTillAllPodsComplete() { printf "\nAll pods have completed.\n\n" break else - printf "\n${num_noncompleted_pods} pod(s) is/are still pending/running (time till timeout=${time_till_timeout} seconds). Will check again in "${pod_wait_time_in_seconds}" seconds. Sleeping for now.\n\n" - printf "\nYou can take a break too if you want. Just kill this run and connect back to it later, for fetching and parsing outputs, using the following command: \n" - printf " only_parse=true instance_id=${instance_id} project_id=${project_id} project_number=${project_number} zone=${zone} machine_type=${machine_type} use_custom_csi_driver=${use_custom_csi_driver} gcsfuse_src_dir=\"${gcsfuse_src_dir}\" " + message="\n${num_noncompleted_pods} pod(s) is/are still pending/running (time till timeout=${time_till_timeout} seconds). Will check again in "${pod_wait_time_in_seconds}" seconds. Sleeping for now.\n\n" + message+="\nYou can take a break too if you want. Just kill this run and connect back to it later, for fetching and parsing outputs, using the following command: \n" + message+=" only_parse=true instance_id=${instance_id} project_id=${project_id} project_number=${project_number} zone=${zone} machine_type=${machine_type} use_custom_csi_driver=${use_custom_csi_driver} gcsfuse_src_dir=\"${gcsfuse_src_dir}\" " if test -d "${csi_src_dir}"; then - printf "csi_src_dir=\"${csi_src_dir}\" " + message+="csi_src_dir=\"${csi_src_dir}\" " fi - printf "pod_wait_time_in_seconds=${pod_wait_time_in_seconds} pod_timeout_in_seconds=${pod_timeout_in_seconds} workload_config=\"${workload_config}\" cluster_name=${cluster_name} output_dir=\"${output_dir}\" output_gsheet_id=\"${output_gsheet_id}\" output_gsheet_keyfile=\"${output_gsheet_keyfile}\" $0 \n" - printf "\nbut remember that this will reset the start-timer for pod timeout.\n\n" - printf "\nTo ssh to any specific pod, use the following command: \n" - printf " gcloud container clusters get-credentials ${cluster_name} --location=${zone}\n" - printf " kubectl config set-context --current --namespace=${appnamespace}\n" - printf " kubectl exec -it pods/ [-c {gke-gcsfuse-sidecar|fio-tester|dlio-tester}] --namespace=${appnamespace} -- /bin/bash \n" - printf "\nTo view cpu/memory usage of different pods/containers: \n" - printf " kubectl top pod [] --namespace=${appnamespace} [--containers] \n" - printf "\nTo view the latest status of all the pods in this cluster/namespace: \n" - printf " kubectl get pods --namespace=${appnamespace} [-o wide] [--watch] \n" - printf "\nTo output the configuration of all or one of the pods in this cluster/namespace (useful for debugging): \n" - printf " kubectl get [pods or pods/] --namespace=${appnamespace} -o yaml \n" - printf "\n\n\n" + message+="pod_wait_time_in_seconds=${pod_wait_time_in_seconds} pod_timeout_in_seconds=${pod_timeout_in_seconds} workload_config=\"${workload_config}\" cluster_name=${cluster_name} output_dir=\"${output_dir}\" $0 \n" + message+="\nbut remember that this will reset the start-timer for pod timeout.\n\n" + message+="\nTo ssh to any specific pod, use the following command: \n" + message+=" gcloud container clusters get-credentials ${cluster_name} --location=${zone}\n" + message+=" kubectl config set-context --current --namespace=${appnamespace}\n" + message+=" kubectl exec -it pods/ [-c {gke-gcsfuse-sidecar|fio-tester|dlio-tester}] --namespace=${appnamespace} -- /bin/bash \n" + message+="\nTo view cpu/memory usage of different pods/containers: \n" + message+=" kubectl top pod [] --namespace=${appnamespace} [--containers] \n" + message+="\nTo view the latest status of all the pods in this cluster/namespace: \n" + message+=" kubectl get pods --namespace=${appnamespace} [-o wide] [--watch] \n" + message+="\nTo output the configuration of all or one of the pods in this cluster/namespace (useful for debugging): \n" + message+=" kubectl get [pods or pods/] --namespace=${appnamespace} -o yaml \n" + printf "${message}\n\n\n" fi sleep ${pod_wait_time_in_seconds} unset podslist # necessary to update the value of podslist every iteration From 654c708c720cfc9a9cefbaab69ab254348f9ccf2 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 10 Mar 2025 23:13:23 +0530 Subject: [PATCH 0287/1298] chores (bump): updating direct go-module dependency (#3069) * chores (bump): updating direct go-module dependency * Trigger Build --- go.mod | 112 ++++++++++++++++++++++++++--------------------- go.sum | 135 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 198 insertions(+), 49 deletions(-) diff --git a/go.mod b/go.mod index 0fbea80e24..37c5b607dd 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,15 @@ go 1.24.0 require ( cloud.google.com/go/compute/metadata v0.6.0 - cloud.google.com/go/iam v1.3.1 - cloud.google.com/go/secretmanager v1.14.3 + cloud.google.com/go/iam v1.4.1 + cloud.google.com/go/secretmanager v1.14.5 cloud.google.com/go/storage v1.50.0 contrib.go.opencensus.io/exporter/ocagent v0.7.0 contrib.go.opencensus.io/exporter/prometheus v0.4.2 contrib.go.opencensus.io/exporter/stackdriver v0.13.14 - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0 - github.com/fsouza/fake-gcs-server v1.52.1 + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 + github.com/fsouza/fake-gcs-server v1.52.2 github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.1 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec @@ -25,54 +25,58 @@ require ( github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/mitchellh/mapstructure v1.5.0 - github.com/prometheus/client_golang v1.20.5 + github.com/prometheus/client_golang v1.21.1 github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.62.0 - github.com/spf13/cobra v1.8.1 - github.com/spf13/pflag v1.0.5 + github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 go.opencensus.io v0.24.0 - go.opentelemetry.io/contrib/detectors/gcp v1.34.0 - go.opentelemetry.io/otel v1.34.0 - go.opentelemetry.io/otel/exporters/prometheus v0.56.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0 - go.opentelemetry.io/otel/metric v1.34.0 - go.opentelemetry.io/otel/sdk v1.34.0 - go.opentelemetry.io/otel/sdk/metric v1.34.0 - go.opentelemetry.io/otel/trace v1.34.0 - golang.org/x/net v0.34.0 - golang.org/x/oauth2 v0.25.0 - golang.org/x/sync v0.10.0 - golang.org/x/sys v0.30.0 - golang.org/x/text v0.21.0 - golang.org/x/time v0.9.0 - google.golang.org/api v0.217.0 - google.golang.org/grpc v1.69.4 - google.golang.org/protobuf v1.36.3 + go.opentelemetry.io/contrib/detectors/gcp v1.35.0 + go.opentelemetry.io/otel v1.35.0 + go.opentelemetry.io/otel/exporters/prometheus v0.57.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 + go.opentelemetry.io/otel/metric v1.35.0 + go.opentelemetry.io/otel/sdk v1.35.0 + go.opentelemetry.io/otel/sdk/metric v1.35.0 + go.opentelemetry.io/otel/trace v1.35.0 + golang.org/x/net v0.37.0 + golang.org/x/oauth2 v0.28.0 + golang.org/x/sync v0.12.0 + golang.org/x/sys v0.31.0 + golang.org/x/text v0.23.0 + golang.org/x/time v0.11.0 + google.golang.org/api v0.224.0 + google.golang.org/grpc v1.71.0 + google.golang.org/protobuf v1.36.5 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 ) require ( - cel.dev/expr v0.19.1 // indirect - cloud.google.com/go v0.118.0 // indirect - cloud.google.com/go/auth v0.14.0 // indirect + cel.dev/expr v0.22.0 // indirect + cloud.google.com/go v0.118.3 // indirect + cloud.google.com/go/auth v0.15.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect - cloud.google.com/go/longrunning v0.6.4 // indirect - cloud.google.com/go/monitoring v1.22.1 // indirect - cloud.google.com/go/pubsub v1.45.3 // indirect - cloud.google.com/go/trace v1.11.3 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect - github.com/aws/aws-sdk-go v1.55.5 // indirect + cloud.google.com/go/longrunning v0.6.5 // indirect + cloud.google.com/go/monitoring v1.24.0 // indirect + cloud.google.com/go/pubsub v1.47.0 // indirect + cloud.google.com/go/trace v1.11.4 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect + github.com/alecthomas/kingpin/v2 v2.4.0 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect + github.com/aws/aws-sdk-go v1.55.6 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect + github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/envoyproxy/go-control-plane/envoy v1.32.3 // indirect - github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect + github.com/envoyproxy/go-control-plane v0.13.4 // indirect + github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect github.com/go-logr/logr v1.4.2 // indirect @@ -81,22 +85,29 @@ require ( github.com/golang/protobuf v1.5.4 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/iancoleman/strcase v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/jpillora/backoff v1.0.0 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 // indirect github.com/magiconair/properties v1.8.9 // indirect + github.com/mdlayher/socket v0.4.1 // indirect + github.com/mdlayher/vsock v1.2.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/xattr v0.4.10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/exporter-toolkit v0.13.2 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/prometheus/prometheus v0.301.0 // indirect + github.com/prometheus/prometheus v0.302.1 // indirect github.com/prometheus/statsd_exporter v0.28.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect @@ -105,15 +116,18 @@ require ( github.com/spf13/cast v1.7.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.32.0 // indirect - golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect - google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect + golang.org/x/mod v0.24.0 // indirect + golang.org/x/tools v0.31.0 // indirect + google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index addf615fd4..be15214645 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.22.0 h1:+hFFhLPmquBImfs1BiN2PZmkr5ASse2ZOuaxIs9e4R8= +cel.dev/expr v0.22.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -17,8 +19,12 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.118.0 h1:tvZe1mgqRxpiVa3XlIGMiPcEUbP1gNXELgD4y/IXmeQ= cloud.google.com/go v0.118.0/go.mod h1:zIt2pkedt/mo+DQjcT4/L3NDxzHPR29j5HcclNH+9PM= +cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME= +cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc= cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM= cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A= +cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= +cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= @@ -27,28 +33,41 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.34.0 h1:+k/kmViu4TEi97NGaxAATYtpYBviOWJySPZ+ekA95kk= +cloud.google.com/go/compute v1.34.0/go.mod h1:zWZwtLwZQyonEvIQBuIa0WvraMYK69J5eDCOw9VZU4g= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v1.3.1 h1:KFf8SaT71yYq+sQtRISn90Gyhyf4X8RGgeAVC8XGf3E= cloud.google.com/go/iam v1.3.1/go.mod h1:3wMtuyT4NcbnYNPLMBzYRFiEfjKfJlLVLrisE7bwm34= +cloud.google.com/go/iam v1.4.1 h1:cFC25Nv+u5BkTR/BT1tXdoF2daiVbZ1RLx2eqfQ9RMM= +cloud.google.com/go/iam v1.4.1/go.mod h1:2vUEJpUG3Q9p2UdsyksaKpDzlwOrnMzS30isdReIcLM= cloud.google.com/go/kms v1.20.4 h1:CJ0hMpOg1ANN9tx/a/GPJ+Uxudy8k6f3fvGFuTHiE5A= cloud.google.com/go/kms v1.20.4/go.mod h1:gPLsp1r4FblUgBYPOcvI/bUPpdMg2Jm1ZVKU4tQUfcc= +cloud.google.com/go/kms v1.21.0 h1:x3EeWKuYwdlo2HLse/876ZrKjk2L5r7Uexfm8+p6mSI= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg= cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs= +cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q= +cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY= cloud.google.com/go/monitoring v1.22.1 h1:KQbnAC4IAH+5x3iWuPZT5iN9VXqKMzzOgqcYB6fqPDE= cloud.google.com/go/monitoring v1.22.1/go.mod h1:AuZZXAoN0WWWfsSvET1Cpc4/1D8LXq8KRDU87fMS6XY= +cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM= +cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/pubsub v1.45.3 h1:prYj8EEAAAwkp6WNoGTE4ahe0DgHoyJd5Pbop931zow= cloud.google.com/go/pubsub v1.45.3/go.mod h1:cGyloK/hXC4at7smAtxFnXprKEFTqmMXNNd9w+bd94Q= +cloud.google.com/go/pubsub v1.47.0 h1:Ou2Qu4INnf7ykrFjGv2ntFOjVo8Nloh/+OffF4mUu9w= +cloud.google.com/go/pubsub v1.47.0/go.mod h1:LaENesmga+2u0nDtLkIOILskxsfvn/BXX9Ak1NFxOs8= cloud.google.com/go/secretmanager v1.14.3 h1:XVGHbcXEsbrgi4XHzgK5np81l1eO7O72WOXHhXUemrM= cloud.google.com/go/secretmanager v1.14.3/go.mod h1:Pwzcfn69Ni9Lrk1/XBzo1H9+MCJwJ6CDCoeoQUsMN+c= +cloud.google.com/go/secretmanager v1.14.5 h1:W++V0EL9iL6T2+ec24Dm++bIti0tI6Gx6sCosDBters= +cloud.google.com/go/secretmanager v1.14.5/go.mod h1:GXznZF3qqPZDGZQqETZwZqHw4R6KCaYVvcGiRBA+aqY= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -58,6 +77,8 @@ cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6Q cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE= cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8= +cloud.google.com/go/trace v1.11.4 h1:LKlhVyX6I4+heP31sWvERSKZZ9cPPEZumt7b4SKVK18= +cloud.google.com/go/trace v1.11.4/go.mod h1:lCSHzSPZC1TPwto7zhaRt3KtGYsXFyaErPQ18AUUeUE= contrib.go.opencensus.io/exporter/ocagent v0.7.0 h1:BEfdCTXfMV30tLZD8c9n64V/tIZX5+9sXiuFLnrr1k8= contrib.go.opencensus.io/exporter/ocagent v0.7.0/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= @@ -69,23 +90,38 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0/go.mod h1:6fTWu4m3jocfUZLYF5KsZC1TUfRvEjs7lM4crme/irw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0 h1:4PoDbd/9/06IpwLGxSfvfNoEr9urvfkrN6mmJangGCg= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0/go.mod h1:EycllQ1gupHbjqbcmfCr/H6FKSGSmEUONJ2ivb86qeY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 h1:Jtr816GUk6+I2ox9L/v+VcOwN6IyGOEDTSNHfD6m9sY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0/go.mod h1:E05RN++yLx9W4fXPtX978OLo9P0+fBacauUdET1BckA= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0 h1:jJKWl98inONJAr/IZrdFQUWcwUO95DLY1XMD1ZIut+g= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0/go.mod h1:l2fIqmwB+FKSfvn3bAD/0i+AXAxhIZjTK2svT/mgUXs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 h1:GYUJLfvd++4DMuMhCFLgLXvFwofIxh/qOwoGuS/LTew= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= +github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= +github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= +github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -104,7 +140,12 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= +github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= @@ -118,11 +159,15 @@ github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+ github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= github.com/envoyproxy/go-control-plane/envoy v1.32.3 h1:hVEaommgvzTjTd4xCaFd+kEQ2iYBtGxP6luyLrx6uOk= github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= +github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= +github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= +github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= +github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= @@ -131,6 +176,8 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsouza/fake-gcs-server v1.52.1 h1:Hx3G2ZpyBzHGmW7cHURWWoTm6jM3M5fcWMIAHBYlJyc= github.com/fsouza/fake-gcs-server v1.52.1/go.mod h1:Paxf25VmSNMN52L+2/cVulF5fkLUA0YJIYjTGJiwf3c= +github.com/fsouza/fake-gcs-server v1.52.2 h1:j6ne83nqHrlX5EEor7WWVIKdBsztGtwJ1J2mL+k+iio= +github.com/fsouza/fake-gcs-server v1.52.2/go.mod h1:47HKyIkz6oLTes1R8vEaHLwXfzYsGfmDUk1ViHHAUsA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -154,6 +201,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -202,6 +251,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -225,6 +275,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g= +github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= @@ -234,12 +286,17 @@ github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkM github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -267,6 +324,7 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -281,6 +339,8 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALr github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -295,13 +355,20 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 h1:sIXJOMrYnQZJu7OB7ANSF4MYri2fTEGIsRLz6LwI4xE= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= +github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= +github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= +github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.83 h1:W4Kokksvlz3OKf3OqIlzDNKd4MERlC2oN8YptwJ0+GA= github.com/minio/minio-go/v7 v7.0.83/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY= +github.com/minio/minio-go/v7 v7.0.86 h1:DcgQ0AUjLJzRH6y/HrxiZ8CXarA70PAIufXHodP4s+k= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -312,6 +379,7 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= @@ -334,6 +402,8 @@ github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrb github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= +github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -348,6 +418,8 @@ github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJ github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/exporter-toolkit v0.13.2 h1:Z02fYtbqTMy2i/f+xZ+UK5jy/bl1Ex3ndzh06T/Q9DQ= +github.com/prometheus/exporter-toolkit v0.13.2/go.mod h1:tCqnfx21q6qN1KA4U3Bfb8uWzXfijIrJz3/kTIqMV7g= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -358,6 +430,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/prometheus/prometheus v0.301.0 h1:0z8dgegmILivNomCd79RKvVkIols8vBGPKmcIBc7OyY= github.com/prometheus/prometheus v0.301.0/go.mod h1:BJLjWCKNfRfjp7Q48DrAjARnCi7GhfUVvUFEAWTssZM= +github.com/prometheus/prometheus v0.302.1 h1:xqVdrwrB4WNpdgJqxsz5loqFWNUZitsK8myqLuSZ6Ag= +github.com/prometheus/prometheus v0.302.1/go.mod h1:YcyCoTbUR/TM8rY3Aoeqr0AWTu/pu1Ehh+trpX3eRzg= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= github.com/prometheus/statsd_exporter v0.28.0 h1:S3ZLyLm/hOKHYZFOF0h4zYmd0EeKyPF9R1pFBYXUgYY= github.com/prometheus/statsd_exporter v0.28.0/go.mod h1:Lq41vNkMLfiPANmI+uHb5/rpFFUTxPXiiNpmsAYLvDI= @@ -383,8 +457,12 @@ github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -400,16 +478,21 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.einride.tech/aip v0.68.0 h1:4seM66oLzTpz50u4K1zlJyOXQ3tCzcJN7I22tKkjipw= go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg= +go.einride.tech/aip v0.68.1 h1:16/AfSxcQISGN5z9C5lM+0mLYXihrHbQ1onvYTr93aQ= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -422,26 +505,46 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= +go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA= +go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/exporters/prometheus v0.56.0 h1:GnCIi0QyG0yy2MrJLzVrIM7laaJstj//flf1zEJCG+E= go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk= +go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk= +go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0 h1:jBpDk4HAUsrnVO1FsfCfCOTEc/MkInJmvfCHYLFiT80= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0/go.mod h1:H9LUIM1daaeZaz91vZcfeM0fejXPmgCYE8ZhzqfJuiU= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg= go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -452,6 +555,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -464,6 +569,8 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= +golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -484,6 +591,8 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -519,6 +628,8 @@ golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -528,6 +639,8 @@ golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= +golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -540,6 +653,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -586,6 +701,8 @@ golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -597,11 +714,15 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -642,6 +763,8 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= +golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -665,6 +788,8 @@ google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSr google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.217.0 h1:GYrUtD289o4zl1AhiTZL0jvQGa2RDLyC+kX1N/lfGOU= google.golang.org/api v0.217.0/go.mod h1:qMc2E8cBAbQlRypBTBWHklNJlaZZJBwDv81B1Iu8oSI= +google.golang.org/api v0.224.0 h1:Ir4UPtDsNiwIOHdExr3fAj4xZ42QjK7uQte3lORLJwU= +google.golang.org/api v0.224.0/go.mod h1:3V39my2xAGkodXy0vEqcEtkqgw2GtrFL5WuBZlCTCOQ= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -704,10 +829,16 @@ google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422 h1:6GUHKGv2huWOHKmDXLMNE94q3fBDlEHI+oTRIZSebK0= google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422/go.mod h1:1NPAxoesyw/SgLPqaUp9u1f9PWCLAk/jVmhx7gJZStg= +google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= +google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= +google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -723,6 +854,8 @@ google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= +google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -739,6 +872,8 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= +google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 2dfdd3522b983ff1f7b5eefc68328553107c6744 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 11 Mar 2025 14:09:02 +0530 Subject: [PATCH 0288/1298] Revert "turn flag on (#2995)" (#3077) This reverts commit 3b4023a99d16fc978c0aaf075bd96f1193581eb6. --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index f556accd24..d34ec14636 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -319,7 +319,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.BoolP("enable-atomic-rename-object", "", true, "Enables support for atomic rename object operation on HNS bucket.") + flagSet.BoolP("enable-atomic-rename-object", "", false, "Enables support for atomic rename object operation on HNS bucket.") if err := flagSet.MarkHidden("enable-atomic-rename-object"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 7bbcd09853..7a26f3ec34 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -75,7 +75,7 @@ flag-name: "enable-atomic-rename-object" type: "bool" usage: "Enables support for atomic rename object operation on HNS bucket." - default: true + default: false hide-flag: true - config-path: "enable-hns" From fb4fa9297bb6e503865fb5f45630b9b076892f36 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:19:10 +0530 Subject: [PATCH 0289/1298] fix unit tests (#3078) --- internal/gcsx/bucket_manager_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/gcsx/bucket_manager_test.go b/internal/gcsx/bucket_manager_test.go index 5a0241342f..e45757f9c9 100644 --- a/internal/gcsx/bucket_manager_test.go +++ b/internal/gcsx/bucket_manager_test.go @@ -16,6 +16,7 @@ package gcsx import ( "context" + "strings" "testing" "time" @@ -156,7 +157,8 @@ func (t *BucketManagerTest) TestSetUpBucketMethodWhenBucketDoesNotExist() { bucket, err := bm.SetUpBucket(context.Background(), invalidBucketName, false, common.NewNoopMetrics()) - ExpectEq("error in iterating through objects: storage: bucket doesn't exist", err.Error()) + AssertNe(nil, err) + ExpectTrue(strings.Contains(err.Error(), "error in iterating through objects: storage: bucket doesn't exist")) ExpectNe(nil, bucket.Syncer) } @@ -180,6 +182,7 @@ func (t *BucketManagerTest) TestSetUpBucketMethodWhenBucketDoesNotExist_IsMultiB bucket, err := bm.SetUpBucket(context.Background(), invalidBucketName, true, common.NewNoopMetrics()) - ExpectEq("error in iterating through objects: storage: bucket doesn't exist", err.Error()) + AssertNe(nil, err) + ExpectTrue(strings.Contains(err.Error(), "error in iterating through objects: storage: bucket doesn't exist")) ExpectNe(nil, bucket.Syncer) } From 617e473fdcdd02134ef7ea004cd660b2e1f0c53f Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 12 Mar 2025 06:49:33 +0000 Subject: [PATCH 0290/1298] Move presubmit e2e_tests/zonal/e2e-tests-master.cfg to e2e_tests/e2e-tests-master-zb.cfg (#3082) --- .../{zonal/e2e-tests-master.cfg => e2e-tests-master-zb.cfg} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/{zonal/e2e-tests-master.cfg => e2e-tests-master-zb.cfg} (100%) diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/zonal/e2e-tests-master.cfg b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-master-zb.cfg similarity index 100% rename from perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/zonal/e2e-tests-master.cfg rename to perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-master-zb.cfg From 796c6120851b29cc2ed6104388ff189dcce87224 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 12 Mar 2025 11:08:00 +0000 Subject: [PATCH 0291/1298] [testing-on-gke] Add support to reuse prebuilt custom csi driver (#3068) * [testing-on-gke] Introduce custom_csi_driver custom_csi_driver is a new env-var based option for fio/dlio GKE test tool, which allows user to pass a pre-built custom csi driver, rather than having to build a new one each time. It works in conjunction with use_custom_csi_driver. If a non-empty custom_csi_driver is passed, then the value of use_custom_csi_driver is overridden and the passed custom-csi-driver is passed. If is not passed, or passed as "", then a new custom csi driver is built if use_custom_csi_driver is passed as true (legacy behaviour). This commit doesn't really implement the functionality of custom_csi_driver, but only introduces the flag. The implementation will be in later commits. * [testing-on-gke] Implement flag custom_csi_driver custom_csi_driver is a new env-var based option for fio/dlio GKE test tool, which allows user to pass a pre-built custom csi driver, rather than having to build a new one each time. It works in conjunction with use_custom_csi_driver. If a non-empty custom_csi_driver is passed, then the value of use_custom_csi_driver is overridden and the passed custom-csi-driver is passed. If is not passed, or passed as "", then a new custom csi driver is built if use_custom_csi_driver is passed as true (legacy behaviour). This commit doesn't really implement the functionality of custom_csi_driver, but only introduces the flag. The implementation will be in later commits. * move csi driver verification up * improve a log format * avoid make install for just-built custom csi driver * fix indentation --- .../testing_on_gke/examples/dlio/run_tests.py | 9 +- .../templates/dlio-tester.yaml | 4 + .../dlio/unet3d-loading-test/values.yaml | 1 + .../loading-test/templates/fio-tester.yaml | 4 + .../examples/fio/loading-test/values.yaml | 2 +- .../testing_on_gke/examples/fio/run_tests.py | 9 +- .../testing_on_gke/examples/run-gke-tests.sh | 113 ++++++++++++------ .../examples/utils/run_tests_common.py | 10 ++ 8 files changed, 103 insertions(+), 49 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py index 4f940730d5..41cbfe9604 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py @@ -38,9 +38,7 @@ def createHelmInstallCommands( - dlioWorkloads: set, - instanceId: str, - machineType: str, + dlioWorkloads: set, instanceId: str, machineType: str, customCSIDriver: str ) -> list: """Creates helm install commands for the given dlioWorkload objects.""" helm_commands = [] @@ -82,6 +80,7 @@ def createHelmInstallCommands( f"--set resourceRequests.cpu={resourceRequests['cpu']}", f"--set resourceRequests.memory={resourceRequests['memory']}", f'--set numEpochs={dlioWorkload.numEpochs}', + f'--set gcsfuse.customCSIDriver={customCSIDriver}', ] helm_command = ' '.join(commands) @@ -94,9 +93,7 @@ def main(args) -> None: args.workload_config ) helmInstallCommands = createHelmInstallCommands( - dlioWorkloads, - args.instance_id, - args.machine_type, + dlioWorkloads, args.instance_id, args.machine_type, args.custom_csi_driver ) buckets = [dlioWorkload.bucket for dlioWorkload in dlioWorkloads] role = 'roles/storage.objectUser' diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml index 32dbb606e7..cd6487e178 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml @@ -29,6 +29,10 @@ spec: cloud.google.com/gke-ephemeral-storage-local-ssd: "true" node.kubernetes.io/instance-type: {{ .Values.nodeType }} containers: + {{- if not (eq .Values.gcsfuse.customCSIDriver "") }} + - name: gke-gcsfuse-sidecar + image: {{ .Values.gcsfuse.customCSIDriver }} + {{- end }} - name: dlio-tester image: {{ .Values.image }} ports: diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml index 69589d8da6..5a3e66df91 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml @@ -47,3 +47,4 @@ gcsfuse: fileCacheCapacity: "-1" fileCacheForRangeRead: "true" mountOptions: "implicit-dirs" + customCSIDriver: "" diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml index 7009abb5a4..9640df20c2 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml @@ -28,6 +28,10 @@ spec: cloud.google.com/gke-ephemeral-storage-local-ssd: "true" node.kubernetes.io/instance-type: {{ .Values.nodeType }} containers: + {{- if not (eq .Values.gcsfuse.customCSIDriver "") }} + - name: gke-gcsfuse-sidecar + image: {{ .Values.gcsfuse.customCSIDriver }} + {{- end }} - name: fio-tester image: {{ .Values.image }} ports: diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml index e06c75581f..e81446387f 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml @@ -48,4 +48,4 @@ gcsfuse: fileCacheCapacity: "-1" fileCacheForRangeRead: "true" mountOptions: "implicit-dirs" - + customCSIDriver: "" diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py b/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py index 4fbf6b22c3..f38ab9e192 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py @@ -37,9 +37,7 @@ def createHelmInstallCommands( - fioWorkloads: set, - instanceId: str, - machineType: str, + fioWorkloads: set, instanceId: str, machineType: str, customCSIDriver: str ) -> list: """Creates helm install commands for the given fioWorkload objects.""" helm_commands = [] @@ -81,6 +79,7 @@ def createHelmInstallCommands( f"--set resourceRequests.cpu={resourceRequests['cpu']}", f"--set resourceRequests.memory={resourceRequests['memory']}", f'--set numEpochs={fioWorkload.numEpochs}', + f'--set gcsfuse.customCSIDriver={customCSIDriver}', ] helm_command = ' '.join(commands) @@ -93,9 +92,7 @@ def main(args) -> None: args.workload_config ) helmInstallCommands = createHelmInstallCommands( - fioWorkloads, - args.instance_id, - args.machine_type, + fioWorkloads, args.instance_id, args.machine_type, args.custom_csi_driver ) buckets = (fioWorkload.bucket for fioWorkload in fioWorkloads) role = 'roles/storage.objectUser' diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index f04666eb57..f7a714ed61 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -36,8 +36,9 @@ fi # Utilities function exitWithSuccess() { exit 0; } function exitWithFailure() { exit 1; } -function echoerror() { >&2 echo "Error: "$@; } -function exitWithError() { echoerror $@ ; exitWithFailure ; } +function echoerror() { >&2 echo "Error: "$@ ; } +function exitWithError() { echoerror "$@" ; exitWithFailure ; } +function returnWithError() { echoerror "$@" ; return 1 ; } # Default values, to be used for parameters in case user does not specify them. # GCP related @@ -50,6 +51,7 @@ readonly DEFAULT_NUM_SSD=16 readonly DEFAULT_APPNAMESPACE=default readonly DEFAULT_KSA=default readonly DEFAULT_USE_CUSTOM_CSI_DRIVER=true +readonly DEFAULT_CUSTOM_CSI_DRIVER= # GCSFuse/GKE GCSFuse CSI Driver source code related readonly DEFAULT_SRC_DIR="$(realpath .)/src" readonly csi_driver_github_path=https://github.com/googlecloudplatform/gcs-fuse-csi-driver @@ -80,6 +82,7 @@ function printHelp() { echo "machine_type=" echo "num_nodes=" echo "num_ssd=" + echo "custom_csi_driver=:, default=\"${DEFAULT_CUSTOM_CSI_DRIVER}\". If it is non-empty, then use_custom_csi_driver is assumed true, but a custom driver is not built and the given custom csi driver is used instead. >" echo "use_custom_csi_driver=" # GCSFuse/GKE GCSFuse CSI Driver source code related echo "src_dir=<\"directory/to/clone/github/repos/if/needed\", used for locally cloning in case gcsfuse_src_dir or csi_src_dir are not passed, default=\"${DEFAULT_SRC_DIR}\">" @@ -108,6 +111,17 @@ if ([ $# -gt 0 ] && ([ "$1" == "-help" ] || [ "$1" == "--help" ] || [ "$1" == "- exitWithSuccess fi +verify_csi_driver_image() { + if [[ $# < 1 ]]; then + returnWithError "No arguments passed to verify_csi_driver_image. Expected: \$1= ." + fi + local csi_driver_image=${1} + echo "Checking ${csi_driver_image} ..." + if ! gcloud -q container images describe ${csi_driver_image} >/dev/null; then + returnWithError "${csi_driver_image} is not a valid GCSFuse csi driver image. !!! Please check if you missed adding /gcs-fuse-csi-driver-sidecar-mounter before the hash. !!!" + fi +} + # Set environment variables. # GCP related if test -z "${project_id}"; then @@ -129,7 +143,34 @@ test -n "${num_ssd}" || export num_ssd=${DEFAULT_NUM_SSD} export appnamespace=${DEFAULT_APPNAMESPACE} # test -n "${ksa}" || export ksa=${DEFAULT_KSA} -test -n "${use_custom_csi_driver}" || export use_custom_csi_driver="${DEFAULT_USE_CUSTOM_CSI_DRIVER}" + +applied_custom_csi_driver= +if test -z "${custom_csi_driver}"; then + echo "custom_csi_driver has not been set, so assuming \"${DEFAULT_CUSTOM_CSI_DRIVER}\" for it ..." + export custom_csi_driver="${DEFAULT_CUSTOM_CSI_DRIVER}" + if test -z "${use_custom_csi_driver}"; then + echo "use_custom_csi_driver has not been set, so assuming \"${DEFAULT_USE_CUSTOM_CSI_DRIVER}\" for it ..." + export use_custom_csi_driver="${DEFAULT_USE_CUSTOM_CSI_DRIVER}" + elif [[ ${use_custom_csi_driver} = "true" ]]; then + echo "User has enabled use_custom_csi_driver, without passing a custom_csi_driver, so a custom driver will be built in this run." + elif [[ ${use_custom_csi_driver} != "false" ]]; then + exitWithError "Unsupported value passed for use_custom_csi_driver: ${use_custom_csi_driver}. Supported values: true/false ." + fi +else + echo "User passed custom_csi_driver=${custom_csi_driver}. This will be used this run." + printf "\nVerifying that ${custom_csi_driver} is a valid GCSFuse csi driver image ...\n\n" + verify_csi_driver_image ${custom_csi_driver} + if test -z "${use_custom_csi_driver}"; then + echo "use_custom_csi_driver has not been set, so setting it to true as custom_csi_driver has been set to \"${custom_csi_driver}\"" + export use_custom_csi_driver=true + elif [[ ${use_custom_csi_driver} = "false" ]]; then + exitWithError "User has disabled use_custom_csi_driver, while passing a custom_csi_driver. This is unsupported." + elif [[ ${use_custom_csi_driver} != "true" ]]; then + exitWithError "Unsupported value passed for use_custom_csi_driver: ${use_custom_csi_driver}. Supported values: true or false ." + fi + applied_custom_csi_driver=${custom_csi_driver} +fi + test -n "${gcsfuse_branch}" || export gcsfuse_branch="${DEFAULT_GCSFUSE_BRANCH}" # GCSFuse/GKE GCSFuse CSI Driver source code related @@ -210,6 +251,7 @@ function printRunParameters() { echo "appnamespace=\"${appnamespace}\"" echo "ksa=\"${ksa}\"" echo "use_custom_csi_driver=\"${use_custom_csi_driver}\"" + echo "custom_csi_driver=\"${custom_csi_driver}\"" # GCSFuse/GKE GCSFuse CSI Driver source code related echo "src_dir=\"${src_dir}\"" echo "gcsfuse_src_dir=\"${gcsfuse_src_dir}\"" @@ -460,20 +502,12 @@ function ensureRequiredNodePoolConfiguration() { fi } -function enableManagedCsiDriverIfNeeded() { - if ${use_custom_csi_driver}; then - printf "\nDisabling csi add-on ...\n\n" - gcloud -q container clusters update ${cluster_name} \ +function enableManagedCsiDriver() { + printf "\nEnabling csi add-on ...\n\n" + gcloud -q container clusters update ${cluster_name} \ --project=${project_id} \ - --update-addons GcsFuseCsiDriver=DISABLED \ + --update-addons GcsFuseCsiDriver=ENABLED \ --location=${zone} - else - printf "\nEnabling csi add-on ...\n\n" - gcloud -q container clusters update ${cluster_name} \ - --project=${project_id} \ - --update-addons GcsFuseCsiDriver=ENABLED \ - --location=${zone} - fi } function activateCluster() { @@ -516,10 +550,7 @@ uuid() { } function createCustomCsiDriverIfNeeded() { - if ${use_custom_csi_driver}; then - echo "Disabling managed CSI driver ..." - gcloud -q container clusters update ${cluster_name} --project=${project_id} --update-addons GcsFuseCsiDriver=DISABLED --location=${zone} - + if ${use_custom_csi_driver} && test -z "${applied_custom_csi_driver}"; then printf "\nCreating a new custom CSI driver ...\n\n" # Create a bucket (if needed) for storing GCSFuse binaries. @@ -552,7 +583,6 @@ function createCustomCsiDriverIfNeeded() { # Build and install csi driver ensureGcsFuseCsiDriverCode cd "${csi_src_dir}" - make uninstall || true make generate-spec-yaml printf "\nBuilding a new custom CSI driver using the above GCSFuse binary ...\n\n" registry=gcr.io/${project_id}/${USER}/${cluster_name} @@ -564,20 +594,17 @@ function createCustomCsiDriverIfNeeded() { fi stagingversion=$(uuid) make build-image-and-push-multi-arch REGISTRY=${registry} GCSFUSE_PATH=gs://${package_bucket} STAGINGVERSION=${stagingversion} - printf "\nInstalling the new custom CSI driver built above ...\n\n" - make install PROJECT=${project_id} REGISTRY=${registry} STAGINGVERSION=${stagingversion} - cd - - # Wait some time after csi driver installation before deploying pods - # to avoid failures caused by 'the webhook failed to inject the - # sidecar container into the Pod spec' error. - printf "\nSleeping 30 seconds after csi custom driver installation before deploying pods ...\n\n" + readonly subregistry=gcs-fuse-csi-driver-sidecar-mounter + applied_custom_csi_driver=${registry}/${subregistry}:${stagingversion} + printf "\n\nCreated custom csi driver \" ${applied_custom_csi_driver} \" . To use it in future runs, please pass environment variable \" custom_csi_driver=${applied_custom_csi_driver} \" .\n\n" + + # Verify that the csi-driver image is a good image to use.. + printf "\nVerifying that ${applied_custom_csi_driver} is a valid GCSFuse csi driver image ...\n\n" sleep 30 + verify_csi_driver_image ${applied_custom_csi_driver} - else - echo "" - echo "Enabling managed CSI driver ..." - gcloud -q container clusters update ${cluster_name} --project=${project_id} --update-addons GcsFuseCsiDriver=ENABLED --location=${zone} + cd - fi } @@ -595,12 +622,17 @@ function deleteAllPods() { function deployAllFioHelmCharts() { printf "\nDeploying all fio helm charts ...\n\n" - cd "${gke_testing_dir}"/examples/fio && python3 ./run_tests.py --workload-config "${workload_config}" --instance-id ${instance_id} --machine-type="${machine_type}" --project-id=${project_id} --project-number=${project_number} --namespace=${appnamespace} --ksa=${ksa} && cd - + cd "${gke_testing_dir}"/examples/fio + python3 ./run_tests.py --workload-config "${workload_config}" --instance-id ${instance_id} --machine-type="${machine_type}" --project-id=${project_id} --project-number=${project_number} --namespace=${appnamespace} --ksa=${ksa} --custom-csi-driver=${applied_custom_csi_driver} + cd - } function deployAllDlioHelmCharts() { printf "\nDeploying all dlio helm charts ...\n\n" - cd "${gke_testing_dir}"/examples/dlio && python3 ./run_tests.py --workload-config "${workload_config}" --instance-id ${instance_id} --machine-type="${machine_type}" --project-id=${project_id} --project-number=${project_number} --namespace=${appnamespace} --ksa=${ksa} && cd - + cd "${gke_testing_dir}"/examples/dlio + python3 ./run_tests.py --workload-config "${workload_config}" --instance-id ${instance_id} --machine-type="${machine_type}" --project-id=${project_id} --project-number=${project_number} --namespace=${appnamespace} --ksa=${ksa} --custom-csi-driver=${applied_custom_csi_driver} + + cd - } function waitTillAllPodsComplete() { @@ -637,8 +669,13 @@ function waitTillAllPodsComplete() { break else message="\n${num_noncompleted_pods} pod(s) is/are still pending/running (time till timeout=${time_till_timeout} seconds). Will check again in "${pod_wait_time_in_seconds}" seconds. Sleeping for now.\n\n" - message+="\nYou can take a break too if you want. Just kill this run and connect back to it later, for fetching and parsing outputs, using the following command: \n" - message+=" only_parse=true instance_id=${instance_id} project_id=${project_id} project_number=${project_number} zone=${zone} machine_type=${machine_type} use_custom_csi_driver=${use_custom_csi_driver} gcsfuse_src_dir=\"${gcsfuse_src_dir}\" " + message+="\nYou can take a break too if you want. Just kill this run and connect back to it later, for fetching and parsing outputs, using the following command: \n\n" + message+=" only_parse=true instance_id=${instance_id} project_id=${project_id} project_number=${project_number} zone=${zone} machine_type=${machine_type}" + message+=" use_custom_csi_driver=${use_custom_csi_driver}" + if test -n "${custom_csi_driver}"; then + message+=" custom_csi_driver=${custom_csi_driver}" + fi + message+=" gcsfuse_src_dir=\"${gcsfuse_src_dir}\" " if test -d "${csi_src_dir}"; then message+="csi_src_dir=\"${csi_src_dir}\" " fi @@ -687,7 +724,7 @@ if test -z ${only_parse} || ! ${only_parse} ; then ensureGcpAuthsAndConfig ensureGkeCluster # ensureRequiredNodePoolConfiguration - enableManagedCsiDriverIfNeeded + enableManagedCsiDriver activateCluster createKubernetesServiceAccountForCluster @@ -712,3 +749,7 @@ deleteAllPods # parse outputs fetchAndParseFioOutputs fetchAndParseDlioOutputs + +if test -z "${custom_csi_driver}" && test -n "${applied_custom_csi_driver}"; then + printf "\nTo reuse this custom CSI driver in future runs, pass environment variable \" custom_csi_driver=${applied_custom_csi_driver} \" .\n\n" +fi diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py b/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py index f207bb1ca2..196fe7d432 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py @@ -106,6 +106,16 @@ def parse_args(): ' not actually run them.' ), ) + parser.add_argument( + '--custom-csi-driver', + metavar='custom csi driver to be used to run the pods', + help=( + 'pass the full path to the custom csi driver image in the Artifact' + ' registry to be used to run the workloads' + ), + required=False, + default='', + ) args = parser.parse_args() for argument in [ From 9339ee78ae8e045ddbd91ca4fc8719c5a6ee3533 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 13 Mar 2025 10:05:27 +0530 Subject: [PATCH 0292/1298] Fix streaming upload error conversion and add unit tests. (#3053) * fix buffered writes errors in uploader routine and unit tests. * fix tests * fix tests * fix tests * fix tests * fix tests * fix tests * fix review comments. * separate stale file handle tests to separate file for streaming writes. * use atomic pointer type to prevent data race. * add unit test for upload error --- .../bufferedwrites/buffered_write_handler.go | 31 ++---- .../buffered_write_handler_test.go | 31 +++--- internal/bufferedwrites/upload_handler.go | 52 ++++----- .../bufferedwrites/upload_handler_test.go | 81 +++++--------- internal/fs/inode/file.go | 15 ++- internal/fs/stale_file_handle_common_test.go | 65 ++++++++++-- .../fs/stale_file_handle_local_file_test.go | 33 ++---- ...ile_handle_streaming_writes_common_test.go | 84 +++++++++++++++ ...handle_streaming_writes_local_file_test.go | 74 +++++++++++++ ...andle_streaming_writes_synced_file_test.go | 100 ++++++++++++++++++ .../fs/stale_file_handle_synced_file_test.go | 62 +++-------- internal/storage/fake/fake_object_writer.go | 5 + 12 files changed, 439 insertions(+), 194 deletions(-) create mode 100644 internal/fs/stale_file_handle_streaming_writes_common_test.go create mode 100644 internal/fs/stale_file_handle_streaming_writes_local_file_test.go create mode 100644 internal/fs/stale_file_handle_streaming_writes_synced_file_test.go diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 5d7f285979..dcc2f5101f 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -85,7 +85,6 @@ type WriteFileInfo struct { } var ErrOutOfOrderWrite = errors.New("outOfOrder write detected") -var ErrUploadFailure = errors.New("error while uploading object to GCS") type CreateBWHandlerRequest struct { Object *gcs.Object @@ -124,14 +123,11 @@ func NewBWHandler(req *CreateBWHandlerRequest) (bwh BufferedWriteHandler, err er } func (wh *bufferedWriteHandlerImpl) Write(data []byte, offset int64) (err error) { - // Fail early if the uploadHandler has failed. - select { - case <-wh.uploadHandler.SignalUploadFailure(): - return ErrUploadFailure - default: - break + // Fail early if the uploadHandler has already failed. + err = wh.uploadHandler.UploadError() + if err != nil { + return } - if offset != wh.totalSize && offset != wh.truncatedSize { logger.Errorf("BufferedWriteHandler.OutOfOrderError for object: %s, expectedOffset: %d, actualOffset: %d", wh.uploadHandler.objectName, wh.totalSize, offset) @@ -190,27 +186,20 @@ func (wh *bufferedWriteHandlerImpl) Sync() (err error) { // Only logging an error in case of resource leak as upload succeeded. logger.Errorf("blockPool.ClearFreeBlockChannel() failed during sync: %v", err) } - - select { - case <-wh.uploadHandler.SignalUploadFailure(): - return ErrUploadFailure - default: - return nil - } + err = wh.uploadHandler.UploadError() + return } // Flush finalizes the upload. func (wh *bufferedWriteHandlerImpl) Flush() (*gcs.MinObject, error) { // Fail early if upload already failed. - select { - case <-wh.uploadHandler.SignalUploadFailure(): - return nil, ErrUploadFailure - default: - break + err := wh.uploadHandler.UploadError() + if err != nil { + return nil, err } // In case it is a truncated file, upload empty blocks as required. - err := wh.writeDataForTruncatedSize() + err = wh.writeDataForTruncatedSize() if err != nil { return nil, err } diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 55a62ba734..817810839a 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -15,6 +15,7 @@ package bufferedwrites import ( + "errors" "strings" "testing" "time" @@ -31,6 +32,8 @@ import ( const chunkTransferTimeoutSecs int64 = 10 +var errUploadFailure = errors.New("error while uploading object to GCS") + type BufferedWriteTest struct { bwh BufferedWriteHandler suite.Suite @@ -161,12 +164,12 @@ func (testSuite *BufferedWriteTest) TestWriteWithSignalUploadFailureInBetween() assert.Equal(testSuite.T(), bwhImpl.mtime, fileInfo.Mtime) assert.Equal(testSuite.T(), int64(5), fileInfo.TotalSize) - // Close the channel to simulate failure in uploader. - close(bwhImpl.uploadHandler.SignalUploadFailure()) + // Set an error to simulate failure in uploader. + bwhImpl.uploadHandler.uploadError.Store(&errUploadFailure) err = testSuite.bwh.Write([]byte("hello"), 5) require.Error(testSuite.T(), err) - assert.Equal(testSuite.T(), err, ErrUploadFailure) + assert.Equal(testSuite.T(), err, errUploadFailure) } func (testSuite *BufferedWriteTest) TestWriteAtTruncatedOffset() { @@ -237,12 +240,12 @@ func (testSuite *BufferedWriteTest) TestFlushWithSignalUploadFailureDuringWrite( require.Nil(testSuite.T(), err) bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) - // Close the channel to simulate failure in uploader. - close(bwhImpl.uploadHandler.SignalUploadFailure()) + // Set an error to simulate failure in uploader. + bwhImpl.uploadHandler.uploadError.Store(&errUploadFailure) obj, err := testSuite.bwh.Flush() require.Error(testSuite.T(), err) - assert.Equal(testSuite.T(), err, ErrUploadFailure) + assert.Equal(testSuite.T(), err, errUploadFailure) assert.Nil(testSuite.T(), obj) } @@ -251,20 +254,20 @@ func (testSuite *BufferedWriteTest) TestFlushWithMultiBlockWritesAndSignalUpload assert.NoError(testSuite.T(), err) // Upload and sync 5 blocks. testSuite.TestSync5InProgressBlocks() - // Close the channel to simulate failure in uploader. + // Set an error to simulate failure in uploader. bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) - close(bwhImpl.uploadHandler.SignalUploadFailure()) + bwhImpl.uploadHandler.uploadError.Store(&errUploadFailure) // Write 5 more blocks. for i := 0; i < 5; i++ { err := testSuite.bwh.Write(buffer, int64(blockSize*(i+5))) require.Error(testSuite.T(), err) - assert.Equal(testSuite.T(), ErrUploadFailure, err) + assert.Equal(testSuite.T(), errUploadFailure, err) } obj, err := testSuite.bwh.Flush() require.Error(testSuite.T(), err) - assert.Equal(testSuite.T(), err, ErrUploadFailure) + assert.Equal(testSuite.T(), err, errUploadFailure) assert.Nil(testSuite.T(), obj) } @@ -294,14 +297,14 @@ func (testSuite *BufferedWriteTest) TestSyncBlocksWithError() { err = testSuite.bwh.Write(buffer, int64(blockSize*i)) require.Nil(testSuite.T(), err) } - // Close the channel to simulate failure in uploader. + // Set an error to simulate failure in uploader. bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) - close(bwhImpl.uploadHandler.SignalUploadFailure()) + bwhImpl.uploadHandler.uploadError.Store(&errUploadFailure) err = testSuite.bwh.Sync() assert.Error(testSuite.T(), err) - assert.Equal(testSuite.T(), ErrUploadFailure, err) + assert.Equal(testSuite.T(), errUploadFailure, err) } func (testSuite *BufferedWriteTest) TestFlushWithNonZeroTruncatedLengthForEmptyObject() { @@ -415,5 +418,5 @@ func (testSuite *BufferedWriteTest) TestReFlushAfterUploadFails() { require.Error(testSuite.T(), err) assert.Nil(testSuite.T(), obj) - assert.ErrorContains(testSuite.T(), err, ErrUploadFailure.Error()) + assert.ErrorContains(testSuite.T(), err, errUploadFailure.Error()) } diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 3c62d34078..922b6fe7fe 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -22,6 +22,7 @@ import ( "fmt" "io" "sync" + "sync/atomic" "github.com/googlecloudplatform/gcsfuse/v2/internal/block" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" @@ -43,10 +44,8 @@ type UploadHandler struct { // writer to resumable upload the blocks to GCS. writer gcs.Writer - // signalUploadFailure channel will propagate the upload error to file - // inode. This signals permanent failure in the buffered write job. - signalUploadFailure chan error - + // uploadError stores atomic pointer to the error seen by uploader. + uploadError atomic.Pointer[error] // CancelFunc persisted to cancel the uploads in case of unlink operation. cancelFunc context.CancelFunc @@ -78,7 +77,6 @@ func newUploadHandler(req *CreateUploadHandlerRequest) *UploadHandler { objectName: req.ObjectName, obj: req.Object, blockSize: req.BlockSize, - signalUploadFailure: make(chan error, 1), chunkTransferTimeout: req.ChunkTransferTimeoutSecs, } return uh @@ -116,17 +114,25 @@ func (uh *UploadHandler) createObjectWriter() (err error) { return } +func (uh *UploadHandler) UploadError() (err error) { + if uploadError := uh.uploadError.Load(); uploadError != nil { + err = *uploadError + } + return +} + // uploader is the single-threaded goroutine that uploads blocks. func (uh *UploadHandler) uploader() { for currBlock := range uh.uploadCh { - select { - case <-uh.signalUploadFailure: - default: - _, err := io.Copy(uh.writer, currBlock.Reader()) - if err != nil { - logger.Errorf("buffered write upload failed for object %s: error in io.Copy: %v", uh.objectName, err) - uh.closeUploadFailureChannel() - } + if uh.UploadError() != nil { + uh.wg.Done() + continue + } + _, err := io.Copy(uh.writer, currBlock.Reader()) + if err != nil { + logger.Errorf("buffered write upload failed for object %s: error in io.Copy: %v", uh.objectName, err) + err = gcs.GetGCSError(err) + uh.uploadError.Store(&err) } // Put back the uploaded block on the freeBlocksChannel for re-use. uh.freeBlocksCh <- currBlock @@ -150,8 +156,10 @@ func (uh *UploadHandler) Finalize() (*gcs.MinObject, error) { obj, err := uh.bucket.FinalizeUpload(context.Background(), uh.writer) if err != nil { - uh.closeUploadFailureChannel() - return nil, fmt.Errorf("FinalizeUpload failed for object %s: %w", uh.objectName, err) + // FinalizeUpload already returns GCSerror so no need to convert again. + uh.uploadError.Store(&err) + logger.Errorf("FinalizeUpload failed for object %s: %v", uh.objectName, err) + return nil, err } return obj, nil } @@ -165,10 +173,6 @@ func (uh *UploadHandler) CancelUpload() { uh.wg.Wait() } -func (uh *UploadHandler) SignalUploadFailure() chan error { - return uh.signalUploadFailure -} - func (uh *UploadHandler) AwaitBlocksUpload() { uh.wg.Wait() } @@ -192,13 +196,3 @@ func (uh *UploadHandler) Destroy() { } } } - -// Closes the channel if not already closed to signal upload failure. -func (uh *UploadHandler) closeUploadFailureChannel() { - select { - case <-uh.signalUploadFailure: - break - default: - close(uh.signalUploadFailure) - } -} diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 0a93701198..19a06f908b 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -160,8 +160,7 @@ func (t *UploadHandlerTest) TestFinalizeWhenFinalizeUploadFails() { require.Error(t.T(), err) assert.Nil(t.T(), obj) assert.ErrorContains(t.T(), err, "taco") - assert.ErrorContains(t.T(), err, "FinalizeUpload failed for object") - assertUploadFailureSignal(t.T(), t.uh) + assertUploadFailureError(t.T(), t.uh) } func (t *UploadHandlerTest) TestUploadSingleBlockThrowsErrorInCopy() { @@ -180,8 +179,8 @@ func (t *UploadHandlerTest) TestUploadSingleBlockThrowsErrorInCopy() { err = t.uh.Upload(b) require.NoError(t.T(), err) - // Expect an error on the signalUploadFailure channel due to error while copying content to GCS writer. - assertUploadFailureSignal(t.T(), t.uh) + // Expect an error on upload due to error while copying content to GCS writer. + assertUploadFailureError(t.T(), t.uh) assertAllBlocksProcessed(t.T(), t.uh) assert.Equal(t.T(), 1, len(t.uh.freeBlocksCh)) } @@ -207,19 +206,22 @@ func (t *UploadHandlerTest) TestUploadMultipleBlocksThrowsErrorInCopy() { require.NoError(t.T(), err) } - assertUploadFailureSignal(t.T(), t.uh) + assertUploadFailureError(t.T(), t.uh) assertAllBlocksProcessed(t.T(), t.uh) - assert.Equal(t.T(), 4, len(t.uh.freeBlocksCh)) + assert.Equal(t.T(), 2, len(t.uh.freeBlocksCh)) } -func assertUploadFailureSignal(t *testing.T, handler *UploadHandler) { +func assertUploadFailureError(t *testing.T, handler *UploadHandler) { t.Helper() - - select { - case <-handler.signalUploadFailure: - break - case <-time.After(200 * time.Millisecond): - t.Error("Expected an error on signalUploadFailure channel") + for { + select { + case <-time.After(200 * time.Millisecond): + t.Error("Expected an error in uploader") + default: + if handler.UploadError() != nil { + return + } + } } } @@ -239,15 +241,22 @@ func assertAllBlocksProcessed(t *testing.T, handler *UploadHandler) { } } -func TestSignalUploadFailure(t *testing.T) { - mockSignalUploadFailure := make(chan error) - uploadHandler := &UploadHandler{ - signalUploadFailure: mockSignalUploadFailure, - } +func TestUploadErrorReturnsError(t *testing.T) { + mockUploadError := fmt.Errorf("error") + uploadHandler := &UploadHandler{} + uploadHandler.uploadError.Store(&mockUploadError) + + actualUploadError := uploadHandler.UploadError() + + assert.Equal(t, mockUploadError, actualUploadError) +} - actualChannel := uploadHandler.SignalUploadFailure() +func TestUploadErrorReturnsNil(t *testing.T) { + uploadHandler := &UploadHandler{} - assert.Equal(t, mockSignalUploadFailure, actualChannel) + actualUploadError := uploadHandler.UploadError() + + assert.Nil(t, actualUploadError) } func (t *UploadHandlerTest) TestMultipleBlockAwaitBlocksUpload() { @@ -390,35 +399,3 @@ func (t *UploadHandlerTest) createBlocks(count int) []block.Block { return blocks } - -func (t *UploadHandlerTest) TestUploadHandler_closeUploadFailureChannel() { - testCases := []struct { - name string - initialState chan error - }{ - { - name: "Channel_initially_open", - initialState: make(chan error), - }, - { - name: "Channel_already_closed", - initialState: func() chan error { ch := make(chan error); close(ch); return ch }(), - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func() { - uh := &UploadHandler{ - signalUploadFailure: tc.initialState, - } - - uh.closeUploadFailureChannel() - - select { - case <-uh.signalUploadFailure: - default: - t.T().Error("expected channel to be closed but it was not") - } - }) - } -} diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 717d161f36..a546a15cc1 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -598,6 +598,12 @@ func (f *FileInode) writeUsingTempFile(ctx context.Context, data []byte, offset // LOCKS_REQUIRED(f.mu) func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, offset int64) error { err := f.bwh.Write(data, offset) + var preconditionErr *gcs.PreconditionError + if errors.As(err, &preconditionErr) { + return &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("f.bwh.Write(): %w", err), + } + } if err != nil && !errors.Is(err, bufferedwrites.ErrOutOfOrderWrite) { return fmt.Errorf("write to buffered write handler failed: %w", err) } @@ -768,7 +774,14 @@ func (f *FileInode) Sync(ctx context.Context) (gcsSynced bool, err error) { if f.bwh != nil { // bwh.Sync does not finalize the upload, so return gcsSynced as false. - return false, f.bwh.Sync() + err = f.bwh.Sync() + var preconditionErr *gcs.PreconditionError + if errors.As(err, &preconditionErr) { + return false, &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("f.bwh.Sync(): %w", err), + } + } + return } err = f.syncUsingContent(ctx) if err != nil { diff --git a/internal/fs/stale_file_handle_common_test.go b/internal/fs/stale_file_handle_common_test.go index 25f82d5b3d..00fba5e849 100644 --- a/internal/fs/stale_file_handle_common_test.go +++ b/internal/fs/stale_file_handle_common_test.go @@ -17,7 +17,10 @@ package fs_test import ( "os" "path" + "syscall" + "testing" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" @@ -34,21 +37,71 @@ type staleFileHandleCommon struct { suite.Suite } +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// + +func commonServerConfig() *cfg.Config { + return &cfg.Config{ + FileSystem: cfg.FileSystemConfig{ + PreconditionErrors: true, + }, + MetadataCache: cfg.MetadataCacheConfig{ + TtlSecs: 0, + }, + } + +} + +func clobberFile(t *testing.T, fileName, content string) { + t.Helper() + _, err := storageutil.CreateObject( + ctx, + bucket, + fileName, + []byte(content)) + assert.NoError(t, err) +} + +func createGCSObject(t *testing.T, fileName, content string) *os.File { + t.Helper() + _, err := storageutil.CreateObject( + ctx, + bucket, + fileName, + []byte(content)) + assert.NoError(t, err) + // Open file handle to read or write. + fh, err := os.OpenFile(path.Join(mntDir, fileName), os.O_RDWR|syscall.O_DIRECT, filePerms) + assert.NoError(t, err) + return fh +} + +func (t *staleFileHandleCommon) SetupSuite() { + t.serverCfg.NewConfig = commonServerConfig() + t.fsTest.SetUpTestSuite() +} + +func (t *staleFileHandleCommon) TearDownTest() { + // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. + t.fsTest.TearDown() +} + +func (t *staleFileHandleCommon) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} + // ////////////////////////////////////////////////////////////////////// // Tests // ////////////////////////////////////////////////////////////////////// + func (t *staleFileHandleCommon) TestClobberedFileSyncAndCloseThrowsStaleFileHandleError() { // Dirty the file by giving it some contents. n, err := t.f1.Write([]byte("taco")) assert.NoError(t.T(), err) assert.Equal(t.T(), 4, n) // Replace the underlying object with a new generation. - _, err = storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("foobar")) - assert.NoError(t.T(), err) + clobberFile(t.T(), "foo", "foobar") err = t.f1.Sync() diff --git a/internal/fs/stale_file_handle_local_file_test.go b/internal/fs/stale_file_handle_local_file_test.go index 106a2105d5..f660389837 100644 --- a/internal/fs/stale_file_handle_local_file_test.go +++ b/internal/fs/stale_file_handle_local_file_test.go @@ -17,7 +17,6 @@ package fs_test import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/suite" ) @@ -30,32 +29,20 @@ type staleFileHandleLocalFile struct { staleFileHandleCommon } -func TestStaleFileHandleLocalFile(t *testing.T) { - suite.Run(t, new(staleFileHandleLocalFile)) -} - -func (t *staleFileHandleLocalFile) SetupSuite() { - t.serverCfg.NewConfig = &cfg.Config{ - FileSystem: cfg.FileSystemConfig{ - PreconditionErrors: true, - }, - MetadataCache: cfg.MetadataCacheConfig{ - TtlSecs: 0, - }, - } - t.fsTest.SetUpTestSuite() -} - -func (t *staleFileHandleLocalFile) TearDownSuite() { - t.fsTest.TearDownTestSuite() -} +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// func (t *staleFileHandleLocalFile) SetupTest() { // Create a local file. _, t.f1 = operations.CreateLocalFile(ctx, t.T(), mntDir, bucket, "foo") } -func (t *staleFileHandleLocalFile) TearDownTest() { - // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. - t.fsTest.TearDown() +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// + +// Executes all stale handle tests for local files. +func TestStaleFileHandleLocalFile(t *testing.T) { + suite.Run(t, new(staleFileHandleLocalFile)) } diff --git a/internal/fs/stale_file_handle_streaming_writes_common_test.go b/internal/fs/stale_file_handle_streaming_writes_common_test.go new file mode 100644 index 0000000000..aa2a0808c5 --- /dev/null +++ b/internal/fs/stale_file_handle_streaming_writes_common_test.go @@ -0,0 +1,84 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs_test + +import ( + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type staleFileHandleStreamingWritesCommon struct { + // fsTest has f1 *osFile and f2 *osFile which we will reuse here. + fsTest + suite.Suite +} + +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// + +func (t *staleFileHandleStreamingWritesCommon) SetupSuite() { + serverCfg := commonServerConfig() + serverCfg.Write.EnableStreamingWrites = true + serverCfg.Write.BlockSizeMb = operations.MiB + serverCfg.Write.MaxBlocksPerFile = 1 + + t.serverCfg.NewConfig = serverCfg + t.mountCfg.DisableWritebackCaching = true + + t.fsTest.SetUpTestSuite() +} + +func (t *staleFileHandleStreamingWritesCommon) TearDownTest() { + // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. + t.fsTest.TearDown() +} + +func (t *staleFileHandleStreamingWritesCommon) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} + +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// + +func (t *staleFileHandleStreamingWritesCommon) TestWriteFileSyncFileClobberedFlushThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + data, err := operations.GenerateRandomData(operations.MiB * 4) + assert.NoError(t.T(), err) + _, err = t.f1.WriteAt(data, 0) + assert.NoError(t.T(), err) + err = t.f1.Sync() + assert.NoError(t.T(), err) + // Replace the underlying object with a new generation. + clobberFile(t.T(), "foo", "foobar") + + err = t.f1.Close() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file. + t.f1 = nil + // Validate that object is not updated with un-synced content. + contents, err := storageutil.ReadObject(ctx, bucket, "foo") + assert.NoError(t.T(), err) + assert.Equal(t.T(), "foobar", string(contents)) +} diff --git a/internal/fs/stale_file_handle_streaming_writes_local_file_test.go b/internal/fs/stale_file_handle_streaming_writes_local_file_test.go new file mode 100644 index 0000000000..66dea4cbc5 --- /dev/null +++ b/internal/fs/stale_file_handle_streaming_writes_local_file_test.go @@ -0,0 +1,74 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs_test + +import ( + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type staleFileHandleStreamingWritesLocalFile struct { + staleFileHandleStreamingWritesCommon +} + +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// + +func (t *staleFileHandleStreamingWritesLocalFile) SetupTest() { + // Create a local file. + _, t.f1 = operations.CreateLocalFile(ctx, t.T(), mntDir, bucket, "foo") +} + +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// + +func (t *staleFileHandleStreamingWritesLocalFile) TestClobberedWriteFileSyncAndCloseThrowsStaleFileHandleError() { + // Replace the underlying object with a new generation. + clobberFile(t.T(), "foo", "foobar") + // Writing to file will return Stale File Handle Error. + data, err := operations.GenerateRandomData(operations.MiB * 4) + assert.NoError(t.T(), err) + + _, err = t.f1.WriteAt(data, 0) + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = t.f1.Sync() + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = t.f1.Close() + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file. + t.f1 = nil + // Validate that object is not updated with un-synced content. + contents, err := storageutil.ReadObject(ctx, bucket, "foo") + assert.NoError(t.T(), err) + assert.Equal(t.T(), "foobar", string(contents)) +} + +// Executes all stale handle tests for local files with streaming writes. +func TestStaleFileHandleStreamingWritesLocalFile(t *testing.T) { + ts := new(staleFileHandleStreamingWritesLocalFile) + suite.Run(t, ts) +} diff --git a/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go b/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go new file mode 100644 index 0000000000..2d0aea1ea8 --- /dev/null +++ b/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go @@ -0,0 +1,100 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs_test + +import ( + "os" + "path" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type staleFileHandleStreamingWritesSyncedFile struct { + staleFileHandleStreamingWritesCommon +} + +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// + +func (t *staleFileHandleStreamingWritesSyncedFile) SetupTest() { + // Create an empty object on bucket. + t.f1 = createGCSObject(t.T(), "foo", "") +} + +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// + +func (t *staleFileHandleStreamingWritesSyncedFile) TestWriteToClobberedFileThrowsStaleFileHandleError() { + // Replace the underlying object with a new generation. + clobberFile(t.T(), "foo", "foobar") + // Writing to file will return Stale File Handle Error. + data, err := operations.GenerateRandomData(operations.MiB * 4) + assert.NoError(t.T(), err) + + _, err = t.f1.WriteAt(data, 0) + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = t.f1.Sync() + assert.NoError(t.T(), err) + err = t.f1.Close() + assert.NoError(t.T(), err) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file. + t.f1 = nil + // Validate that object is not updated with un-synced content. + contents, err := storageutil.ReadObject(ctx, bucket, "foo") + assert.NoError(t.T(), err) + assert.Equal(t.T(), "foobar", string(contents)) +} + +func (t *staleFileHandleStreamingWritesSyncedFile) TestRenameFileWriteThrowsStaleFileHandleError() { + // Rename the object. + err := os.Rename(t.f1.Name(), path.Join(mntDir, "bar")) + assert.NoError(t.T(), err) + // Writing to file will return Stale File Handle Error. + data, err := operations.GenerateRandomData(operations.MiB * 4) + assert.NoError(t.T(), err) + + _, err = t.f1.WriteAt(data, 0) + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + err = t.f1.Sync() + assert.NoError(t.T(), err) + err = t.f1.Close() + assert.NoError(t.T(), err) + // Make f1 nil, so that another attempt is not taken in TearDown to close the + // file. + t.f1 = nil + // Validate that object is not updated with un-synced content. + contents, err := storageutil.ReadObject(ctx, bucket, "bar") + assert.NoError(t.T(), err) + assert.Equal(t.T(), "", string(contents)) +} + +// Executes all stale handle tests for gcs synced files with streaming writes. +func TestStaleFileHandleStreamingWritesSyncedFile(t *testing.T) { + ts := new(staleFileHandleStreamingWritesSyncedFile) + suite.Run(t, ts) +} diff --git a/internal/fs/stale_file_handle_synced_file_test.go b/internal/fs/stale_file_handle_synced_file_test.go index 2a71303d8b..95f1c56826 100644 --- a/internal/fs/stale_file_handle_synced_file_test.go +++ b/internal/fs/stale_file_handle_synced_file_test.go @@ -17,10 +17,8 @@ package fs_test import ( "os" "path" - "syscall" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" @@ -35,57 +33,25 @@ type staleFileHandleSyncedFile struct { staleFileHandleCommon } -func TestStaleFileHandleSyncedFile(t *testing.T) { - suite.Run(t, new(staleFileHandleSyncedFile)) -} - -func (t *staleFileHandleSyncedFile) SetupSuite() { - t.serverCfg.NewConfig = &cfg.Config{ - FileSystem: cfg.FileSystemConfig{ - PreconditionErrors: true, - }, - MetadataCache: cfg.MetadataCacheConfig{ - TtlSecs: 0, - }, - } - t.fsTest.SetUpTestSuite() -} +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// -func (t *staleFileHandleSyncedFile) TearDownSuite() { - t.fsTest.TearDownTestSuite() -} func (t *staleFileHandleSyncedFile) SetupTest() { // Create an object on bucket. - _, err := storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("bar")) - assert.NoError(t.T(), err) - // Open file handle to read or write. - t.f1, err = os.OpenFile(path.Join(mntDir, "foo"), os.O_RDWR|syscall.O_DIRECT, filePerms) - assert.NoError(t.T(), err) -} - -func (t *staleFileHandleSyncedFile) TearDownTest() { - // fsTest Cleanups to clean up mntDir and close t.f1 and t.f2. - t.fsTest.TearDown() + t.f1 = createGCSObject(t.T(), "foo", "bar") } // ////////////////////////////////////////////////////////////////////// // Tests // ////////////////////////////////////////////////////////////////////// + func (t *staleFileHandleSyncedFile) TestClobberedFileReadThrowsStaleFileHandleError() { // Replace the underlying object with a new generation. - _, err := storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("foobar")) - assert.NoError(t.T(), err) + clobberFile(t.T(), "foo", "foobar") buffer := make([]byte, 6) - _, err = t.f1.Read(buffer) + _, err := t.f1.Read(buffer) operations.ValidateStaleNFSFileHandleError(t.T(), err) // Validate that object is updated with new content. @@ -96,14 +62,9 @@ func (t *staleFileHandleSyncedFile) TestClobberedFileReadThrowsStaleFileHandleEr func (t *staleFileHandleSyncedFile) TestClobberedFileFirstWriteThrowsStaleFileHandleError() { // Replace the underlying object with a new generation. - _, err := storageutil.CreateObject( - ctx, - bucket, - "foo", - []byte("foobar")) - assert.NoError(t.T(), err) + clobberFile(t.T(), "foo", "foobar") - _, err = t.f1.Write([]byte("taco")) + _, err := t.f1.Write([]byte("taco")) operations.ValidateStaleNFSFileHandleError(t.T(), err) // Attempt to sync to file should not result in error as we first check if the @@ -164,3 +125,8 @@ func (t *staleFileHandleSyncedFile) TestFileDeletedRemotelySyncAndCloseThrowsSta // file. t.f1 = nil } + +// Executes all stale handle tests for gcs synced files. +func TestStaleFileHandleSyncedFile(t *testing.T) { + suite.Run(t, new(staleFileHandleSyncedFile)) +} diff --git a/internal/storage/fake/fake_object_writer.go b/internal/storage/fake/fake_object_writer.go index c213d975ee..581bc33c1b 100644 --- a/internal/storage/fake/fake_object_writer.go +++ b/internal/storage/fake/fake_object_writer.go @@ -39,6 +39,11 @@ type FakeObjectWriter struct { } func (w *FakeObjectWriter) Write(p []byte) (n int, err error) { + contents := w.buf.Bytes() + // Validate for preconditions. + if err := preconditionChecks(w.bkt, w.req, contents); err != nil { + return 0, err + } return w.buf.Write(p) } From dfa228cd2a727c42dbb8478d3d52d11861bc43a8 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 13 Mar 2025 10:06:45 +0530 Subject: [PATCH 0293/1298] Flush local files with streaming writes for rename operation. (#3060) * test changes * fix tests * test changes * revert test changes * fix tests * fix unit tests. * fix comments * fix integration tests * fix review comments. * fix review comments * fix locking of fileInode * remove unused import * refactor Rename method. * fix function name. * add single line comment * refactor flushPendingBufferedWrites * fixes for review comments. * fixes for review comments. * remove lock comment. * add test for sync after rename. * use random names for new file created on rename. --- internal/fs/fs.go | 79 +++++++------------ internal/fs/streaming_writes_common_test.go | 22 ++++++ .../streaming_writes_empty_gcs_object_test.go | 22 +----- .../fs/streaming_writes_local_file_test.go | 1 + tools/integration_tests/local_file/rename.go | 2 +- .../streaming_writes/rename_file_test.go | 51 +++++++++++- 6 files changed, 100 insertions(+), 77 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index d99f460dc7..cb2ae83d1e 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2019,87 +2019,64 @@ func (fs *fileSystem) Rename( } } - // If object to be renamed is a local file inode (un-synced), rename operation is not supported. - localChild, err := fs.lookUpLocalFileInode(oldParent, op.OldName) + child, err := fs.lookUpOrCreateChildInode(ctx, oldParent, op.OldName) if err != nil { return err } - if localChild != nil { - fs.unlockAndDecrementLookupCount(localChild, 1) - return fmt.Errorf("cannot rename open file %q: %w", op.OldName, syscall.ENOTSUP) + if child == nil { + return fuse.ENOENT } + child.DecrementLookupCount(1) + child.Unlock() - // Else find the object in the old location (on GCS). - oldParent.Lock() - child, err := oldParent.LookUpChild(ctx, op.OldName) - oldParent.Unlock() - - if err != nil { - err = fmt.Errorf("LookUpChild: %w", err) - return err + childBktOwned, ok := child.(inode.BucketOwnedInode) + if !ok { // Won't happen in ideal case. + return fmt.Errorf("child inode (id %v) is not owned by any bucket", child.ID()) } - if child == nil { - err = fuse.ENOENT - return err - } - - if child.FullName.IsDir() { + if child.Name().IsDir() { // If 'enable-hns' flag is false, the bucket type is set to 'NonHierarchical' even for HNS buckets because the control client is nil. // Therefore, an additional 'enable hns' check is not required here. - if child.Bucket.BucketType().Hierarchical { + if childBktOwned.Bucket().BucketType().Hierarchical { return fs.renameHierarchicalDir(ctx, oldParent, op.OldName, newParent, op.NewName) } return fs.renameNonHierarchicalDir(ctx, oldParent, op.OldName, newParent, op.NewName) } - - return fs.renameFile(ctx, op, child, oldParent, newParent) + childFileInode, ok := child.(*inode.FileInode) + if !ok { + return fmt.Errorf("child inode (id %v) is neither file nor directory inode", child.ID()) + } + // TODO(b/402335988): Fix rename flow for local files when streaming writes is disabled. + // If object to be renamed is a local file inode and streaming writes are disabled, rename operation is not supported. + if childFileInode.IsLocal() && !fs.newConfig.Write.EnableStreamingWrites { + return fmt.Errorf("cannot rename open file %q: %w", op.OldName, syscall.ENOTSUP) + } + return fs.renameFile(ctx, op, childFileInode, oldParent, newParent) } // LOCKS_EXCLUDED(oldParent) // LOCKS_EXCLUDED(newParent) -func (fs *fileSystem) renameFile(ctx context.Context, op *fuseops.RenameOp, child *inode.Core, oldParent inode.DirInode, newParent inode.DirInode) error { - updatedMinObject, err := fs.flushPendingWrites(ctx, child) +func (fs *fileSystem) renameFile(ctx context.Context, op *fuseops.RenameOp, oldObject *inode.FileInode, oldParent, newParent inode.DirInode) error { + updatedMinObject, err := fs.flushPendingWrites(ctx, oldObject) if err != nil { - return fmt.Errorf("flushPendingWrites error :%v", err) + return fmt.Errorf("flushPendingWrites: %w", err) } - - if (child.Bucket.BucketType().Hierarchical && fs.enableAtomicRenameObject) || child.Bucket.BucketType().Zonal { + if (oldObject.Bucket().BucketType().Hierarchical && fs.enableAtomicRenameObject) || oldObject.Bucket().BucketType().Zonal { return fs.renameHierarchicalFile(ctx, oldParent, op.OldName, updatedMinObject, newParent, op.NewName) } return fs.renameNonHierarchicalFile(ctx, oldParent, op.OldName, updatedMinObject, newParent, op.NewName) } -// LOCKS_EXCLUDED(fs.mu) // LOCKS_EXCLUDED(fileInode) -func (fs *fileSystem) flushPendingWrites(ctx context.Context, child *inode.Core) (minObject *gcs.MinObject, err error) { +func (fs *fileSystem) flushPendingWrites(ctx context.Context, fileInode *inode.FileInode) (minObject *gcs.MinObject, err error) { // We will return modified minObject if flush is done, otherwise the original // minObject is returned. Original minObject is the one passed in the request. - minObject = child.MinObject + fileInode.Lock() + defer fileInode.Unlock() + minObject = fileInode.Source() if !fs.newConfig.Write.EnableStreamingWrites { return } - - fs.mu.Lock() - existingInode, ok := fs.generationBackedInodes[child.FullName] - fs.mu.Unlock() - // If this is not an existing file, then nothing to do. - // We shouldn't hit scenario ideally since we checked for local file and - // explicit-dirs (flat bucket) before reaching this step. - if !ok { - logger.Warnf("Encountered a non-fileInode in rename file path %d", existingInode.ID()) - return - } - - fileInode, isFileInode := existingInode.(*inode.FileInode) - // Same as above comment. This should be a file for sure. - if !isFileInode { - logger.Errorf("Encountered a non-fileInode in rename file path %d", existingInode.ID()) - return - } - - fileInode.Lock() - defer fileInode.Unlock() // Try to flush if there are any pending writes. err = fs.flushFile(ctx, fileInode) minObject = fileInode.Source() diff --git a/internal/fs/streaming_writes_common_test.go b/internal/fs/streaming_writes_common_test.go index bec2d460a9..7ba4f8300c 100644 --- a/internal/fs/streaming_writes_common_test.go +++ b/internal/fs/streaming_writes_common_test.go @@ -19,6 +19,8 @@ package fs_test import ( "os" + "path" + "strings" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" @@ -55,3 +57,23 @@ func (t *StreamingWritesCommonTest) TestUnlinkAfterWrite() { t.TestUnlinkBeforeWrite() } + +func (t *StreamingWritesCommonTest) TestRenameFileWithPendingWrites() { + _, err := t.f1.Write([]byte("tacos")) + assert.NoError(t.T(), err) + newFilePath := path.Join(mntDir, "test.txt") + // Check that new file doesn't exist. + _, err = os.Stat(newFilePath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + + err = os.Rename(t.f1.Name(), newFilePath) + + assert.NoError(t.T(), err) + _, err = os.Stat(t.f1.Name()) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + content, err := os.ReadFile(newFilePath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), "tacos", string(content)) +} diff --git a/internal/fs/streaming_writes_empty_gcs_object_test.go b/internal/fs/streaming_writes_empty_gcs_object_test.go index 3774dc64be..95be49b140 100644 --- a/internal/fs/streaming_writes_empty_gcs_object_test.go +++ b/internal/fs/streaming_writes_empty_gcs_object_test.go @@ -19,7 +19,6 @@ package fs_test import ( "os" "path" - "strings" "syscall" "testing" @@ -43,6 +42,7 @@ func (t *StreamingWritesEmptyGCSObjectTest) SetupSuite() { MaxBlocksPerFile: 10, }, } + t.mountCfg.DisableWritebackCaching = true t.fsTest.SetUpTestSuite() } @@ -66,26 +66,6 @@ func (t *StreamingWritesEmptyGCSObjectTest) SetupTest() { assert.NoError(t.T(), err) } -func (t *StreamingWritesEmptyGCSObjectTest) TestRenameFileWithPendingWrites() { - _, err := t.f1.Write([]byte("tacos")) - assert.NoError(t.T(), err) - newFilePath := path.Join(mntDir, "test.txt") - // Check that new file doesn't exist. - _, err = os.Stat(newFilePath) - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - - err = os.Rename(t.f1.Name(), newFilePath) - - assert.NoError(t.T(), err) - _, err = os.Stat(t.f1.Name()) - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - content, err := os.ReadFile(newFilePath) - assert.NoError(t.T(), err) - assert.Equal(t.T(), "tacos", string(content)) -} - func (t *StreamingWritesEmptyGCSObjectTest) TearDownTest() { t.fsTest.TearDown() } diff --git a/internal/fs/streaming_writes_local_file_test.go b/internal/fs/streaming_writes_local_file_test.go index 5d9dcc4c75..3366eeed2c 100644 --- a/internal/fs/streaming_writes_local_file_test.go +++ b/internal/fs/streaming_writes_local_file_test.go @@ -47,6 +47,7 @@ func (t *StreamingWritesLocalFileTest) SetupSuite() { }, MetadataCache: cfg.MetadataCacheConfig{TtlSecs: 0}, } + t.mountCfg.DisableWritebackCaching = true t.fsTest.SetUpTestSuite() } diff --git a/tools/integration_tests/local_file/rename.go b/tools/integration_tests/local_file/rename.go index d4c50e4978..8f31976bc1 100644 --- a/tools/integration_tests/local_file/rename.go +++ b/tools/integration_tests/local_file/rename.go @@ -41,7 +41,7 @@ func verifyRenameOperationNotSupported(err error, t *testing.T) { // Tests //////////////////////////////////////////////////////////////////////// -func (t *CommonLocalFileTestSuite) TestRenameOfLocalFileFails() { +func (t *localFileTestSuite) TestRenameOfLocalFileFails() { testDirPath = setup.SetupTestDirectory(testDirName) // Create local file with some content. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) diff --git a/tools/integration_tests/streaming_writes/rename_file_test.go b/tools/integration_tests/streaming_writes/rename_file_test.go index 77da2580f5..02d8e84a06 100644 --- a/tools/integration_tests/streaming_writes/rename_file_test.go +++ b/tools/integration_tests/streaming_writes/rename_file_test.go @@ -22,16 +22,14 @@ import ( "github.com/stretchr/testify/require" ) -// os.Rename can't be invoked over local files. It's failing with file not found error. -// Hence running this test only for empty GCS file. -func (t *defaultMountEmptyGCSFile) TestRenameBeforeFileIsFlushed() { +func (t *defaultMountCommonTest) TestRenameBeforeFileIsFlushed() { operations.WriteWithoutClose(t.f1, FileContents, t.T()) operations.WriteWithoutClose(t.f1, FileContents, t.T()) operations.VerifyStatFile(t.filePath, int64(2*len(FileContents)), FilePerms, t.T()) err := t.f1.Sync() require.NoError(t.T(), err) - newFile := "newFile.txt" + newFile := "new" + t.fileName destDirPath := path.Join(testDirPath, newFile) err = operations.RenameFile(t.filePath, destDirPath) @@ -43,3 +41,48 @@ func (t *defaultMountEmptyGCSFile) TestRenameBeforeFileIsFlushed() { // Check if old object is deleted. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) } + +func (t *defaultMountCommonTest) TestSyncAfterRenameSucceeds() { + data, err := operations.GenerateRandomData(operations.MiB * 4) + require.NoError(t.T(), err) + _, err = t.f1.WriteAt(data, 0) + require.NoError(t.T(), err) + operations.VerifyStatFile(t.filePath, operations.MiB*4, FilePerms, t.T()) + err = t.f1.Sync() + require.NoError(t.T(), err) + newFile := "new" + t.fileName + err = operations.RenameFile(t.filePath, path.Join(testDirPath, newFile)) + require.NoError(t.T(), err) + + err = t.f1.Sync() + + // Verify that sync succeeds after rename. + require.NoError(t.T(), err) + // Verify the new object contents. + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, string(data), t.T()) + require.NoError(t.T(), t.f1.Close()) + // Check if old object is deleted. + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) +} + +func (t *defaultMountCommonTest) TestAfterRenameWriteFailsWithStaleNFSFileHandleError() { + data, err := operations.GenerateRandomData(operations.MiB * 4) + require.NoError(t.T(), err) + _, err = t.f1.WriteAt(data, 0) + require.NoError(t.T(), err) + operations.VerifyStatFile(t.filePath, operations.MiB*4, FilePerms, t.T()) + err = t.f1.Sync() + require.NoError(t.T(), err) + newFile := "new" + t.fileName + err = operations.RenameFile(t.filePath, path.Join(testDirPath, newFile)) + require.NoError(t.T(), err) + + _, err = t.f1.WriteAt(data, operations.MiB*4) + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Verify the new object contents. + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, string(data), t.T()) + require.NoError(t.T(), t.f1.Close()) + // Check if old object is deleted. + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) +} From fbbea08b0cb6e0665694e310e317ac833a08093a Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 13 Mar 2025 10:17:44 +0000 Subject: [PATCH 0294/1298] Fix typo RUN_TESTS_FOR_ZONAL_BUCKET -> RUN_TESTS_WITH_ZONAL_BUCKET (#3085) The e2e tests bottom script and kokoro configs use the env var RUN_TESTS_WITH_ZONAL_BUCKET, while the top level e2e test script uses RUN_TESTS_FOR_ZONAL_BUCKET instead. Changing all instances of RUN_TESTS_FOR_ZONAL_BUCKET to RUN_TESTS_WITH_ZONAL_BUCKET in this change for consistency. --- .../scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh | 5 +++-- .../continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh index cf7948a15f..ca4118e36e 100755 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/build.sh @@ -35,8 +35,9 @@ if [ $# -gt 0 ]; then >&2 echo "$0: ZONAL_BUCKET_ARG (\$1) passed as $1 . Expected: true or false" exit 1 fi -elif test -n ${RUN_TESTS_FOR_ZONAL_BUCKET}; then - ZONAL_BUCKET_ARG=${RUN_TESTS_FOR_ZONAL_BUCKET} +elif test -n "${RUN_TESTS_WITH_ZONAL_BUCKET}" && ${RUN_TESTS_WITH_ZONAL_BUCKET}; then + echo "Running for zonal bucket(s) ..."; + ZONAL_BUCKET_ARG=${RUN_TESTS_WITH_ZONAL_BUCKET} fi cd "${KOKORO_ARTIFACTS_DIR}/github/gcsfuse" diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh index 9889dba41e..10555f63e8 100755 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh @@ -28,7 +28,7 @@ readonly BUCKET_LOCATION="u-us-prp1" readonly RUN_TESTS_WITH_PRESUBMIT_FLAG=false # This flag, if set true, will indicate to underlying script to also run for zonal buckets. -readonly RUN_TESTS_FOR_ZONAL_BUCKET=false +readonly RUN_TESTS_WITH_ZONAL_BUCKET=false cd "${KOKORO_ARTIFACTS_DIR}/github/gcsfuse" @@ -63,7 +63,7 @@ gcloud config set project $PROJECT_ID set +e # $1 argument is refering to value of testInstalledPackage -./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG ${RUN_TESTS_FOR_ZONAL_BUCKET} +./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG ${RUN_TESTS_WITH_ZONAL_BUCKET} exit_code=$? set -e From daee60d67ab262617bd32c986e469aeb794debf6 Mon Sep 17 00:00:00 2001 From: Chris Cotter Date: Thu, 13 Mar 2025 12:56:44 -0400 Subject: [PATCH 0295/1298] Update SDK to storage/v1.51.0 (#3083) --- go.mod | 26 ++------- go.sum | 181 +++++++-------------------------------------------------- 2 files changed, 28 insertions(+), 179 deletions(-) diff --git a/go.mod b/go.mod index 37c5b607dd..b7ad568cfe 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( cloud.google.com/go/compute/metadata v0.6.0 cloud.google.com/go/iam v1.4.1 cloud.google.com/go/secretmanager v1.14.5 - cloud.google.com/go/storage v1.50.0 + cloud.google.com/go/storage v1.51.0 contrib.go.opencensus.io/exporter/ocagent v0.7.0 contrib.go.opencensus.io/exporter/prometheus v0.4.2 contrib.go.opencensus.io/exporter/stackdriver v0.13.14 @@ -47,7 +47,7 @@ require ( golang.org/x/sys v0.31.0 golang.org/x/text v0.23.0 golang.org/x/time v0.11.0 - google.golang.org/api v0.224.0 + google.golang.org/api v0.225.0 google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.5 gopkg.in/natefinch/lumberjack.v2 v2.2.1 @@ -56,7 +56,7 @@ require ( require ( cel.dev/expr v0.22.0 // indirect - cloud.google.com/go v0.118.3 // indirect + cloud.google.com/go v0.119.0 // indirect cloud.google.com/go/auth v0.15.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect cloud.google.com/go/longrunning v0.6.5 // indirect @@ -65,16 +65,12 @@ require ( cloud.google.com/go/trace v1.11.4 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect - github.com/alecthomas/kingpin/v2 v2.4.0 // indirect - github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/aws/aws-sdk-go v1.55.6 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/envoyproxy/go-control-plane v0.13.4 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect @@ -90,22 +86,15 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/iancoleman/strcase v0.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jpillora/backoff v1.0.0 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 // indirect github.com/magiconair/properties v1.8.9 // indirect - github.com/mdlayher/socket v0.4.1 // indirect - github.com/mdlayher/vsock v1.2.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/xattr v0.4.10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/exporter-toolkit v0.13.2 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/prometheus v0.302.1 // indirect github.com/prometheus/statsd_exporter v0.28.0 // indirect @@ -116,18 +105,15 @@ require ( github.com/spf13/cast v1.7.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect - github.com/xhit/go-str2duration/v2 v2.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/tools v0.31.0 // indirect - google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect + google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index be15214645..9ea15f77da 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= -cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cel.dev/expr v0.22.0 h1:+hFFhLPmquBImfs1BiN2PZmkr5ASse2ZOuaxIs9e4R8= cel.dev/expr v0.22.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -17,12 +15,8 @@ cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKV cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.118.0 h1:tvZe1mgqRxpiVa3XlIGMiPcEUbP1gNXELgD4y/IXmeQ= -cloud.google.com/go v0.118.0/go.mod h1:zIt2pkedt/mo+DQjcT4/L3NDxzHPR29j5HcclNH+9PM= -cloud.google.com/go v0.118.3 h1:jsypSnrE/w4mJysioGdMBg4MiW/hHx/sArFpaBWHdME= -cloud.google.com/go v0.118.3/go.mod h1:Lhs3YLnBlwJ4KA6nuObNMZ/fCbOQBPuWKPoE0Wa/9Vc= -cloud.google.com/go/auth v0.14.0 h1:A5C4dKV/Spdvxcl0ggWwWEzzP7AZMJSEIgrkngwhGYM= -cloud.google.com/go/auth v0.14.0/go.mod h1:CYsoRL1PdiDuqeQpZE0bP2pnPrGqFcOkI0nldEQis+A= +cloud.google.com/go v0.119.0 h1:tw7OjErMzJKbbjaEHkrt60KQrK5Wus/boCZ7tm5/RNE= +cloud.google.com/go v0.119.0/go.mod h1:fwB8QLzTcNevxqi8dcpR+hoMIs3jBherGS9VUBDAW08= cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= @@ -33,39 +27,26 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v1.34.0 h1:+k/kmViu4TEi97NGaxAATYtpYBviOWJySPZ+ekA95kk= -cloud.google.com/go/compute v1.34.0/go.mod h1:zWZwtLwZQyonEvIQBuIa0WvraMYK69J5eDCOw9VZU4g= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.3.1 h1:KFf8SaT71yYq+sQtRISn90Gyhyf4X8RGgeAVC8XGf3E= -cloud.google.com/go/iam v1.3.1/go.mod h1:3wMtuyT4NcbnYNPLMBzYRFiEfjKfJlLVLrisE7bwm34= cloud.google.com/go/iam v1.4.1 h1:cFC25Nv+u5BkTR/BT1tXdoF2daiVbZ1RLx2eqfQ9RMM= cloud.google.com/go/iam v1.4.1/go.mod h1:2vUEJpUG3Q9p2UdsyksaKpDzlwOrnMzS30isdReIcLM= -cloud.google.com/go/kms v1.20.4 h1:CJ0hMpOg1ANN9tx/a/GPJ+Uxudy8k6f3fvGFuTHiE5A= -cloud.google.com/go/kms v1.20.4/go.mod h1:gPLsp1r4FblUgBYPOcvI/bUPpdMg2Jm1ZVKU4tQUfcc= cloud.google.com/go/kms v1.21.0 h1:x3EeWKuYwdlo2HLse/876ZrKjk2L5r7Uexfm8+p6mSI= +cloud.google.com/go/kms v1.21.0/go.mod h1:zoFXMhVVK7lQ3JC9xmhHMoQhnjEDZFoLAr5YMwzBLtk= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= -cloud.google.com/go/longrunning v0.6.4 h1:3tyw9rO3E2XVXzSApn1gyEEnH2K9SynNQjMlBi3uHLg= -cloud.google.com/go/longrunning v0.6.4/go.mod h1:ttZpLCe6e7EXvn9OxpBRx7kZEB0efv8yBO6YnVMfhJs= cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q= cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY= -cloud.google.com/go/monitoring v1.22.1 h1:KQbnAC4IAH+5x3iWuPZT5iN9VXqKMzzOgqcYB6fqPDE= -cloud.google.com/go/monitoring v1.22.1/go.mod h1:AuZZXAoN0WWWfsSvET1Cpc4/1D8LXq8KRDU87fMS6XY= cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM= cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.45.3 h1:prYj8EEAAAwkp6WNoGTE4ahe0DgHoyJd5Pbop931zow= -cloud.google.com/go/pubsub v1.45.3/go.mod h1:cGyloK/hXC4at7smAtxFnXprKEFTqmMXNNd9w+bd94Q= cloud.google.com/go/pubsub v1.47.0 h1:Ou2Qu4INnf7ykrFjGv2ntFOjVo8Nloh/+OffF4mUu9w= cloud.google.com/go/pubsub v1.47.0/go.mod h1:LaENesmga+2u0nDtLkIOILskxsfvn/BXX9Ak1NFxOs8= -cloud.google.com/go/secretmanager v1.14.3 h1:XVGHbcXEsbrgi4XHzgK5np81l1eO7O72WOXHhXUemrM= -cloud.google.com/go/secretmanager v1.14.3/go.mod h1:Pwzcfn69Ni9Lrk1/XBzo1H9+MCJwJ6CDCoeoQUsMN+c= cloud.google.com/go/secretmanager v1.14.5 h1:W++V0EL9iL6T2+ec24Dm++bIti0tI6Gx6sCosDBters= cloud.google.com/go/secretmanager v1.14.5/go.mod h1:GXznZF3qqPZDGZQqETZwZqHw4R6KCaYVvcGiRBA+aqY= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= @@ -73,10 +54,8 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.50.0 h1:3TbVkzTooBvnZsk7WaAQfOsNrdoM8QHusXA1cpk6QJs= -cloud.google.com/go/storage v1.50.0/go.mod h1:l7XeiD//vx5lfqE3RavfmU9yvk5Pp0Zhcv482poyafY= -cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE= -cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8= +cloud.google.com/go/storage v1.51.0 h1:ZVZ11zCiD7b3k+cH5lQs/qcNaoSz3U9I0jgwVzqDlCw= +cloud.google.com/go/storage v1.51.0/go.mod h1:YEJfu/Ki3i5oHC/7jyTgsGZwdQ8P9hqMqvpi5kRKGgc= cloud.google.com/go/trace v1.11.4 h1:LKlhVyX6I4+heP31sWvERSKZZ9cPPEZumt7b4SKVK18= cloud.google.com/go/trace v1.11.4/go.mod h1:lCSHzSPZC1TPwto7zhaRt3KtGYsXFyaErPQ18AUUeUE= contrib.go.opencensus.io/exporter/ocagent v0.7.0 h1:BEfdCTXfMV30tLZD8c9n64V/tIZX5+9sXiuFLnrr1k8= @@ -88,38 +67,23 @@ contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waT dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 h1:3c8yed4lgqTt+oTQ+JNMDo+F4xprBf+O/il4ZC0nRLw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0/go.mod h1:obipzmGjfSjam60XLwGfqUkJsfiheAl+TUjG+4yzyPM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 h1:o90wcURuxekmXrtxmYWTyNla0+ZEHhud6DI1ZTxd1vI= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0/go.mod h1:6fTWu4m3jocfUZLYF5KsZC1TUfRvEjs7lM4crme/irw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0 h1:4PoDbd/9/06IpwLGxSfvfNoEr9urvfkrN6mmJangGCg= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.25.0/go.mod h1:EycllQ1gupHbjqbcmfCr/H6FKSGSmEUONJ2ivb86qeY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 h1:Jtr816GUk6+I2ox9L/v+VcOwN6IyGOEDTSNHfD6m9sY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0/go.mod h1:E05RN++yLx9W4fXPtX978OLo9P0+fBacauUdET1BckA= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0 h1:jJKWl98inONJAr/IZrdFQUWcwUO95DLY1XMD1ZIut+g= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.49.0/go.mod h1:l2fIqmwB+FKSfvn3bAD/0i+AXAxhIZjTK2svT/mgUXs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 h1:GYUJLfvd++4DMuMhCFLgLXvFwofIxh/qOwoGuS/LTew= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0/go.mod h1:wRbFgBQUVm1YXrvWKofAEmq9HNJTDphbAaJSSX01KUI= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= -github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= -github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= -github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= -github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -138,13 +102,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 h1:boJj011Hh+874zpIySeApCX4GeOjPl9qhRF3QuIZq+Q= -github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -157,15 +116,11 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M= github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA= -github.com/envoyproxy/go-control-plane/envoy v1.32.3 h1:hVEaommgvzTjTd4xCaFd+kEQ2iYBtGxP6luyLrx6uOk= -github.com/envoyproxy/go-control-plane/envoy v1.32.3/go.mod h1:F6hWupPfh75TBXGKA++MCT/CZHFq5r9/uwt/kQYkZfE= github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A= github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI= github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= -github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8= github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -174,8 +129,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fsouza/fake-gcs-server v1.52.1 h1:Hx3G2ZpyBzHGmW7cHURWWoTm6jM3M5fcWMIAHBYlJyc= -github.com/fsouza/fake-gcs-server v1.52.1/go.mod h1:Paxf25VmSNMN52L+2/cVulF5fkLUA0YJIYjTGJiwf3c= github.com/fsouza/fake-gcs-server v1.52.2 h1:j6ne83nqHrlX5EEor7WWVIKdBsztGtwJ1J2mL+k+iio= github.com/fsouza/fake-gcs-server v1.52.2/go.mod h1:47HKyIkz6oLTes1R8vEaHLwXfzYsGfmDUk1ViHHAUsA= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -199,10 +152,8 @@ github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= -github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -249,9 +200,8 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= @@ -273,8 +223,6 @@ github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= -github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g= github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -286,26 +234,17 @@ github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkM github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= -github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= -github.com/jacobsa/fuse v0.0.0-20241025064006-8ccd61173b05 h1:PDBoLGtxDPVzWurS8Vd5SFbafRRg2gSNVjLEaJlGTxw= -github.com/jacobsa/fuse v0.0.0-20241025064006-8ccd61173b05/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= -github.com/jacobsa/fuse v0.0.0-20250227120101-009e36f010a3 h1:v7S0NR1fovnc7mkbZ+cvV8WkJbtIWU8kfOZzdaEzABY= -github.com/jacobsa/fuse v0.0.0-20250227120101-009e36f010a3/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= github.com/jacobsa/fuse v0.0.0-20250228153609-219b4762b40e h1:1k0eO4yveJsWQDzjA7YbnqDfT631ld5wGfr9uPN15N0= github.com/jacobsa/fuse v0.0.0-20250228153609-219b4762b40e/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= @@ -324,7 +263,6 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -337,8 +275,6 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= @@ -355,20 +291,15 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 h1:sIXJOMrYnQZJu7OB7ANSF4MYri2fTEGIsRLz6LwI4xE= -github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mdlayher/socket v0.4.1 h1:eM9y2/jlbs1M615oshPQOHZzj6R6wMT7bX5NPiQvn2U= -github.com/mdlayher/socket v0.4.1/go.mod h1:cAqeGjoufqdxWkD7DkpyS+wcefOtmu5OQ8KuoJGIReA= -github.com/mdlayher/vsock v1.2.1 h1:pC1mTJTvjo1r9n9fbm7S1j04rCgCzhCOS5DY0zqHlnQ= -github.com/mdlayher/vsock v1.2.1/go.mod h1:NRfCibel++DgeMD8z/hP+PPTjlNJsdPOmxcnENvE+SE= +github.com/minio/crc64nvme v1.0.0 h1:MeLcBkCTD4pAoU7TciAfwsfxgkhM2u5hCe48hSEVFr0= +github.com/minio/crc64nvme v1.0.0/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.83 h1:W4Kokksvlz3OKf3OqIlzDNKd4MERlC2oN8YptwJ0+GA= -github.com/minio/minio-go/v7 v7.0.83/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY= github.com/minio/minio-go/v7 v7.0.86 h1:DcgQ0AUjLJzRH6y/HrxiZ8CXarA70PAIufXHodP4s+k= +github.com/minio/minio-go/v7 v7.0.86/go.mod h1:VbfO4hYwUu3Of9WqGLBZ8vl3Hxnxo4ngxK4hzQDf4x4= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -379,7 +310,6 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= @@ -400,8 +330,6 @@ github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqr github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= @@ -418,8 +346,6 @@ github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJ github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= -github.com/prometheus/exporter-toolkit v0.13.2 h1:Z02fYtbqTMy2i/f+xZ+UK5jy/bl1Ex3ndzh06T/Q9DQ= -github.com/prometheus/exporter-toolkit v0.13.2/go.mod h1:tCqnfx21q6qN1KA4U3Bfb8uWzXfijIrJz3/kTIqMV7g= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -428,8 +354,6 @@ github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/prometheus/prometheus v0.301.0 h1:0z8dgegmILivNomCd79RKvVkIols8vBGPKmcIBc7OyY= -github.com/prometheus/prometheus v0.301.0/go.mod h1:BJLjWCKNfRfjp7Q48DrAjARnCi7GhfUVvUFEAWTssZM= github.com/prometheus/prometheus v0.302.1 h1:xqVdrwrB4WNpdgJqxsz5loqFWNUZitsK8myqLuSZ6Ag= github.com/prometheus/prometheus v0.302.1/go.mod h1:YcyCoTbUR/TM8rY3Aoeqr0AWTu/pu1Ehh+trpX3eRzg= github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= @@ -455,12 +379,8 @@ github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= @@ -478,21 +398,16 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= -github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.einride.tech/aip v0.68.0 h1:4seM66oLzTpz50u4K1zlJyOXQ3tCzcJN7I22tKkjipw= -go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg= go.einride.tech/aip v0.68.1 h1:16/AfSxcQISGN5z9C5lM+0mLYXihrHbQ1onvYTr93aQ= +go.einride.tech/aip v0.68.1/go.mod h1:XaFtaj4HuA3Zwk9xoBtTWgNubZ0ZZXv9BZJCkuKuWbg= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -503,48 +418,30 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0 h1:JRxssobiPg23otYU5SbWtQC//snGVIM3Tx6QRzlQBao= -go.opentelemetry.io/contrib/detectors/gcp v1.34.0/go.mod h1:cV4BMFcscUR/ckqLkbfQmF0PRsq8w/lMGzdbCSveBHo= go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA= go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 h1:PS8wXpbyaDJQ2VDHHncMe9Vct0Zn1fEjpsjrLxGJoSc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0/go.mod h1:HDBUsEjOuRC0EzKZ1bSaRGZWUBAzo+MhAcUUORSr4D0= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/exporters/prometheus v0.56.0 h1:GnCIi0QyG0yy2MrJLzVrIM7laaJstj//flf1zEJCG+E= -go.opentelemetry.io/otel/exporters/prometheus v0.56.0/go.mod h1:JQcVZtbIIPM+7SWBB+T6FK+xunlyidwLp++fN0sUaOk= go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk= go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0 h1:jBpDk4HAUsrnVO1FsfCfCOTEc/MkInJmvfCHYLFiT80= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.34.0/go.mod h1:H9LUIM1daaeZaz91vZcfeM0fejXPmgCYE8ZhzqfJuiU= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -553,8 +450,6 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -567,8 +462,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= @@ -591,8 +484,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -626,8 +517,6 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -637,8 +526,6 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= -golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -651,8 +538,6 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -697,10 +582,6 @@ golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -712,15 +593,11 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -763,8 +640,6 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -786,10 +661,8 @@ google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.217.0 h1:GYrUtD289o4zl1AhiTZL0jvQGa2RDLyC+kX1N/lfGOU= -google.golang.org/api v0.217.0/go.mod h1:qMc2E8cBAbQlRypBTBWHklNJlaZZJBwDv81B1Iu8oSI= -google.golang.org/api v0.224.0 h1:Ir4UPtDsNiwIOHdExr3fAj4xZ42QjK7uQte3lORLJwU= -google.golang.org/api v0.224.0/go.mod h1:3V39my2xAGkodXy0vEqcEtkqgw2GtrFL5WuBZlCTCOQ= +google.golang.org/api v0.225.0 h1:+4/IVqBQm0MV5S+JW3kdEGC1WtOmM2mXN1LKH1LdNlw= +google.golang.org/api v0.225.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -827,18 +700,12 @@ google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7Fc google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422 h1:6GUHKGv2huWOHKmDXLMNE94q3fBDlEHI+oTRIZSebK0= -google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422/go.mod h1:1NPAxoesyw/SgLPqaUp9u1f9PWCLAk/jVmhx7gJZStg= -google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb h1:ITgPrl429bc6+2ZraNSzMDk3I95nmQln2fuPstKwFDE= -google.golang.org/genproto v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24= -google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb h1:p31xT4yrYrSM/G4Sn2+TNUkVhFCbG9y8itM2S6Th950= -google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422 h1:3UsHvIr4Wc2aW4brOaSCmcxh9ksica6fHEr8P1XhkYw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250106144421-5f5ef82da422/go.mod h1:3ENsm/5D1mzDyhpzeRi1NR784I0BcofWBoSc5QqqMK4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb h1:TLPQVbx1GJ8VKZxz52VAxl1EBgKXXbTiU9Fc5fZeLn4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf h1:114fkUG+I9ba4UmaoNZt0UtiRmBng3KJIB/E0avfYII= +google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= +google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf h1:BdIVRm+fyDUn8lrZLPSlBCfM/YKDwUBYgDoLv9+DYo0= +google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -852,8 +719,6 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= -google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -870,8 +735,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= From 08a65884b622b6f82c0721d09668a1e4f6d75fc7 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 17 Mar 2025 17:25:59 +0530 Subject: [PATCH 0296/1298] Remove unnecessary allocation of memory in random reader (#3088) * Remove unnecessary allocation of memory in random reader * Added warning logs for non-io.EOF error * format error * minor comment update --- internal/gcsx/random_reader.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index c7ec214181..0b8d7f3d71 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -350,9 +350,12 @@ func (rr *randomReader) ReadAt( // is a 15-20x improvement in throughput: 150-200 MB/s instead of 10 MB/s. if rr.reader != nil && rr.start < offset && offset-rr.start < maxReadSize { bytesToSkip := offset - rr.start - p := make([]byte, bytesToSkip) - n, _ := io.ReadFull(rr.reader, p) - rr.start += int64(n) + discardedBytes, copyError := io.CopyN(io.Discard, rr.reader, int64(bytesToSkip)) + // io.EOF is expected if the reader is shorter than the requested offset to read. + if copyError != nil && !errors.Is(copyError, io.EOF) { + logger.Warnf("Error while skipping reader bytes: %v", copyError) + } + rr.start += discardedBytes } // If we have an existing reader, but it's positioned at the wrong place, From 31cd7b67317b7dd96a9c8773f2daedf3439ee3fa Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 18 Mar 2025 09:44:38 +0530 Subject: [PATCH 0297/1298] Fix context canceled error conversion and add stale file handle tests for streaming writes. (#3096) * fix context canceled and add stale file handle tests. * remove log line. * fix formatting. * fix review comments * fix review comments --- internal/bufferedwrites/upload_handler.go | 7 ++ tools/cd_scripts/e2e_test.sh | 1 + tools/integration_tests/run_e2e_tests.sh | 1 + .../setup_test.go | 89 ++++++++++++++ ...ile_handle_streaming_writes_common_test.go | 91 ++++++++++++++ ...le_streaming_writes_empty_gcs_file_test.go | 111 ++++++++++++++++++ ...handle_streaming_writes_local_file_test.go | 49 ++++++++ 7 files changed, 349 insertions(+) create mode 100644 tools/integration_tests/stale_handle_streaming_writes/setup_test.go create mode 100644 tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go create mode 100644 tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go create mode 100644 tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_local_file_test.go diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 922b6fe7fe..1e7ca96fd1 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -19,6 +19,7 @@ package bufferedwrites import ( "context" + "errors" "fmt" "io" "sync" @@ -129,6 +130,12 @@ func (uh *UploadHandler) uploader() { continue } _, err := io.Copy(uh.writer, currBlock.Reader()) + if errors.Is(err, context.Canceled) { + // Context canceled error indicates that the file was deleted from the + // same mount. In this case, we suppress the error to match local + // filesystem behavior. + err = nil + } if err != nil { logger.Errorf("buffered write upload failed for object %s: error in io.Copy: %v", uh.objectName, err) err = gcs.GetGCSError(err) diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index c9874b8885..ab851948ac 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -160,6 +160,7 @@ TEST_DIR_PARALLEL=( "concurrent_operations" "mount_timeout" "stale_handle" + "stale_handle_streaming_writes" "negative_stat_cache" "streaming_writes" ) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index d613b23775..9fb5d3575a 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -104,6 +104,7 @@ TEST_DIR_PARALLEL=( "benchmarking" "mount_timeout" "stale_handle" + "stale_handle_streaming_writes" "negative_stat_cache" "streaming_writes" ) diff --git a/tools/integration_tests/stale_handle_streaming_writes/setup_test.go b/tools/integration_tests/stale_handle_streaming_writes/setup_test.go new file mode 100644 index 0000000000..4936835a57 --- /dev/null +++ b/tools/integration_tests/stale_handle_streaming_writes/setup_test.go @@ -0,0 +1,89 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stale_handle_streaming_writes + +import ( + "context" + "log" + "os" + "testing" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +const ( + testDirName = "StaleHandleStreamingWritesTest" +) + +var ( + flags []string + mountFunc func([]string) error + // root directory is the directory to be unmounted. + rootDir string + storageClient *storage.Client + ctx context.Context +) + +//////////////////////////////////////////////////////////////////////// +// TestMain +//////////////////////////////////////////////////////////////////////// + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + // Create storage client before running tests. + ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() + + // To run mountedDirectory tests, we need both testBucket and mountedDirectory + // flags to be set, as operations tests validates content from the bucket. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + rootDir = setup.MountedDirectory() + setup.RunTestsForMountedDirectoryFlag(m) + } + + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + rootDir = setup.MntDir() + // Define flag set to run the tests. + flagsSet := [][]string{ + {"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1"}, + } + // Run all tests for GRPC. + setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc", "") + + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + + var successCode int + for i := range flagsSet { + log.Printf("Running tests with flags: %v", flagsSet[i]) + flags = flagsSet[i] + successCode = m.Run() + if successCode != 0 { + break + } + } + os.Exit(successCode) +} diff --git a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go new file mode 100644 index 0000000000..89adba4ea9 --- /dev/null +++ b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go @@ -0,0 +1,91 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stale_handle_streaming_writes + +import ( + "os" + "path" + + "cloud.google.com/go/storage" + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type staleFileHandleStreamingWritesCommon struct { + f1 *os.File + data string + testDirPath string + fileName string + filePath string + suite.Suite +} + +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func (t *staleFileHandleStreamingWritesCommon) SetupSuite() { + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) + t.testDirPath = setup.SetupTestDirectory(testDirName) + t.data = setup.GenerateRandomString(operations.MiB * 5) +} + +func (t *staleFileHandleStreamingWritesCommon) TearDownSuite() { + setup.UnmountGCSFuse(rootDir) +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *staleFileHandleStreamingWritesCommon) TestFileDeletedLocallySyncAndCloseDoNotThrowError() { + // Dirty the file by giving it some contents. + bytesWrote, err := t.f1.WriteAt([]byte(t.data), 0) + assert.NoError(t.T(), err) + // Delete the file. + operations.RemoveFile(t.f1.Name()) + // Verify unlink operation succeeds. + operations.ValidateNoFileOrDirError(t.T(), t.f1.Name()) + + // Write should not give an error. + _, err = t.f1.WriteAt([]byte(t.data), int64(bytesWrote)) + + assert.NoError(t.T(), err) + operations.SyncFile(t.f1, t.T()) + operations.CloseFileShouldNotThrowError(t.f1, t.T()) +} + +func (t *staleFileHandleStreamingWritesCommon) TestClosingFileHandleForClobberedFileReturnsStaleFileHandleError() { + // Dirty the file by giving it some contents. + _, err := t.f1.WriteAt([]byte(t.data), 0) + assert.NoError(t.T(), err) + // Clobber file by replacing the underlying object with a new generation. + err = WriteToObject(ctx, storageClient, path.Join(testDirName, t.fileName), FileContents, storage.Conditions{}) + assert.NoError(t.T(), err) + operations.SyncFile(t.f1, t.T()) + + // Closing the file/writer returns stale NFS file handle error. + err = t.f1.Close() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, FileContents, t.T()) +} diff --git a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go new file mode 100644 index 0000000000..aceca19ff9 --- /dev/null +++ b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go @@ -0,0 +1,111 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stale_handle_streaming_writes + +import ( + "path" + "testing" + + "cloud.google.com/go/storage" + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type staleFileHandleStreamingWritesEmptyGcsFile struct { + staleFileHandleStreamingWritesCommon +} + +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func (t *staleFileHandleStreamingWritesEmptyGcsFile) SetupTest() { + t.fileName = setup.GenerateRandomString(5) + // Create an empty object on GCS + CreateObjectInGCSTestDir(ctx, storageClient, testDirName, t.fileName, "", t.T()) + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, "", t.T()) + t.filePath = path.Join(t.testDirPath, t.fileName) + t.f1 = operations.OpenFile(t.filePath, t.T()) +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestFirstWriteToClobberedFileThrowsStaleFileHandleError() { + // Clobber file by replacing the underlying object with a new generation. + err := WriteToObject(ctx, storageClient, path.Join(testDirName, t.fileName), FileContents, storage.Conditions{}) + assert.NoError(t.T(), err) + + // Attempt first write to the file. + _, err = t.f1.WriteAt([]byte(t.data), 0) + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.SyncFile(t.f1, t.T()) + operations.CloseFileShouldNotThrowError(t.f1, t.T()) +} + +func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestWriteOnRenamedFileThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + n, err := t.f1.WriteAt([]byte(t.data), 0) + assert.NoError(t.T(), err) + newFile := "new" + t.fileName + err = operations.RenameFile(t.filePath, path.Join(t.testDirPath, newFile)) + assert.NoError(t.T(), err) + + // Attempt to write to file should give stale NFS file handle erorr. + _, err = t.f1.WriteAt([]byte(t.data), int64(n)) + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + // Sync and Close succeeds. + operations.SyncFile(t.f1, t.T()) + operations.CloseFileShouldNotThrowError(t.f1, t.T()) + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, t.data, t.T()) +} + +func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestFileDeletedRemotelyWriteAndSyncDoNoThrowStaleFileHandleError() { + // Dirty the file by giving it some contents. + n, err := t.f1.WriteAt([]byte(t.data), 0) + assert.NoError(t.T(), err) + // Delete the file remotely. + err = DeleteObjectOnGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + assert.NoError(t.T(), err) + // Verify unlink operation succeeds. + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) + _, err = t.f1.WriteAt([]byte(t.data), int64(n)) + assert.NoError(t.T(), err) + operations.SyncFile(t.f1, t.T()) + + // Closing the file/writer returns stale NFS file handle error. + err = t.f1.Close() + + operations.ValidateStaleNFSFileHandleError(t.T(), err) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestStaleFileHandleStreamingWritesEmptyGcsFileTest(t *testing.T) { + suite.Run(t, new(staleFileHandleStreamingWritesEmptyGcsFile)) +} diff --git a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_local_file_test.go b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_local_file_test.go new file mode 100644 index 0000000000..1b3a836d64 --- /dev/null +++ b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_local_file_test.go @@ -0,0 +1,49 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package stale_handle_streaming_writes + +import ( + "testing" + + . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/suite" +) + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +type staleFileHandleStreamingWritesLocalFile struct { + staleFileHandleStreamingWritesCommon +} + +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func (t *staleFileHandleStreamingWritesLocalFile) SetupTest() { + t.fileName = setup.GenerateRandomString(5) + // Create a local file. + t.filePath, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, t.testDirPath, t.fileName, t.T()) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestStaleFileHandleStreamingWritesLocalFileTest(t *testing.T) { + suite.Run(t, new(staleFileHandleStreamingWritesLocalFile)) +} From 8f46d8603c37b5a2d4b25181001d68beb48cb726 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 18 Mar 2025 10:35:37 +0530 Subject: [PATCH 0298/1298] Update viper (#3090) @mitchehllh announced his intent to archive some of his unmaintained projects. So, migrate to the more actively developed version of mapstructure which is go-viper/mapstructure. This is required to update viper. References: * https://gist.github.com/mitchellh/90029601268e59a29e64e55bab1c5bdc * https://github.com/mitchellh/mapstructure/issues/349 --- cfg/decode_hook.go | 2 +- cmd/root.go | 2 +- go.mod | 9 ++------- go.sum | 18 ++++-------------- 4 files changed, 8 insertions(+), 23 deletions(-) diff --git a/cfg/decode_hook.go b/cfg/decode_hook.go index 898edb6d41..1bbfe39cbd 100644 --- a/cfg/decode_hook.go +++ b/cfg/decode_hook.go @@ -15,7 +15,7 @@ package cfg import ( - "github.com/mitchellh/mapstructure" + "github.com/go-viper/mapstructure/v2" ) // DecodeHook will be called by Viper while constructing the config object. diff --git a/cmd/root.go b/cmd/root.go index 297ed2fed4..8b817de5b7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -20,10 +20,10 @@ import ( "os" "strings" + "github.com/go-viper/mapstructure/v2" "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/util" - "github.com/mitchellh/mapstructure" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" diff --git a/go.mod b/go.mod index b7ad568cfe..5baeb0a03b 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 github.com/fsouza/fake-gcs-server v1.52.2 + github.com/go-viper/mapstructure/v2 v2.2.1 github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.1 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec @@ -24,13 +25,12 @@ require ( github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3 github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 - github.com/mitchellh/mapstructure v1.5.0 github.com/prometheus/client_golang v1.21.1 github.com/prometheus/client_model v0.6.1 github.com/prometheus/common v0.62.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 - github.com/spf13/viper v1.19.0 + github.com/spf13/viper v1.20.0 github.com/stretchr/testify v1.10.0 go.opencensus.io v0.24.0 go.opentelemetry.io/contrib/detectors/gcp v1.35.0 @@ -85,11 +85,9 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.18.0 // indirect - github.com/magiconair/properties v1.8.9 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/xattr v0.4.10 // indirect @@ -99,7 +97,6 @@ require ( github.com/prometheus/prometheus v0.302.1 // indirect github.com/prometheus/statsd_exporter v0.28.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.7.1 // indirect @@ -110,10 +107,8 @@ require ( go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect - gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 9ea15f77da..43abece57a 100644 --- a/go.sum +++ b/go.sum @@ -152,6 +152,8 @@ github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ4 github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= @@ -238,8 +240,6 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5uk github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -291,8 +291,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= -github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/minio/crc64nvme v1.0.0 h1:MeLcBkCTD4pAoU7TciAfwsfxgkhM2u5hCe48hSEVFr0= github.com/minio/crc64nvme v1.0.0/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= @@ -300,8 +298,6 @@ github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.86 h1:DcgQ0AUjLJzRH6y/HrxiZ8CXarA70PAIufXHodP4s+k= github.com/minio/minio-go/v7 v7.0.86/go.mod h1:VbfO4hYwUu3Of9WqGLBZ8vl3Hxnxo4ngxK4hzQDf4x4= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= @@ -368,8 +364,6 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -383,8 +377,8 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= +github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -462,8 +456,6 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= -golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -744,8 +736,6 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= From 5d1a22f22f80bad7bc547859ce0859d0f1dcf3ab Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 18 Mar 2025 14:01:50 +0530 Subject: [PATCH 0299/1298] Update semantics doc for streaming writes (#3079) * add information about streaming writes * review comments * review comments Co-authored-by: Kislay Kishore * Update docs/semantics.md Co-authored-by: Kislay Kishore * Update docs/semantics.md Co-authored-by: Kislay Kishore * Update docs/semantics.md Co-authored-by: Kislay Kishore * Update docs/semantics.md Co-authored-by: Kislay Kishore * review comments * Update docs/semantics.md Co-authored-by: Kislay Kishore * Update docs/semantics.md Co-authored-by: Kislay Kishore --------- Co-authored-by: Kislay Kishore --- docs/semantics.md | 143 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 111 insertions(+), 32 deletions(-) diff --git a/docs/semantics.md b/docs/semantics.md index 9b10079dec..0e5a74446d 100644 --- a/docs/semantics.md +++ b/docs/semantics.md @@ -1,25 +1,31 @@ # Read/Writes -**Reads** +## Reads Cloud Storage FUSE makes API calls to Cloud Storage to read an object directly, without downloading it to a local directory. A TCP connection is established, in which the entire object, or just portions as specified by the application/operating system via an offset, can be read back. Files that have not been modified are read portion by portion on demand. Cloud Storage FUSE uses a heuristic to detect when a file is being read sequentially, and will issue fewer, larger read requests to Cloud Storage in this case, increasing performance. -**Writes** +## Writes -For modifications to existing file objects, Cloud Storage FUSE downloads the entire -backing object's contents from Cloud Storage. The contents are stored in a local -temporary file (temp-file for short) whose location is controlled by the flag ```--temp-dir```. Later, -when the file is closed or fsync'd, Cloud Storage FUSE writes the contents of -the local file back to Cloud Storage as a new object generation, and deletes temp-file. Modifying even -a single bit of an object results in the full re-upload of the object. The +### Default Write Path + +Files are written locally as a temporary file (temp-file for short) whose +location is controlled by the flag `--temp-dir`. Upon closing or fsyncing +the file, the file is then written to your Cloud Storage bucket and the +temp-file is deleted. + +For modifications to existing files, Cloud Storage FUSE downloads the +entire +backing object's contents from Cloud Storage, storing them in the same temporary +directory as mentioned above. When the file is closed or fsync'd, Cloud Storage +FUSE writes the contents of the local file back to Cloud Storage as a new object +generation, and deletes +temp-file. Modifying even a single bit of an object results in the full +re-upload of the object. The exception is if an append is done to the end of a file, where the original file is at least 2MB, then only the appended content is uploaded. -For new objects, objects are first written to the same temporary directory as -mentioned above. Upon closing or fsyncing the file, the file is then written to -your Cloud Storage bucket. As new and modified files are fully staged in the local temporary directory until they are written out to Cloud Storage, you must ensure that there is enough free space available to handle staged content @@ -27,27 +33,84 @@ when writing large files. #### Notes -- Prior to version 1.2.0, you will notice that an empty file is created in the - Cloud Storage bucket as a hold. Upon closing or fsyncing the file, the file - is written to your Cloud Storage bucket, with the existing empty file now - reflecting the accurate file size and content. Starting with version 1.2, - the default behavior is to not create this zero-byte file, which increases - write performance. If needed, it can be re-enabled by setting the - `create-empty-file: true` configuration in the config file. -- If the application never sends fsync for a file, it will leave behind its - temp-file (in temp-dir), which will not be cleared until the user unmounts - the bucket. As an example, if you are writing a large file, and temp-dir - does not have enough free space available, then you will get 'out of space' - error. Then the temp-file will not be deleted until you do an fsync for that - file, or unmount the bucket. - -**Concurrency** +- Prior to version 1.2.0, you will notice that an empty file is created in the + Cloud Storage bucket as a hold. Upon closing or fsyncing the file, the file + is written to your Cloud Storage bucket, with the existing empty file now + reflecting the accurate file size and content. Starting with version 1.2, + the default behavior is to not create this zero-byte file, which increases + write performance. If needed, it can be re-enabled by setting the + `create-empty-file: true` configuration in the config file. +- If the application never sends fsync for a file, it will leave behind its + temp-file (in temp-dir), which will not be cleared until the user unmounts + the bucket. As an example, if you are writing a large file, and temp-dir + does not have enough free space available, then you will get 'out of space' + error. Then the temp-file will not be deleted until you do an fsync for that + file, or unmount the bucket. + +### With Streaming Writes + +Starting with version 2.9.1, GCSFuse supports streaming-writes, which is a new +write +path that uploads data directly to Google Cloud Storage (GCS) as it's written +without fully staging the file in the temp-dir. This reduces both latency and +disk space usage, making it particularly beneficial for large, sequential writes +such as checkpoints. Streaming writes can be enabled using +`--enable-streaming-writes` flag or `write:enable-streaming-writes:true` in the +config file. + +**Memory Usage:** Each file opened for streaming writes will consume +approximately 64MB of RAM during the upload process. This memory is released +when the file handle is closed. This should be considered when planning resource +allocation for applications using streaming writes. + +#### Note on Streaming Writes: + +- **New files, Sequential Writes:** Streaming writes are designed for sequential + writes to a new file only. Modifying existing files, or doing out-of-order + writes (whether from the same file handle or concurrent writes from multiple + file handles) will cause GCSFuse to automatically revert to the existing write + path of staging writes to a temporary file on disk. An informational log + message will be emitted when this fallback occurs. + +- **Concurrent Writes to the Same File:** While concurrent writes to the same + file are possible, they are not the primary use case for this initial phase of + streaming writes. If a (rare, often server-related) error occurs during + concurrent writes, all file handles must be closed before any future writes + can resume. This phase of streaming writes is optimized for single-stream + writes to new files, such as for AI/ML checkpointing. + +- **File System Semantics Change:** + - **FSync operation does not finalize the object:** When streaming writes + are enabled, the fsync operation will not finalize the object on GCS. + Instead, the object will be finalized only when the file is closed. + Only finalized objects are visible to the end user. This is a key + difference from the default non-streaming-writes behavior and should be considered when + using streaming writes. Relying on fsync for data durability with + streaming writes enabled is not recommended. Data is guaranteed to be + on GCS only after the file is closed. + - **Rename Operation Syncs the File:** Rename operation on a file undergoing + writes via streaming writes will be finalized and then renamed. This means + that any follow up writes will automatically revert to the existing + behavior of staging writes to a temporary file on disk. + - **Read Operations During Write:** Today the application can read the data + when the writes are in progress for that file. With buffered writes, the + application will not be able to read the file until the corresponding GCS + object is finalized i.e., fclose() is called. Applications should not read from a file while it is being written to + using streaming writes. + - **Write Stalls and Chunk Uploads:** Streaming writes do not currently + implement chunk-level timeouts or retries. Write operations may stall, and + chunk uploads that encounter errors will eventually fail after the default + 32-second deadline. + +___ + +# Concurrency Multiple readers can access the same or different objects within a bucket without issue. Likewise, multiple writers can modify different objects in the same bucket simultaneously without any issue. Concurrent writes to the same gcs object are supported from the same mount and behave similar to native file system. However, when different mounts try to write to the same object, the flush from first mount wins. Other mounts that have not updated their local file descriptors after the object is modified will encounter a ```syscall.ESTALE``` error when attempting to save their edits due to precondition checks. Therefore, to ensure data is consistently written, it is strongly recommended that multiple sources do not modify the same object. -**Write/Read consistency** +### Write/Read consistency Cloud Storage by nature is [strongly consistent](https://cloud.google.com/storage/docs/consistency). Cloud Storage FUSE offers close-to-open and fsync-to-open consistency. Once a file is closed, consistency is guaranteed in the following open and read immediately. @@ -57,7 +120,7 @@ Examples: - Machine A opens a file and writes then successfully closes or syncs it, and the file was not concurrently unlinked from the point of view of A. Machine B then opens the file after machine A finishes closing or syncing. Machine B will observe a version of the file at least as new as the one created by machine A. - Machine A and B both open the same file, which contains the text ‘ABC’. Machine A modifies the file to ‘ABC-123’ and closes/syncs the file which gets written back to Cloud Storage. After, Machine B, which still has the file open, instead modifies the file to ‘ABC-XYZ’, and saves and closes the file. As the last writer wins, the current state of the file will read ‘ABC-XYZ’. -**Stale File Handle Errors** +### Stale File Handle Errors To ensure consistency, Cloud Storage FUSE returns a ```syscall.ESTALE``` error when an application tries to access stale data. This can occur in the following circumstances: @@ -68,6 +131,8 @@ To ensure consistency, Cloud Storage FUSE returns a ```syscall.ESTALE``` error w These changes in Cloud Storage FUSE prioritize data integrity and provide users with clear indications of potential conflicts, preventing silent data loss and ensuring a more robust and reliable experience. +___ + # Caching Cloud Storage FUSE has four forms of optional caching: stat, type, list, and file. Stat and type caches are enabled by default. Using Cloud Storage FUSE with file caching, list caching, stat caching, or type caching enabled can significantly increase performance but reduces consistency guarantees. @@ -77,7 +142,7 @@ The default behavior is appropriate, and brings significant performance benefits **Important**: The rest of this document assumes that caching is disabled (by setting ```--stat-cache-ttl 0``` and ```--type-cache-ttl 0``` or ```metadata-cache:ttl-secs: 0```). This is not the default. If you want the consistency guarantees discussed in this document, you must use these options to disable caching. -**Stat caching** +## Stat caching The cost of the consistency guarantees discussed in the rest of this document is that Cloud Storage FUSE must frequently send stat object requests to Cloud Storage in order to get the freshest possible answer for the kernel when it asks about a particular name or inode, which happens frequently. This can make what appear to the user to be simple operations, like ```ls -l```, take quite a long time. @@ -117,7 +182,7 @@ Warning: Using stat caching breaks the consistency guarantees discussed in this - The mounted bucket is only modified on a single machine, via a single Cloud Storage FUSE mount. - The mounted bucket is modified by multiple actors, but the user is confident that they don't need the guarantees discussed in this document. -**Type caching** +## Type caching Because Cloud Storage does not forbid an object named ```foo``` from existing next to an object named ```foo/``` (see the Name conflicts section), when Cloud Storage FUSE is asked to look up the name "foo" it must stat both objects. @@ -140,7 +205,7 @@ The behavior of type cache is controlled by the following flags/config parameter - The mounted bucket is never modified. - The type (file or directory) for any given path never changes. -**File caching** +## File caching The Cloud Storage FUSE file cache feature is a client-based read cache that lets repeat file reads to be served from a faster local cache storage media of your choice. @@ -176,7 +241,7 @@ Additional file cache [behavior](https://cloud.google.com/storage/docs/gcsfuse-c - If a Cloud Storage FUSE client modifies a cached file or its metadata, then the file is immediately invalidated and consistency is ensured in the following read by the same client. However, if different clients access the same file or its metadata, and its entries are cached, then the cached version of the file or metadata is read and not the updated version until the file is invalidated by that specific client's TTL setting. -**Kernel List Cache** +## Kernel List Cache As the name suggests, the Cloud Storage FUSE kernel-list-cache is used to cache the directory listing (output of `ls`) in kernel page-cache. It significantly improves the workload which involves repeated listing. For multi node/mount-point scenario, this is recommended to be used only for read only workloads, e.g. for Serving and Training workloads. @@ -203,6 +268,8 @@ By default, the list cache is disabled. It can be enabled by configuring the `-- For now, for backward compatibility, both are accepted, and the minimum of the two, rounded to the next higher multiple of a second, is used as TTL for both stat-cache and type-cache, when ```metadata-cache: ttl-secs``` is not set. 1. Both stat-cache and type-cache internally use the same TTL. +___ + # Files and Directories As Cloud Storage FUSE is a way to mount a bucket as a local filesystem, and directories are essential to filesystems, Cloud Storage FUSE presents directories logically using ```/``` prefixes. Cloud Storage object names map directly to file paths using the separator '/'. Object names ending in a slash represent a directory, and all other object names represent a file. Directories are by default not implicitly defined; they exist only if a matching object ending in a slash exists. @@ -273,12 +340,16 @@ HNS-enabled buckets offer several advantages over standard buckets when used wit - HNS buckets treat folders as first-class entities, closely aligning with traditional file system semantics. Commands like mkdir now directly create folder resources within the bucket, unlike with traditional buckets where directories were simulated using prefixes and 0-byte objects. - List object calls ([BucketHandle.Objects](https://cloud.google.com/storage/docs/json_api/v1/objects/list)), are replaced with [get folder](https://cloud.google.com/storage/docs/json_api/v1/folders/getfoldermetadata) calls, resulting in quicker response times and fewer overall list calls for every lookup operation. +___ + # Generations With each record in Cloud Storage is stored object and metadata [generation numbers](https://cloud.google.com/storage/docs/generations-preconditions). These provide a total order on requests to modify an object's contents and metadata, compatible with causality. So if insert operation A happens before insert operation B, then the generation number resulting from A will be less than that resulting from B. In the discussion below, the term "generation" refers to both object generation and meta-generation numbers from Cloud Storage. In other words, what we call "generation" is a pair ```(G, M)``` of Cloud Storage object generation number ```G``` and associated meta-generation number ```M```. +___ + # File inodes As in any file system, file inodes in a Cloud Storage FUSE file system logically contain file contents and metadata. A file inode is initialized with a particular generation of a particular object within Cloud Storage (the "source generation"), and its contents are initially exactly the contents and metadata of that generation. @@ -332,6 +403,8 @@ Cloud Storage FUSE sets the following pieces of Cloud Storage object metadata fo - contentType is set to Cloud Storage's best guess as to the MIME type of the file, based on its file extension. - The custom metadata key gcsfuse_mtime is set to track mtime, as discussed above. +___ + # Directory Inodes Cloud Storage FUSE directory inodes exist simply to satisfy the kernel and export a way to look up child inodes. Unlike file inodes: @@ -353,10 +426,14 @@ Cloud Storage FUSE makes similar calls while deleting a directory: it lists obje Note that by definition, implicit directories cannot be empty. +___ + # Symlink inodes Cloud Storage FUSE represents symlinks with empty Cloud Storage objects that contain the custom metadata key ```gcsfuse_symlink_target```, with the value giving the target of a symlink. In other respects they work like a file inode, including receiving the same permissions. +___ + # Permissions and ownership **Inodes** @@ -371,6 +448,8 @@ The fuse kernel layer itself restricts file system access to the mounting user ( This can be overridden by setting ```-o allow_other``` to allow other users to access the file system. However, there may be [security implications](https://github.com/torvalds/linux/blob/a33f32244d8550da8b4a26e277ce07d5c6d158b5/Documentation/filesystems/fuse.txt#L218-L310). +___ + # Non-standard filesystem behaviors See [Key Differences from a POSIX filesystem](https://cloud.google.com/storage/docs/gcs-fuse#expandable-1) From 979078e0e03ccb603d69bfda1e75f6451930568d Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 18 Mar 2025 15:47:52 +0530 Subject: [PATCH 0300/1298] Upgrade dependencies (#3089) --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 5baeb0a03b..36a242c128 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.24.0 require ( cloud.google.com/go/compute/metadata v0.6.0 - cloud.google.com/go/iam v1.4.1 - cloud.google.com/go/secretmanager v1.14.5 + cloud.google.com/go/iam v1.4.2 + cloud.google.com/go/secretmanager v1.14.6 cloud.google.com/go/storage v1.51.0 contrib.go.opencensus.io/exporter/ocagent v0.7.0 contrib.go.opencensus.io/exporter/prometheus v0.4.2 @@ -27,7 +27,7 @@ require ( github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/prometheus/client_golang v1.21.1 github.com/prometheus/client_model v0.6.1 - github.com/prometheus/common v0.62.0 + github.com/prometheus/common v0.63.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 github.com/spf13/viper v1.20.0 @@ -47,7 +47,7 @@ require ( golang.org/x/sys v0.31.0 golang.org/x/text v0.23.0 golang.org/x/time v0.11.0 - google.golang.org/api v0.225.0 + google.golang.org/api v0.226.0 google.golang.org/grpc v1.71.0 google.golang.org/protobuf v1.36.5 gopkg.in/natefinch/lumberjack.v2 v2.2.1 diff --git a/go.sum b/go.sum index 43abece57a..2cb90e1c2f 100644 --- a/go.sum +++ b/go.sum @@ -31,8 +31,8 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4 cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/iam v1.4.1 h1:cFC25Nv+u5BkTR/BT1tXdoF2daiVbZ1RLx2eqfQ9RMM= -cloud.google.com/go/iam v1.4.1/go.mod h1:2vUEJpUG3Q9p2UdsyksaKpDzlwOrnMzS30isdReIcLM= +cloud.google.com/go/iam v1.4.2 h1:4AckGYAYsowXeHzsn/LCKWIwSWLkdb0eGjH8wWkd27Q= +cloud.google.com/go/iam v1.4.2/go.mod h1:REGlrt8vSlh4dfCJfSEcNjLGq75wW75c5aU3FLOYq34= cloud.google.com/go/kms v1.21.0 h1:x3EeWKuYwdlo2HLse/876ZrKjk2L5r7Uexfm8+p6mSI= cloud.google.com/go/kms v1.21.0/go.mod h1:zoFXMhVVK7lQ3JC9xmhHMoQhnjEDZFoLAr5YMwzBLtk= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= @@ -47,8 +47,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/pubsub v1.47.0 h1:Ou2Qu4INnf7ykrFjGv2ntFOjVo8Nloh/+OffF4mUu9w= cloud.google.com/go/pubsub v1.47.0/go.mod h1:LaENesmga+2u0nDtLkIOILskxsfvn/BXX9Ak1NFxOs8= -cloud.google.com/go/secretmanager v1.14.5 h1:W++V0EL9iL6T2+ec24Dm++bIti0tI6Gx6sCosDBters= -cloud.google.com/go/secretmanager v1.14.5/go.mod h1:GXznZF3qqPZDGZQqETZwZqHw4R6KCaYVvcGiRBA+aqY= +cloud.google.com/go/secretmanager v1.14.6 h1:/ooktIMSORaWk9gm3vf8+Mg+zSrUplJFKBztP993oL0= +cloud.google.com/go/secretmanager v1.14.6/go.mod h1:0OWeM3qpJ2n71MGgNfKsgjC/9LfVTcUqXFUlGxo5PzY= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -340,8 +340,8 @@ github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9 github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= -github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= +github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= +github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= @@ -653,8 +653,8 @@ google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0M google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.225.0 h1:+4/IVqBQm0MV5S+JW3kdEGC1WtOmM2mXN1LKH1LdNlw= -google.golang.org/api v0.225.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY= +google.golang.org/api v0.226.0 h1:9A29y1XUD+YRXfnHkO66KggxHBZWg9LsTGqm7TkUvtQ= +google.golang.org/api v0.226.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= From 68431c82e69b7e2129d1077662fd20a3136681a3 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Wed, 19 Mar 2025 09:51:28 +0530 Subject: [PATCH 0301/1298] Refactor methods for saving log files to KOKORO in case of test failures. (#3066) * fix save files to kokoro logic. * fix loggging * fix tests * refactor test files * fix regex for proxy server log fiels * remove comment * log proxy server config only single time in log file * refactor setup.go * fix comment * fixes for review comment. --- .../gcp_ubuntu/e2e_tests/e2e-tests-master.cfg | 2 +- .../e2e_tests/e2e-tests-release.cfg | 2 +- .../emulator_tests/proxy_server/config.go | 4 -- .../emulator_tests/proxy_server/main.go | 7 ++- .../read_stall/read_stall_test.go | 22 +++---- .../common_failure_test.go | 6 +- .../emulator_tests/util/test_helper.go | 2 - .../write_stall/writes_stall_on_sync_test.go | 19 +++++-- .../cache_file_for_range_read_false_test.go | 5 +- .../cache_file_for_range_read_true_test.go | 5 +- .../read_cache/disabled_cache_ttl_test.go | 5 +- .../read_cache/job_chunk_test.go | 5 +- .../read_cache/local_modification_test.go | 5 +- .../read_cache/range_read_test.go | 5 +- .../read_cache/read_only_test.go | 5 +- .../read_cache/remount_test.go | 5 +- .../read_cache/small_cache_ttl_test.go | 4 +- tools/integration_tests/util/setup/setup.go | 57 ++++++++++++++----- 18 files changed, 87 insertions(+), 78 deletions(-) diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-master.cfg b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-master.cfg index e7accaef8f..42898727eb 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-master.cfg +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-master.cfg @@ -15,8 +15,8 @@ action { define_artifacts { regex: "gcsfuse-failed-integration-test-logs-*" + regex: "proxy-server-failed-integration-test-logs-*" strip_prefix: "github/gcsfuse/perfmetrics/scripts" - regex: "proxy*" } } diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-release.cfg b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-release.cfg index e7accaef8f..42898727eb 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-release.cfg +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/e2e-tests-release.cfg @@ -15,8 +15,8 @@ action { define_artifacts { regex: "gcsfuse-failed-integration-test-logs-*" + regex: "proxy-server-failed-integration-test-logs-*" strip_prefix: "github/gcsfuse/perfmetrics/scripts" - regex: "proxy*" } } diff --git a/tools/integration_tests/emulator_tests/proxy_server/config.go b/tools/integration_tests/emulator_tests/proxy_server/config.go index 20130ccdb1..5adb94c869 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/config.go +++ b/tools/integration_tests/emulator_tests/proxy_server/config.go @@ -60,9 +60,5 @@ func parseConfigFile(configPath string) (*Config, error) { return nil, fmt.Errorf("unable to decode into struct, %v", err) } - if *fDebug { - printConfig(config) - } - return &config, nil } diff --git a/tools/integration_tests/emulator_tests/proxy_server/main.go b/tools/integration_tests/emulator_tests/proxy_server/main.go index be98aa843f..ca48622d96 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/main.go +++ b/tools/integration_tests/emulator_tests/proxy_server/main.go @@ -35,8 +35,9 @@ const PortAndProxyProcessIdInfoLogFormat = "Listening Proxy Server On Port [%s] var ( // Flag to accept config-file path. fConfigPath = flag.String("config-path", "configs/config.yaml", "Path to the file") - // Flag to turn on fDebug logs. - fDebug = flag.Bool("debug", false, "Enable proxy server fDebug logs.") + // Flag to turn on/off fDebug logs. + fDebug = flag.Bool("debug", true, "Enable proxy server fDebug logs.") + // Log file to write proxy server logs. fLogFilePath = flag.String("log-file", "", "Path to the log file") // Initialized before the server gets started. gConfig *Config @@ -232,7 +233,7 @@ func main() { log.SetOutput(logFile) if *fDebug { - log.Printf("%+v\n", gConfig) + printConfig(*gConfig) } gOpManager = NewOperationManager(*gConfig) diff --git a/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go b/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go index 02fa529364..86a83a58cb 100644 --- a/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go +++ b/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go @@ -41,28 +41,30 @@ const ( ) type readStall struct { - port int - proxyProcessId int - flags []string + port int + proxyProcessId int + proxyServerLogFile string + flags []string + configPath string suite.Suite } -func (r *readStall) SetupSuite() { - configPath := "../proxy_server/configs/read_stall_5s.yaml" +func (r *readStall) SetupTest() { + r.configPath = "../proxy_server/configs/read_stall_5s.yaml" + r.proxyServerLogFile = setup.CreateProxyServerLogFile(r.T()) var err error - r.port, r.proxyProcessId, err = emulator_tests.StartProxyServer(configPath, setup.CreateProxyServerLogFile(r.T())) + r.port, r.proxyProcessId, err = emulator_tests.StartProxyServer(r.configPath, r.proxyServerLogFile) require.NoError(r.T(), err) setup.AppendProxyEndpointToFlagSet(&r.flags, r.port) setup.MountGCSFuseWithGivenMountFunc(r.flags, mountFunc) -} - -func (r *readStall) SetupTest() { testDirPath = setup.SetupTestDirectory(r.T().Name()) } -func (r *readStall) TearDownSuite() { +func (r *readStall) TearDownTest() { setup.UnmountGCSFuse(rootDir) assert.NoError(r.T(), emulator_tests.KillProxyServerProcess(r.proxyProcessId)) + setup.SaveGCSFuseLogFileInCaseOfFailure(r.T()) + setup.SaveProxyServerLogFileInCaseOfFailure(r.proxyServerLogFile, r.T()) } //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go index 2c1c752616..15c578daf9 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go @@ -47,6 +47,7 @@ type commonFailureTestSuite struct { data []byte port int proxyProcessId int + proxyServerLogFile string gcsObjectValidator } @@ -71,8 +72,9 @@ func (t *commonFailureTestSuite) SetupSuite() { func (t *commonFailureTestSuite) setupTest() { t.T().Helper() // Start proxy server for each test to ensure the config is initialized per test. + t.proxyServerLogFile = setup.CreateProxyServerLogFile(t.T()) var err error - t.port, t.proxyProcessId, err = emulator_tests.StartProxyServer(t.configPath, setup.CreateProxyServerLogFile(t.T())) + t.port, t.proxyProcessId, err = emulator_tests.StartProxyServer(t.configPath, t.proxyServerLogFile) require.NoError(t.T(), err) setup.AppendProxyEndpointToFlagSet(&t.flags, t.port) // Create storage client before running tests. @@ -89,6 +91,8 @@ func (t *commonFailureTestSuite) TearDownTest() { setup.UnmountGCSFuse(rootDir) assert.NoError(t.T(), t.closeStorageClient()) assert.NoError(t.T(), emulator_tests.KillProxyServerProcess(t.proxyProcessId)) + setup.SaveGCSFuseLogFileInCaseOfFailure(t.T()) + setup.SaveProxyServerLogFileInCaseOfFailure(t.proxyServerLogFile, t.T()) } func (t *commonFailureTestSuite) writingWithNewFileHandleAlsoFails(data []byte, off int64) { diff --git a/tools/integration_tests/emulator_tests/util/test_helper.go b/tools/integration_tests/emulator_tests/util/test_helper.go index ae4de8db0e..ae1f468a90 100644 --- a/tools/integration_tests/emulator_tests/util/test_helper.go +++ b/tools/integration_tests/emulator_tests/util/test_helper.go @@ -19,7 +19,6 @@ import ( "errors" "fmt" "io" - "log" "os" "os/exec" "regexp" @@ -56,7 +55,6 @@ func StartProxyServer(configPath, logFilePath string) (int, int, error) { if err != nil { return -1, -1, fmt.Errorf("error executing proxy server cmd: %v", err) } - log.Printf("Proxy Server log file: %s", logFilePath) port, proxyProcessId, err := getPortAndProcessInfoFromLogFile(logFilePath) if err != nil { return -1, -1, fmt.Errorf("error getting port information from log file: %v", err) diff --git a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go index 0e18c472fe..b8cb54736e 100644 --- a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go @@ -38,15 +38,17 @@ const ( ) type chunkTransferTimeoutInfinity struct { - port int - proxyProcessId int - flags []string + port int + proxyProcessId int + proxyServerLogFile string + flags []string } func (s *chunkTransferTimeoutInfinity) Setup(t *testing.T) { configPath := "../proxy_server/configs/write_stall_40s.yaml" + s.proxyServerLogFile = setup.CreateProxyServerLogFile(t) var err error - s.port, s.proxyProcessId, err = emulator_tests.StartProxyServer(configPath, setup.CreateProxyServerLogFile(t)) + s.port, s.proxyProcessId, err = emulator_tests.StartProxyServer(configPath, s.proxyServerLogFile) require.NoError(t, err) setup.AppendProxyEndpointToFlagSet(&s.flags, s.port) setup.MountGCSFuseWithGivenMountFunc(s.flags, mountFunc) @@ -55,6 +57,8 @@ func (s *chunkTransferTimeoutInfinity) Setup(t *testing.T) { func (s *chunkTransferTimeoutInfinity) Teardown(t *testing.T) { setup.UnmountGCSFuse(rootDir) assert.NoError(t, emulator_tests.KillProxyServerProcess(s.proxyProcessId)) + setup.SaveGCSFuseLogFileInCaseOfFailure(t) + setup.SaveProxyServerLogFileInCaseOfFailure(s.proxyServerLogFile, t) } //////////////////////////////////////////////////////////////////////// @@ -133,14 +137,17 @@ func TestChunkTransferTimeout(t *testing.T) { t.Run(fmt.Sprintf("Flags_%v", flags), func(t *testing.T) { for _, scenario := range stallScenarios { t.Run(scenario.name, func(t *testing.T) { - port, proxyProcessId, err := emulator_tests.StartProxyServer(scenario.configPath, setup.CreateProxyServerLogFile(t)) + proxyServerLogFile := setup.CreateProxyServerLogFile(t) + port, proxyProcessId, err := emulator_tests.StartProxyServer(scenario.configPath, proxyServerLogFile) require.NoError(t, err) setup.AppendProxyEndpointToFlagSet(&flags, port) setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) - defer func() { // Defer unmount and killing the server. + defer func() { // Defer unmount, killing the proxy server and saving log files. setup.UnmountGCSFuse(rootDir) assert.NoError(t, emulator_tests.KillProxyServerProcess(proxyProcessId)) + setup.SaveGCSFuseLogFileInCaseOfFailure(t) + setup.SaveProxyServerLogFileInCaseOfFailure(proxyServerLogFile, t) }() testDir := scenario.name + setup.GenerateRandomString(3) diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go index 7c48e8f687..56d1f2f286 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go @@ -19,7 +19,6 @@ import ( "log" "os" "path" - "strings" "testing" "time" @@ -52,9 +51,7 @@ func (s *cacheFileForRangeReadFalseTest) Setup(t *testing.T) { } func (s *cacheFileForRangeReadFalseTest) Teardown(t *testing.T) { - if t.Failed() { - setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) - } + setup.SaveGCSFuseLogFileInCaseOfFailure(t) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go index 3c6408616d..e65677fd6e 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go @@ -18,7 +18,6 @@ import ( "context" "log" "path" - "strings" "testing" "time" @@ -48,9 +47,7 @@ func (s *cacheFileForRangeReadTrueTest) Setup(t *testing.T) { } func (s *cacheFileForRangeReadTrueTest) Teardown(t *testing.T) { - if t.Failed() { - setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) - } + setup.SaveGCSFuseLogFileInCaseOfFailure(t) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/disabled_cache_ttl_test.go b/tools/integration_tests/read_cache/disabled_cache_ttl_test.go index 3469a6724e..40b312dced 100644 --- a/tools/integration_tests/read_cache/disabled_cache_ttl_test.go +++ b/tools/integration_tests/read_cache/disabled_cache_ttl_test.go @@ -17,7 +17,6 @@ package read_cache import ( "context" "log" - "strings" "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -47,9 +46,7 @@ func (s *disabledCacheTTLTest) Setup(t *testing.T) { } func (s *disabledCacheTTLTest) Teardown(t *testing.T) { - if t.Failed() { - setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) - } + setup.SaveGCSFuseLogFileInCaseOfFailure(t) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/job_chunk_test.go b/tools/integration_tests/read_cache/job_chunk_test.go index 63a40259b3..39e515a360 100644 --- a/tools/integration_tests/read_cache/job_chunk_test.go +++ b/tools/integration_tests/read_cache/job_chunk_test.go @@ -18,7 +18,6 @@ import ( "context" "log" "path" - "strings" "sync" "testing" @@ -52,9 +51,7 @@ func (s *jobChunkTest) Setup(t *testing.T) { } func (s *jobChunkTest) Teardown(t *testing.T) { - if t.Failed() { - setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) - } + setup.SaveGCSFuseLogFileInCaseOfFailure(t) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/local_modification_test.go b/tools/integration_tests/read_cache/local_modification_test.go index e3ee0386cc..504c73fb2f 100644 --- a/tools/integration_tests/read_cache/local_modification_test.go +++ b/tools/integration_tests/read_cache/local_modification_test.go @@ -18,7 +18,6 @@ import ( "context" "log" "path" - "strings" "testing" "cloud.google.com/go/storage" @@ -46,9 +45,7 @@ func (s *localModificationTest) Setup(t *testing.T) { } func (s *localModificationTest) Teardown(t *testing.T) { - if t.Failed() { - setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) - } + setup.SaveGCSFuseLogFileInCaseOfFailure(t) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/range_read_test.go b/tools/integration_tests/read_cache/range_read_test.go index 95fb1a0d67..b7d25ee724 100644 --- a/tools/integration_tests/read_cache/range_read_test.go +++ b/tools/integration_tests/read_cache/range_read_test.go @@ -17,7 +17,6 @@ package read_cache import ( "context" "log" - "strings" "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -48,9 +47,7 @@ func (s *rangeReadTest) Setup(t *testing.T) { } func (s *rangeReadTest) Teardown(t *testing.T) { - if t.Failed() { - setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) - } + setup.SaveGCSFuseLogFileInCaseOfFailure(t) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/read_only_test.go b/tools/integration_tests/read_cache/read_only_test.go index e1524d4628..4d07bfe14c 100644 --- a/tools/integration_tests/read_cache/read_only_test.go +++ b/tools/integration_tests/read_cache/read_only_test.go @@ -17,7 +17,6 @@ package read_cache import ( "context" "log" - "strings" "testing" "cloud.google.com/go/storage" @@ -46,9 +45,7 @@ func (s *readOnlyTest) Setup(t *testing.T) { } func (s *readOnlyTest) Teardown(t *testing.T) { - if t.Failed() { - setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) - } + setup.SaveGCSFuseLogFileInCaseOfFailure(t) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/remount_test.go b/tools/integration_tests/read_cache/remount_test.go index e45fd61118..aefd97ab49 100644 --- a/tools/integration_tests/read_cache/remount_test.go +++ b/tools/integration_tests/read_cache/remount_test.go @@ -18,7 +18,6 @@ import ( "context" "log" "path" - "strings" "testing" "time" @@ -48,9 +47,7 @@ func (s *remountTest) Setup(t *testing.T) { } func (s *remountTest) Teardown(t *testing.T) { - if t.Failed() { - setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) - } + setup.SaveGCSFuseLogFileInCaseOfFailure(t) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/read_cache/small_cache_ttl_test.go b/tools/integration_tests/read_cache/small_cache_ttl_test.go index cb99a5f87e..5aeee77c9a 100644 --- a/tools/integration_tests/read_cache/small_cache_ttl_test.go +++ b/tools/integration_tests/read_cache/small_cache_ttl_test.go @@ -48,9 +48,7 @@ func (s *smallCacheTTLTest) Setup(t *testing.T) { } func (s *smallCacheTTLTest) Teardown(t *testing.T) { - if t.Failed() { - setup.SaveLogFileToKOKOROArtifact("gcsfuse-failed-integration-test-logs-" + strings.Replace(t.Name(), "/", "-", -1)) - } + setup.SaveGCSFuseLogFileInCaseOfFailure(t) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index a6c7436641..301d9968cb 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -46,10 +46,12 @@ var testOnTPCEndPoint = flag.Bool("testOnTPCEndPoint", false, "Run tests on TPC var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) const ( - FilePermission_0600 = 0600 - DirPermission_0755 = 0755 - Charset = "abcdefghijklmnopqrstuvwxyz0123456789" - PathEnvVariable = "PATH" + FilePermission_0600 = 0600 + DirPermission_0755 = 0755 + Charset = "abcdefghijklmnopqrstuvwxyz0123456789" + PathEnvVariable = "PATH" + GCSFuseLogFilePrefix = "gcsfuse-failed-integration-test-logs-" + ProxyServerLogFilePrefix = "proxy-server-failed-integration-test-logs-" ) var ( @@ -164,13 +166,13 @@ func SetUpTestDir() error { var err error testDir, err = os.MkdirTemp("", "gcsfuse_readwrite_test_") if err != nil { - return fmt.Errorf("TempDir: %w\n", err) + return fmt.Errorf("TempDir: %w", err) } if !TestInstalledPackage() { err = util.BuildGcsfuse(testDir) if err != nil { - return fmt.Errorf("BuildGcsfuse(%q): %w\n", TestDir(), err) + return fmt.Errorf("BuildGcsfuse(%q): %w", TestDir(), err) } binFile = path.Join(TestDir(), "bin/gcsfuse") sbinFile = path.Join(TestDir(), "sbin/mount.gcsfuse") @@ -193,7 +195,7 @@ func SetUpTestDir() error { err = os.Mkdir(mntDir, 0755) if err != nil { - return fmt.Errorf("Mkdir(%q): %v\n", MntDir(), err) + return fmt.Errorf("Mkdir(%q): %v", MntDir(), err) } return nil } @@ -233,19 +235,44 @@ func UnMountBucket() { func SaveLogFileInCaseOfFailure(successCode int) { if successCode != 0 { - // Logfile name will be gcsfuse-failed-integration-test-log-xxxxx - failedlogsFileName := "gcsfuse-failed-integration-test-logs-" + GenerateRandomString(5) - SaveLogFileToKOKOROArtifact(failedlogsFileName) + SaveLogFileAsArtifact(LogFile(), GCSFuseLogFilePrefix+GenerateRandomString(5)) } } -func SaveLogFileToKOKOROArtifact(artifactName string) { - log.Printf("log file is available on kokoro artifacts with file name: %s", artifactName) - logFileInKokoroArtifact := path.Join(os.Getenv("KOKORO_ARTIFACTS_DIR"), artifactName) - err := operations.CopyFile(logFile, logFileInKokoroArtifact) +// Saves logFile as given artifactName in KOKORO or +// TestDir based on where the test is ran. +func SaveLogFileAsArtifact(logFile, artifactName string) { + logDir := os.Getenv("KOKORO_ARTIFACTS_DIR") + if logDir == "" { + // Save log files in TestDir as this run is not on KOKORO. + logDir = TestDir() + } + artifactPath := path.Join(logDir, artifactName) + err := operations.CopyFile(logFile, artifactPath) if err != nil { - log.Fatalf("Error in coping logfile in kokoro artifact: %v", err) + log.Fatalf("Error in copying logfile to artifact path: %v", err) + } + log.Printf("Log file saved at %v", artifactPath) +} + +// In case of test failure saves GCSFuse log file to +// KOKORO artifacts directory if test ran on KOKORO +// or saves to TestDir if test ran on local. +func SaveGCSFuseLogFileInCaseOfFailure(tb testing.TB) { + if !tb.Failed() { + return + } + SaveLogFileAsArtifact(LogFile(), GCSFuseLogFilePrefix+strings.ReplaceAll(tb.Name(), "/", "_")+GenerateRandomString(5)) +} + +// In case of test failure saves ProxyServerLogFile to +// KOKORO artifacts directory if test ran on KOKORO +// or saves to TestDir if test ran on local. +func SaveProxyServerLogFileInCaseOfFailure(proxyServerLogFile string, tb testing.TB) { + if !tb.Failed() { + return } + SaveLogFileAsArtifact(proxyServerLogFile, ProxyServerLogFilePrefix+strings.ReplaceAll(tb.Name(), "/", "_")+GenerateRandomString(5)) } func UnMountAndThrowErrorInFailure(flags []string, successCode int) { From 5af2f3f49531f1522fb6fca5b29a6bad75d06ced Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 19 Mar 2025 14:39:13 +0530 Subject: [PATCH 0302/1298] Enable metrics while running perf presubmit tests (#3100) --- perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index 7506857653..54df25fe75 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -63,7 +63,7 @@ git fetch origin -q function execute_perf_test() { mkdir -p gcs - GCSFUSE_FLAGS="--implicit-dirs" + GCSFUSE_FLAGS="--implicit-dirs --prometheus-port=48341" BUCKET_NAME=presubmit-perf-tests MOUNT_POINT=gcs # The VM will itself exit if the gcsfuse mount fails. From 5a9528d96f09ccef2f9f95edc5c91d913af2a55c Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 20 Mar 2025 15:23:29 +0530 Subject: [PATCH 0303/1298] Integrate with resumable upload writer.Flush() (#3099) * integrate with flush writer * add unit tests * add unit tests * revert upload handler changes * review comments --- internal/fs/fs.go | 1 - internal/gcsx/prefix_bucket.go | 4 + internal/gcsx/prefix_bucket_test.go | 27 +++++ internal/monitor/bucket.go | 7 ++ internal/ratelimit/throttled_bucket.go | 8 ++ internal/storage/bucket_handle.go | 14 +++ internal/storage/bucket_handle_test.go | 106 ++++++++++-------- internal/storage/caching/fast_stat_bucket.go | 18 +++ .../storage/caching/fast_stat_bucket_test.go | 61 ++++++++++ internal/storage/debug_bucket.go | 8 ++ internal/storage/fake/bucket.go | 12 ++ internal/storage/fake/fake_object_writer.go | 8 ++ internal/storage/gcs/bucket.go | 5 + internal/storage/mock/mock_writer.go | 6 + internal/storage/mock/testify_mock_bucket.go | 5 + internal/storage/mock_bucket.go | 28 +++++ internal/storage/testify_mock_bucket.go | 5 + 17 files changed, 276 insertions(+), 47 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index cb2ae83d1e..983c27a590 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -872,7 +872,6 @@ func (fs *fileSystem) createDirInode(ic inode.Core, inodes map[inode.Name]inode. // LOCK_FUNCTION(in) func (fs *fileSystem) lookUpOrCreateInodeIfNotStale(ic inode.Core) (in inode.Inode) { - // Sanity check. if err := ic.SanityCheck(); err != nil { panic(err.Error()) } diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index ea4bd5dae3..4aa9bd222d 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -119,6 +119,10 @@ func (b *prefixBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs return } +func (b *prefixBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (offset int64, err error) { + return b.wrapped.FlushPendingWrites(ctx, w) +} + func (b *prefixBucket) CopyObject( ctx context.Context, req *gcs.CopyObjectRequest) (o *gcs.Object, err error) { diff --git a/internal/gcsx/prefix_bucket_test.go b/internal/gcsx/prefix_bucket_test.go index f26e624136..879c93a826 100644 --- a/internal/gcsx/prefix_bucket_test.go +++ b/internal/gcsx/prefix_bucket_test.go @@ -337,6 +337,33 @@ func (t *PrefixBucketTest) CreateObjectChunkWriterAndFinalizeUpload() { assert.Equal(t.T(), string(content), string(actual)) } +func (t *PrefixBucketTest) CreateObjectChunkWriterAndFlushPendingWrites() { + var err error + suffix := "taco" + content := []byte("foobar") + + // Create the object. + w, err := t.bucket.CreateObjectChunkWriter( + t.ctx, + &gcs.CreateObjectRequest{ + Name: suffix, + ContentEncoding: "gzip", + Contents: nil, + }, + 1024, nil) + assert.NoError(t.T(), err) + _, err = w.Write(content) + assert.NoError(t.T(), err) + offset, err := t.bucket.FlushPendingWrites(t.ctx, w) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), int64(len(content)), offset) + // Read it through the back door. + actual, err := storageutil.ReadObject(t.ctx, t.wrapped, t.prefix+suffix) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), string(content), string(actual)) +} + func (t *PrefixBucketTest) Test_CopyObject() { var err error suffix := "taco" diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index 0bcb27caf7..b951b4884c 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -103,6 +103,13 @@ func (mb *monitoringBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (* return o, err } +func (mb *monitoringBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (int64, error) { + startTime := time.Now() + offset, err := mb.wrapped.FlushPendingWrites(ctx, w) + recordRequest(ctx, mb.metricHandle, "FlushPendingWrites", startTime) + return offset, err +} + func (mb *monitoringBucket) CopyObject( ctx context.Context, req *gcs.CopyObjectRequest) (*gcs.Object, error) { diff --git a/internal/ratelimit/throttled_bucket.go b/internal/ratelimit/throttled_bucket.go index 7905989b4c..2b7244950c 100644 --- a/internal/ratelimit/throttled_bucket.go +++ b/internal/ratelimit/throttled_bucket.go @@ -115,6 +115,14 @@ func (b *throttledBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gc return b.wrapped.FinalizeUpload(ctx, w) } +func (b *throttledBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (int64, error) { + // FlushPendingWrites is not throttled to prevent permanent data loss in case the + // limiter's burst size is exceeded. + // Note: CreateObjectChunkWriter, a prerequisite for FlushPendingWrites, + // is throttled. + return b.wrapped.FlushPendingWrites(ctx, w) +} + func (b *throttledBucket) CopyObject( ctx context.Context, req *gcs.CopyObjectRequest) (o *gcs.Object, err error) { diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 9fcf325672..fb9908daf7 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -251,6 +251,20 @@ func (bh *bucketHandle) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gc return } +func (bh *bucketHandle) FlushPendingWrites(ctx context.Context, w gcs.Writer) (offset int64, err error) { + defer func() { + err = gcs.GetGCSError(err) + }() + + offset, err = w.Flush() + if err != nil { + err = fmt.Errorf("error in FlushPendingWrites : %w", err) + return + } + + return offset, err +} + func (bh *bucketHandle) CopyObject(ctx context.Context, req *gcs.CopyObjectRequest) (o *gcs.Object, err error) { defer func() { err = gcs.GetGCSError(err) diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 4156f29529..8c63326e56 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -621,22 +621,7 @@ func (testSuite *BucketHandleTest) TestBucketHandle_FinalizeUploadSuccess() { for _, tt := range tests { testSuite.T().Run(tt.name, func(t *testing.T) { - progressFunc := func(_ int64) {} - wr, err := testSuite.bucketHandle.CreateObjectChunkWriter(context.Background(), - &gcs.CreateObjectRequest{ - Name: tt.objectName, - GenerationPrecondition: tt.generation, - }, - tt.chunkSize, - progressFunc, - ) - require.NoError(t, err) - objWr, ok := (wr).(*ObjectWriter) - require.True(t, ok) - require.NotNil(t, objWr) - assert.Equal(t, tt.objectName, objWr.ObjectName()) - assert.Equal(t, tt.chunkSize, objWr.ChunkSize) - assert.Equal(t, reflect.ValueOf(progressFunc).Pointer(), reflect.ValueOf(objWr.ProgressFunc).Pointer()) + wr := testSuite.createObjectChunkWriter(t, tt.objectName, tt.generation, tt.chunkSize) o, err := testSuite.bucketHandle.FinalizeUpload(context.Background(), wr) @@ -646,49 +631,78 @@ func (testSuite *BucketHandleTest) TestBucketHandle_FinalizeUploadSuccess() { } } -func (testSuite *BucketHandleTest) createObject(objName string) { - testSuite.T().Helper() - var generation int64 = 0 - wr, err := testSuite.bucketHandle.CreateObjectChunkWriter(context.Background(), - &gcs.CreateObjectRequest{ - Name: objName, - GenerationPrecondition: &generation, - }, - 100, - func(_ int64) {}) - require.NoError(testSuite.T(), err) - assert.Equal(testSuite.T(), objName, wr.ObjectName()) - - o, err := testSuite.bucketHandle.FinalizeUpload(context.Background(), wr) - - require.NoError(testSuite.T(), err) - assert.NotNil(testSuite.T(), o) -} - func (testSuite *BucketHandleTest) TestFinalizeUploadWithGenerationAsZeroWhenObjectAlreadyExists() { createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + // Pre-create the object (creating writer and finalizing upload). objName := "pre_created_test_object" - testSuite.createObject(objName) - // Create Object Writer again when object already exists. var generation int64 = 0 - wr, err := testSuite.bucketHandle.CreateObjectChunkWriter(context.Background(), - &gcs.CreateObjectRequest{ - Name: objName, - GenerationPrecondition: &generation, - }, - 100, - func(_ int64) {}) + wr := testSuite.createObjectChunkWriter(testSuite.T(), objName, &generation, 100) + o, err := testSuite.bucketHandle.FinalizeUpload(context.Background(), wr) require.NoError(testSuite.T(), err) - assert.Equal(testSuite.T(), objName, wr.ObjectName()) + assert.NotNil(testSuite.T(), o) + // Create Object Writer again when object already exists. + wr = testSuite.createObjectChunkWriter(testSuite.T(), objName, &generation, 100) - o, err := testSuite.bucketHandle.FinalizeUpload(context.Background(), wr) + o, err = testSuite.bucketHandle.FinalizeUpload(context.Background(), wr) assert.Error(testSuite.T(), err) assert.IsType(testSuite.T(), &gcs.PreconditionError{}, err) assert.Nil(testSuite.T(), o) } +func (testSuite *BucketHandleTest) createObjectChunkWriter(t *testing.T, objectName string, generation *int64, chunkSize int) gcs.Writer { + t.Helper() + progressFunc := func(_ int64) {} + wr, err := testSuite.bucketHandle.CreateObjectChunkWriter(context.Background(), + &gcs.CreateObjectRequest{ + Name: objectName, + GenerationPrecondition: generation, + }, + chunkSize, + progressFunc, + ) + require.NoError(t, err) + objWr, ok := (wr).(*ObjectWriter) + require.True(t, ok) + require.NotNil(t, objWr) + assert.Equal(t, objectName, objWr.ObjectName()) + assert.Equal(t, chunkSize, objWr.ChunkSize) + assert.Equal(t, reflect.ValueOf(progressFunc).Pointer(), reflect.ValueOf(objWr.ProgressFunc).Pointer()) + + return wr +} + +func (testSuite *BucketHandleTest) TestFlushPendingWritesFails() { + // These tests only run with HTTP client because fake storage server is not + // integrated with GRPC. + var generation0 int64 = 0 + tests := []struct { + bucketType string + }{ + { + bucketType: "multiregion", + }, + { + bucketType: "zone", + }, + } + + for _, tt := range tests { + testSuite.T().Run(tt.bucketType, func(t *testing.T) { + createBucketHandle(testSuite, &controlpb.StorageLayout{ + LocationType: tt.bucketType, + }, nil) + wr := testSuite.createObjectChunkWriter(t, TestObjectName, &generation0, 100) + + _, err := testSuite.bucketHandle.FlushPendingWrites(context.Background(), wr) + + require.Error(t, err) + assert.ErrorContains(testSuite.T(), err, "Flush not supported unless client uses gRPC and Append is set to true") + }) + } +} + func (testSuite *BucketHandleTest) TestGetProjectValueWhenGcloudProjectionIsNoAcl() { proj := getProjectionValue(gcs.NoAcl) diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index 078f3be2c9..3a9fb73dd2 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -255,6 +255,24 @@ func (b *fastStatBucket) FinalizeUpload(ctx context.Context, writer gcs.Writer) return o, err } +func (b *fastStatBucket) FlushPendingWrites(ctx context.Context, writer gcs.Writer) (int64, error) { + name := writer.ObjectName() + hit, o := b.cache.LookUp(name, b.clock.Now()) + if hit { + // Throw away any existing record for this object. + b.invalidate(name) + } + + offset, err := b.wrapped.FlushPendingWrites(ctx, writer) + + // Record the new object if err is nil. + if err == nil && hit && o != nil { + o.Size = uint64(offset) + b.insertMinObject(o) + } + return offset, err +} + // LOCKS_EXCLUDED(b.mu) func (b *fastStatBucket) CopyObject( ctx context.Context, diff --git a/internal/storage/caching/fast_stat_bucket_test.go b/internal/storage/caching/fast_stat_bucket_test.go index 86e370a376..4fb94a9ff8 100644 --- a/internal/storage/caching/fast_stat_bucket_test.go +++ b/internal/storage/caching/fast_stat_bucket_test.go @@ -278,6 +278,67 @@ func (t *FinalizeUploadTest) WrappedSucceeds() { ExpectNe(nil, o) } +//////////////////////////////////////////////////////////////////////// +// FinalizeUpload +//////////////////////////////////////////////////////////////////////// + +type FlushPendingWritesTest struct { + fastStatBucketTest +} + +func init() { RegisterTestSuite(&FlushPendingWritesTest{}) } + +func (t *FlushPendingWritesTest) WrappedFails() { + const name = "taco" + writer := &storage.ObjectWriter{ + Writer: &gostorage.Writer{ObjectAttrs: gostorage.ObjectAttrs{Name: name}}, + } + // Expect cache Lookup. + ExpectCall(t.cache, "LookUp")(name, timeutil.TimeEq(t.clock.Now())). + WillOnce(Return(true, &gcs.MinObject{})) + // Expect cache Erase. + ExpectCall(t.cache, "Erase")(name) + // Expect call to Wrapped method. + var wrappedWriter gcs.Writer + ExpectCall(t.wrapped, "FlushPendingWrites")(Any(), Any()). + WillOnce(DoAll(SaveArg(1, &wrappedWriter), Return(10, errors.New("taco")))) + + // Call. + gotOffset, err := t.bucket.FlushPendingWrites(context.TODO(), writer) + + ExpectEq(writer, wrappedWriter) + ExpectEq(10, gotOffset) + ExpectThat(err, Error(HasSubstr("taco"))) +} + +func (t *FlushPendingWritesTest) WrappedSucceeds() { + const name = "taco" + minObject := &gcs.MinObject{} + writer := &storage.ObjectWriter{ + Writer: &gostorage.Writer{ObjectAttrs: gostorage.ObjectAttrs{Name: name}}, + } + var err error + // Expect cache Lookup. + ExpectCall(t.cache, "LookUp")(name, timeutil.TimeEq(t.clock.Now())). + WillOnce(Return(true, minObject)) + // Expect cache Erase. + ExpectCall(t.cache, "Erase")(name) + // Wrapped. + ExpectCall(t.wrapped, "FlushPendingWrites")(Any(), Any()). + WillOnce(Return(10, nil)) + // Insert. + var cachedMinObject *gcs.MinObject + ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))). + WillOnce(DoAll(SaveArg(0, &cachedMinObject))) + + // Call + offset, err := t.bucket.FlushPendingWrites(context.TODO(), writer) + + AssertEq(nil, err) + ExpectEq(10, offset) + ExpectEq(10, cachedMinObject.Size) +} + //////////////////////////////////////////////////////////////////////// // CopyObject //////////////////////////////////////////////////////////////////////// diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index 3a5b168229..9122c954f0 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -189,6 +189,14 @@ func (b *debugBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs. return } +func (b *debugBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (offset int64, err error) { + id, desc, start := b.startRequest("FlushPendingWrites(%q)", w.ObjectName()) + defer b.finishRequest(id, desc, start, &err) + + offset, err = b.wrapped.FlushPendingWrites(ctx, w) + return +} + func (b *debugBucket) CopyObject( ctx context.Context, req *gcs.CopyObjectRequest) (o *gcs.Object, err error) { diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index 1134347286..da5e0ea44e 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -691,6 +691,18 @@ func (b *bucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.CreateObj return NewFakeObjectWriter(b, req) } +func (b *bucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (int64, error) { + b.mu.Lock() + defer b.mu.Unlock() + + offset, err := w.Flush() + if err != nil { + return 0, err + } + + return offset, nil +} + func (b *bucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { b.mu.Lock() defer b.mu.Unlock() diff --git a/internal/storage/fake/fake_object_writer.go b/internal/storage/fake/fake_object_writer.go index 581bc33c1b..1f81bfdf17 100644 --- a/internal/storage/fake/fake_object_writer.go +++ b/internal/storage/fake/fake_object_writer.go @@ -63,6 +63,14 @@ func (w *FakeObjectWriter) Close() error { return err } +func (w *FakeObjectWriter) Flush() (int64, error) { + err := w.Close() + if err != nil { + return 0, err + } + return int64(w.buf.Len()), nil +} + func (w *FakeObjectWriter) ObjectName() string { return w.Name } diff --git a/internal/storage/gcs/bucket.go b/internal/storage/gcs/bucket.go index 512c45a72f..9c30476880 100644 --- a/internal/storage/gcs/bucket.go +++ b/internal/storage/gcs/bucket.go @@ -41,6 +41,7 @@ const ( // purposes, such as the fake implementation in fake/bucket.go. type Writer interface { io.WriteCloser + Flush() (int64, error) ObjectName() string Attrs() *storage.ObjectAttrs } @@ -105,6 +106,10 @@ type Bucket interface { // operation and creates an object on GCS. FinalizeUpload(ctx context.Context, writer Writer) (*MinObject, error) + // FlushPendingWrites is used for zonal buckets to flush any pending data in + // the writer buffer. The object is not finalized and can be appended further. + FlushPendingWrites(ctx context.Context, writer Writer) (int64, error) + // Copy an object to a new name, preserving all metadata. Any existing // generation of the destination name will be overwritten. // diff --git a/internal/storage/mock/mock_writer.go b/internal/storage/mock/mock_writer.go index c3f3c73775..087ecdf346 100644 --- a/internal/storage/mock/mock_writer.go +++ b/internal/storage/mock/mock_writer.go @@ -35,6 +35,7 @@ func (mw *Writer) Write(p []byte) (n int, err error) { args := mw.Called(p) return args.Int(0), args.Error(1) } + func (mw *Writer) Attrs() *storage.ObjectAttrs { args := mw.Called() return args.Get(0).(*storage.ObjectAttrs) @@ -45,6 +46,11 @@ func (mw *Writer) Close() error { return args.Error(0) } +func (mw *Writer) Flush() (int64, error) { + args := mw.Called() + return args.Get(0).(int64), args.Error(1) +} + func (mw *Writer) ObjectName() string { args := mw.Called() return args.String(0) diff --git a/internal/storage/mock/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go index 7649fead3e..260d227b7c 100644 --- a/internal/storage/mock/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -65,6 +65,11 @@ func (m *TestifyMockBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (* return args.Get(0).(*gcs.MinObject), args.Error(1) } +func (m *TestifyMockBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (int64, error) { + args := m.Called(ctx, w) + return int64(args.Int(0)), args.Error(1) +} + func (m *TestifyMockBucket) CopyObject(ctx context.Context, req *gcs.CopyObjectRequest) (*gcs.Object, error) { args := m.Called(ctx, req) return args.Get(0).(*gcs.Object), args.Error(1) diff --git a/internal/storage/mock_bucket.go b/internal/storage/mock_bucket.go index add828a6a8..c7ec9d2bea 100644 --- a/internal/storage/mock_bucket.go +++ b/internal/storage/mock_bucket.go @@ -188,6 +188,34 @@ func (m *mockBucket) FinalizeUpload(p0 context.Context, p1 gcs.Writer) (o0 *gcs. return } +func (m *mockBucket) FlushPendingWrites(p0 context.Context, p1 gcs.Writer) (o0 int64, o1 error) { + // Get a file name and line number for the caller. + _, file, line, _ := runtime.Caller(1) + + // Hand the call off to the controller, which does most of the work. + retVals := m.controller.HandleMethodCall( + m, + "FlushPendingWrites", + file, + line, + []interface{}{p0, p1}) + + if len(retVals) != 2 { + panic(fmt.Sprintf("mockBucket.FlushPendingWrites: invalid return values: %v", retVals)) + } + + // o0 int64 (offset) + if retVals[0] != nil { + o0 = retVals[0].(int64) + } + // o1 error + if retVals[1] != nil { + o1 = retVals[1].(error) + } + + return +} + func (m *mockBucket) DeleteObject(p0 context.Context, p1 *gcs.DeleteObjectRequest) (o0 error) { // Get a file name and line number for the caller. _, file, line, _ := runtime.Caller(1) diff --git a/internal/storage/testify_mock_bucket.go b/internal/storage/testify_mock_bucket.go index b1e9757136..2b080cde10 100644 --- a/internal/storage/testify_mock_bucket.go +++ b/internal/storage/testify_mock_bucket.go @@ -65,6 +65,11 @@ func (m *TestifyMockBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (* return args.Get(0).(*gcs.MinObject), args.Error(1) } +func (m *TestifyMockBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (int64, error) { + args := m.Called(ctx, w) + return args.Get(0).(int64), args.Error(1) +} + func (m *TestifyMockBucket) CopyObject(ctx context.Context, req *gcs.CopyObjectRequest) (*gcs.Object, error) { args := m.Called(ctx, req) return args.Get(0).(*gcs.Object), args.Error(1) From b24f739321f2552a5bc6abad6bbfefc1049a2a9d Mon Sep 17 00:00:00 2001 From: Ankita Luthra Date: Fri, 21 Mar 2025 08:54:25 +0530 Subject: [PATCH 0304/1298] Enable parallel downloads config (#3097) * adds logic to turn ON parallel downloads when file cache is ON * adds call to new method resolveParallelDownloadsValue * adds unit tests for success failure rationlize case * make enable parallel downloads flag true when cache dir is set * fixes comments * fixes metrics test to include parallel downloads metrics * fixes error string in prom_test * fixes prom test * fixes PR comments * minor fixes * fixes prom test * remove unnecessary code * adds comment * adds a TODO --- cfg/constants.go | 7 ++- cfg/rationalize.go | 9 +++ cfg/rationalize_test.go | 57 +++++++++++++++++++ .../integration_tests/monitoring/prom_test.go | 7 ++- 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/cfg/constants.go b/cfg/constants.go index d48ea9f8b9..0b3c9ff7df 100644 --- a/cfg/constants.go +++ b/cfg/constants.go @@ -79,8 +79,11 @@ const ( MetadataNegativeCacheTTLConfigKey = "metadata-cache.negative-ttl-secs" // StatCacheMaxSizeConfigKey is the Viper configuration key for the maximum //size of the metadata stat cache in megabytes. - StatCacheMaxSizeConfigKey = "metadata-cache.stat-cache-max-size-mb" - maxSupportedStatCacheMaxSizeMB = util.MaxMiBsInUint64 + StatCacheMaxSizeConfigKey = "metadata-cache.stat-cache-max-size-mb" + // FileCacheParallelDownloadsConfigKey is the Viper configuration key for the + //parallel-downloads enablement. + FileCacheParallelDownloadsConfigKey = "file-cache.enable-parallel-downloads" + maxSupportedStatCacheMaxSizeMB = util.MaxMiBsInUint64 ) // CacheUtilMinimumAlignSizeForWriting is the minimum buffer size used for memory-aligned diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 9d0319078e..40485fe4e9 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -97,6 +97,14 @@ func resolveCloudMetricsUploadIntervalSecs(m *MetricsConfig) { } } +func resolveParallelDownloadsValue(v isSet, fc *FileCacheConfig, c *Config) { + // Parallel downloads should be default ON when file cache is enabled, in case + // it is explicitly set by the user, use that value. + if IsFileCacheEnabled(c) && !v.IsSet(FileCacheParallelDownloadsConfigKey) { + fc.EnableParallelDownloads = true + } +} + // Rationalize updates the config fields based on the values of other fields. func Rationalize(v isSet, c *Config) error { var err error @@ -116,6 +124,7 @@ func Rationalize(v isSet, c *Config) error { resolveMetadataCacheTTL(v, &c.MetadataCache) resolveStatCacheMaxSizeMB(v, &c.MetadataCache) resolveCloudMetricsUploadIntervalSecs(&c.Metrics) + resolveParallelDownloadsValue(v, &c.FileCache, c) return nil } diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index f1373c8222..910ae53043 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -430,3 +430,60 @@ func TestRationalizeMetricsConfig(t *testing.T) { }) } } + +func TestRationalize_ParallelDownloadsConfig(t *testing.T) { + testCases := []struct { + name string + flags flagSet + config *Config + expectedParallelDownloads bool + }{ + { + name: "valid_config_file_cache_enabled", + config: &Config{ + CacheDir: ResolvedPath("/some-path"), + FileCache: FileCacheConfig{ + MaxSizeMb: 500, + }, + }, + expectedParallelDownloads: true, + }, + { + name: "valid_config_file_cache_disabled", + config: &Config{}, + expectedParallelDownloads: false, + }, + { + name: "valid_config_cache_dir_not_set_and_max_size_mb_set", + config: &Config{ + FileCache: FileCacheConfig{ + MaxSizeMb: 500, + }, + }, + expectedParallelDownloads: false, + }, + { + name: "valid_config_parallel_download_explicit_false", + // flagset here is representing viper config, value true is not actual value of the flag + // it just means flag is SET by the user + flags: flagSet{"file-cache.enable-parallel-downloads": true}, + config: &Config{ + CacheDir: ResolvedPath("/some-path"), + FileCache: FileCacheConfig{ + MaxSizeMb: 500, + }, + }, + expectedParallelDownloads: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := Rationalize(tc.flags, tc.config) + + if assert.NoError(t, err) { + assert.Equal(t, tc.expectedParallelDownloads, tc.config.FileCache.EnableParallelDownloads) + } + }) + } +} diff --git a/tools/integration_tests/monitoring/prom_test.go b/tools/integration_tests/monitoring/prom_test.go index e634d771ed..e01a7be894 100644 --- a/tools/integration_tests/monitoring/prom_test.go +++ b/tools/integration_tests/monitoring/prom_test.go @@ -174,7 +174,8 @@ func assertNonZeroCountMetric(testSuite *PromTest, metricName, labelName, labelV } } - assert.Fail(testSuite.T(), "Didn't find the metric with name: %s, labelName: %s and labelValue: %s", metricName, labelName, labelValue) + assert.Fail(testSuite.T(), fmt.Sprintf("Didn't find the metric with name: %s, labelName: %s and labelValue: %s", + metricName, labelName, labelValue)) } // assertNonZeroHistogramMetric asserts that the specified histogram metric is present and is positive for at least one of the buckets in the Prometheus export. @@ -239,7 +240,6 @@ func (testSuite *PromTest) TestReadMetrics() { require.NoError(testSuite.T(), err) assertNonZeroCountMetric(testSuite, "file_cache_read_count", "cache_hit", "false") assertNonZeroCountMetric(testSuite, "file_cache_read_count", "read_type", "Sequential") - assertNonZeroCountMetric(testSuite, "file_cache_read_bytes_count", "read_type", "Sequential") assertNonZeroHistogramMetric(testSuite, "file_cache_read_latencies", "cache_hit", "false") assertNonZeroCountMetric(testSuite, "fs_ops_count", "fs_op", "OpenFile") assertNonZeroCountMetric(testSuite, "fs_ops_count", "fs_op", "ReadFile") @@ -247,10 +247,11 @@ func (testSuite *PromTest) TestReadMetrics() { assertNonZeroCountMetric(testSuite, "gcs_request_count", "gcs_method", "NewReader") assertNonZeroCountMetric(testSuite, "gcs_reader_count", "io_method", "opened") assertNonZeroCountMetric(testSuite, "gcs_reader_count", "io_method", "closed") - assertNonZeroCountMetric(testSuite, "gcs_read_count", "read_type", "Sequential") + assertNonZeroCountMetric(testSuite, "gcs_read_count", "read_type", "Parallel") assertNonZeroCountMetric(testSuite, "gcs_download_bytes_count", "", "") assertNonZeroCountMetric(testSuite, "gcs_read_bytes_count", "", "") assertNonZeroHistogramMetric(testSuite, "gcs_request_latencies", "gcs_method", "NewReader") + //TODO: file_cache_read_bytes_count should be added once with waitForDownload is true same as sequential for default pd, } func TestPromOCSuite(t *testing.T) { From 7e7642ca7bd53d35b851da317f319911248fda30 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 21 Mar 2025 05:22:27 +0000 Subject: [PATCH 0305/1298] [testing-on-gke] Add e2e-latency output metrics (#3084) * [testing-on-gke] remove obsolete unused gcsfuse scnearios * add e2e latency output metrics in fio runs --- .../examples/dlio/dlio_workload.py | 3 +-- .../examples/dlio/parse_logs.py | 4 --- .../templates/dlio-tester.yaml | 22 ---------------- .../dlio/unet3d-loading-test/values.yaml | 2 +- .../examples/fio/fio_workload.py | 3 +-- .../loading-test/templates/fio-tester.yaml | 22 ---------------- .../examples/fio/loading-test/values.yaml | 2 +- .../testing_on_gke/examples/fio/parse_logs.py | 25 ++++++++++++++----- .../examples/utils/parse_logs_common.py | 2 -- 9 files changed, 23 insertions(+), 62 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py index e5cce6af48..8c7127bca2 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py @@ -105,8 +105,7 @@ class DlioWorkload: (essentially the data needed to create a job file for DLIO run). Members: - 1. scenario (string): One of "local-ssd", "gcsfuse-generic", - "gcsfuse-file-cache" and "gcsfuse-no-file-cache". + 1. scenario (string): One of "local-ssd", "gcsfuse-generic". 2. numFilesTrain (int): DLIO numFilesTrain argument e.g. 500000 etc. 3. recordLength (int): DLIO recordLength argument e.g. 100, 1000000 etc. 4. bucket (str): Name of a GCS bucket to read input files from. diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py index 7fea8e0086..7bcf0e6dcd 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py @@ -97,8 +97,6 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: "records": "local-ssd": [record1, record2, record3, ...] "gcsfuse-generic": [record1, record2, record3, ...] - "gcsfuse-file-cache": [record1, record2, record3, ...] - "gcsfuse-no-file-cache": [record1, record2, record3, ...] """ output = {} @@ -145,8 +143,6 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: "records": { "local-ssd": [], "gcsfuse-generic": [], - "gcsfuse-file-cache": [], - "gcsfuse-no-file-cache": [], }, } diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml index cd6487e178..f1a3b7fb4f 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml @@ -131,26 +131,4 @@ spec: volumeAttributes: bucketName: {{ .Values.bucketName }} mountOptions: "{{ .Values.gcsfuse.mountOptions }}" - {{- else if eq .Values.scenario "gcsfuse-file-cache" }} - csi: - driver: gcsfuse.csi.storage.gke.io - volumeAttributes: - bucketName: {{ .Values.bucketName }} - metadataCacheTTLSeconds: "{{ .Values.gcsfuse.metadataCacheTTLSeconds }}" - metadataTypeCacheCapacity: "{{ .Values.gcsfuse.metadataTypeCacheCapacity }}" - metadataStatCacheCapacity: "{{ .Values.gcsfuse.metadataStatCacheCapacity }}" - fileCacheCapacity: "{{ .Values.gcsfuse.fileCacheCapacity }}" - fileCacheForRangeRead: "{{ .Values.gcsfuse.fileCacheForRangeRead }}" - gcsfuseLoggingSeverity: "debug" - mountOptions: implicit-dirs - {{- else }} - csi: - driver: gcsfuse.csi.storage.gke.io - volumeAttributes: - bucketName: {{ .Values.bucketName }} - metadataCacheTTLSeconds: "{{ .Values.gcsfuse.metadataCacheTTLSeconds }}" - metadataTypeCacheCapacity: "{{ .Values.gcsfuse.metadataTypeCacheCapacity }}" - metadataStatCacheCapacity: "{{ .Values.gcsfuse.metadataStatCacheCapacity }}" - gcsfuseLoggingSeverity: "debug" - mountOptions: implicit-dirs {{- end }} diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml index 5a3e66df91..247942dd9b 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/values.yaml @@ -19,7 +19,7 @@ image: jiaxun/dlio:v1.2.0 bucketName: gke-dlio-test-data -# scenario controls the kind of storage that is used for the load testing. local-ssd means directly on LSSD; gcsfuse-generic means on a gcsfuse mount with gcsfuse.mountOptions sent from the caller; gcsfuse-no-file-cache and gcsfuse-file-cache mean as their name suggests. +# scenario controls the kind of storage that is used for the load testing. local-ssd means directly on LSSD; gcsfuse-generic means on a gcsfuse mount with gcsfuse.mountOptions sent from the caller. scenario: local-ssd nodeType: n2-standard-96 instanceId: ldap-yyyymmdd-hhmmss diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py index 3cae5b61b3..b0031e89a2 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py @@ -110,8 +110,7 @@ class FioWorkload: (essentially the data needed to create a job file for FIO run). Members: - 1. scenario (string): One of "local-ssd", "gcsfuse-generic", - "gcsfuse-file-cache" and "gcsfuse-no-file-cache". + 1. scenario (string): One of "local-ssd", "gcsfuse-generic". 2. fileSize (string): fio filesize field in string format e.g. '100', '10K', '10M' etc. 3. blockSize (string): equivalent of bs field in fio job file e.g. '8K', diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml index 9640df20c2..fe09263219 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml @@ -191,26 +191,4 @@ spec: volumeAttributes: bucketName: {{ .Values.bucketName }} mountOptions: "{{ .Values.gcsfuse.mountOptions }}" - {{- else if eq .Values.scenario "gcsfuse-file-cache" }} - csi: - driver: gcsfuse.csi.storage.gke.io - volumeAttributes: - bucketName: {{ .Values.bucketName }} - metadataCacheTTLSeconds: "{{ .Values.gcsfuse.metadataCacheTTLSeconds }}" - metadataTypeCacheCapacity: "{{ .Values.gcsfuse.metadataTypeCacheCapacity }}" - metadataStatCacheCapacity: "{{ .Values.gcsfuse.metadataStatCacheCapacity }}" - fileCacheCapacity: "{{ .Values.gcsfuse.fileCacheCapacity }}" - fileCacheForRangeRead: "true" - gcsfuseLoggingSeverity: "debug" - mountOptions: implicit-dirs - {{- else }} - csi: - driver: gcsfuse.csi.storage.gke.io - volumeAttributes: - bucketName: {{ .Values.bucketName }} - metadataCacheTTLSeconds: "{{ .Values.gcsfuse.metadataCacheTTLSeconds }}" - metadataTypeCacheCapacity: "{{ .Values.gcsfuse.metadataTypeCacheCapacity }}" - metadataStatCacheCapacity: "{{ .Values.gcsfuse.metadataStatCacheCapacity }}" - gcsfuseLoggingSeverity: "debug" - mountOptions: implicit-dirs {{- end }} diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml index e81446387f..3a0662169e 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/values.yaml @@ -19,7 +19,7 @@ image: ubuntu:24.04 bucketName: gke-dlio-test-data -# scenario controls the kind of storage that is used for the load testing. local-ssd means directly on LSSD; gcsfuse-generic means on a gcsfuse mount with gcsfuse.mountOptions sent from the caller; gcsfuse-no-file-cache and gcsfuse-file-cache mean as their name suggests. +# scenario controls the kind of storage that is used for the load testing. local-ssd means directly on LSSD; gcsfuse-generic means on a gcsfuse mount with gcsfuse.mountOptions sent from the caller. scenario: local-ssd nodeType: n2-standard-96 instanceId: ldap-yyyymmdd-hhmmss diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py index 80adb12c36..cb08e3d608 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py @@ -51,6 +51,11 @@ "blockSize": "", "filesPerThread": 0, "numThreads": 0, + "e2e_latency_ns_max": 0, + "e2e_latency_ns_p50": 0, + "e2e_latency_ns_p90": 0, + "e2e_latency_ns_p99": 0, + "e2e_latency_ns_p99.9": 0, } @@ -102,8 +107,6 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: "records": "local-ssd": [record1, record2, record3, ...] "gcsfuse-generic": [record1, record2, record3, ...] - "gcsfuse-file-cache": [record1, record2, record3, ...] - "gcsfuse-no-file-cache": [record1, record2, record3, ...] """ output = {} @@ -191,8 +194,6 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: "records": { "local-ssd": [], "gcsfuse-generic": [], - "gcsfuse-file-cache": [], - "gcsfuse-no-file-cache": [], }, } @@ -254,6 +255,13 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: r["blockSize"] = bs r["filesPerThread"] = nrfiles r["numThreads"] = numjobs + clat_ns = per_epoch_output_data["jobs"][0]["read"]["clat_ns"] + r["e2e_latency_ns_max"] = clat_ns["max"] + clat_ns_percentile = clat_ns["percentile"] + r["e2e_latency_ns_p50"] = clat_ns_percentile["50.000000"] + r["e2e_latency_ns_p90"] = clat_ns_percentile["90.000000"] + r["e2e_latency_ns_p99"] = clat_ns_percentile["99.000000"] + r["e2e_latency_ns_p99.9"] = clat_ns_percentile["99.900000"] # This print is for debugging in case something goes wrong. pprint.pprint(r) @@ -278,7 +286,9 @@ def writeRecordsToCsvOutputFile(output: dict, output_file_path: str): " Lowest" " Memory (MB),GCSFuse Highest Memory (MB),GCSFuse Lowest CPU" " (core),GCSFuse Highest CPU" - " (core),Pod,Start,End,GcsfuseMoutOptions,BlockSize,FilesPerThread,NumThreads,InstanceID\n" + " (core),Pod,Start,End,GcsfuseMoutOptions,BlockSize,FilesPerThread,NumThreads,InstanceID," + "e2e_latency_ns_max,e2e_latency_ns_p50,e2e_latency_ns_p90,e2e_latency_ns_p99,e2e_latency_ns_p99.9" # + "\n" ) for key in output: @@ -316,7 +326,10 @@ def writeRecordsToCsvOutputFile(output: dict, output_file_path: str): continue output_file_fwr.write( - f"{record_set['mean_file_size']},{record_set['read_type']},{scenario},{r['epoch']},{r['duration']},{r['throughput_mb_per_second']},{r['IOPS']},{r['throughput_over_local_ssd']},{r['lowest_memory']},{r['highest_memory']},{r['lowest_cpu']},{r['highest_cpu']},{r['pod_name']},{r['start']},{r['end']},\"{r['gcsfuse_mount_options']}\",{r['blockSize']},{r['filesPerThread']},{r['numThreads']},{args.instance_id}\n" + f"{record_set['mean_file_size']},{record_set['read_type']},{scenario},{r['epoch']},{r['duration']},{r['throughput_mb_per_second']},{r['IOPS']},{r['throughput_over_local_ssd']},{r['lowest_memory']},{r['highest_memory']},{r['lowest_cpu']},{r['highest_cpu']},{r['pod_name']},{r['start']},{r['end']},\"{r['gcsfuse_mount_options']}\",{r['blockSize']},{r['filesPerThread']},{r['numThreads']},{args.instance_id}," + ) + output_file_fwr.write( + f"{r['e2e_latency_ns_max']},{r['e2e_latency_ns_p50']},{r['e2e_latency_ns_p90']},{r['e2e_latency_ns_p99']},{r['e2e_latency_ns_p99.9']}\n" ) output_file_fwr.close() diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py b/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py index 936efc9da7..500b888076 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py @@ -23,8 +23,6 @@ SUPPORTED_SCENARIOS = [ "local-ssd", "gcsfuse-generic", - "gcsfuse-no-file-cache", - "gcsfuse-file-cache", ] From ec59e7701d968608d35a2eb9c96cba5dc4c55ea5 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 21 Mar 2025 05:25:59 +0000 Subject: [PATCH 0306/1298] [testing-on-gke] remove obsolete unused gcsfuse scnearios (#3081) From d81cc6a0cec94d5e42cc8510867a2b6afccda671 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 21 Mar 2025 06:29:15 +0000 Subject: [PATCH 0307/1298] Increase fio/dlio pod timeout to 15k seconds (#3107) --- .../dlio/unet3d-loading-test/templates/dlio-tester.yaml | 2 +- .../examples/fio/loading-test/templates/fio-tester.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml index f1a3b7fb4f..b84f7d54e9 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/unet3d-loading-test/templates/dlio-tester.yaml @@ -24,7 +24,7 @@ metadata: {{- end }} spec: restartPolicy: Never - activeDeadlineSeconds: 5000 + activeDeadlineSeconds: 15000 nodeSelector: cloud.google.com/gke-ephemeral-storage-local-ssd: "true" node.kubernetes.io/instance-type: {{ .Values.nodeType }} diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml index fe09263219..256f15019e 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml @@ -23,7 +23,7 @@ metadata: {{- end }} spec: restartPolicy: Never - activeDeadlineSeconds: 5000 + activeDeadlineSeconds: 15000 nodeSelector: cloud.google.com/gke-ephemeral-storage-local-ssd: "true" node.kubernetes.io/instance-type: {{ .Values.nodeType }} From 6dcb136e4d2643fef8a0cfb800c44c766f09126b Mon Sep 17 00:00:00 2001 From: Ankita Luthra Date: Fri, 21 Mar 2025 14:37:00 +0530 Subject: [PATCH 0308/1298] Experimental Parallel downloads flag default on (#3105) * adds logic to turn ON parallel downloads when file cache is ON * adds call to new method resolveParallelDownloadsValue * adds unit tests for success failure rationlize case * make enable parallel downloads flag true when cache dir is set * fixes comments * fixes metrics test to include parallel downloads metrics * fixes error string in prom_test * fixes prom test * fixes PR comments * minor fixes * fixes prom test * remove unnecessary code * adds comment * adds experimental parallel downloads true * adds experimental parallel download true * update unit tests with experimental pd flag ON * complete metrics TODO * fixes file cache config tests * increases default chunk size for pd to 200 MB * fixes file cache config test for default chunk size * makes marker map check for more readable in range context * makes marker map check for more readable in range context * fixes test --- cfg/config.go | 4 +- cfg/params.yaml | 4 +- cmd/config_validation_test.go | 38 ++++++++++--------- cmd/root_test.go | 38 ++++++++++--------- .../cache/file/downloader/downloader_test.go | 2 +- internal/cache/file/downloader/job_test.go | 6 +-- .../downloader/parallel_downloads_job_test.go | 24 +++++++----- 7 files changed, 63 insertions(+), 53 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index d34ec14636..a86e3c2cd9 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -391,7 +391,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("file-cache-cache-file-for-range-read", "", false, "Whether to cache file for range reads.") - flagSet.IntP("file-cache-download-chunk-size-mb", "", 50, "Size of chunks in MiB that each concurrent request downloads.") + flagSet.IntP("file-cache-download-chunk-size-mb", "", 200, "Size of chunks in MiB that each concurrent request downloads.") flagSet.BoolP("file-cache-enable-crc", "", false, "Performs CRC to ensure that file is correctly downloaded into cache.") @@ -407,7 +407,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("file-cache-enable-parallel-downloads", "", false, "Enable parallel downloads.") - flagSet.BoolP("file-cache-experimental-parallel-downloads-default-on", "", false, "Enable parallel downloads by default on experimental basis.") + flagSet.BoolP("file-cache-experimental-parallel-downloads-default-on", "", true, "Enable parallel downloads by default on experimental basis.") if err := flagSet.MarkHidden("file-cache-experimental-parallel-downloads-default-on"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 7a26f3ec34..38ea9736a1 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -95,7 +95,7 @@ flag-name: "file-cache-download-chunk-size-mb" type: "int" usage: "Size of chunks in MiB that each concurrent request downloads." - default: "50" + default: "200" - config-path: "file-cache.enable-crc" flag-name: "file-cache-enable-crc" @@ -121,7 +121,7 @@ flag-name: "file-cache-experimental-parallel-downloads-default-on" type: "bool" usage: "Enable parallel downloads by default on experimental basis." - default: false + default: true hide-flag: true - config-path: "file-cache.max-parallel-downloads" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 64d4b4c729..359125cb08 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -55,15 +55,16 @@ func getConfigObjectWithConfigFile(t *testing.T, configFilePath string) (*cfg.Co func defaultFileCacheConfig(t *testing.T) cfg.FileCacheConfig { t.Helper() return cfg.FileCacheConfig{ - CacheFileForRangeRead: false, - DownloadChunkSizeMb: 50, - EnableCrc: false, - EnableParallelDownloads: false, - MaxParallelDownloads: int64(max(16, 2*runtime.NumCPU())), - MaxSizeMb: -1, - ParallelDownloadsPerFile: 16, - WriteBufferSize: 4 * 1024 * 1024, - EnableODirect: false, + CacheFileForRangeRead: false, + DownloadChunkSizeMb: 200, + EnableCrc: false, + EnableParallelDownloads: false, + ExperimentalParallelDownloadsDefaultOn: true, + MaxParallelDownloads: int64(max(16, 2*runtime.NumCPU())), + MaxSizeMb: -1, + ParallelDownloadsPerFile: 16, + WriteBufferSize: 4 * 1024 * 1024, + EnableODirect: false, } } @@ -353,15 +354,16 @@ func TestValidateConfigFile_FileCacheConfigSuccessful(t *testing.T) { configFile: "testdata/valid_config.yaml", expectedConfig: &cfg.Config{ FileCache: cfg.FileCacheConfig{ - CacheFileForRangeRead: true, - DownloadChunkSizeMb: 300, - EnableCrc: true, - EnableParallelDownloads: false, - MaxParallelDownloads: 200, - MaxSizeMb: 40, - ParallelDownloadsPerFile: 10, - WriteBufferSize: 8192, - EnableODirect: true, + CacheFileForRangeRead: true, + DownloadChunkSizeMb: 300, + EnableCrc: true, + EnableParallelDownloads: false, + MaxParallelDownloads: 200, + MaxSizeMb: 40, + ParallelDownloadsPerFile: 10, + WriteBufferSize: 8192, + EnableODirect: true, + ExperimentalParallelDownloadsDefaultOn: true, }, }, }, diff --git a/cmd/root_test.go b/cmd/root_test.go index 4f02911d06..6bb46852b9 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -311,15 +311,16 @@ func TestArgsParsing_FileCacheFlags(t *testing.T) { expectedConfig: &cfg.Config{ CacheDir: "/some/valid/dir", FileCache: cfg.FileCacheConfig{ - CacheFileForRangeRead: true, - DownloadChunkSizeMb: 20, - EnableCrc: true, - EnableParallelDownloads: true, - MaxParallelDownloads: 40, - MaxSizeMb: 100, - ParallelDownloadsPerFile: 2, - WriteBufferSize: 4 * 1024 * 1024, - EnableODirect: false, + CacheFileForRangeRead: true, + DownloadChunkSizeMb: 20, + EnableCrc: true, + EnableParallelDownloads: true, + ExperimentalParallelDownloadsDefaultOn: true, + MaxParallelDownloads: 40, + MaxSizeMb: 100, + ParallelDownloadsPerFile: 2, + WriteBufferSize: 4 * 1024 * 1024, + EnableODirect: false, }, }, }, @@ -328,15 +329,16 @@ func TestArgsParsing_FileCacheFlags(t *testing.T) { args: []string{"gcsfuse", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileCache: cfg.FileCacheConfig{ - CacheFileForRangeRead: false, - DownloadChunkSizeMb: 50, - EnableCrc: false, - EnableParallelDownloads: false, - MaxParallelDownloads: int64(max(16, 2*runtime.NumCPU())), - MaxSizeMb: -1, - ParallelDownloadsPerFile: 16, - WriteBufferSize: 4 * 1024 * 1024, - EnableODirect: false, + CacheFileForRangeRead: false, + DownloadChunkSizeMb: 200, + EnableCrc: false, + EnableParallelDownloads: false, + ExperimentalParallelDownloadsDefaultOn: true, + MaxParallelDownloads: int64(max(16, 2*runtime.NumCPU())), + MaxSizeMb: -1, + ParallelDownloadsPerFile: 16, + WriteBufferSize: 4 * 1024 * 1024, + EnableODirect: false, }, }, }, diff --git a/internal/cache/file/downloader/downloader_test.go b/internal/cache/file/downloader/downloader_test.go index 405f093859..32ffc88429 100644 --- a/internal/cache/file/downloader/downloader_test.go +++ b/internal/cache/file/downloader/downloader_test.go @@ -74,7 +74,7 @@ func (dt *downloaderTest) setupHelper() { } func (dt *downloaderTest) SetUp(*TestInfo) { - dt.defaultFileCacheConfig = &cfg.FileCacheConfig{EnableCrc: true} + dt.defaultFileCacheConfig = &cfg.FileCacheConfig{EnableCrc: true, ExperimentalParallelDownloadsDefaultOn: true} dt.setupHelper() } diff --git a/internal/cache/file/downloader/job_test.go b/internal/cache/file/downloader/job_test.go index ca0ed1fc20..27f01e3aad 100644 --- a/internal/cache/file/downloader/job_test.go +++ b/internal/cache/file/downloader/job_test.go @@ -1023,7 +1023,7 @@ func (dt *downloaderTest) Test_When_Parallel_Download_Is_Disabled() { AssertFalse(result) } -func (dt *downloaderTest) Test_When_Experimental_Default_Parallel_Download_On() { +func (dt *downloaderTest) Test_When_Experimental_Default_Parallel_Download_Explicitly_Set_On() { //Arrange - initJobTest is being called in setup of downloader.go dt.job.fileCacheConfig.ExperimentalParallelDownloadsDefaultOn = true @@ -1032,11 +1032,11 @@ func (dt *downloaderTest) Test_When_Experimental_Default_Parallel_Download_On() AssertTrue(result) } -func (dt *downloaderTest) Test_When_Experimental_Default_Parallel_Download_Off() { +func (dt *downloaderTest) Test_When_Experimental_Default_Parallel_Download_On() { result := dt.job.IsExperimentalParallelDownloadsDefaultOn() - AssertFalse(result) + AssertTrue(result) } func (dt *downloaderTest) Test_createCacheFile_WhenNonParallelDownloads() { diff --git a/internal/cache/file/downloader/parallel_downloads_job_test.go b/internal/cache/file/downloader/parallel_downloads_job_test.go index be83201018..78b5f8c111 100644 --- a/internal/cache/file/downloader/parallel_downloads_job_test.go +++ b/internal/cache/file/downloader/parallel_downloads_job_test.go @@ -47,11 +47,12 @@ func init() { RegisterTestSuite(¶llelDownloaderTest{}) } func (dt *parallelDownloaderTest) SetUp(*TestInfo) { dt.defaultFileCacheConfig = &cfg.FileCacheConfig{ - EnableParallelDownloads: true, - ParallelDownloadsPerFile: 3, - DownloadChunkSizeMb: 3, - EnableCrc: true, - WriteBufferSize: 4 * 1024 * 1024, + ExperimentalParallelDownloadsDefaultOn: true, + EnableParallelDownloads: true, + ParallelDownloadsPerFile: 3, + DownloadChunkSizeMb: 3, + EnableCrc: true, + WriteBufferSize: 4 * 1024 * 1024, } dt.setupHelper() } @@ -81,30 +82,35 @@ func (dt *parallelDownloaderTest) Test_downloadRange() { // Download end 1MiB of object start, end := int64(9*util.MiB), int64(10*util.MiB) offsetWriter := io.NewOffsetWriter(file, start) - _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, make(map[int64]int64)) + rangeMap := make(map[int64]int64) + + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, rangeMap) AssertEq(nil, err) verifyContentAtOffset(file, start, end) // Download start 4MiB of object start, end = int64(0*util.MiB), int64(4*util.MiB) offsetWriter = io.NewOffsetWriter(file, start) - _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, make(map[int64]int64)) + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, rangeMap) AssertEq(nil, err) verifyContentAtOffset(file, start, end) + AssertEq(int64(4*util.MiB), rangeMap[start]) // Download middle 1B of object start, end = int64(5*util.MiB), int64(5*util.MiB+1) offsetWriter = io.NewOffsetWriter(file, start) - _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, make(map[int64]int64)) + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, rangeMap) AssertEq(nil, err) verifyContentAtOffset(file, start, end) + AssertEq(int64(5*util.MiB+1), rangeMap[start]) // Download 0B of object start, end = int64(5*util.MiB), int64(5*util.MiB) offsetWriter = io.NewOffsetWriter(file, start) - _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, make(map[int64]int64)) + _, err = dt.job.downloadRange(context.Background(), offsetWriter, start, end, nil, rangeMap) AssertEq(nil, err) verifyContentAtOffset(file, start, end) + AssertEq(int64(5*util.MiB+1), rangeMap[start]) } func (dt *parallelDownloaderTest) Test_parallelDownloadObjectToFile() { From 0acdaa8a8c6d8593df47c9db69604a74a171068e Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 24 Mar 2025 15:32:49 +0530 Subject: [PATCH 0309/1298] Integrate flush writer with upload handler (#3103) * integrate with upload handler changes This reverts commit c004a38f0947a74077caca0ca2544448925a599f. * fix tests * review comments --- internal/bufferedwrites/upload_handler.go | 43 +++++++++++++--- .../bufferedwrites/upload_handler_test.go | 51 +++++++++++++++++++ 2 files changed, 87 insertions(+), 7 deletions(-) diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 1e7ca96fd1..0eee536872 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -152,13 +152,11 @@ func (uh *UploadHandler) Finalize() (*gcs.MinObject, error) { uh.wg.Wait() close(uh.uploadCh) - if uh.writer == nil { - // Writer may not have been created for empty file creation flow or for very - // small writes of size less than 1 block. - err := uh.createObjectWriter() - if err != nil { - return nil, fmt.Errorf("createObjectWriter failed for object %s: %w", uh.objectName, err) - } + // Writer may not have been created for empty file creation flow or for very + // small writes of size less than 1 block. + err := uh.ensureWriter() + if err != nil { + return nil, fmt.Errorf("uh.ensureWriter() failed: %v", err) } obj, err := uh.bucket.FinalizeUpload(context.Background(), uh.writer) @@ -171,6 +169,37 @@ func (uh *UploadHandler) Finalize() (*gcs.MinObject, error) { return obj, nil } +func (uh *UploadHandler) ensureWriter() error { + if uh.writer == nil { + err := uh.createObjectWriter() + if err != nil { + return fmt.Errorf("createObjectWriter failed for object %s: %w", uh.objectName, err) + } + } + return nil +} + +// FlushPendingWrites uploads any data in the write buffer. +func (uh *UploadHandler) FlushPendingWrites() (int64, error) { + uh.wg.Wait() + + // Writer may not have been created for empty file creation flow or for very + // small writes of size less than 1 block. + err := uh.ensureWriter() + if err != nil { + return 0, fmt.Errorf("uh.ensureWriter() failed: %v", err) + } + + offset, err := uh.bucket.FlushPendingWrites(context.Background(), uh.writer) + if err != nil { + // FlushUpload already returns GCS error so no need to convert again. + uh.uploadError.Store(&err) + logger.Errorf("FlushUpload failed for object %s: %v", uh.objectName, err) + return 0, err + } + return offset, nil +} + func (uh *UploadHandler) CancelUpload() { if uh.cancelFunc != nil { // cancel the context to cancel the ongoing GCS upload. diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 19a06f908b..4e7b0e5541 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -163,6 +163,57 @@ func (t *UploadHandlerTest) TestFinalizeWhenFinalizeUploadFails() { assertUploadFailureError(t.T(), t.uh) } +func (t *UploadHandlerTest) TestFlushWithWriterAlreadyPresent() { + writer := &storagemock.Writer{} + mockOffset := 10 + t.mockBucket.On("FlushPendingWrites", mock.Anything, writer).Return(mockOffset, nil) + t.uh.writer = writer + + offset, err := t.uh.FlushPendingWrites() + + require.NoError(t.T(), err) + assert.EqualValues(t.T(), mockOffset, offset) +} + +func (t *UploadHandlerTest) TestFlushWithNoWriter() { + writer := &storagemock.Writer{} + t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) + assert.Nil(t.T(), t.uh.writer) + mockOffset := 10 + t.mockBucket.On("FlushPendingWrites", mock.Anything, writer).Return(mockOffset, nil) + + offset, err := t.uh.FlushPendingWrites() + + require.NoError(t.T(), err) + assert.EqualValues(t.T(), mockOffset, offset) +} + +func (t *UploadHandlerTest) TestFlushWithNoWriterWhenCreateObjectWriterFails() { + t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("taco")) + assert.Nil(t.T(), t.uh.writer) + + offset, err := t.uh.FlushPendingWrites() + + require.Error(t.T(), err) + assert.ErrorContains(t.T(), err, "taco") + assert.ErrorContains(t.T(), err, "createObjectWriter") + assert.EqualValues(t.T(), 0, offset) +} + +func (t *UploadHandlerTest) TestFlushWhenFlushPendingWritesFails() { + writer := &storagemock.Writer{} + t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) + assert.Nil(t.T(), t.uh.writer) + t.mockBucket.On("FlushPendingWrites", mock.Anything, writer).Return(0, fmt.Errorf("taco")) + + offset, err := t.uh.FlushPendingWrites() + + require.Error(t.T(), err) + assert.EqualValues(t.T(), 0, offset) + assert.ErrorContains(t.T(), err, "taco") + assertUploadFailureError(t.T(), t.uh) +} + func (t *UploadHandlerTest) TestUploadSingleBlockThrowsErrorInCopy() { // Create a block with test data. b, err := t.blockPool.Get() From fc52ae5e41dd1b0116f6371492afb3a111fba3c1 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 24 Mar 2025 11:06:40 +0000 Subject: [PATCH 0310/1298] re-enable disabled UT (#3112) Re-enabling a unit-test that was disabled during rapid-bucket development because it was failing at the time. I have re-enabled it here because it is now passing. More details in the issue ticket below. --- internal/cache/file/downloader/job_test.go | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/internal/cache/file/downloader/job_test.go b/internal/cache/file/downloader/job_test.go index 27f01e3aad..eae1681843 100644 --- a/internal/cache/file/downloader/job_test.go +++ b/internal/cache/file/downloader/job_test.go @@ -954,23 +954,23 @@ func (dt *downloaderTest) Test_validateCRC_ForTamperedFileWhenEnableCRCIsFalse() } func (dt *downloaderTest) Test_validateCRC_WheContextIsCancelled() { - // objectName := "path/in/gcs/file2.txt" - // objectSize := 10 * util.MiB - // objectContent := testutil.GenerateRandomBytes(objectSize) - // dt.initJobTest(objectName, objectContent, DefaultSequentialReadSizeMb, uint64(2*objectSize), func() {}) - // // Start download - // offset := int64(10 * util.MiB) - // _, err := dt.job.Download(context.Background(), offset, true) - // AssertEq(nil, err) - // AssertTrue((dt.job.status.Name == Downloading) || (dt.job.status.Name == Completed), fmt.Sprintf("got job status: %v", dt.job.status.Name)) - // AssertEq(nil, dt.job.status.Err) - // AssertGe(dt.job.status.Offset, offset) - - // dt.job.cancelFunc() - // dt.waitForCrcCheckToBeCompleted() - - // AssertEq(Invalid, dt.job.status.Name) - // dt.verifyInvalidError(dt.job.status.Err) + objectName := "path/in/gcs/file2.txt" + objectSize := 10 * util.MiB + objectContent := testutil.GenerateRandomBytes(objectSize) + dt.initJobTest(objectName, objectContent, DefaultSequentialReadSizeMb, uint64(2*objectSize), func() {}) + // Start download + offset := int64(10 * util.MiB) + _, err := dt.job.Download(context.Background(), offset, true) + AssertEq(nil, err) + AssertTrue((dt.job.status.Name == Downloading) || (dt.job.status.Name == Completed), fmt.Sprintf("got job status: %v", dt.job.status.Name)) + AssertEq(nil, dt.job.status.Err) + AssertGe(dt.job.status.Offset, offset) + + dt.job.cancelFunc() + dt.waitForCrcCheckToBeCompleted() + + AssertEq(Invalid, dt.job.status.Name) + dt.verifyInvalidError(dt.job.status.Err) } func (dt *downloaderTest) Test_handleError_SetStatusAsInvalidWhenContextIsCancelled() { From e56e56bcacde3087fbaec817fe4b2290845373cb Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 25 Mar 2025 16:25:17 +0000 Subject: [PATCH 0311/1298] Test branch --- cfg/config.go | 12 ++++++++++++ cfg/params.yaml | 10 ++++++++-- cmd/root.go | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index a86e3c2cd9..99355ddfef 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -52,6 +52,8 @@ type Config struct { Logging LoggingConfig `yaml:"logging"` + MachineType string `yaml:"machine-type"` + MetadataCache MetadataCacheConfig `yaml:"metadata-cache"` Metrics MetricsConfig `yaml:"metrics"` @@ -463,6 +465,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.StringP("log-severity", "", "info", "Specifies the logging severity expressed as one of [trace, debug, info, warning, error, off]") + flagSet.StringP("machine-type", "", "", "Type of the machine on which gcsfuse is being run for e.g. a3-highgpu-4g") + + if err := flagSet.MarkHidden("machine-type"); err != nil { + return err + } + flagSet.IntP("max-conns-per-host", "", 0, "The max number of TCP connections allowed per server. This is effective when client-protocol is set to 'http1'. A value of 0 indicates no limit on TCP connections (limited by the machine specifications).") flagSet.IntP("max-idle-conns-per-host", "", 100, "The number of maximum idle connections allowed per server.") @@ -808,6 +816,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("machine-type", flagSet.Lookup("machine-type")); err != nil { + return err + } + if err := v.BindPFlag("gcs-connection.max-conns-per-host", flagSet.Lookup("max-conns-per-host")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 38ea9736a1..5350b7d991 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -493,6 +493,13 @@ usage: "Specifies the logging severity expressed as one of [trace, debug, info, warning, error, off]" default: "info" +- config-path: "machine-type" + flag-name: "machine-type" + type: "string" + usage: "Type of the machine on which gcsfuse is being run for e.g. a3-highgpu-4g" + default: "" + hide-flag: true + - config-path: "metadata-cache.deprecated-stat-cache-capacity" flag-name: "stat-cache-capacity" type: "int" @@ -663,8 +670,7 @@ - config-path: "write.create-empty-file" flag-name: "create-empty-file" type: "bool" - usage: "For a new file, it creates an empty file in Cloud Storage bucket as a - hold." + usage: "For a new file, it creates an empty file in Cloud Storage bucket as a hold." default: false - config-path: "write.enable-streaming-writes" diff --git a/cmd/root.go b/cmd/root.go index 8b817de5b7..ef63111c5d 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -18,6 +18,7 @@ import ( "fmt" "log" "os" + "strconv" "strings" "github.com/go-viper/mapstructure/v2" @@ -83,6 +84,14 @@ of Cloud Storage FUSE, see https://cloud.google.com/storage/docs/gcs-fuse.`, ); cfgErr != nil { return } + if configObj.MachineType != "" { + rootCmd.PersistentFlags().VisitAll(func(f *pflag.Flag) { + if f.Name == "implicit-dirs" { + p := true + f.Value = newBoolValue(p, &p) + } + }) + } if cfgErr = cfg.ValidateConfig(v, &configObj); cfgErr != nil { return } @@ -156,3 +165,29 @@ var ExecuteMountCmd = func() { log.Fatalf("Error occurred during command execution: %v", err) } } + +// -- bool Value +type boolValue bool + +func newBoolValue(val bool, p *bool) *boolValue { + *p = val + return (*boolValue)(p) +} + +func (b *boolValue) Set(s string) error { + v, err := strconv.ParseBool(s) + *b = boolValue(v) + return err +} + +func (b *boolValue) Type() string { + return "bool" +} + +func (b *boolValue) String() string { return strconv.FormatBool(bool(*b)) } + +func (b *boolValue) IsBoolFlag() bool { return true } + +func boolConv(sval string) (interface{}, error) { + return strconv.ParseBool(sval) +} From 68e4700b16d35404978858e6ec01a41fce0ca10f Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 25 Mar 2025 16:25:48 +0000 Subject: [PATCH 0312/1298] Revert "Test branch" This reverts commit e56e56bcacde3087fbaec817fe4b2290845373cb. --- cfg/config.go | 12 ------------ cfg/params.yaml | 10 ++-------- cmd/root.go | 35 ----------------------------------- 3 files changed, 2 insertions(+), 55 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 99355ddfef..a86e3c2cd9 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -52,8 +52,6 @@ type Config struct { Logging LoggingConfig `yaml:"logging"` - MachineType string `yaml:"machine-type"` - MetadataCache MetadataCacheConfig `yaml:"metadata-cache"` Metrics MetricsConfig `yaml:"metrics"` @@ -465,12 +463,6 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.StringP("log-severity", "", "info", "Specifies the logging severity expressed as one of [trace, debug, info, warning, error, off]") - flagSet.StringP("machine-type", "", "", "Type of the machine on which gcsfuse is being run for e.g. a3-highgpu-4g") - - if err := flagSet.MarkHidden("machine-type"); err != nil { - return err - } - flagSet.IntP("max-conns-per-host", "", 0, "The max number of TCP connections allowed per server. This is effective when client-protocol is set to 'http1'. A value of 0 indicates no limit on TCP connections (limited by the machine specifications).") flagSet.IntP("max-idle-conns-per-host", "", 100, "The number of maximum idle connections allowed per server.") @@ -816,10 +808,6 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } - if err := v.BindPFlag("machine-type", flagSet.Lookup("machine-type")); err != nil { - return err - } - if err := v.BindPFlag("gcs-connection.max-conns-per-host", flagSet.Lookup("max-conns-per-host")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 5350b7d991..38ea9736a1 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -493,13 +493,6 @@ usage: "Specifies the logging severity expressed as one of [trace, debug, info, warning, error, off]" default: "info" -- config-path: "machine-type" - flag-name: "machine-type" - type: "string" - usage: "Type of the machine on which gcsfuse is being run for e.g. a3-highgpu-4g" - default: "" - hide-flag: true - - config-path: "metadata-cache.deprecated-stat-cache-capacity" flag-name: "stat-cache-capacity" type: "int" @@ -670,7 +663,8 @@ - config-path: "write.create-empty-file" flag-name: "create-empty-file" type: "bool" - usage: "For a new file, it creates an empty file in Cloud Storage bucket as a hold." + usage: "For a new file, it creates an empty file in Cloud Storage bucket as a + hold." default: false - config-path: "write.enable-streaming-writes" diff --git a/cmd/root.go b/cmd/root.go index ef63111c5d..8b817de5b7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -18,7 +18,6 @@ import ( "fmt" "log" "os" - "strconv" "strings" "github.com/go-viper/mapstructure/v2" @@ -84,14 +83,6 @@ of Cloud Storage FUSE, see https://cloud.google.com/storage/docs/gcs-fuse.`, ); cfgErr != nil { return } - if configObj.MachineType != "" { - rootCmd.PersistentFlags().VisitAll(func(f *pflag.Flag) { - if f.Name == "implicit-dirs" { - p := true - f.Value = newBoolValue(p, &p) - } - }) - } if cfgErr = cfg.ValidateConfig(v, &configObj); cfgErr != nil { return } @@ -165,29 +156,3 @@ var ExecuteMountCmd = func() { log.Fatalf("Error occurred during command execution: %v", err) } } - -// -- bool Value -type boolValue bool - -func newBoolValue(val bool, p *bool) *boolValue { - *p = val - return (*boolValue)(p) -} - -func (b *boolValue) Set(s string) error { - v, err := strconv.ParseBool(s) - *b = boolValue(v) - return err -} - -func (b *boolValue) Type() string { - return "bool" -} - -func (b *boolValue) String() string { return strconv.FormatBool(bool(*b)) } - -func (b *boolValue) IsBoolFlag() bool { return true } - -func boolConv(sval string) (interface{}, error) { - return strconv.ParseBool(sval) -} From c3612696ab87b31ea1723ac07ebcfb0acff7888e Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 26 Mar 2025 12:18:50 +0530 Subject: [PATCH 0313/1298] Integrate buffered write handler with writer.flush (#3108) * update buffered_write_handler sync to flush object for zonal buckets * review comments * convert to table driven tests --- .../bufferedwrites/buffered_write_handler.go | 24 ++++++- .../buffered_write_handler_test.go | 71 ++++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index dcc2f5101f..0534b7293d 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -179,8 +179,30 @@ func (wh *bufferedWriteHandlerImpl) appendBuffer(data []byte) (err error) { } func (wh *bufferedWriteHandlerImpl) Sync() (err error) { - // Upload all the pending buffers and release the buffers. + // Upload current block (for both regional and zonal buckets). + if wh.current != nil && wh.current.Size() != 0 { + err := wh.uploadHandler.Upload(wh.current) + if err != nil { + return err + } + wh.current = nil + } + // Upload all the pending buffers. wh.uploadHandler.AwaitBlocksUpload() + // The FlushPendingWrites method synchronizes all bytes currently residing in + // the Writer's buffer to Cloud Storage, thereby making them available for + // other operations like read. + // This functionality is exclusively supported on zonal buckets. + if wh.uploadHandler.bucket.BucketType().Zonal { + n, err := wh.uploadHandler.FlushPendingWrites() + if err != nil { + return err + } + if n != wh.totalSize { + return fmt.Errorf("could not upload entire data, expected offset %d, Got %d", wh.totalSize, n) + } + } + // Release memory used by buffers. err = wh.blockPool.ClearFreeBlockChannel() if err != nil { // Only logging an error in case of resource leak as upload succeeded. diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 817810839a..b17effbce9 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -15,6 +15,7 @@ package bufferedwrites import ( + "context" "errors" "strings" "testing" @@ -22,6 +23,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/jacobsa/timeutil" "github.com/stretchr/testify/assert" @@ -44,7 +46,12 @@ func TestBufferedWriteTestSuite(t *testing.T) { } func (testSuite *BufferedWriteTest) SetupTest() { - bucket := fake.NewFakeBucket(timeutil.RealClock(), "FakeBucketName", gcs.BucketType{}) + bucketType := gcs.BucketType{} + testSuite.setupTestWithBucketType(bucketType) +} + +func (testSuite *BufferedWriteTest) setupTestWithBucketType(bucketType gcs.BucketType) { + bucket := fake.NewFakeBucket(timeutil.RealClock(), "FakeBucketName", bucketType) bwh, err := NewBWHandler(&CreateBWHandlerRequest{ Object: nil, ObjectName: "testObject", @@ -289,6 +296,68 @@ func (testSuite *BufferedWriteTest) TestSync5InProgressBlocks() { assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) } +func (testSuite *BufferedWriteTest) TestSyncPartialBlockTableDriven() { + testCases := []struct { + name string + bucketType gcs.BucketType + numBlocks float32 + }{ + { + name: "multi_regional_bucket_2.5_blocks", + bucketType: gcs.BucketType{}, + numBlocks: 2.5, + }, + { + name: "multi_regional_bucket_.5_blocks", + bucketType: gcs.BucketType{}, + numBlocks: .5, + }, + { + name: "zonal_bucket_2.5_blocks", + bucketType: gcs.BucketType{Zonal: true}, + numBlocks: 2.5, + }, + { + name: "zonal_bucket_.5_blocks", + bucketType: gcs.BucketType{Zonal: true}, + numBlocks: .5, + }, + } + + for _, tc := range testCases { + testSuite.T().Run(tc.name, func(t *testing.T) { + testSuite.setupTestWithBucketType(tc.bucketType) + buffer, err := operations.GenerateRandomData(int64(blockSize * tc.numBlocks)) + assert.NoError(testSuite.T(), err) + err = testSuite.bwh.Write(buffer, 0) + require.Nil(testSuite.T(), err) + + // Wait for 3 blocks to upload successfully. + err = testSuite.bwh.Sync() + + assert.NoError(t, err) + assert.NoError(testSuite.T(), err) + bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) + // Current block should also be uploaded. + assert.Nil(testSuite.T(), bwhImpl.current) + assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) + assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) + // Read the object from back door. + content, err := storageutil.ReadObject(context.Background(), bwhImpl.uploadHandler.bucket, bwhImpl.uploadHandler.objectName) + if tc.bucketType.Zonal { + require.NoError(testSuite.T(), err) + assert.Equal(testSuite.T(), buffer, content) + } else { + // Since the object is not finalized, the object will not be available + // on GCS for non-zonal buckets. + require.Error(t, err) + var notFoundErr *gcs.NotFoundError + assert.ErrorAs(t, err, ¬FoundErr) + } + }) + } +} + func (testSuite *BufferedWriteTest) TestSyncBlocksWithError() { buffer, err := operations.GenerateRandomData(blockSize) assert.NoError(testSuite.T(), err) From 3bfb675b320f01d5950f002335b5c119f9382151 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 27 Mar 2025 04:27:31 +0000 Subject: [PATCH 0314/1298] Replace `--debug_*` with `--log-severity=trace` in e2e tests (#3124) * Replace --debug_* with --log-severity=trace in e2e This is to avoid unnecessary error logs in e2e runs. * empty commit to trigger e2e presubmit * fix a test failure --- .../mounting/dynamic_mounting/dynamic_mounting.go | 4 +--- .../mounting/only_dir_mounting/only_dir_mounting.go | 4 +--- .../persistent_mounting/perisistent_mounting.go | 12 ++++-------- .../util/mounting/static_mounting/static_mounting.go | 4 +--- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go index 8093740130..371ed5e6cd 100644 --- a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go +++ b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go @@ -36,9 +36,7 @@ const PrefixBucketForDynamicMountingTest = "golang-grpc-test-gcsfuse-dynamic-mou var testBucketForDynamicMounting = PrefixBucketForDynamicMountingTest + setup.GenerateRandomString(5) func MountGcsfuseWithDynamicMounting(flags []string) (err error) { - defaultArg := []string{"--debug_gcs", - "--debug_fs", - "--debug_fuse", + defaultArg := []string{"--log-severity=trace", "--log-file=" + setup.LogFile(), setup.MntDir()} diff --git a/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go b/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go index 2ac882a39d..65ec973f1a 100644 --- a/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go +++ b/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go @@ -28,9 +28,7 @@ import ( func MountGcsfuseWithOnlyDir(flags []string) (err error) { defaultArg := []string{"--only-dir", setup.OnlyDirMounted(), - "--debug_gcs", - "--debug_fs", - "--debug_fuse", + "--log-severity=trace", "--log-file=" + setup.LogFile(), setup.TestBucket(), setup.MntDir()} diff --git a/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go b/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go index e9678f2559..e674eb5f78 100644 --- a/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go +++ b/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go @@ -24,15 +24,15 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -// make e.g --debug_gcs in debug_gcs +// Change e.g --log_severity=trace to log_severity=trace func makePersistentMountingArgs(flags []string) (args []string, err error) { var s string for i := range flags { // We are already passing flags with -o flag. s = strings.Replace(flags[i], "--o=", "", -1) - // e.g. Convert --debug_gcs to __debug_gcs + // e.g. Convert --log-severity=trace to __log_severity=trace s = strings.Replace(s, "-", "_", -1) - // e.g. Convert __debug_gcs to debug_gcs + // e.g. Convert __log_severity=trace to log_severity=trace s = strings.Replace(s, "__", "", -1) args = append(args, s) } @@ -43,11 +43,7 @@ func mountGcsfuseWithPersistentMounting(flags []string) (err error) { defaultArg := []string{setup.TestBucket(), setup.MntDir(), "-o", - "debug_gcs", - "-o", - "debug_fs", - "-o", - "debug_fuse", + "log_severity=trace", "-o", "log_file=" + setup.LogFile(), } diff --git a/tools/integration_tests/util/mounting/static_mounting/static_mounting.go b/tools/integration_tests/util/mounting/static_mounting/static_mounting.go index 065551348f..e7e46eba5d 100644 --- a/tools/integration_tests/util/mounting/static_mounting/static_mounting.go +++ b/tools/integration_tests/util/mounting/static_mounting/static_mounting.go @@ -30,9 +30,7 @@ func MountGcsfuseWithStaticMounting(flags []string) (err error) { "--key-file=/tmp/sa.key.json") } - defaultArg = append(defaultArg, "--debug_gcs", - "--debug_fs", - "--debug_fuse", + defaultArg = append(defaultArg, "--log-severity=trace", "--log-file="+setup.LogFile(), setup.TestBucket(), setup.MntDir()) From 079e7361e1071805e49a5a9b49afac83b1303fd0 Mon Sep 17 00:00:00 2001 From: Vikas Jain <615592+mustvicky@users.noreply.github.com> Date: Thu, 27 Mar 2025 15:53:30 +0530 Subject: [PATCH 0315/1298] Update pull_request_template.md to ask for backward incompatible change(#3109) * Update pull_request_template.md Updating pull request template to capture any breaking change at the code-review time. Unrelated small change: Also removed the NA on b/ link since we do require bugs to be part of PRs now. Bug: b/405312149 --- .github/pull_request_template.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 155a7c4c23..96b112a6cf 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,11 @@ ### Description ### Link to the issue in case of a bug fix. -NA + ### Testing details 1. Manual - NA 2. Unit tests - NA 3. Integration tests - NA + +### Any backward incompatible change? If so, please explain. From 00c485d000446a8658775f09e490718c2fbdab00 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 1 Apr 2025 05:44:29 +0000 Subject: [PATCH 0316/1298] [testing-on-gke] Suppress unnecessary script logs (#3125) --- .../testing_on_gke/examples/run-gke-tests.sh | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index f7a714ed61..4c062d8f21 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -273,36 +273,36 @@ function printRunParameters() { function installDependencies() { printf "\nInstalling dependencies ...\n\n" # Refresh software repositories. - sudo apt-get update + sudo apt-get update >/dev/null # Get some common software dependencies. - sudo apt-get install -y apt-transport-https ca-certificates gnupg curl + sudo apt-get install -y apt-transport-https ca-certificates gnupg curl >/dev/null # Ensure that realpath is installed. - which realpath + which realpath >/dev/null # Ensure that make is installed. - which make || ( sudo apt-get install -y make time && which make ) + which make >/dev/null || ( sudo apt-get install -y make time >/dev/null && which make >/dev/null ) # Ensure that go is installed. which go || (version=1.22.4 && wget -O go_tar.tar.gz https://go.dev/dl/go${version}.linux-amd64.tar.gz 1>/dev/null && sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz 1>/dev/null && sudo mv go /usr/local && echo $PATH && export PATH=$PATH:/usr/local/go/bin && echo $PATH && echo 'export PATH=$PATH:/usr/local/go/bin'>>~/.bashrc && go version) # for some reason, the above is unable to update the value of $PATH, so doing it explicitly below. export PATH=$PATH:/usr/local/go/bin - which go + which go >/dev/null # Ensure that python3 is installed. - which python3 || ( sudo apt-get install -y python3 && which python3 ) + which python3 || ( sudo apt-get install -y python3 >/dev/null && which python3 >/dev/null ) # Install more python tools. - sudo apt-get -y install python3-dev python3-venv python3-pip + sudo apt-get -y install python3-dev python3-venv python3-pip >/dev/null # Enable python virtual environment. python3 -m venv .venv source .venv/bin/activate # Ensure that pip is installed. - sudo apt-get install -y pip + sudo apt-get install -y pip >/dev/null # python3 -m pip install --upgrade pip # python3 -m pip --version # Ensure that python-absl is installed. - pip install absl-py + pip install absl-py >/dev/null # Ensure that helm is installed which helm || (cd "${src_dir}" && (test -d "./helm" || git clone https://github.com/helm/helm.git) && cd helm && make && ls -lh bin/ && mkdir -pv ~/bin && cp -fv bin/helm ~/bin/ && chmod +x ~/bin/helm && export PATH=$PATH:$HOME/bin && echo $PATH && which helm && cd - && cd -) # for some reason, the above is unable to update the value of $PATH, so doing it explicitly below. export PATH=$PATH:$HOME/bin - which helm + which helm >/dev/null # Ensure that kubectl is installed if ! which kubectl; then # Install the latest gcloud cli. Find full instructions at https://cloud.google.com/kubernetes-engine/docs/how-to/cluster-access-for-kubectl . @@ -311,34 +311,34 @@ function installDependencies() { # Add the gcloud CLI distribution URI as a package source (Debian 9+ or Ubuntu 18.04+) echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list # Update and install the gcloud CLI - sudo apt-get update - sudo apt-get install -y google-cloud-cli + sudo apt-get update >/dev/null + sudo apt-get install -y google-cloud-cli >/dev/null # install kubectl - gcloud components install kubectl || sudo apt-get install -y kubectl + gcloud components install kubectl >/dev/null || sudo apt-get install -y kubectl >/dev/null kubectl version --client fi # Ensure that gke-gcloud-auth-plugin is installed. - gke-gcloud-auth-plugin --version || ((gcloud components install gke-gcloud-auth-plugin || sudo apt-get install -y google-cloud-cli-gke-gcloud-auth-plugin) && gke-gcloud-auth-plugin --version) + gke-gcloud-auth-plugin --version || ((gcloud components install gke-gcloud-auth-plugin >/dev/null || sudo apt-get install -y google-cloud-cli-gke-gcloud-auth-plugin >/dev/null) && gke-gcloud-auth-plugin --version) # Ensure that docker is installed. if ! which docker ; then - sudo apt install apt-transport-https ca-certificates curl software-properties-common -y + sudo apt install apt-transport-https ca-certificates curl software-properties-common -y >/dev/null curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable" apt-cache policy docker-ce - sudo apt install docker-ce -y + sudo apt install docker-ce -y >/dev/null fi # Install mash, as it is needed for fetching cpu/memory values for test runs # in cloudtop. Even if mash install fails, don't panic, go ahead and install # google-cloud-monitoring as an alternative. - which mash || sudo apt-get install -y monarch-tools || true + which mash || sudo apt-get install -y monarch-tools >/dev/null || true # Ensure that gcloud monitoring tools are installed. This is alternative to # mash on gce vm. # pip install --upgrade google-cloud-storage # pip install --ignore-installed --upgrade google-api-python-client # pip install --ignore-installed --upgrade google-cloud - pip install --upgrade google-cloud-monitoring + pip install --upgrade google-cloud-monitoring >/dev/null # Ensure that jq is installed. - which jq || sudo apt-get install -y jq + which jq || sudo apt-get install -y jq >/dev/null # Ensure sudoless docker is installed. if ! docker ps 1>/dev/null ; then echoerror "sudoless docker is not installed on this machine ($(hostname)). Please install sudoless-docker using the following commands and re-run this script ($0)" From 4739929faa32792fd80e8657fb054f67b75749f5 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 2 Apr 2025 12:03:25 +0530 Subject: [PATCH 0317/1298] create new inode on change in length (#3123) * create new inode on change in length * add e2e tests * Revert "add e2e tests" This reverts commit 66708cad2ad443521547aa9168419a28800fca14. * add size to other inode's Generation too * add unit test * update comment * unit tests * header fix * review comments * fix tests * revert object generation change * revert composite tests as they are not representative of current state of server. We will add integration tests in follow up PR. * review comments --- internal/fs/fs.go | 1 + internal/fs/inode/explicit_dir.go | 1 + internal/fs/inode/file.go | 13 ++- .../fs/inode/file_streaming_writes_test.go | 25 ++++++ internal/fs/inode/file_test.go | 19 ++++ internal/fs/inode/inode.go | 27 ++++-- internal/fs/inode/inode_test.go | 87 +++++++++++++++++++ internal/fs/inode/symlink.go | 1 + 8 files changed, 165 insertions(+), 9 deletions(-) create mode 100644 internal/fs/inode/inode_test.go diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 983c27a590..b0945d500b 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -901,6 +901,7 @@ func (fs *fileSystem) lookUpOrCreateInodeIfNotStale(ic inode.Core) (in inode.Ino oGen := inode.Generation{ Object: ic.MinObject.Generation, Metadata: ic.MinObject.MetaGeneration, + Size: ic.MinObject.Size, } // Retry loop for the stale index entry case below. On entry, we hold fs.mu diff --git a/internal/fs/inode/explicit_dir.go b/internal/fs/inode/explicit_dir.go index 482836a869..4cfa9c9fb9 100644 --- a/internal/fs/inode/explicit_dir.go +++ b/internal/fs/inode/explicit_dir.go @@ -68,6 +68,7 @@ func NewExplicitDirInode( dirInode.generation = Generation{ Object: m.Generation, Metadata: m.MetaGeneration, + Size: m.Size, } } diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index a546a15cc1..0bf5366461 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -239,8 +239,8 @@ func (f *FileInode) clobbered(ctx context.Context, forceFetchFromGcs bool, inclu } // We are clobbered iff the generation doesn't match our source generation. - oGen := Generation{o.Generation, o.MetaGeneration} - b = f.SourceGeneration().Compare(oGen) != 0 + oGen := Generation{o.Generation, o.MetaGeneration, o.Size} + b = oGen.Compare(f.SourceGeneration()) != 0 return } @@ -385,8 +385,17 @@ func (f *FileInode) SourceGenerationIsAuthoritative() bool { // // LOCKS_REQUIRED(f) func (f *FileInode) SourceGeneration() (g Generation) { + g.Size = f.src.Size g.Object = f.src.Generation g.Metadata = f.src.MetaGeneration + // If bwh is not nil, it's size takes precedence as that is being actively + // written to GCS. + // Since temporary file does not write to GCS on the go, it's size is not + // used as source object's size. + if f.bwh != nil { + writeFileInfo := f.bwh.WriteFileInfo() + g.Size = uint64(writeFileInfo.TotalSize) + } return } diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index a68159287c..8fecafcffd 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -30,6 +30,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/syncutil" "github.com/jacobsa/timeutil" @@ -512,6 +513,30 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndSync() { } } +func (t *FileStreamingWritesTest) TestSourceGenerationSizeForLocalFileIsReflected() { + assert.True(t.T(), t.in.IsLocal()) + err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0) + require.NoError(t.T(), err) + + sg := t.in.SourceGeneration() + assert.Nil(t.T(), t.backingObj) + assert.EqualValues(t.T(), 0, sg.Object) + assert.EqualValues(t.T(), 0, sg.Metadata) + assert.EqualValues(t.T(), 5, sg.Size) +} + +func (t *FileStreamingWritesTest) TestSourceGenerationSizeForSyncedFileIsReflected() { + t.createInode(fileName, emptyGCSFile) + assert.False(t.T(), t.in.IsLocal()) + err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0) + require.NoError(t.T(), err) + + sg := t.in.SourceGeneration() + assert.EqualValues(t.T(), t.backingObj.Generation, sg.Object) + assert.EqualValues(t.T(), t.backingObj.MetaGeneration, sg.Metadata) + assert.EqualValues(t.T(), 5, sg.Size) +} + func (t *FileStreamingWritesTest) TestTruncateOnFileUsingTempFileDoesNotRecreatesBWH() { assert.True(t.T(), t.in.IsLocal()) require.NotNil(t.T(), t.in.bwh) diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 1029a0d94d..a97bfaaeef 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -32,6 +32,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/syncutil" "github.com/jacobsa/timeutil" @@ -171,6 +172,17 @@ func (t *FileTest) TestInitialSourceGeneration() { sg := t.in.SourceGeneration() assert.Equal(t.T(), t.backingObj.Generation, sg.Object) assert.Equal(t.T(), t.backingObj.MetaGeneration, sg.Metadata) + assert.Equal(t.T(), t.backingObj.Size, sg.Size) +} + +func (t *FileTest) TestSourceGenerationSizeAfterWriteDoesNotChange() { + err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0) + require.NoError(t.T(), err) + + sg := t.in.SourceGeneration() + assert.Equal(t.T(), t.backingObj.Generation, sg.Object) + assert.Equal(t.T(), t.backingObj.MetaGeneration, sg.Metadata) + assert.Equal(t.T(), t.backingObj.Size, sg.Size) } func (t *FileTest) TestInitialAttributes() { @@ -418,6 +430,7 @@ func (t *FileTest) TestWriteThenSync() { assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), t.in.SourceGeneration().Size, m.Size) assert.Equal(t.T(), uint64(len("paco")), m.Size) assert.Equal(t.T(), writeTime.UTC().Format(time.RFC3339Nano), @@ -488,6 +501,7 @@ func (t *FileTest) TestWriteToLocalFileThenSync() { assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), t.in.SourceGeneration().Size, m.Size) assert.Equal(t.T(), uint64(len("tacos")), m.Size) assert.Equal(t.T(), writeTime.UTC().Format(time.RFC3339Nano), @@ -551,6 +565,7 @@ func (t *FileTest) TestSyncEmptyLocalFile() { assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), t.in.SourceGeneration().Size, m.Size) assert.Equal(t.T(), uint64(0), m.Size) // Validate MinObject in MRDWrapper is same as the MinObject in inode. assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) @@ -622,6 +637,7 @@ func (t *FileTest) TestAppendThenSync() { assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), t.in.SourceGeneration().Size, m.Size) assert.Equal(t.T(), uint64(len("tacoburrito")), m.Size) assert.Equal(t.T(), writeTime.UTC().Format(time.RFC3339Nano), @@ -697,6 +713,7 @@ func (t *FileTest) TestTruncateDownwardThenSync() { assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), t.in.SourceGeneration().Size, m.Size) assert.Equal(t.T(), uint64(2), m.Size) assert.Equal(t.T(), truncateTime.UTC().Format(time.RFC3339Nano), @@ -769,6 +786,7 @@ func (t *FileTest) TestTruncateUpwardThenFlush() { assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) + assert.Equal(t.T(), t.in.SourceGeneration().Size, m.Size) assert.Equal(t.T(), uint64(6), m.Size) // Check attributes. @@ -1055,6 +1073,7 @@ func (t *FileTest) TestSyncFlush_Clobbered() { assert.True(t.T(), errors.As(err, &fcErr), "expected FileClobberedError but got %v", err) assert.Equal(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) assert.Equal(t.T(), t.backingObj.MetaGeneration, t.in.SourceGeneration().Metadata) + assert.Equal(t.T(), t.backingObj.Size, t.in.SourceGeneration().Size) // The object in the bucket should not have been changed. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} diff --git a/internal/fs/inode/inode.go b/internal/fs/inode/inode.go index b9131645b7..ba26c481ec 100644 --- a/internal/fs/inode/inode.go +++ b/internal/fs/inode/inode.go @@ -84,28 +84,41 @@ type GenerationBackedInode interface { type Generation struct { Object int64 Metadata int64 + Size uint64 } -// Compare returns -1, 0, or 1 according to whether g is less than, equal to, or greater -// than other. -func (g Generation) Compare(other Generation) int { +// Compare returns -1, 0, or 1 according to whether src is less than, equal to, +// or greater than existing. +// Note: Ordering matters here, latest represents the object fetched from GCS +// and current represents inode cached object's generation. +func (latest Generation) Compare(current Generation) int { // Compare first on object generation number. switch { - case g.Object < other.Object: + case latest.Object < current.Object: return -1 - case g.Object > other.Object: + case latest.Object > current.Object: return 1 } // Break ties on meta-generation. switch { - case g.Metadata < other.Metadata: + case latest.Metadata < current.Metadata: return -1 - case g.Metadata > other.Metadata: + case latest.Metadata > current.Metadata: return 1 } + // Break ties on object size. + // Because objects in zonal buckets can be appended without altering their + // generation or metageneration, the following case applies exclusively to + // zonal buckets. + if latest.Size > current.Size { + return 1 + } + // We ignore latest.Size < current.Size case as little staleness is expected + // on the GCS object's size. + return 0 } diff --git a/internal/fs/inode/inode_test.go b/internal/fs/inode/inode_test.go new file mode 100644 index 0000000000..5f28d28254 --- /dev/null +++ b/internal/fs/inode/inode_test.go @@ -0,0 +1,87 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package inode_test + +import ( + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" + "github.com/stretchr/testify/assert" +) + +func TestGenerationCompare(t *testing.T) { + testCases := []struct { + name string + latest inode.Generation + current inode.Generation + expected int + }{ + { + name: "latest.Object > current.Object", + latest: inode.Generation{Object: 2, Metadata: 1, Size: 100}, + current: inode.Generation{Object: 1, Metadata: 1, Size: 100}, + expected: 1, + }, + { + name: "latest.Object < current.Object", + latest: inode.Generation{Object: 1, Metadata: 1, Size: 100}, + current: inode.Generation{Object: 2, Metadata: 1, Size: 100}, + expected: -1, + }, + { + name: "latest.Object == current.Object, latest.Metadata < current.Metadata", + latest: inode.Generation{Object: 1, Metadata: 1, Size: 100}, + current: inode.Generation{Object: 1, Metadata: 2, Size: 100}, + expected: -1, + }, + { + name: "latest.Object == current.Object, latest.Metadata > current.Metadata", + latest: inode.Generation{Object: 1, Metadata: 2, Size: 100}, + current: inode.Generation{Object: 1, Metadata: 1, Size: 100}, + expected: 1, + }, + { + name: "latest.Object == current.Object, latest.Metadata < current.Metadata", + latest: inode.Generation{Object: 1, Metadata: 1, Size: 100}, + current: inode.Generation{Object: 1, Metadata: 2, Size: 100}, + expected: -1, + }, + { + name: "latest.Object == current.Object, latest.Metadata == current.Metadata, latest.Size > current.Size", + latest: inode.Generation{Object: 1, Metadata: 1, Size: 200}, + current: inode.Generation{Object: 1, Metadata: 1, Size: 100}, + expected: 1, + }, + { + name: "latest.Object == current.Object, latest.Metadata == current.Metadata, latest.Size < current.Size", + latest: inode.Generation{Object: 1, Metadata: 1, Size: 100}, + current: inode.Generation{Object: 1, Metadata: 1, Size: 200}, + expected: 0, + }, + { + name: "latest.Object == current.Object, latest.Metadata == current.Metadata, latest.Size == current.Size", + latest: inode.Generation{Object: 1, Metadata: 1, Size: 100}, + current: inode.Generation{Object: 1, Metadata: 1, Size: 100}, + expected: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + actual := tc.latest.Compare(tc.current) + assert.Equal(t, tc.expected, actual) + }) + } +} diff --git a/internal/fs/inode/symlink.go b/internal/fs/inode/symlink.go index 8a271e976d..8c9d59fd29 100644 --- a/internal/fs/inode/symlink.go +++ b/internal/fs/inode/symlink.go @@ -75,6 +75,7 @@ func NewSymlinkInode( sourceGeneration: Generation{ Object: m.Generation, Metadata: m.MetaGeneration, + Size: m.Size, }, attrs: fuseops.InodeAttributes{ Nlink: 1, From 34ecb3d4641e30134504f481588db0e3c544e2c3 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Wed, 2 Apr 2025 16:29:36 +0530 Subject: [PATCH 0318/1298] Use new writer flush before reads for zonal buckets with streaming writes. (#3121) * add flush before read for zonal buckets with streaming writes * add tests for SourceGenerationAuthoritative and SyncUsingBufferedWritesHandler * improve test * improve wording * fix wording * fix indentation * fix lint * fix tests * fix lint * fix review comments. * Update internal/fs/inode/file.go * fix test file * fix test file --------- Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> --- internal/fs/handle/file.go | 7 + internal/fs/inode/file.go | 45 +++-- .../fs/inode/file_streaming_writes_test.go | 134 +++++++++++--- internal/fs/inode/file_test.go | 165 ++++++++++-------- 4 files changed, 241 insertions(+), 110 deletions(-) diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 34e6d15b0d..73bfcf127d 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -108,6 +108,13 @@ func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequen // state, or clear fh.reader if it's not possible to create one (probably // because the inode is dirty). fh.inode.Lock() + // Ensure all pending writes to Zonal Buckets are flushed before issuing a read. + err = fh.inode.SyncPendingBufferedWrites() + if err != nil { + fh.inode.Unlock() + err = fmt.Errorf("fh.inode.SyncPendingBufferedWrites: %w", err) + return + } err = fh.tryEnsureReader(ctx, sequentialReadSizeMb) if err != nil { fh.inode.Unlock() diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 0bf5366461..a6c5910bbb 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -372,13 +372,17 @@ func (f *FileInode) Source() *gcs.MinObject { // If true, it is safe to serve reads directly from the object given by // f.Source(), rather than calling f.ReadAt. Doing so may be more efficient, // because f.ReadAt may cause the entire object to be faulted in and requires -// the inode to be locked during the read. +// the inode to be locked during the read. SourceGenerationAuthoritative requires +// SyncPendingBufferedWrites method has been called on f within same inode lock for +// streaming writes with zonal bucket. +// TODO(b/406160290): Check if this can be improved. // // LOCKS_REQUIRED(f.mu) func (f *FileInode) SourceGenerationIsAuthoritative() bool { - // When streaming writes are enabled, writes are done via bufferedWritesHandler(bwh). - // Hence checking both f.content & f.bwh to be nil - return f.content == nil && f.bwh == nil + // Source generation is authoritative if: + // 1. No pending writes exists on the inode (both content and bwh are nil). + // 2. The bucket is zonal and there are no pending writes in the temporary file. + return (f.content == nil && f.bwh == nil) || (f.bucket.BucketType().Zonal && f.content == nil) } // Equivalent to the generation returned by f.Source(). @@ -651,6 +655,27 @@ func (f *FileInode) flushUsingBufferedWriteHandler() error { return nil } +// SyncPendingBufferedWrites flushes any pending writes on the bwh to GCS. +// It is a no-op when bwh is nil. +// +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) SyncPendingBufferedWrites() error { + if f.bwh == nil { + return nil + } + err := f.bwh.Sync() + var preconditionErr *gcs.PreconditionError + if errors.As(err, &preconditionErr) { + return &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("f.bwh.Sync(): %w", err), + } + } + if err != nil { + return fmt.Errorf("f.bwh.Sync(): %w", err) + } + return nil +} + // Set the mtime for this file. May involve a round trip to GCS. // // LOCKS_REQUIRED(f.mu) @@ -782,13 +807,11 @@ func (f *FileInode) Sync(ctx context.Context) (gcsSynced bool, err error) { } if f.bwh != nil { - // bwh.Sync does not finalize the upload, so return gcsSynced as false. - err = f.bwh.Sync() - var preconditionErr *gcs.PreconditionError - if errors.As(err, &preconditionErr) { - return false, &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("f.bwh.Sync(): %w", err), - } + // SyncPendingBufferedWrites does not finalize the upload, + // so return gcsSynced as false. + err = f.SyncPendingBufferedWrites() + if err != nil { + err = fmt.Errorf("could not sync what has been written so far: %w", err) } return } diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 8fecafcffd..c3904a34a9 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -43,7 +43,11 @@ import ( const localFile = "local" const emptyGCSFile = "emptyGCS" -type FileStreamingWritesTest struct { +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type FileStreamingWritesCommon struct { suite.Suite ctx context.Context bucket gcs.Bucket @@ -51,23 +55,39 @@ type FileStreamingWritesTest struct { backingObj *gcs.MinObject in *FileInode } +type FileStreamingWritesTest struct { + FileStreamingWritesCommon +} -func TestFileStreamingWritesTestSuite(t *testing.T) { - suite.Run(t, new(FileStreamingWritesTest)) +type FileStreamingWritesZonalBucketTest struct { + FileStreamingWritesCommon } -func (t *FileStreamingWritesTest) SetupTest() { +//////////////////////////////////////////////////////////////////////// +// Helper +//////////////////////////////////////////////////////////////////////// + +func (t *FileStreamingWritesCommon) setupTest() { // Enabling invariant check for all tests. syncutil.EnableInvariantChecking() t.ctx = context.Background() - t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)) - t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{}) - // Create the inode. t.createInode(fileName, localFile) } -func (t *FileStreamingWritesTest) TearDownTest() { +func (t *FileStreamingWritesZonalBucketTest) SetupTest() { + t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)) + t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{Zonal: true}) + t.setupTest() +} + +func (t *FileStreamingWritesTest) SetupTest() { + t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local)) + t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{Zonal: false}) + t.setupTest() +} + +func (t *FileStreamingWritesCommon) TearDownTest() { t.in.Unlock() } @@ -75,7 +95,7 @@ func (t *FileStreamingWritesTest) SetupSubTest() { t.SetupTest() } -func (t *FileStreamingWritesTest) createInode(fileName string, fileType string) { +func (t *FileStreamingWritesCommon) createInode(fileName string, fileType string) { if fileType != emptyGCSFile && fileType != localFile { t.T().Errorf("fileType should be either local or empty") } @@ -132,17 +152,61 @@ func (t *FileStreamingWritesTest) createInode(fileName string, fileType string) GlobalMaxBlocks: 10, }} - // Create write handler for the local inode created above. - err := t.in.CreateBufferedOrTempWriter(t.ctx) - assert.Nil(t.T(), err) - t.in.Lock() } //////////////////////////////////////////////////////////////////////// -// Tests +// Tests (Zonal Bucket) //////////////////////////////////////////////////////////////////////// +func TestFileStreamingWritesWithZonalBucketTestSuite(t *testing.T) { + suite.Run(t, new(FileStreamingWritesZonalBucketTest)) +} + +func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritativeReturnsTrueForZonalBuckets() { + assert.True(t.T(), t.in.SourceGenerationIsAuthoritative()) +} + +func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritativeReturnsTrueAfterWriteForZonalBuckets() { + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) + + assert.True(t.T(), t.in.SourceGenerationIsAuthoritative()) +} + +func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBuckets() { + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("pizza"), 0)) + + assert.NoError(t.T(), t.in.SyncPendingBufferedWrites()) + content, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + require.NoError(t.T(), err) + assert.Equal(t.T(), "pizza", string(content)) +} + +// ////////////////////////////////////////////////////////////////////// +// Tests (Non Zonal Bucket) +// ////////////////////////////////////////////////////////////////////// + +func TestFileStreamingWritesTestSuite(t *testing.T) { + suite.Run(t, new(FileStreamingWritesTest)) +} + +func (t *FileStreamingWritesTest) TestSourceGenerationIsAuthoritativeReturnsTrueForNonZonalBuckets() { + assert.True(t.T(), t.in.SourceGenerationIsAuthoritative()) +} + +func (t *FileStreamingWritesTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWriteForNonZonalBuckets() { + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) + + assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) +} + +func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBuckets() { + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) + + assert.NoError(t.T(), t.in.SyncPendingBufferedWrites()) + operations.ValidateObjectNotFoundErr(t.ctx, t.T(), t.bucket, t.in.Name().GcsObjectName()) +} + func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempFile() { testCases := []struct { name string @@ -168,11 +232,13 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF for _, tc := range testCases { t.Run(tc.name, func() { + err := t.in.CreateBufferedOrTempWriter(t.ctx) + assert.Nil(t.T(), err) assert.True(t.T(), t.in.IsLocal()) createTime := t.clock.Now() t.clock.AdvanceTime(15 * time.Minute) // Sequential Write at offset 0 - err := t.in.Write(t.ctx, []byte("taco"), 0) + err = t.in.Write(t.ctx, []byte("taco"), 0) require.Nil(t.T(), err) require.NotNil(t.T(), t.in.bwh) // validate attributes. @@ -207,11 +273,13 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF } func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { + err := t.in.CreateBufferedOrTempWriter(t.ctx) + assert.Nil(t.T(), err) assert.True(t.T(), t.in.IsLocal()) createTime := t.in.mtimeClock.Now() require.NotNil(t.T(), t.in.bwh) // Out of order write. - err := t.in.Write(t.ctx, []byte("taco"), 6) + err = t.in.Write(t.ctx, []byte("taco"), 6) require.Nil(t.T(), err) // Ensure bwh cleared and temp file created. assert.Nil(t.T(), t.in.bwh) @@ -240,7 +308,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { assert.True(t.T(), gcsSynced) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), "hello\x00taco", string(contents)) } @@ -261,7 +329,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesOnClobberedFileThrowsError // Validate Object on GCS not updated. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} objGot, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), storageutil.ConvertObjToMinObject(objWritten), objGot) } @@ -343,13 +411,13 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndFlush() { assert.False(t.T(), t.in.IsLocal()) // Check attributes. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("tacos")), attrs.Size) assert.Equal(t.T(), t.clock.Now().UTC(), attrs.Mtime.UTC()) // Validate Object on GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) @@ -357,7 +425,7 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndFlush() { // Mtime metadata is not written for buffered writes. assert.Equal(t.T(), "", m.Metadata["gcsfuse_mtime"]) contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), "tacos", string(contents)) }) } @@ -387,8 +455,10 @@ func (t *FileStreamingWritesTest) TestFlushEmptyFile() { assert.False(t.T(), t.in.IsLocal()) } t.clock.AdvanceTime(10 * time.Second) + err := t.in.CreateBufferedOrTempWriter(t.ctx) + assert.NoError(t.T(), err) - err := t.in.Flush(t.ctx) + err = t.in.Flush(t.ctx) require.Nil(t.T(), err) // Ensure bwh cleared. @@ -397,7 +467,7 @@ func (t *FileStreamingWritesTest) TestFlushEmptyFile() { assert.False(t.T(), t.in.IsLocal()) // Check attributes. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) // For synced file, mtime is updated by SetInodeAttributes call. if tc.isLocal { @@ -406,7 +476,7 @@ func (t *FileStreamingWritesTest) TestFlushEmptyFile() { // Validate Object on GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) @@ -414,7 +484,7 @@ func (t *FileStreamingWritesTest) TestFlushEmptyFile() { // Mtime metadata is not written for buffered writes. assert.Equal(t.T(), "", m.Metadata["gcsfuse_mtime"]) contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), "", string(contents)) }) } @@ -443,6 +513,8 @@ func (t *FileStreamingWritesTest) TestFlushClobberedFile() { t.createInode(fileName, emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) } + err := t.in.CreateBufferedOrTempWriter(t.ctx) + assert.NoError(t.T(), err) t.clock.AdvanceTime(10 * time.Second) // Clobber the file. objWritten, err := storageutil.CreateObject(t.ctx, t.bucket, fileName, []byte("taco")) @@ -505,7 +577,7 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndSync() { var notFoundErr *gcs.NotFoundError assert.ErrorAs(t.T(), err, ¬FoundErr) } else { - assert.NoError(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), uint64(0), m.Size) } @@ -538,10 +610,12 @@ func (t *FileStreamingWritesTest) TestSourceGenerationSizeForSyncedFileIsReflect } func (t *FileStreamingWritesTest) TestTruncateOnFileUsingTempFileDoesNotRecreatesBWH() { + err := t.in.CreateBufferedOrTempWriter(t.ctx) + assert.NoError(t.T(), err) assert.True(t.T(), t.in.IsLocal()) require.NotNil(t.T(), t.in.bwh) // Out of order write. - err := t.in.Write(t.ctx, []byte("taco"), 2) + err = t.in.Write(t.ctx, []byte("taco"), 2) require.Nil(t.T(), err) // Ensure bwh cleared and temp file created. assert.Nil(t.T(), t.in.bwh) @@ -562,7 +636,7 @@ func (t *FileStreamingWritesTest) TestTruncateOnFileUsingTempFileDoesNotRecreate assert.True(t.T(), gcsSynced) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), "\x00\x00taco\x00\x00\x00\x00", string(contents)) } @@ -650,6 +724,8 @@ func (t *FakeBufferedWriteHandler) Destroy() error { return nil } func (t *FakeBufferedWriteHandler) Unlink() {} func (t *FileStreamingWritesTest) TestWriteUsingBufferedWritesFails() { + err := t.in.CreateBufferedOrTempWriter(t.ctx) + assert.NoError(t.T(), err) assert.True(t.T(), t.in.IsLocal()) require.NotNil(t.T(), t.in.bwh) writeErr := errors.New("write error") @@ -659,7 +735,7 @@ func (t *FileStreamingWritesTest) TestWriteUsingBufferedWritesFails() { }, } - err := t.in.Write(context.Background(), []byte("hello"), 0) + err = t.in.Write(context.Background(), []byte("hello"), 0) require.Error(t.T(), err) assert.Regexp(t.T(), writeErr.Error(), err.Error()) diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index a97bfaaeef..1eb67db0fb 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -185,9 +185,32 @@ func (t *FileTest) TestSourceGenerationSizeAfterWriteDoesNotChange() { assert.Equal(t.T(), t.backingObj.Size, sg.Size) } +func (t *FileTest) TestSourceGenerationIsAuthoritativeReturnsTrue() { + assert.True(t.T(), t.in.SourceGenerationIsAuthoritative()) +} + +func (t *FileTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWrite() { + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) + + assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) +} + +func (t *FileTest) TestSyncPendingBufferedWritesReturnsNilAndNoOpForNonStreamingWrites() { + contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + require.NoError(t.T(), err) + assert.Equal(t.T(), t.initialContents, string(contents)) + + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("bar"), 0)) + assert.NoError(t.T(), t.in.SyncPendingBufferedWrites()) + + contents, err = storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + require.NoError(t.T(), err) + assert.Equal(t.T(), t.initialContents, string(contents)) +} + func (t *FileTest) TestInitialAttributes() { attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len(t.initialContents)), attrs.Size) assert.Equal(t.T(), uint32(1), attrs.Nlink) @@ -212,7 +235,7 @@ func (t *FileTest) TestInitialAttributes_MtimeFromObjectMetadata_Gcsfuse() { // Ask it for its attributes. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) } @@ -230,7 +253,7 @@ func (t *FileTest) TestInitialAttributes_MtimeFromObjectMetadata_Gsutil() { // Ask it for its attributes. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime.UTC(), mtime) } @@ -251,7 +274,7 @@ func (t *FileTest) TestInitialAttributes_MtimeFromObjectMetadata_GcsfuseOutranks // Ask it for its attributes. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, canonicalMtime) } @@ -336,7 +359,7 @@ func (t *FileTest) TestWrite() { // Check attributes. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("pacoburrito")), attrs.Size) assert.Equal(t.T(), attrs.Mtime, writeTime) @@ -370,7 +393,7 @@ func (t *FileTest) TestTruncate() { // Check attributes. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("ta")), attrs.Size) assert.Equal(t.T(), attrs.Mtime, truncateTime) @@ -409,7 +432,7 @@ func (t *FileTest) TestWriteThenSync() { if tc.callSync { gcsSynced, err := t.in.Sync(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.True(t.T(), gcsSynced) } else { err = t.in.Flush(t.ctx) @@ -426,7 +449,7 @@ func (t *FileTest) TestWriteThenSync() { statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) @@ -439,12 +462,12 @@ func (t *FileTest) TestWriteThenSync() { // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), "paco", string(contents)) // Check attributes. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("paco")), attrs.Size) assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) @@ -485,7 +508,7 @@ func (t *FileTest) TestWriteToLocalFileThenSync() { if tc.callSync { gcsSynced, err := t.in.Sync(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.True(t.T(), gcsSynced) } else { err = t.in.Flush(t.ctx) @@ -497,7 +520,7 @@ func (t *FileTest) TestWriteToLocalFileThenSync() { // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) @@ -510,11 +533,11 @@ func (t *FileTest) TestWriteToLocalFileThenSync() { assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), "tacos", string(contents)) // Check attributes. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("tacos")), attrs.Size) assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) }) @@ -549,7 +572,7 @@ func (t *FileTest) TestSyncEmptyLocalFile() { if tc.callSync { gcsSynced, err := t.in.Sync(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.True(t.T(), gcsSynced) } else { err = t.in.Flush(t.ctx) @@ -561,7 +584,7 @@ func (t *FileTest) TestSyncEmptyLocalFile() { // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) @@ -576,11 +599,11 @@ func (t *FileTest) TestSyncEmptyLocalFile() { assert.WithinDuration(t.T(), mtime, creationTime, Delta) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), "", string(contents)) // Check attributes. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) }) } @@ -619,7 +642,7 @@ func (t *FileTest) TestAppendThenSync() { if tc.callSync { gcsSynced, err := t.in.Sync(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.True(t.T(), gcsSynced) } else { err = t.in.Flush(t.ctx) @@ -633,7 +656,7 @@ func (t *FileTest) TestAppendThenSync() { statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) @@ -649,12 +672,12 @@ func (t *FileTest) TestAppendThenSync() { // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), "tacoburrito", string(contents)) // Check attributes. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("tacoburrito")), attrs.Size) assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) @@ -692,7 +715,7 @@ func (t *FileTest) TestTruncateDownwardThenSync() { if tc.callSync { gcsSynced, err := t.in.Sync(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.True(t.T(), gcsSynced) } else { err = t.in.Flush(t.ctx) @@ -709,7 +732,7 @@ func (t *FileTest) TestTruncateDownwardThenSync() { statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) @@ -721,7 +744,7 @@ func (t *FileTest) TestTruncateDownwardThenSync() { // Check attributes. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) assert.Equal(t.T(), attrs.Mtime, truncateTime.UTC()) @@ -762,7 +785,7 @@ func (t *FileTest) TestTruncateUpwardThenFlush() { if tc.callSync { gcsSynced, err := t.in.Sync(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.True(t.T(), gcsSynced) } else { err = t.in.Flush(t.ctx) @@ -778,12 +801,12 @@ func (t *FileTest) TestTruncateUpwardThenFlush() { // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) + + require.NoError(t.T(), err) + assert.NotNil(t.T(), m) assert.Equal(t.T(), truncateTime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) - - assert.Nil(t.T(), err) - assert.NotNil(t.T(), m) assert.Equal(t.T(), t.in.SourceGeneration().Object, m.Generation) assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) assert.Equal(t.T(), t.in.SourceGeneration().Size, m.Size) @@ -791,7 +814,7 @@ func (t *FileTest) TestTruncateUpwardThenFlush() { // Check attributes. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(6), attrs.Size) assert.Equal(t.T(), attrs.Mtime, truncateTime.UTC()) @@ -808,7 +831,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes assert.Nil(t.T(), err) // Fetch the attributes and check if the file is empty. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) err = t.in.Truncate(t.ctx, 6) @@ -816,12 +839,12 @@ func (t *FileTest) TestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes assert.Nil(t.T(), err) // The inode should return the new size. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(6), attrs.Size) // Data shouldn't be updated to GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} _, _, err = t.bucket.StatObject(t.ctx, statReq) - assert.NotNil(t.T(), err) + require.Error(t.T(), err) assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) } @@ -837,7 +860,7 @@ func (t *FileTest) TestTruncateDownwardForLocalFileShouldUpdateLocalFileAttribut assert.Nil(t.T(), err) // Validate the new data is written correctly. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(7), attrs.Size) err = t.in.Truncate(t.ctx, 2) @@ -845,12 +868,12 @@ func (t *FileTest) TestTruncateDownwardForLocalFileShouldUpdateLocalFileAttribut assert.Nil(t.T(), err) // The inode should return the new size. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) // Data shouldn't be updated to GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} _, _, err = t.bucket.StatObject(t.ctx, statReq) - assert.NotNil(t.T(), err) + require.Error(t.T(), err) assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) } @@ -879,7 +902,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() // Fetch the attributes and check if the file is empty. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) if tc.performWrite { @@ -888,7 +911,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) // Fetch the attributes and check if the file size reflects the write. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) } @@ -897,12 +920,12 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() assert.Nil(t.T(), err) // The inode should return the new size. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(10), attrs.Size) // Data shouldn't be updated to GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} _, _, err = t.bucket.StatObject(t.ctx, statReq) - assert.NotNil(t.T(), err) + require.Error(t.T(), err) assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) }) } @@ -929,7 +952,7 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable assert.Nil(t.T(), t.in.bwh) // Fetch the attributes and check if the file is empty. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) if tc.performWrite { @@ -938,7 +961,7 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) // Fetch the attributes and check if the file size reflects the write. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) } @@ -947,12 +970,12 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable assert.Nil(t.T(), err) // The inode should return the new size. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(10), attrs.Size) // Data shouldn't be updated to GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} minObject, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), minObject.Size) }) } @@ -1002,7 +1025,7 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { assert.Nil(t.T(), t.in.bwh) // Fetch the attributes and check if the file is empty. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) if tc.performWrite { @@ -1011,13 +1034,13 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) // Fetch the attributes and check if the file size reflects the write. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(7), attrs.Size) } err = t.in.Truncate(t.ctx, tc.truncateSize) - assert.NotNil(t.T(), err) + require.Error(t.T(), err) assert.ErrorContains(t.T(), err, "cannot truncate") }) } @@ -1059,6 +1082,7 @@ func (t *FileTest) TestSyncFlush_Clobbered() { var gcsSynced bool // Sync. The call should not succeed, and we expect a FileClobberedError. gcsSynced, err = t.in.Sync(t.ctx) + require.Error(t.T(), err) assert.False(t.T(), gcsSynced) } else { // Flush. The call should not succeed, and we expect a FileClobberedError. @@ -1079,7 +1103,7 @@ func (t *FileTest) TestSyncFlush_Clobbered() { statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), newObj.Generation, m.Generation) assert.Equal(t.T(), newObj.Size, m.Size) @@ -1119,14 +1143,14 @@ func (t *FileTest) TestSetMtime_ContentNotFaultedIn() { // The inode should agree about the new mtime. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) // The inode should have added the mtime to the backing object's metadata. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), mtime.UTC().Format(time.RFC3339Nano), @@ -1150,14 +1174,14 @@ func (t *FileTest) TestSetMtime_ContentClean() { // The inode should agree about the new mtime. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) // The inode should have added the mtime to the backing object's metadata. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), mtime.UTC().Format(time.RFC3339Nano), @@ -1181,12 +1205,12 @@ func (t *FileTest) TestSetMtime_ContentDirty() { // The inode should agree about the new mtime. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) // Sync. gcsSynced, err := t.in.Sync(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.True(t.T(), gcsSynced) // Now the object in the bucket should have the appropriate mtime. @@ -1196,7 +1220,7 @@ func (t *FileTest) TestSetMtime_ContentDirty() { // Validate MinObject in MRDWrapper is same as the MinObject in inode. assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), mtime.UTC().Format(time.RFC3339Nano), @@ -1224,7 +1248,7 @@ func (t *FileTest) TestSetMtime_SourceObjectGenerationChanged() { statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), newObj.Generation, m.Generation) assert.Equal(t.T(), 0, len(m.Metadata)) @@ -1253,7 +1277,7 @@ func (t *FileTest) TestSetMtime_SourceObjectMetaGenerationChanged() { statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotNil(t.T(), m) assert.Equal(t.T(), newObj.Generation, m.Generation) assert.Equal(t.T(), newObj.MetaGeneration, m.MetaGeneration) @@ -1270,7 +1294,7 @@ func (t *FileTest) TestSetMtimeForUnlinkedFileIsNoOp() { require.Nil(t.T(), err) afterUpdateAttr, err := t.in.Attributes(t.ctx) - require.Nil(t.T(), err) + require.NoError(t.T(), err) assert.NotEqual(t.T(), mtime, afterUpdateAttr.Mtime) assert.Equal(t.T(), beforeUpdateAttr.Mtime, afterUpdateAttr.Mtime) } @@ -1286,7 +1310,7 @@ func (t *FileTest) TestTestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() assert.Nil(t.T(), err) // Validate the attributes on an empty file. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) // Set mtime. @@ -1296,14 +1320,14 @@ func (t *FileTest) TestTestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() assert.Nil(t.T(), err) // The inode should agree about the new mtime. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) assert.Equal(t.T(), attrs.Ctime, mtime) assert.Equal(t.T(), attrs.Atime, mtime) // Data shouldn't be updated to GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} _, _, err = t.bucket.StatObject(t.ctx, statReq) - assert.NotNil(t.T(), err) + require.Error(t.T(), err) assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) } @@ -1324,7 +1348,7 @@ func (t *FileTest) TestSetMtimeForLocalFileWhenStreamingWritesAreEnabled() { assert.Nil(t.T(), err) // The inode should agree about the new mtime. attrs, err = t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) assert.Equal(t.T(), attrs.Ctime, mtime) assert.Equal(t.T(), attrs.Atime, mtime) @@ -1376,7 +1400,7 @@ func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateEmptyFile() { assert.NotNil(t.T(), t.in.content) // Validate that file size is 0. sr, err := t.in.content.Stat() - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), int64(0), sr.Size) } @@ -1402,7 +1426,7 @@ func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateFileForNonLocalFile assert.Nil(t.T(), t.in.bwh) // Validate that file size is 0. sr, err := t.in.content.Stat() - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), int64(0), sr.Size) } @@ -1423,7 +1447,7 @@ func (t *FileTest) TestUnlinkLocalFile() { // Data shouldn't be updated to GCS. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} _, _, err = t.bucket.StatObject(t.ctx, statReq) - assert.NotNil(t.T(), err) + require.Error(t.T(), err) assert.Equal(t.T(), "gcs.NotFoundError: object test not found", err.Error()) } @@ -1476,7 +1500,7 @@ func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { n, err := t.in.Read(t.ctx, data, 0) assert.Equal(t.T(), 0, n) - assert.NotNil(t.T(), err) + require.Error(t.T(), err) assert.Equal(t.T(), "cannot read a file when upload in progress", err.Error()) }) } @@ -1490,6 +1514,7 @@ func (t *FileTest) TestReadEmptyGCSFileWhenStreamingWritesAreNotInProgress() { n, err := t.in.Read(t.ctx, data, 0) assert.Equal(t.T(), 0, n) + require.Error(t.T(), err) assert.Contains(t.T(), err.Error(), "EOF") } @@ -1501,7 +1526,7 @@ func (t *FileTest) TestWriteToLocalFileWithInvalidConfigWhenStreamingWritesAreEn err := t.in.Write(t.ctx, []byte("hi"), 0) - assert.NotNil(t.T(), err) + require.Error(t.T(), err) assert.True(t.T(), strings.Contains(err.Error(), "invalid configuration")) } @@ -1536,7 +1561,7 @@ func (t *FileTest) TestMultipleWritesToLocalFileWhenStreamingWritesAreEnabled() assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) // The inode should agree about the new mtime. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(7), attrs.Size) assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) } @@ -1555,7 +1580,7 @@ func (t *FileTest) TestWriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { assert.Equal(t.T(), int64(2), writeFileInfo.TotalSize) // The inode should agree about the new mtime. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) } @@ -1590,7 +1615,7 @@ func (t *FileTest) TestSetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEn assert.Nil(t.T(), err) // The inode should agree about the new mtime. attrs, err := t.in.Attributes(t.ctx) - assert.Nil(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) assert.Equal(t.T(), attrs.Ctime, mtime) assert.Equal(t.T(), attrs.Atime, mtime) From 51a558ce7516a16b9781af3dcac3ac3e8f8233f1 Mon Sep 17 00:00:00 2001 From: marcoa6 <142958842+marcoa6@users.noreply.github.com> Date: Thu, 3 Apr 2025 01:03:52 -0500 Subject: [PATCH 0319/1298] Update README.md (#3136) * Update README.md Updated to add Parallel Downloads and Streaming writes in whats new section * Update README.md Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * Update README.md --------- Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2b066a6156..29573aceb6 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,16 @@ Starting with V1.0, Cloud Storage FUSE is Generally Available and supported by G Cloud Storage FUSE is open source software, released under the [Apache license](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/LICENSE). -## _New_ Cloud Storage FUSE V2 -Cloud Storage FUSE V2 provides important stability, functionality, and performance enhancements, including the introduction of a file cache that allows repeat file reads to be served from a local, faster cache storage of choice, such as a Local SSD, Persistent Disk, or even in-memory /tmpfs. The Cloud Storage FUSE file cache makes AI/ML training faster and more cost-effective by reducing the time spent waiting for data, with up to _**2.3x faster training time and 3.4x higher throughput**_ observed in training runs. This is especially valuable for multi epoch training and can serve small and random I/O operations significantly faster. The file cache feature is disabled by default and is enabled by passing a directory to 'cache-dir'. See [overview of caching](https://cloud.google.com/storage/docs/gcsfuse-cache) for more details. +## _New_ Cloud Storage FUSE V2.x features +Cloud Storage FUSE V2 provides important stability, functionality, and performance enhancements. +### File Cache +The file cache allows repeat file reads to be served from a local, faster cache storage of choice, such as a Local SSD, Persistent Disk, or even in-memory /tmpfs. The Cloud Storage FUSE file cache makes AI/ML training faster and more cost-effective by reducing the time spent waiting for data, with up to _**2.3x faster training time and 3.4x higher throughput**_ observed in training runs. This is especially valuable for multi epoch training and can serve small and random I/O operations significantly faster. The file cache feature is disabled by default and is enabled by passing a directory to 'cache-dir'. See [overview of caching](https://cloud.google.com/storage/docs/gcsfuse-cache) for more details. + +### Parallel Downloads +Parallel downloads uses multiple workers to download a file in parallel using the file cache directory as a prefetch buffer. We recommend using parallel downloads for single-threaded read scenarios that load large files such as model serving and checkpoint restores, with up to _**9x faster model load times**_ . See [Using Parallel Downloads](https://cloud.google.com/storage/docs/cloud-storage-fuse/file-caching#configure-parallel-downloads) for more details. + +### Streaming Writes +Streaming writes is a new write path that uploads data directly to Google Cloud Storage (GCS) as it's written. The previous, and currently default write path temporarily stages the entire write in a local directory, uploading to GCS on close/fsync. This reduces both latency and disk space usage, making it particularly beneficial for large, sequential writes such as checkpoint writes which are up to _**40% faster with streaming writes,**_ as observed in training runs. See [streaming wrties](https://github.com/googlecloudplatform/gcsfuse/blob/master/docs/semantics.md#with-streaming-writes) for more details. # ABOUT ## What is Cloud Storage FUSE? From 0a3a5026b18d53a3608040b3a62ea6528972242b Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 3 Apr 2025 11:49:59 +0530 Subject: [PATCH 0320/1298] Clean up the remnants of config-cli-parity (#3142) * Clean up the remnants of config-cli-parity --- tools/mount_gcsfuse/main.go | 2 -- tools/mount_gcsfuse/main_test.go | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/mount_gcsfuse/main.go b/tools/mount_gcsfuse/main.go index 1bbfaadc24..fba1dbfefa 100644 --- a/tools/mount_gcsfuse/main.go +++ b/tools/mount_gcsfuse/main.go @@ -111,8 +111,6 @@ func makeGcsfuseArgs( return s == "o" }) nonBoolFlags = append(nonBoolFlags, cfg.ConfigFileFlagName) - // TODO: Clean this up after we gain enough confidence on CLI-Config Parity changes. - boolFlags = append(boolFlags, "disable-viper-config") noopOptions := []string{"user", "nouser", "auto", "noauto", "_netdev", "no_netdev"} // Deal with options. diff --git a/tools/mount_gcsfuse/main_test.go b/tools/mount_gcsfuse/main_test.go index e7ad274564..e6f3ca99eb 100644 --- a/tools/mount_gcsfuse/main_test.go +++ b/tools/mount_gcsfuse/main_test.go @@ -43,7 +43,7 @@ func TestMakeGcsfuseArgs(t *testing.T) { "ignore_interrupts": "", "anonymous_access": "false", "log_rotate_compress": "false", - "disable_viper_config": "true"}, + }, expectedFlags: []string{"--implicit-dirs=true", "--foreground=true", "--reuse-token-from-url=false", @@ -53,7 +53,7 @@ func TestMakeGcsfuseArgs(t *testing.T) { "--ignore-interrupts=true", "--anonymous-access=false", "--log-rotate-compress=false", - "--disable-viper-config=true"}, + }, }, { @@ -67,7 +67,7 @@ func TestMakeGcsfuseArgs(t *testing.T) { "ignore-interrupts": "", "anonymous-access": "false", "log_rotate-compress": "false", - "disable-viper-config": "false"}, + }, expectedFlags: []string{"--implicit-dirs=true", "--foreground=true", "--reuse-token-from-url=false", @@ -77,7 +77,7 @@ func TestMakeGcsfuseArgs(t *testing.T) { "--ignore-interrupts=true", "--anonymous-access=false", "--log-rotate-compress=false", - "--disable-viper-config=false"}, + }, }, { From 352467d46566fe07a0e38851c1496795c75f33e0 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 3 Apr 2025 11:27:32 +0000 Subject: [PATCH 0321/1298] [testing-on-gke] handle no memory/cpu errors in output parsing (#3134) Add warning logs about missing cpu/memory values, and return their values as -1,-1 to indicate missing values, but don't fail the run. --- .../testing_on_gke/examples/utils/utils.py | 114 ++++++++++-------- 1 file changed, 66 insertions(+), 48 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py b/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py index 4bdafaf089..7ed2b27f82 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py @@ -265,27 +265,36 @@ def get_memory_from_monitoring_api( namespace_name, ) ] - return round( - min( - min( - (point.value.int64_value if point.value.int64_value >= 0 else 0) - for point in result.points - ) - for result in relevant_results - ) - / 2**20, # convert to MiB/s - 0, # round to integer. - ), round( - max( - max( - (point.value.int64_value if point.value.int64_value > 0 else 0) - for point in result.points - ) - for result in relevant_results - ) - / 2**20, # convert to MiB/s - 0, # round to integer. - ) + if len(relevant_results) > 0: + return round( + min( + min( + (point.value.int64_value if point.value.int64_value >= 0 else 0) + for point in result.points + ) + for result in relevant_results + ) + / 2**20, # convert to MiB/s + 0, # round to integer. + ), round( + max( + max( + (point.value.int64_value if point.value.int64_value > 0 else 0) + for point in result.points + ) + for result in relevant_results + ) + / 2**20, # convert to MiB/s + 0, # round to integer. + ) + else: + print( + f"Warning: No memory data found for epoch in time-range [{start_epoch}," + f" {end_epoch}) in namespace={namespace_name}, cluster={cluster_name}," + f" pod={pod_name}, so marking -1,-1 for" + " it !" + ) + return -1, -1 def get_cpu_from_monitoring_api( @@ -335,30 +344,39 @@ def get_cpu_from_monitoring_api( namespace_name, ) ] - return round( - min( - min( - ( - point.value.double_value - if point.value.double_value != math.nan - else 0 - ) - for point in result.points - ) - for result in relevant_results - ), - 5, # round up to 5 decimal places. - ), round( - max( - max( - ( - point.value.double_value - if point.value.double_value != math.nan - else 0 - ) - for point in result.points - ) - for result in relevant_results - ), - 5, # round up to 5 decimal places. - ) + if len(relevant_results) > 0: + return round( + min( + min( + ( + point.value.double_value + if point.value.double_value != math.nan + else 0 + ) + for point in result.points + ) + for result in relevant_results + ), + 5, # round up to 5 decimal places. + ), round( + max( + max( + ( + point.value.double_value + if point.value.double_value != math.nan + else 0 + ) + for point in result.points + ) + for result in relevant_results + ), + 5, # round up to 5 decimal places. + ) + else: + print( + f"Warning: No cpu data found for epoch in time-range [{start_epoch}," + f" {end_epoch}) in namespace={namespace_name}, cluster={cluster_name}," + f" pod={pod_name}, so marking -1,-1 for" + " it !" + ) + return -1, -1 From 14edb61a3a5f59346c04bdcc3e64ff2d78fc6308 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 4 Apr 2025 05:48:44 +0000 Subject: [PATCH 0322/1298] fix package pass/fail log in run_e2e_tests.sh (#3148) --- tools/integration_tests/run_e2e_tests.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 9fb5d3575a..3b5c973187 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -242,8 +242,9 @@ function run_non_parallel_tests() { if [ $exit_code_non_parallel != 0 ]; then exit_code=$exit_code_non_parallel echo "test fail in non parallel on package (with zonal=${zonal}): " $test_dir_np + else + echo "Passed test package in non-parallel (with zonal=${zonal}): " $test_dir_np fi - echo "Passed test package in non-parallel (with zonal=${zonal}): ${test_dir_np} ..." done return $exit_code } @@ -291,8 +292,9 @@ function run_parallel_tests() { if [ $exit_code_parallel != 0 ]; then exit_code=$exit_code_parallel echo "test fail in parallel on package (with zonal=${zonal}): " $package_name + else + echo "Passed test package in parallel (with zonal=${zonal}): " $package_name fi - echo "Passed test package in parallel (with zonal=${zonal}): ${package_name} ." done return $exit_code } From a49a0da73cc1e100e56ad197fcc2f8b58afb05ec Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 4 Apr 2025 22:13:13 +0530 Subject: [PATCH 0323/1298] Update metrics documentation (#3150) * Point to metrics cloud docs on how to enable and access metrics. --- docs/metrics.md | 150 +----------------------------------------------- 1 file changed, 1 insertion(+), 149 deletions(-) diff --git a/docs/metrics.md b/docs/metrics.md index 32d9c360d2..ace049a1f0 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -1,151 +1,3 @@ # GCSFuse Metrics -GCSFuse supports exporting custom metrics to Google Cloud monitoring. -Metrics are collected using OpenTelemetry and exported via Cloud Monitoring exporter. -As of today, GCSFuse exports following metrics related to filesystem and -gcs calls. - -## File system metrics: -* **fs/ops_count:** Cumulative number of operations processed by file system. It allows -grouping by op_type to get counts for individual operations. -* **fs/ops_error_count:** Cumulative number of errors generated by file system operations. -This metric can be grouped by op_type and error_category. -Each error is mapped to an error_category in a many-to-one relationship. -* **fs/ops_latency:** Cumulative distribution of file system operation latencies. We -can group by op_type. - -## GCS metrics -* **gcs/download_bytes_count:** Cumulative number of bytes downloaded from GCS along -with read type. Read type specifies sequential or random or parallel read. -* **gcs/read_bytes_count:** Cumulative number of bytes read from GCS objects. This -is different from download_bytes_count. For eg: we might download x number of -bytes from GCS but read only -``` -2. Cloud monitoring api has to be [enabled](https://cloud.google.com/monitoring/api/enable-api) -on the Google cloud project. -3. Service account with which the GCSFuse is running should have -**monitoring.metricsDescriptors.create** permission. -4. Install the [Ops agent](https://cloud.google.com/monitoring/agent/ops-agent/install-index) on the VM. -5. For viewing the metrics: - 1. In the Google cloud console, go to **Metrics Explorer** page within **Monitoring**. - 2. In the toolbar, select the **Explorer** tab. - 3. Select the **configuration** tab. - 4. Expand the **Select a metric** menu. All the GCSFuse metrics will be under - **Global > Custom > metric name** - 5. Example graph for fs/ops_count -![fs/ops_count](https://user-images.githubusercontent.com/101323867/188802087-6423f4f1-2aa6-4501-8db6-3d1997986f68.png) - -## Prometheus metrics - -1. Specify Prometheus port via field `metrics:prometheus-port` in configuration file, or `--prometheus-port` cli flag. The Prometheus metrics endpoint will be exposed on this port and a path of `/metrics`. - -For example, use the following configuration file: - -```yaml -metrics: - prometheus-port: 8080 -``` - -Or, use the cli flag: - -```bash -gcsfuse --prometheus-port 8080 -``` - -2. Run the following command to validate the Prometheus metrics endpoint is available. - -```bash -curl http://localhost:8080/metrics -``` - -The output is similar to the following: - -```text -# HELP file_cache_read_bytes_count The cumulative number of bytes read from file cache along with read type - Sequential/Random -# TYPE file_cache_read_bytes_count counter -file_cache_read_bytes_count{read_type="Random"} 0 -file_cache_read_bytes_count{read_type="Sequential"} 80 -# HELP file_cache_read_count Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false -# TYPE file_cache_read_count counter -file_cache_read_count{cache_hit="false",read_type="Random"} 215 -file_cache_read_count{cache_hit="false",read_type="Sequential"} 5 -# HELP file_cache_read_latencies The cumulative distribution of the file cache read latencies along with cache hit - true/false -# TYPE file_cache_read_latencies histogram -file_cache_read_latencies_bucket{cache_hit="false",le="1"} 215 -file_cache_read_latencies_bucket{cache_hit="false",le="2"} 216 -file_cache_read_latencies_bucket{cache_hit="false",le="3"} 216 -file_cache_read_latencies_bucket{cache_hit="false",le="4"} 216 -file_cache_read_latencies_bucket{cache_hit="false",le="5"} 216 -... -file_cache_read_latencies_sum{cache_hit="false"} 483.62783500000023 -file_cache_read_latencies_count{cache_hit="false"} 220 -# HELP fs_ops_count The cumulative number of ops processed by the file system. -# TYPE fs_ops_count counter -fs_ops_count{fs_op="FlushFile"} 9 -fs_ops_count{fs_op="GetInodeAttributes"} 91 -fs_ops_count{fs_op="LookUpInode"} 584 -fs_ops_count{fs_op="OpenDir"} 122 -fs_ops_count{fs_op="OpenFile"} 9 -fs_ops_count{fs_op="ReadDir"} 184 -fs_ops_count{fs_op="ReadFile"} 220 -fs_ops_count{fs_op="ReleaseDirHandle"} 122 -fs_ops_count{fs_op="ReleaseFileHandle"} 9 -fs_ops_count{fs_op="StatFS"} 10 -# HELP fs_ops_error_count The cumulative number of errors generated by file system operations -# TYPE fs_ops_error_count counter -fs_ops_error_count{fs_error="function not implemented",fs_error_category="function not implemented",fs_op="GetXattr"} 1 -fs_ops_error_count{fs_error="function not implemented",fs_error_category="function not implemented",fs_op="ListXattr"} 1 -fs_ops_error_count{fs_error="interrupted system call",fs_error_category="interrupt errors",fs_op="LookUpInode"} 58 -fs_ops_error_count{fs_error="no such file or directory",fs_error_category="no such file or directory",fs_op="LookUpInode"} 6 -# HELP fs_ops_latency The cumulative distribution of file system operation latencies -# TYPE fs_ops_latency histogram -fs_ops_latency_bucket{fs_op="FlushFile",le="1"} 9 -fs_ops_latency_bucket{fs_op="FlushFile",le="2"} 9 -fs_ops_latency_bucket{fs_op="FlushFile",le="3"} 9 -fs_ops_latency_bucket{fs_op="FlushFile",le="4"} 9 -fs_ops_latency_bucket{fs_op="FlushFile",le="5"} 9 -... -fs_ops_latency_sum{fs_op="FlushFile"} 0.28800000000000003 -fs_ops_latency_count{fs_op="FlushFile"} 9 -# HELP gcs_download_bytes_count The cumulative number of bytes downloaded from GCS along with type - Sequential/Random -# TYPE gcs_download_bytes_count counter -gcs_download_bytes_count{read_type="Sequential"} 2.0971528e+08 -# HELP gcs_read_count Specifies the number of gcs reads made along with type - Sequential/Random -# TYPE gcs_read_count counter -gcs_read_count{read_type="Sequential"} 5 -``` - -3. Follow [Prometheus documentation](https://prometheus.io/docs/introduction/first_steps/#configuring-prometheus) -to specify the target Prometheus metric endpoint under the `scrape_configs` section in the Prometheus configuration file. - -## References: -* More details around adding custom metrics using OpenTelemetry can be found [here](https://cloud.google.com/monitoring/custom-metrics/open-telemetry) +For instructions on how to enable and use Cloud Storage FUSE metrics, refer to the metrics guide at . From 7b21a0f4d00a344f752a65de73971d49fc7f0efd Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 7 Apr 2025 10:33:55 +0530 Subject: [PATCH 0324/1298] Remove handle-sigterm flag. (#3145) handle-sigterm flag is a hidden flag and has been set to true for quite some time. Given that it was always hidden, it's okay to remove it without introducing any backward incompatibility. --- cfg/config.go | 12 ------------ cfg/params.yaml | 8 -------- cmd/config_validation_test.go | 3 --- cmd/legacy_main.go | 5 +---- cmd/root_test.go | 3 --- 5 files changed, 1 insertion(+), 30 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index a86e3c2cd9..b45bb28e10 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -106,8 +106,6 @@ type FileSystemConfig struct { Gid int64 `yaml:"gid"` - HandleSigterm bool `yaml:"handle-sigterm"` - IgnoreInterrupts bool `yaml:"ignore-interrupts"` KernelListCacheTtlSecs int64 `yaml:"kernel-list-cache-ttl-secs"` @@ -431,12 +429,6 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.IntP("gid", "", -1, "GID owner of all inodes.") - flagSet.BoolP("handle-sigterm", "", true, "Instructs gcsfuse to handle SIGTERM to gracefully shutdown") - - if err := flagSet.MarkHidden("handle-sigterm"); err != nil { - return err - } - flagSet.DurationP("http-client-timeout", "", 0*time.Nanosecond, "The time duration that http client will wait to get response from the server. A value of 0 indicates no timeout.") flagSet.BoolP("ignore-interrupts", "", true, "Instructs gcsfuse to ignore system interrupt signals (like SIGINT, triggered by Ctrl+C). This prevents those signals from immediately terminating gcsfuse inflight operations.") @@ -752,10 +744,6 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } - if err := v.BindPFlag("file-system.handle-sigterm", flagSet.Lookup("handle-sigterm")); err != nil { - return err - } - if err := v.BindPFlag("gcs-connection.http-client-timeout", flagSet.Lookup("http-client-timeout")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 38ea9736a1..8de127a5ae 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -179,14 +179,6 @@ default: -1 usage: "GID owner of all inodes." -- config-path: "file-system.handle-sigterm" - flag-name: "handle-sigterm" - type: "bool" - usage: >- - Instructs gcsfuse to handle SIGTERM to gracefully shutdown - default: true - hide-flag: true - - config-path: "file-system.ignore-interrupts" flag-name: "ignore-interrupts" type: "bool" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 359125cb08..d36ef83510 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -519,7 +519,6 @@ func TestValidateConfigFile_FileSystemConfigSuccessful(t *testing.T) { TempDir: "", PreconditionErrors: true, Uid: -1, - HandleSigterm: true, }, }, }, @@ -539,7 +538,6 @@ func TestValidateConfigFile_FileSystemConfigSuccessful(t *testing.T) { TempDir: "", PreconditionErrors: true, Uid: -1, - HandleSigterm: true, }, }, }, @@ -559,7 +557,6 @@ func TestValidateConfigFile_FileSystemConfigSuccessful(t *testing.T) { TempDir: cfg.ResolvedPath(path.Join(hd, "temp")), PreconditionErrors: false, Uid: 8, - HandleSigterm: true, }, }, }, diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index ba16e48259..66b7d19e65 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -58,10 +58,7 @@ const ( func registerTerminatingSignalHandler(mountPoint string, c *cfg.Config) { // Register for SIGINT. signalChan := make(chan os.Signal, 1) - signal.Notify(signalChan, os.Interrupt) - if c.FileSystem.HandleSigterm { - signal.Notify(signalChan, unix.SIGTERM) - } + signal.Notify(signalChan, os.Interrupt, unix.SIGTERM) // Start a goroutine that will unmount when the signal is received. go func() { diff --git a/cmd/root_test.go b/cmd/root_test.go index 6bb46852b9..741297506f 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -659,7 +659,6 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { TempDir: cfg.ResolvedPath(path.Join(hd, "temp")), PreconditionErrors: false, Uid: 8, - HandleSigterm: true, }, }, }, @@ -679,7 +678,6 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { TempDir: "", PreconditionErrors: true, Uid: -1, - HandleSigterm: true, }, }, }, @@ -699,7 +697,6 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { TempDir: "", PreconditionErrors: true, Uid: -1, - HandleSigterm: true, }, }, }, From 7e3384e23ae5cadfbaaff2156d90be97f7aca2f9 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 7 Apr 2025 12:02:22 +0530 Subject: [PATCH 0325/1298] [Random Reader Refactoring] Create hidden flag to enable new implementation (#3153) * create hidden flag for random reader refactoring * fix typo in tests * fix typo in tests * fix comment --- cfg/config.go | 12 ++++++++++++ cfg/params.yaml | 7 +++++++ cmd/root_test.go | 31 +++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/cfg/config.go b/cfg/config.go index b45bb28e10..1fbb64ee61 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -34,6 +34,8 @@ type Config struct { EnableHns bool `yaml:"enable-hns"` + EnableNewReader bool `yaml:"enable-new-reader"` + FileCache FileCacheConfig `yaml:"file-cache"` FileSystem FileSystemConfig `yaml:"file-system"` @@ -335,6 +337,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } + flagSet.BoolP("enable-new-reader", "", false, "Enables support for new reader implementation.") + + if err := flagSet.MarkHidden("enable-new-reader"); err != nil { + return err + } + flagSet.BoolP("enable-nonexistent-type-cache", "", false, "Once set, if an inode is not found in GCS, a type cache entry with type NonexistentType will be created. This also means new file/dir created might not be seen. For example, if this flag is set, and metadata-cache-ttl-secs is set, then if we create the same file/node in the meantime using the same mount, since we are not refreshing the cache, it will still return nil.") flagSet.BoolP("enable-otel", "", true, "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus.") @@ -652,6 +660,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("enable-new-reader", flagSet.Lookup("enable-new-reader")); err != nil { + return err + } + if err := v.BindPFlag("metadata-cache.enable-nonexistent-type-cache", flagSet.Lookup("enable-nonexistent-type-cache")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 8de127a5ae..c72b456b56 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -85,6 +85,13 @@ default: true hide-flag: true +- config-path: "enable-new-reader" + flag-name: "enable-new-reader" + type: "bool" + usage: "Enables support for new reader implementation." + default: false + hide-flag: true + - config-path: "file-cache.cache-file-for-range-read" flag-name: "file-cache-cache-file-for-range-read" type: "bool" diff --git a/cmd/root_test.go b/cmd/root_test.go index 741297506f..9c41519914 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -871,6 +871,37 @@ func TestArgsParsing_EnableAtomicRenameObjectFlag(t *testing.T) { } } +func TestArgsParsing_EnableNewReaderFlag(t *testing.T) { + tests := []struct { + name string + args []string + expectedEnableNewReader bool + }{ + { + name: "normal", + args: []string{"gcsfuse", "--enable-new-reader=true", "abc", "pqr"}, + expectedEnableNewReader: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var gotEnableNewReader bool + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { + gotEnableNewReader = cfg.EnableNewReader + return nil + }) + require.Nil(t, err) + cmd.SetArgs(convertToPosixArgs(tc.args, cmd)) + + err = cmd.Execute() + + require.NoError(t, err) + assert.Equal(t, tc.expectedEnableNewReader, gotEnableNewReader) + }) + } +} + func TestArgsParsing_MetricsFlags(t *testing.T) { tests := []struct { name string From 80cc64c9059781c7eb081dfb4f2fbb0057a22428 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Mon, 7 Apr 2025 16:20:18 +0530 Subject: [PATCH 0326/1298] Update GCSFuse External Benchmarks with version 2.11.1 (#3152) * skeleton update * updating data * edit: disk type * update section title : n2 benchmarking * update steps to run : fio * adding link to previous benchmark * merge notte * nits --- docs/benchmarks.md | 283 +++++++++++++++++++++++++-------------------- 1 file changed, 158 insertions(+), 125 deletions(-) diff --git a/docs/benchmarks.md b/docs/benchmarks.md index eeacaa614a..72f988a558 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -7,139 +7,174 @@ workloads for the given test setup: ## Test setup: * Infra: GCP VM -* VM Type: n2-standard-96 * OS: ubuntu-20.04 -* VM Bandwidth: 100Gbps -* VM location: us-west1-b -* Disk Type: SSD persistent disk -* GCS Bucket location: us-west1 -* Framework: FIO -* GCSFuse version: 2.2.0 - -## Reads - -### FIO spec +* Framework: FIO (version 3.39) +* GCSFuse version: 2.11.1 +## FIO workloads +Please read the details about the FIO specification [here](https://fio.readthedocs.io/en/latest/). +### Reads ``` - [global] - ioengine=sync - direct=1 - fadvise_hint=0 - verify=0 - iodepth=64 - invalidate=1 - ramp_time=10s - runtime=60s - time_based=1 - thread=1 - openfiles=1 - group_reporting=1 - allrandrepeat=1 - # Change this to randread to test random reads. - rw=read - # Update the block size value from the table for different experiments. - bs=1M - # Update the file size value from table(file size) for different experiments. - filesize=10M - # Change the test directory (1mb) for different experiments. The directory must exist within the mounted directory. - directory=/mnt/1mb - filename_format=$jobname.$jobnum.$filenum - [experiment] - stonewall - # Number of threads - numjobs=128 -``` - -### Results - -#### Sequential Reads - -| File Size | BlockSize | Bandwidth in (MiB/sec) | Avg Latency (msec) | IOPs | -|-----------|-----------|------------------------|--------------------|----------| -| 128KB | 128K | 862 | 18.54 | 6898.27 | -| 256KB | 128K | 1548 | 10.325 | 12386.03 | -| 1MB | 1M | 5108 | 24.99 | 5113.21 | -| 5MB | 1M | 7282 | 17.505 | 7308.51 | -| 10MB | 1M | 7946 | 16.092 | 7946.63 | -| 50MB | 1M | 7810 | 16.356 | 7818.17 | -| 100MB | 1M | 7839 | 16.295 | 7840.17 | -| 200MB | 1M | 7879 | 16.217 | 7884.45 | -| 1GB | 1M | 7911 | 16.162 | 7910.19 | - -#### Random Reads - -| File Size | BlockSize | Bandwidth in MiB/sec | Avg Latency (msec) | IOPs | -|-----------|-----------|----------------------|--------------------|----------| -| 256KB | 128K | 1264 | 12.648 | 10109.62 | -| 5MB | 1M | 4367 | 29.129 | 4449.03 | -| 10MB | 1M | 3810 | 33.496 | 3825.54 | -| 50MB | 1M | 4370 | 29.185 | 4426.73 | -| 100MB | 1M | 3504 | 36.421 | 3505.01 | -| 200MB | 1M | 3048 | 41.919 | 3044.43 | -| 1GB | 1M | 2120 | 60.246 | 2114.33 | - -## Writes - -### FIO spec - +[global] +allrandrepeat=0 +create_serialize=0 +direct=1 +fadvise_hint=0 +file_service_type=random +group_reporting=1 +iodepth=64 +ioengine=libaio +invalidate=1 +numjobs=128 +openfiles=1 +# Change "read" to "randread" to test random reads. +rw=read +thread=1 +filename_format=$jobname.$jobnum/$filenum + +[experiment] +stonewall +directory=${DIR} +# Update the block size value from the table for different experiments. +bs=128K +# Update the file size value from table(file size) for different experiments. +filesize=128K +# Set nrfiles per thread in such a way that the test runs for 1-2 min. +nrfiles=30 ``` - [global] - ioengine=sync - direct=1 - fadvise_hint=0 - verify=0 - iodepth=64 - invalidate=1 - time_based=0 - file_append=0 - # By default fio creates all files first and then starts writing to them. This option is to disable that behavior. - create_on_open=1 - thread=1 - openfiles=1 - group_reporting=1 - allrandrepeat=1 - # Every file is written only once. Set nrfiles per thread in such a way that the test runs for 1-2 min. - # This will vary based on file size. Change the value from table to get provided results. - nrfiles=2 - filename_format=$jobname.$jobnum.$filenum - # Change this to randwrite to test random writes. - rw=write - # Update the block size value from the table for different - bs=1M - # Update the file size value from table(file size) for different experiments. - filesize=1G - [experiment] - stonewall - # Change the test directory (1mb) for different experiments. The directory must exist within the mounted directory. - directory=gcs/1gb - numjobs=112 - ``` - -**Note:** Benchmarking is done by writing out new files to GCS. Performance +**Note:** Please note an update to our FIO read workload. This change accounts for the bandwidth difference between the current and [previous](https://github.com/GoogleCloudPlatform/gcsfuse/blob/26bc07f3dd210e05a7030954bb3e6070e957bfca/docs/benchmarks.md#sequential-read) n2 benchmarks. +### Writes +``` +[global] +allrandrepeat=1 +# By default fio creates all files first and then starts writing to them. This option is to disable that behavior. +create_on_open=1 +direct=1 +fadvise_hint=0 +file_append=0 +group_reporting=1 +iodepth=64 +ioengine=sync +invalidate=1 +numjobs=112 +openfiles=1 +rw=write +thread=1 +time_based=0 +verify=0 +filename_format=$jobname.$jobnum.$filenum + + +[experiment] +stonewall +directory=${DIR} +# Every file is written only once. Set nrfiles per thread in such a way that the test runs for 1-2 min. +# This will vary based on file size. +nrfiles=30 +# Update the file size value from table(file size) for different experiments. +filesize=256K +# Update the block size value from the table for different experiments. +bs=16K +``` +**Note:** +* Benchmarking is done by writing out new files to GCS. Performance numbers will be different for edits/appends to existing files. -### Results - -#### Sequential Write +* Random writes and sequential write performance will generally be the same, as +all writes are first staged to a local temporary directory before being written +to GCS on close/fsync. -| File Size | BlockSize | nrfiles | Bandwidth in MiB/sec | IOPS(avg) | Avg Latency (msec) | Network Send Traffic (GiB/s) | -|-----------|-----------|---------|----------------------|-----------|--------------------|------------------------------| -| 256KB | 16K | 30 | 212 | 14976.95 | 3.206 | 0.027 | -| 1MB | 1M | 30 | 772 | 794.32 | 1.150 | 0.036 | -| 50MB | 1M | 20 | 3611 | 5948.63 | 8.929 | 1.33 | -| 100MB | 1M | 10 | 3577 | 4672.64 | 1.911 | 1.41 | -| 1GB | 1M | 2 | 1766 | 2121.66 | 49.114 | 1.77 | +## GCSFuse Benchmarking on c4 machine-type +* VM Type: c4-standard-96 +* VM location: us-south1 +* Networking: gVNIC+ tier_1 networking (200Gbps) +* Disk Type: Hyperdisk balanced +* GCS Bucket location: us-south1 + +### Sequential Reads +| File Size | BlockSize | nrfiles |Bandwidth in (GiB/sec) | IOPs | Avg Latency (msec) | +|---|---|---|---|---|---| +| 128K | 128K | 30 | 0.45 | 3650 | 30 | +| 256K | 128K | 30 | 0.81 | 6632 | 16 | +| 1M | 1M | 30 | 2.83 | 2902 | 38 | +| 5M | 1M | 20 | 6.72 | 6874 | 17 | +| 10M | 1M | 20 | 9.33 | 9548 | 15 | +| 50M | 1M | 20 | 15.6 |15.9k | 14 | +| 100M |1M | 10 | 13.2 | 13.5k | 33 | +| 200M | 1M | 10 | 12.4 | 12.7k| 38 | +| 1G | 1M | 10 | 14.5 | 14.8k | 60 | + + + +### Random Reads +| File Size | BlockSize | nrfiles |Bandwidth in (MiB/sec) | IOPs | Avg Latency (msec) | +|---|---|---|---|---|---| +| 256K | 128K | 30 | 626 | 5009 | 24 | +| 5M | 1M | 20 | 4291 | 4290 | 30 | +| 10M | 1M | 20 | 4138 | 4137 | 37 | +| 50M | 1M | 20 | 3552 |3552 | 83 | +| 100M |1M | 10 | 3327 | 3327 | 211 | +| 200M | 1M | 10 | 3139 | 3138 | 286 | +| 1G | 1M | 10 | 3320 | 3320 | 345 | + + +### Sequential Writes +| File Size | BlockSize | nrfiles |Bandwidth in (MiB/sec) | IOPs | Avg Latency (msec) | +|---|---|---|---|---|---| +| 256K | 16K | 30 | 215 | 13.76k | 0.23 | +| 1M | 1M | 30 | 718 | 717 | 1.12 | +| 50M | 1M | 20 | 3592 | 3592 | 2.35 | +| 100M |1M | 10 | 4549 | 4549 | 7.04 | +| 1G | 1M | 2 | 2398 | 2398 | 37.07 | + +## GCSFuse Benchmarking on n2 machine-type +* VM Type: n2-standard-96 +* VM location: us-south1 +* Networking: gVNIC+ tier_1 networking (100Gbps) +* Disk Type: SSD persistent disk +* GCS Bucket location: us-south1 +### Sequential Reads +| File Size | BlockSize | nrfiles |Bandwidth in (MiB/sec) | IOPs | Avg Latency (msec) | +|---|---|---|---|---|---| +| 128K | 128K | 30 | 443 | 3545 | 29 | +| 256K | 128K | 30 | 821 | 6569 | 16 | +| 1M | 1M | 30 | 2710 | 2709 | 40 | +| 5M | 1M | 20 | 5666 | 5666 | 20 | +| 10M | 1M | 20 | 5994 | 5993 | 20 | +| 50M | 1M | 20 | 7986 | 7985 | 28 | +| 100M |1M | 10 | 6469 | 6468 | 68 | +| 200M | 1M | 10 | 6955 | 6954 | 92 | +| 1G | 1M | 10 | 7470 | 7469 | 131 | + + + +### Random Reads +| File Size | BlockSize | nrfiles |Bandwidth in (MiB/sec) | IOPs | Avg Latency (msec) | +|---|---|---|---|---|---| +| 256K | 128K | 30 | 562 | 4499 | 24 | +| 5M | 1M | 20 | 3608 | 3607 | 34 | +| 10M | 1M | 20 | 3185 | 3184 | 45 | +| 50M | 1M | 20 | 3386 | 3386 | 84 | +| 100M |1M | 10 | 3297 | 3297 | 207 | +| 200M | 1M | 10 | 3150 | 3150 | 279 | +| 1G | 1M | 10 | 2730 | 2730 | 457 | + + +### Sequential Writes +| File Size | BlockSize | nrfiles |Bandwidth in (MiB/sec) | IOPs | Avg Latency (msec) | +|---|---|---|---|---|---| +| 256K | 16K | 30 | 192 | 12.27k | 0.27 | +| 1M | 1M | 30 | 683 | 682 | 1.23 | +| 50M | 1M | 20 | 3429 | 3429 | 2.88 | +| 100M |1M | 10 | 3519 | 3518 | 11.83 | +| 1G | 1M | 2 | 1892 | 1891 | 45.40 | -#### Random Write -Random writes and sequential write performance will generally be the same, as -all writes are first staged to a local temporary directory before being written -to GCS on close/fsync. ## Steps to benchmark GCSFuse performance 1. [Create](https://cloud.google.com/compute/docs/instances/create-start-instance#publicimage) - a GCP VM instance. + a GCP VM instance. 2. [Connect](https://cloud.google.com/compute/docs/instances/connecting-to-instance) to the VM instance. 3. Install FIO. @@ -157,10 +192,8 @@ to GCS on close/fsync. gcsfuse ``` -7. Create a FIO job spec file. - The FIO content referred to above. Please read the details about the FIO - specification - [here](https://fio.readthedocs.io/en/latest/). +7. Create a FIO job spec file.\ + The fio workload files can be found [above](#fio-workloads). ``` vi samplejobspec.fio ``` @@ -168,7 +201,7 @@ to GCS on close/fsync. 8. Run the FIO test using following command. ``` - fio samplejobspec.fio + DIR= fio samplejobspec.fio ``` 9. Metrics will be displayed on the terminal after test is completed. \ No newline at end of file From 3050e1133e6381fb7098951ac1a9d97ff01f19f7 Mon Sep 17 00:00:00 2001 From: codechanges Date: Tue, 8 Apr 2025 08:11:40 +0530 Subject: [PATCH 0327/1298] Optimize default values on a high performance nodes (#3118) * Optimize default values * Fix comment in the function * Removed prints * Modified retry logic * Modified retry logic * Fixed typo * Change loop structure * Added constant type * Export only Optimize function * Simplify checks for machine type * Rename variable * Simplified logic to find right profile for the machine type * Renamed a few variables * Use asserts in the test case * Continue on reading error body * Restructuring tests * Refactor http query function * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution --- .github/workflows/ci.yml | 2 +- cfg/config.go | 24 +++ cfg/optimize.go | 301 +++++++++++++++++++++++++++++ cfg/optimize_test.go | 403 +++++++++++++++++++++++++++++++++++++++ cfg/params.yaml | 14 ++ 5 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 cfg/optimize.go create mode 100644 cfg/optimize_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cdeb47827e..c35789ebc9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: matrix: go: [ 1.24.x ] runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 20 steps: - uses: actions/checkout@v2 diff --git a/cfg/config.go b/cfg/config.go index 1fbb64ee61..fbef55bc2c 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -30,6 +30,8 @@ type Config struct { Debug DebugConfig `yaml:"debug"` + DisableAutoconfig bool `yaml:"disable-autoconfig"` + EnableAtomicRenameObject bool `yaml:"enable-atomic-rename-object"` EnableHns bool `yaml:"enable-hns"` @@ -54,6 +56,8 @@ type Config struct { Logging LoggingConfig `yaml:"logging"` + MachineType string `yaml:"machine-type"` + MetadataCache MetadataCacheConfig `yaml:"metadata-cache"` Metrics MetricsConfig `yaml:"metrics"` @@ -313,6 +317,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.StringP("dir-mode", "", "0755", "Permissions bits for directories, in octal.") + flagSet.BoolP("disable-autoconfig", "", false, "Disable optimizing configuration automatically for a machine") + + if err := flagSet.MarkHidden("disable-autoconfig"); err != nil { + return err + } + flagSet.BoolP("disable-parallel-dirops", "", false, "Specifies whether to allow parallel dir operations (lookups and readers)") if err := flagSet.MarkHidden("disable-parallel-dirops"); err != nil { @@ -463,6 +473,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.StringP("log-severity", "", "info", "Specifies the logging severity expressed as one of [trace, debug, info, warning, error, off]") + flagSet.StringP("machine-type", "", "", "Type of the machine on which gcsfuse is being run e.g. a3-highgpu-4g") + + if err := flagSet.MarkHidden("machine-type"); err != nil { + return err + } + flagSet.IntP("max-conns-per-host", "", 0, "The max number of TCP connections allowed per server. This is effective when client-protocol is set to 'http1'. A value of 0 indicates no limit on TCP connections (limited by the machine specifications).") flagSet.IntP("max-idle-conns-per-host", "", 100, "The number of maximum idle connections allowed per server.") @@ -644,6 +660,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("disable-autoconfig", flagSet.Lookup("disable-autoconfig")); err != nil { + return err + } + if err := v.BindPFlag("file-system.disable-parallel-dirops", flagSet.Lookup("disable-parallel-dirops")); err != nil { return err } @@ -808,6 +828,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("machine-type", flagSet.Lookup("machine-type")); err != nil { + return err + } + if err := v.BindPFlag("gcs-connection.max-conns-per-host", flagSet.Lookup("max-conns-per-host")); err != nil { return err } diff --git a/cfg/optimize.go b/cfg/optimize.go new file mode 100644 index 0000000000..c11548a33f --- /dev/null +++ b/cfg/optimize.go @@ -0,0 +1,301 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +package cfg + +import ( + "fmt" + "io" + "net/http" + "reflect" + "slices" + "strings" + "time" + "unicode" +) + +//////////////////////////////////////////////////////////////////////// +// Constants +//////////////////////////////////////////////////////////////////////// + +const ( + maxRetries = 2 + httpTimeout = 50 * time.Millisecond + machineTypeFlg = "machine-type" +) + +//////////////////////////////////////////////////////////////////////// +// Types +//////////////////////////////////////////////////////////////////////// + +type isValueSet interface { + IsSet(string) bool + GetString(string) string + GetBool(string) bool +} + +// flagOverride represents a flag override with its new value. +type flagOverride struct { + newValue interface{} +} + +// flagOverrideSet represents a named set of flag overrides. +type flagOverrideSet struct { + name string + overrides map[string]flagOverride +} + +// machineType represents a specific machine type with associated flag overrides. +type machineType struct { + names []string + flagOverrideSetName string +} + +// optimizationConfig holds the configuration for machine-specific optimizations. +type optimizationConfig struct { + flagOverrideSets []flagOverrideSet + machineTypes []machineType +} + +//////////////////////////////////////////////////////////////////////// +// Variables +//////////////////////////////////////////////////////////////////////// + +var ( + // defaultOptimizationConfig provides a default configuration for optimizations. + defaultOptimizationConfig = optimizationConfig{ + flagOverrideSets: []flagOverrideSet{ + { + name: "high-performance", + overrides: map[string]flagOverride{ + "write.enable-streaming-writes": {newValue: true}, + "metadata-cache.negative-ttl-secs": {newValue: 0}, + "metadata-cache.ttl-secs": {newValue: -1}, + "metadata-cache.stat-cache-max-size-mb": {newValue: 1024}, + "metadata-cache.type-cache-max-size-mb": {newValue: 128}, + "implicit-dirs": {newValue: true}, + "file-system.rename-dir-limit": {newValue: 200000}, + }, + }, + }, + machineTypes: []machineType{ + { + names: []string{ + "a2-megagpu-16g", "a2-ultragpu-8g", "a3-edgegpu-8g", "a3-highgpu-8g", "a3-megagpu-8g", "a3-ultragpu-8g", "a4-highgpu-8g-lowmem", + "ct5l-hightpu-8t", "ct5lp-hightpu-8t", "ct5p-hightpu-4t", "ct5p-hightpu-4t-tpu", "ct6e-standard-4t", "ct6e-standard-4t-tpu", "ct6e-standard-8t", "ct6e-standard-8t-tpu"}, + flagOverrideSetName: "high-performance", + }, + // Add more machine types here as needed. + }, + } + + // metadataEndpoints are the endpoints to try for fetching metadata. + // Use an array to make provision for https endpoint in the future: https://cloud.google.com/compute/docs/metadata/querying-metadata#metadata_server_endpoints + metadataEndpoints = []string{ + "http://metadata.google.internal/computeMetadata/v1/instance/machine-type", + } +) + +//////////////////////////////////////////////////////////////////////// +// Helper Functions +//////////////////////////////////////////////////////////////////////// + +// getMetadata fetches metadata from a given endpoint. +func getMetadata(client *http.Client, endpoint string) ([]byte, error) { + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request for %s: %w", endpoint, err) + } + req.Header.Add("Metadata-Flavor", "Google") + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("request to %s failed: %w", endpoint, err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("request to %s returned non-OK status: %d", endpoint, resp.StatusCode) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body from %s: %w", endpoint, err) + } + + return body, nil +} + +// getMachineType fetches the machine type from the metadata server. +func getMachineType(isSet isValueSet) (string, error) { + // Check if the machine-type flag is set and not empty. + if isSet.IsSet(machineTypeFlg) { + if currentMachineType := isSet.GetString(machineTypeFlg); currentMachineType != "" { + return currentMachineType, nil + } + } + client := http.Client{Timeout: httpTimeout} + for retry := 0; retry < maxRetries; retry++ { + for _, endpoint := range metadataEndpoints { + body, err := getMetadata(&client, endpoint) + if err != nil { + continue + } + + currentMachineType := string(body) + parts := strings.Split(currentMachineType, "/") + return parts[len(parts)-1], nil + } + } + + return "", fmt.Errorf("failed to get machine type from any metadata endpoint after retries") +} + +func applyMachineTypeOptimizations(config *optimizationConfig, cfg *Config, isSet isValueSet) ([]string, error) { + currentMachineType, err := getMachineType(isSet) + if err != nil { + return nil, nil // Non-fatal error, continue with default settings. + } + var optimizedFlags []string + + // Find the matching machine type. + mtIndex := slices.IndexFunc(config.machineTypes, func(mt machineType) bool { + return slices.ContainsFunc(mt.names, func(name string) bool { + return strings.HasPrefix(currentMachineType, name) + }) + }) + + // If no matching machine type is found, return. + if mtIndex == -1 { + return optimizedFlags, nil + } + mt := &config.machineTypes[mtIndex] + + // Find the corresponding flag override set. + flgOverrideSetIndex := slices.IndexFunc(config.flagOverrideSets, func(fos flagOverrideSet) bool { + return fos.name == mt.flagOverrideSetName + }) + + // If no matching flag override set is found, return. + if flgOverrideSetIndex == -1 { + return optimizedFlags, nil + } + flgOverrideSet := &config.flagOverrideSets[flgOverrideSetIndex] + + // Apply all overrides from the set. + for flag, override := range flgOverrideSet.overrides { + err := setFlagValue(cfg, flag, override, isSet) + if err == nil { + optimizedFlags = append(optimizedFlags, flag) + } + } + return optimizedFlags, nil +} + +// Optimize applies machine-type specific optimizations. +func Optimize(cfg *Config, isSet isValueSet) ([]string, error) { + // Check if disable-autoconfig is set to true. + if isSet.GetBool("disable-autoconfig") { + return nil, nil + } + optimizedFlags, err := applyMachineTypeOptimizations(&defaultOptimizationConfig, cfg, isSet) + return optimizedFlags, err +} + +// convertToCamelCase converts a string from snake-case to CamelCase. +func convertToCamelCase(input string) string { + if input == "" { + return "" + } + + // Split the string by hyphen. + parts := strings.Split(input, "-") + + // Capitalize each part and join them together. + for i, part := range parts { + if len(part) > 0 { + runes := []rune(part) + runes[0] = unicode.ToUpper(runes[0]) + parts[i] = string(runes) + } + } + + return strings.Join(parts, "") +} + +// setFlagValue uses reflection to set the value of a flag in ServerConfig. +func setFlagValue(cfg *Config, flag string, override flagOverride, isSet isValueSet) error { + // Split the flag name into parts to traverse nested structs. + parts := strings.Split(flag, ".") + if len(parts) == 0 { + return fmt.Errorf("invalid flag name: %s", flag) + } + + // Start with the Config. + v := reflect.ValueOf(cfg).Elem() + var field reflect.Value + // Traverse nested structs. + for _, part := range parts { + field = v.FieldByName(convertToCamelCase(part)) + if !field.IsValid() { + return fmt.Errorf("invalid flag name: %s", flag) + } + v = field + } + + // Check if the field exists. + if !field.IsValid() { + return fmt.Errorf("invalid flag name: %s", flag) + } + + // Check if the field is settable. + if !field.CanSet() { + return fmt.Errorf("cannot set flag: %s", flag) + } + + // Construct the full flag name for IsSet check. + fullFlagName := strings.ToLower(flag) + + // Only override if the user hasn't set it. + if !isSet.IsSet(fullFlagName) { + // Set the value based on the field type. + + switch field.Kind() { + case reflect.Bool: + boolValue, ok := override.newValue.(bool) + if !ok { + return fmt.Errorf("invalid boolean value for flag %s: %v", flag, override.newValue) + } + field.SetBool(boolValue) + case reflect.Int, reflect.Int64: + intValue, ok := override.newValue.(int) + if !ok { + return fmt.Errorf("invalid integer value for flag %s: %v", flag, override.newValue) + } + field.SetInt(int64(intValue)) + case reflect.String: + stringValue, ok := override.newValue.(string) + if !ok { + return fmt.Errorf("invalid string value for flag %s: %v", flag, override.newValue) + } + field.SetString(stringValue) + default: + return fmt.Errorf("unsupported flag type for flag %s", flag) + } + } + + return nil +} + +func isFlagPresent(flags []string, flag string) bool { + return slices.Contains(flags, flag) +} diff --git a/cfg/optimize_test.go b/cfg/optimize_test.go new file mode 100644 index 0000000000..d11e16389b --- /dev/null +++ b/cfg/optimize_test.go @@ -0,0 +1,403 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +package cfg + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Mock IsValueSet for testing. +type mockIsValueSet struct { + setFlags map[string]bool + boolFlags map[string]bool + stringFlags map[string]string +} + +func (m *mockIsValueSet) IsValueSet(flag string) bool { + return m.setFlags[flag] +} + +func (m *mockIsValueSet) IsSet(flag string) bool { + return m.setFlags[flag] +} + +func (m *mockIsValueSet) GetBool(flag string) bool { + return m.boolFlags[flag] +} + +func (m *mockIsValueSet) GetString(flag string) string { + return m.stringFlags[flag] +} + +func (m *mockIsValueSet) Set(flag string) { + m.setFlags[flag] = true +} + +func (m *mockIsValueSet) SetString(flag string, value string) { + m.stringFlags[flag] = value +} + +func (m *mockIsValueSet) SetBool(flag string, value bool) { + m.boolFlags[flag] = value +} + +func (m *mockIsValueSet) Unset(flag string) { + delete(m.setFlags, flag) +} + +// Helper function to create a test server. +func createTestServer(t *testing.T, handler http.HandlerFunc) *httptest.Server { + t.Helper() + server := httptest.NewServer(handler) + return server +} + +// Helper function to close a test server. +func closeTestServer(t *testing.T, server *httptest.Server) { + t.Helper() + server.Close() +} + +// Helper function to reset metadataEndpoints. +func resetMetadataEndpoints(t *testing.T) { + t.Helper() + metadataEndpoints = []string{ + "http://metadata.google.internal/computeMetadata/v1/instance/machine-type", + } +} + +func TestGetMachineType_Success(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns a machine type. + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "zones/us-central1-a/machineTypes/n1-standard-1") + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + + machineType, err := getMachineType(&mockIsValueSet{}) + + require.NoError(t, err) + assert.Equal(t, "n1-standard-1", machineType) +} + +func TestGetMachineType_Failure(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns a non-200 status code. + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + + _, err := getMachineType(&mockIsValueSet{}) + + assert.Error(t, err) +} + +// Add a test wherein machine-type is set by the flag +// and getMachineType returns the same value +func TestGetMachineType_FlagIsSet(t *testing.T) { + resetMetadataEndpoints(t) + // Create a mockIsValueSet where machine-type is set. + isSet := &mockIsValueSet{ + setFlags: map[string]bool{"machine-type": true}, + stringFlags: map[string]string{"machine-type": "test-machine-type"}, + } + + machineType, err := getMachineType(isSet) + + require.NoError(t, err) + assert.Equal(t, "test-machine-type", machineType) +} + +func TestGetMachineType_QuotaError(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns a quota error. + retryCount := 0 + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + retryCount++ + if retryCount < maxRetries { + w.WriteHeader(http.StatusTooManyRequests) + } else { + fmt.Fprint(w, "zones/us-central1-a/machineTypes/n1-standard-1") + } + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + + machineType, err := getMachineType(&mockIsValueSet{}) + + require.NoError(t, err) + assert.Equal(t, "n1-standard-1", machineType) +} +func TestOptimize_DisableAutoConfig(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns a matching machine type. + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "zones/us-central1-a/machineTypes/a3-highgpu-8g") + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{"disable-autoconfig": true}, boolFlags: map[string]bool{"disable-autoconfig": true}} + + _, err := Optimize(cfg, isSet) + + require.NoError(t, err) + assert.False(t, cfg.Write.EnableStreamingWrites) + assert.EqualValues(t, 0, cfg.MetadataCache.NegativeTtlSecs) + assert.EqualValues(t, 0, cfg.MetadataCache.TtlSecs) + assert.EqualValues(t, 0, cfg.MetadataCache.StatCacheMaxSizeMb) + assert.EqualValues(t, 0, cfg.MetadataCache.TypeCacheMaxSizeMb) + assert.False(t, cfg.ImplicitDirs) + assert.EqualValues(t, 0, cfg.FileSystem.RenameDirLimit) +} + +func TestApplyMachineTypeOptimizations_MatchingMachineType(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns a matching machine type. + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "zones/us-central1-a/machineTypes/a3-highgpu-8g") + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + config := defaultOptimizationConfig + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{}} + + optimizedFlags, err := applyMachineTypeOptimizations(&config, cfg, isSet) + + require.NoError(t, err) + assert.NotEmpty(t, optimizedFlags) + assert.EqualValues(t, 0, cfg.MetadataCache.NegativeTtlSecs) + assert.EqualValues(t, -1, cfg.MetadataCache.TtlSecs) + assert.EqualValues(t, 1024, cfg.MetadataCache.StatCacheMaxSizeMb) + assert.EqualValues(t, 128, cfg.MetadataCache.TypeCacheMaxSizeMb) + assert.True(t, cfg.ImplicitDirs) + assert.EqualValues(t, 200000, cfg.FileSystem.RenameDirLimit) +} + +func TestApplyMachineTypeOptimizations_NonMatchingMachineType(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns a non-matching machine type. + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "zones/us-central1-a/machineTypes/n1-standard-1") + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + config := defaultOptimizationConfig + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{}} + + optimizedFlags, err := applyMachineTypeOptimizations(&config, cfg, isSet) + + require.NoError(t, err) + assert.Empty(t, optimizedFlags) + assert.False(t, cfg.Write.EnableStreamingWrites) +} + +func TestApplyMachineTypeOptimizations_UserSetFlag(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns a matching machine type. + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "zones/us-central1-a/machineTypes/a3-highgpu-8g") + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + config := defaultOptimizationConfig + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{"file-system.rename-dir-limit": true}} + // Simulate setting config value by user + cfg.FileSystem.RenameDirLimit = 10000 + + optimizedFlags, err := applyMachineTypeOptimizations(&config, cfg, isSet) + + require.NoError(t, err) + assert.NotEmpty(t, optimizedFlags) + assert.EqualValues(t, 0, cfg.MetadataCache.NegativeTtlSecs) + assert.EqualValues(t, -1, cfg.MetadataCache.TtlSecs) + assert.EqualValues(t, 1024, cfg.MetadataCache.StatCacheMaxSizeMb) + assert.EqualValues(t, 128, cfg.MetadataCache.TypeCacheMaxSizeMb) + assert.True(t, cfg.ImplicitDirs) + assert.EqualValues(t, 10000, cfg.FileSystem.RenameDirLimit) +} + +func TestApplyMachineTypeOptimizations_MissingFlagOverrideSet(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns a machine type with a missing FlagOverrideSet. + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "zones/us-central1-a/machineTypes/a3-highgpu-8g") + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + config := optimizationConfig{ + flagOverrideSets: []flagOverrideSet{}, // Empty FlagOverrideSets. + machineTypes: []machineType{ + { + names: []string{"a3-highgpu-8g"}, + flagOverrideSetName: "high-performance", + }, + }, + } + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{}} + + _, err := applyMachineTypeOptimizations(&config, cfg, isSet) + + require.NoError(t, err) +} + +func TestApplyMachineTypeOptimizations_GetMachineTypeError(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns an error. + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + config := defaultOptimizationConfig + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{}} + + _, err := applyMachineTypeOptimizations(&config, cfg, isSet) + + assert.NoError(t, err) +} + +func TestApplyMachineTypeOptimizations_NoError(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns a matching machine type. + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "zones/us-central1-a/machineTypes/a3-highgpu-8g") + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + config := defaultOptimizationConfig + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{}} + + _, err := applyMachineTypeOptimizations(&config, cfg, isSet) + + assert.NoError(t, err) +} + +func TestSetFlagValue_Bool(t *testing.T) { + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{}} + + err := setFlagValue(cfg, "implicit-dirs", flagOverride{newValue: true}, isSet) + + require.NoError(t, err) + assert.True(t, cfg.ImplicitDirs) +} + +func TestSetFlagValue_String(t *testing.T) { + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{}} + + err := setFlagValue(cfg, "app-name", flagOverride{newValue: "optimal_gcsfuse"}, isSet) + + require.NoError(t, err) + assert.Equal(t, "optimal_gcsfuse", cfg.AppName) +} + +func TestSetFlagValue_Int(t *testing.T) { + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{}} + + err := setFlagValue(cfg, "metadata-cache.stat-cache-max-size-mb", flagOverride{newValue: 1024}, isSet) + + require.NoError(t, err) + assert.EqualValues(t, 1024, cfg.MetadataCache.StatCacheMaxSizeMb) +} + +func TestSetFlagValue_InvalidFlagName(t *testing.T) { + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{}} + + err := setFlagValue(cfg, "invalid-flag", flagOverride{newValue: true}, isSet) + + assert.Error(t, err) +} + +func TestApplyMachineTypeOptimizations_NoMachineTypes(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns a matching machine type. + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "zones/us-central1-a/machineTypes/a3-highgpu-8g") + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + config := optimizationConfig{ + flagOverrideSets: []flagOverrideSet{ + { + name: "high-performance", + overrides: map[string]flagOverride{ + "write.enable-streaming-writes": {newValue: true}, + }, + }, + }, + machineTypes: []machineType{}, + } + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{}} + + _, err := applyMachineTypeOptimizations(&config, cfg, isSet) + + require.NoError(t, err) + // Check that no optimizations were applied as no machine mapping is set. + assert.False(t, cfg.Write.EnableStreamingWrites) +} + +func TestOptimize_Success(t *testing.T) { + resetMetadataEndpoints(t) + // Create a test server that returns a matching machine type. + server := createTestServer(t, func(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "zones/us-central1-a/machineTypes/a3-highgpu-8g") + }) + defer closeTestServer(t, server) + // Override metadataEndpoints for testing. + metadataEndpoints = []string{server.URL} + cfg := &Config{} + isSet := &mockIsValueSet{setFlags: map[string]bool{}} + + optimizedFlags, err := Optimize(cfg, isSet) + + require.NoError(t, err) + assert.True(t, cfg.Write.EnableStreamingWrites) + assert.True(t, isFlagPresent(optimizedFlags, "write.enable-streaming-writes")) + assert.EqualValues(t, 0, cfg.MetadataCache.NegativeTtlSecs) + assert.EqualValues(t, -1, cfg.MetadataCache.TtlSecs) + assert.EqualValues(t, 1024, cfg.MetadataCache.StatCacheMaxSizeMb) + assert.EqualValues(t, 128, cfg.MetadataCache.TypeCacheMaxSizeMb) + assert.True(t, cfg.ImplicitDirs) + assert.EqualValues(t, 200000, cfg.FileSystem.RenameDirLimit) +} diff --git a/cfg/params.yaml b/cfg/params.yaml index c72b456b56..c5cf3bc313 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -71,6 +71,13 @@ usage: "Print debug messages when a mutex is held too long." default: false +- config-path: "disable-autoconfig" + flag-name: "disable-autoconfig" + type: "bool" + usage: "Disable optimizing configuration automatically for a machine" + default: false + hide-flag: true + - config-path: "enable-atomic-rename-object" flag-name: "enable-atomic-rename-object" type: "bool" @@ -492,6 +499,13 @@ usage: "Specifies the logging severity expressed as one of [trace, debug, info, warning, error, off]" default: "info" +- config-path: "machine-type" + flag-name: "machine-type" + type: "string" + usage: "Type of the machine on which gcsfuse is being run e.g. a3-highgpu-4g" + default: "" + hide-flag: true + - config-path: "metadata-cache.deprecated-stat-cache-capacity" flag-name: "stat-cache-capacity" type: "int" From 2bea494cbc104b30b70bfcd15c4eea886cb528d8 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 8 Apr 2025 10:43:08 +0530 Subject: [PATCH 0328/1298] Ignore both .vscode/ folder and .vscode symlink. (#3162) --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8656d8cf65..42f2abb3fc 100644 --- a/.gitignore +++ b/.gitignore @@ -39,7 +39,7 @@ _testmain.go # Editors .idea/ -.vscode/ +.vscode # External folders vendor/ From b91bd9297d1ecd91afa8128208c695be86ceaa4e Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 8 Apr 2025 11:03:45 +0530 Subject: [PATCH 0329/1298] Add troubleshooting steps for Permission Denied while accessing files or directories (#3151) * Add permission denied section in Troubleshooting Guide. * Added Note for security risks * Update docs/troubleshooting.md * Update docs/troubleshooting.md * Update docs/troubleshooting.md Co-authored-by: Kislay Kishore --------- Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Co-authored-by: Kislay Kishore --- docs/troubleshooting.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index e2830e9267..db19a79d8d 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -152,3 +152,19 @@ The Writes in GCSFuse are staged locally before they are uploaded to GCS buckets The path can be configured by using the mount flag [--temp-dir](https://cloud.google.com/storage/docs/cloud-storage-fuse/cli-options) to a path which has the disk space if available. By default, it takes the `/tmp` directory of the machine. (sometimes may be limited depending on the machine ). Alternatively, from [GCSFuse version 2.9.1](https://github.com/GoogleCloudPlatform/gcsfuse/releases/tag/v2.9.1) onwards, writes can be configured with streaming writes feature ( which doesnt involve staging the file locally ) with the help of `--enable-streaming-writes` flag + +### Permission Denied When Accessing a Mounted File or Directory + +By default, GCSFuse assigns file-mode 0644 and dir-mode 0755 for mounted files and directories. As a result, other users (such as third-party clients or the root user) may not have the necessary permissions to access the mounted file system. To resolve this issue, you can modify the permissions using the following options: + +- **Adjust File and Directory Permissions:** +Use the `--file-mode` and `--dir-mode` flags to set the appropriate file and directory permissions when mounting. +- **Allow Access for Other Users:** +To allow users other than the mounting user to access the bucket, use the `-o allow_other` flag during the mount process. Additionally, for this flag to function, the `user_allow_other` option must be enabled in the `/etc/fuse.conf` file, or the gcsfuse command must be run as the root user. + +**Note:** Be aware that allowing access to other users can introduce potential [security risks](https://github.com/torvalds/linux/blob/a33f32244d8550da8b4a26e277ce07d5c6d158b5/Documentation/filesystems/fuse.txt#L218-L310). Therefore, it should be done with caution. + +- **Set User and Group IDs:** +Use the `--uid` and `--gid` flags to specify the correct user and group IDs for access. + +Please note that GCSFuse does not support using `chmod` or similar commands to manage file access. For more detailed information, refer to the [Permissions and Ownership](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/semantics.md#permissions-and-ownership). From 78268853c37dad34f74dd4ef513ef89a8f416885 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 8 Apr 2025 12:15:15 +0530 Subject: [PATCH 0330/1298] Remove OpenCensus support for metrics (#3141) --- cmd/legacy_main.go | 13 +- common/oc_metrics.go | 275 ----------------------------------------- common/otel_metrics.go | 37 ++++++ 3 files changed, 40 insertions(+), 285 deletions(-) delete mode 100644 common/oc_metrics.go diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 66b7d19e65..9ca92d5f4e 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -377,16 +377,9 @@ func Mount(newConfig *cfg.Config, bucketName, mountPoint string) (err error) { var metricExporterShutdownFn common.ShutdownFn metricHandle := common.NewNoopMetrics() if cfg.IsMetricsEnabled(&newConfig.Metrics) { - if newConfig.Metrics.EnableOtel { - metricExporterShutdownFn = monitor.SetupOTelMetricExporters(ctx, newConfig) - if metricHandle, err = common.NewOTelMetrics(); err != nil { - metricHandle = common.NewNoopMetrics() - } - } else { - metricExporterShutdownFn = monitor.SetupOpenCensusExporters(newConfig) - if metricHandle, err = common.NewOCMetrics(); err != nil { - metricHandle = common.NewNoopMetrics() - } + metricExporterShutdownFn = monitor.SetupOTelMetricExporters(ctx, newConfig) + if metricHandle, err = common.NewOTelMetrics(); err != nil { + metricHandle = common.NewNoopMetrics() } } shutdownTracingFn := monitor.SetupTracing(ctx, newConfig) diff --git a/common/oc_metrics.go b/common/oc_metrics.go deleted file mode 100644 index f99274cf71..0000000000 --- a/common/oc_metrics.go +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package common - -import ( - "context" - "fmt" - - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "go.opencensus.io/plugin/ochttp" - "go.opencensus.io/stats" - "go.opencensus.io/stats/view" - "go.opencensus.io/tag" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" -) - -const ( - // IOMethod annotates the event that opens or closes a connection or file. - IOMethod = "io_method" - - // GCSMethod annotates the method called in the GCS client library. - GCSMethod = "gcs_method" - - // FSOp annotates the file system op processed. - FSOp = "fs_op" - - // FSErrCategory reduces the cardinality of FSError by grouping errors together. - FSErrCategory = "fs_error_category" - - // ReadType annotates the read operation with the type - Sequential/Random - ReadType = "read_type" - - // CacheHit annotates the read operation from file cache with true or false. - CacheHit = "cache_hit" -) - -type ocMetrics struct { - // GCS measures - gcsReadBytesCount *stats.Int64Measure - gcsReaderCount *stats.Int64Measure - gcsRequestCount *stats.Int64Measure - gcsRequestLatency *stats.Float64Measure - gcsReadCount *stats.Int64Measure - gcsDownloadBytesCount *stats.Int64Measure - - // Ops measures - opsCount *stats.Int64Measure - opsErrorCount *stats.Int64Measure - opsLatency *stats.Float64Measure - - // File cache measures - fileCacheReadCount *stats.Int64Measure - fileCacheReadBytesCount *stats.Int64Measure - fileCacheReadLatency *stats.Float64Measure -} - -func attrsToTags(attrs []MetricAttr) []tag.Mutator { - mutators := make([]tag.Mutator, 0, len(attrs)) - for _, attr := range attrs { - mutators = append(mutators, tag.Upsert(tag.MustNewKey(attr.Key), attr.Value)) - } - return mutators -} - -func attrsToRecordOption(attrs []MetricAttr) []metric.RecordOption { - otelOptions := make([]metric.RecordOption, 0, len(attrs)) - for _, attr := range attrs { - otelOptions = append(otelOptions, metric.WithAttributes(attribute.String(attr.Key, attr.Value))) - } - return otelOptions -} - -func attrsToAddOption(attrs []MetricAttr) []metric.AddOption { - otelOptions := make([]metric.AddOption, 0, len(attrs)) - for _, attr := range attrs { - otelOptions = append(otelOptions, metric.WithAttributes(attribute.String(attr.Key, attr.Value))) - } - return otelOptions -} - -func (o *ocMetrics) GCSReadBytesCount(ctx context.Context, inc int64) { - recordOCMetric(ctx, o.gcsReadBytesCount, inc, nil, "GCS read bytes count") -} - -func (o *ocMetrics) GCSReaderCount(ctx context.Context, inc int64, attrs []MetricAttr) { - recordOCMetric(ctx, o.gcsReaderCount, inc, attrs, "GCS reader count") -} - -func (o *ocMetrics) GCSRequestCount(ctx context.Context, inc int64, attrs []MetricAttr) { - recordOCMetric(ctx, o.gcsRequestCount, inc, attrs, "GCS request count") -} - -func (o *ocMetrics) GCSRequestLatency(ctx context.Context, value float64, attrs []MetricAttr) { - recordOCLatencyMetric(ctx, o.gcsRequestLatency, value, attrs, "GCS request latency") -} -func (o *ocMetrics) GCSReadCount(ctx context.Context, inc int64, attrs []MetricAttr) { - recordOCMetric(ctx, o.gcsReadCount, inc, attrs, "GCS read count") -} -func (o *ocMetrics) GCSDownloadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { - recordOCMetric(ctx, o.gcsDownloadBytesCount, inc, attrs, "GCS download bytes count") -} - -func (o *ocMetrics) OpsCount(ctx context.Context, inc int64, attrs []MetricAttr) { - recordOCMetric(ctx, o.opsCount, inc, attrs, "file system op count") -} -func (o *ocMetrics) OpsLatency(ctx context.Context, value float64, attrs []MetricAttr) { - recordOCLatencyMetric(ctx, o.opsLatency, value, attrs, "file system op latency") -} -func (o *ocMetrics) OpsErrorCount(ctx context.Context, inc int64, attrs []MetricAttr) { - recordOCMetric(ctx, o.opsErrorCount, inc, attrs, "file system op error count") -} - -func (o *ocMetrics) FileCacheReadCount(ctx context.Context, inc int64, attrs []MetricAttr) { - recordOCMetric(ctx, o.fileCacheReadCount, inc, attrs, "file cache read count") -} -func (o *ocMetrics) FileCacheReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { - recordOCMetric(ctx, o.fileCacheReadBytesCount, inc, attrs, "file cache read bytes count") -} -func (o *ocMetrics) FileCacheReadLatency(ctx context.Context, value float64, attrs []MetricAttr) { - recordOCLatencyMetric(ctx, o.fileCacheReadLatency, value, attrs, "file cache read latency") -} - -func recordOCMetric(ctx context.Context, m *stats.Int64Measure, inc int64, attrs []MetricAttr, metricStr string) { - if err := stats.RecordWithTags( - ctx, - attrsToTags(attrs), - m.M(inc), - ); err != nil { - logger.Errorf("Cannot record %s: %v: %v", metricStr, attrs, err) - } -} - -func recordOCLatencyMetric(ctx context.Context, m *stats.Float64Measure, inc float64, attrs []MetricAttr, metricStr string) { - if err := stats.RecordWithTags( - ctx, - attrsToTags(attrs), - m.M(inc), - ); err != nil { - logger.Errorf("Cannot record %s: %v: %v", metricStr, attrs, err) - } -} - -func NewOCMetrics() (MetricHandle, error) { - gcsReadBytesCount := stats.Int64("gcs/read_bytes_count", "The number of bytes read from GCS objects.", stats.UnitBytes) - gcsReaderCount := stats.Int64("gcs/reader_count", "The number of GCS object readers opened or closed.", stats.UnitDimensionless) - gcsRequestCount := stats.Int64("gcs/request_count", "The number of GCS requests processed.", stats.UnitDimensionless) - gcsRequestLatency := stats.Float64("gcs/request_latency", "The latency of a GCS request.", stats.UnitMilliseconds) - gcsReadCount := stats.Int64("gcs/read_count", "Specifies the number of gcs reads made along with type - Sequential/Random", stats.UnitDimensionless) - gcsDownloadBytesCount := stats.Int64("gcs/download_bytes_count", "The cumulative number of bytes downloaded from GCS along with type - Sequential/Random", stats.UnitBytes) - - opsCount := stats.Int64("fs/ops_count", "The number of ops processed by the file system.", stats.UnitDimensionless) - opsLatency := stats.Float64("fs/ops_latency", "The latency of a file system operation.", "us") - opsErrorCount := stats.Int64("fs/ops_error_count", "The number of errors generated by file system operation.", stats.UnitDimensionless) - - fileCacheReadCount := stats.Int64("file_cache/read_count", "Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false", stats.UnitDimensionless) - fileCacheReadBytesCount := stats.Int64("file_cache/read_bytes_count", "The cumulative number of bytes read from file cache along with read type - Sequential/Random", stats.UnitBytes) - fileCacheReadLatency := stats.Float64("file_cache/read_latency", "Latency of read from file cache along with cache hit - true/false", "us") - // OpenCensus views (aggregated measures) - if err := view.Register( - &view.View{ - Name: "gcs/read_bytes_count", - Measure: gcsReadBytesCount, - Description: "The cumulative number of bytes read from GCS objects.", - Aggregation: view.Sum(), - }, - &view.View{ - Name: "gcs/reader_count", - Measure: gcsReaderCount, - Description: "The cumulative number of GCS object readers opened or closed.", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tag.MustNewKey(IOMethod)}, - }, - &view.View{ - Name: "gcs/request_count", - Measure: gcsRequestCount, - Description: "The cumulative number of GCS requests processed.", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tag.MustNewKey(GCSMethod)}, - }, - &view.View{ - Name: "gcs/request_latencies", - Measure: gcsRequestLatency, - Description: "The cumulative distribution of the GCS request latencies.", - Aggregation: ochttp.DefaultLatencyDistribution, - TagKeys: []tag.Key{tag.MustNewKey(GCSMethod)}, - }, - &view.View{ - Name: "gcs/read_count", - Measure: gcsReadCount, - Description: "Specifies the number of gcs reads made along with type - Sequential/Random", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tag.MustNewKey(ReadType)}, - }, - &view.View{ - Name: "gcs/download_bytes_count", - Measure: gcsDownloadBytesCount, - Description: "The cumulative number of bytes downloaded from GCS along with type - Sequential/Random", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tag.MustNewKey(ReadType)}, - }, - &view.View{ - Name: "fs/ops_count", - Measure: opsCount, - Description: "The cumulative number of ops processed by the file system.", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tag.MustNewKey(FSOp)}, - }, - &view.View{ - Name: "fs/ops_error_count", - Measure: opsErrorCount, - Description: "The cumulative number of errors generated by file system operations", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tag.MustNewKey(FSOp), tag.MustNewKey(FSErrCategory)}, - }, - &view.View{ - Name: "fs/ops_latency", - Measure: opsLatency, - Description: "The cumulative distribution of file system operation latencies", - Aggregation: ochttp.DefaultLatencyDistribution, - TagKeys: []tag.Key{tag.MustNewKey(FSOp)}, - }, - // File cache related metrics - &view.View{ - Name: "file_cache/read_count", - Measure: fileCacheReadCount, - Description: "Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tag.MustNewKey(ReadType), tag.MustNewKey(CacheHit)}, - }, - &view.View{ - Name: "file_cache/read_bytes_count", - Measure: fileCacheReadBytesCount, - Description: "The cumulative number of bytes read from file cache along with read type - Sequential/Random", - Aggregation: view.Sum(), - TagKeys: []tag.Key{tag.MustNewKey(ReadType)}, - }, - &view.View{ - Name: "file_cache/read_latencies", - Measure: fileCacheReadLatency, - Description: "The cumulative distribution of the file cache read latencies along with cache hit - true/false", - Aggregation: ochttp.DefaultLatencyDistribution, - TagKeys: []tag.Key{tag.MustNewKey(CacheHit)}, - }); err != nil { - return nil, fmt.Errorf("failed to register OpenCensus metrics for GCS client library: %w", err) - } - return &ocMetrics{ - gcsReadBytesCount: gcsReadBytesCount, - gcsReaderCount: gcsReaderCount, - gcsRequestCount: gcsRequestCount, - gcsRequestLatency: gcsRequestLatency, - gcsReadCount: gcsReadCount, - gcsDownloadBytesCount: gcsDownloadBytesCount, - - opsCount: opsCount, - opsErrorCount: opsErrorCount, - opsLatency: opsLatency, - - fileCacheReadCount: fileCacheReadCount, - fileCacheReadBytesCount: fileCacheReadBytesCount, - fileCacheReadLatency: fileCacheReadLatency, - }, nil -} diff --git a/common/otel_metrics.go b/common/otel_metrics.go index 6ce4acc2c9..57ed13d6ef 100644 --- a/common/otel_metrics.go +++ b/common/otel_metrics.go @@ -20,9 +20,30 @@ import ( "sync/atomic" "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) +const ( + // IOMethod annotates the event that opens or closes a connection or file. + IOMethod = "io_method" + + // GCSMethod annotates the method called in the GCS client library. + GCSMethod = "gcs_method" + + // FSOp annotates the file system op processed. + FSOp = "fs_op" + + // FSErrCategory reduces the cardinality of FSError by grouping errors together. + FSErrCategory = "fs_error_category" + + // ReadType annotates the read operation with the type - Sequential/Random + ReadType = "read_type" + + // CacheHit annotates the read operation from file cache with true or false. + CacheHit = "cache_hit" +) + var ( fsOpsMeter = otel.Meter("fs_op") gcsMeter = otel.Meter("gcs") @@ -47,6 +68,22 @@ type otelMetrics struct { fileCacheReadLatency metric.Float64Histogram } +func attrsToRecordOption(attrs []MetricAttr) []metric.RecordOption { + otelOptions := make([]metric.RecordOption, 0, len(attrs)) + for _, attr := range attrs { + otelOptions = append(otelOptions, metric.WithAttributes(attribute.String(attr.Key, attr.Value))) + } + return otelOptions +} + +func attrsToAddOption(attrs []MetricAttr) []metric.AddOption { + otelOptions := make([]metric.AddOption, 0, len(attrs)) + for _, attr := range attrs { + otelOptions = append(otelOptions, metric.WithAttributes(attribute.String(attr.Key, attr.Value))) + } + return otelOptions +} + func (o *otelMetrics) GCSReadBytesCount(_ context.Context, inc int64) { o.gcsReadBytesCountAtomic.Add(inc) } From 31d35f4d9082a68318eb53ddc2fc972ffd3196f4 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 8 Apr 2025 07:38:46 +0000 Subject: [PATCH 0331/1298] Remove e2e package log_content and all refs to it (#3163) --- tools/cd_scripts/e2e_test.sh | 1 - .../log_content/file_upload_log_test.go | 97 ------------------- .../log_content/log_content_test.go | 58 ----------- tools/integration_tests/run_e2e_tests.sh | 2 - 4 files changed, 158 deletions(-) delete mode 100644 tools/integration_tests/log_content/file_upload_log_test.go delete mode 100644 tools/integration_tests/log_content/log_content_test.go diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index ab851948ac..48f01d8bf7 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -155,7 +155,6 @@ TEST_DIR_PARALLEL=( "implicit_dir" "interrupt" "operations" - "log_content" "kernel_list_cache" "concurrent_operations" "mount_timeout" diff --git a/tools/integration_tests/log_content/file_upload_log_test.go b/tools/integration_tests/log_content/file_upload_log_test.go deleted file mode 100644 index f2cdf5fd83..0000000000 --- a/tools/integration_tests/log_content/file_upload_log_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package log_content - -import ( - "fmt" - "math" - "os" - "path" - "testing" - - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" -) - -const ( - BigFileSize int64 = 50 * operations.MiB - SmallFileSize int64 = operations.MiB - DirForBigFileUploadLogTest = "dirForBigFileUploadLogTest" - DirForSmallFileUploadLogTest = "dirForSmallFileUploadLogTest" - FileName = "fileName.txt" -) - -func uploadFile(t *testing.T, dirNamePrefix string, fileSize int64) { - testDir, err := os.MkdirTemp(setup.MntDir(), dirNamePrefix+"-*") - if err != nil || testDir == "" { - t.Fatalf("Error in creating test-directory:%v", err) - } - // Clean up. - defer operations.RemoveDir(testDir) - - filePath := path.Join(testDir, FileName) - - // Sequentially write the data in file. - operations.WriteFilesSequentially(t, []string{filePath}, fileSize, fileSize) -} - -func extractRelevantLogsFromLogFile(t *testing.T, logFile string, logFileOffset int64) (logString string) { - // Read the entire log file at once. This can be optimized by reading - // a bunch of lines at once, then eliminating the found - // expected substrings one by one. - bytes, err := os.ReadFile(logFile) - if err != nil { - t.Errorf("Failed in reading logfile %q: %v", logFile, err) - } - completeLogString := string(bytes) - - logString = completeLogString[logFileOffset:] - return -} - -func uploadFileAndReturnLogs(t *testing.T, dirName string, fileSize int64) string { - var err error - var logFileOffset int64 - if logFileOffset, err = operations.SizeOfFile(setup.LogFile()); err != nil { - t.Fatal(err) - } - - uploadFile(t, dirName, fileSize) - return extractRelevantLogsFromLogFile(t, setup.LogFile(), logFileOffset) -} - -func TestBigFileUploadLog(t *testing.T) { - logString := uploadFileAndReturnLogs(t, DirForBigFileUploadLogTest, BigFileSize) - - // Big files (> 16 MiB) are uploaded sequentially in chunks of size - // 16 MiB each and each chunk's successful upload generates a log. - gcsWriteChunkSize := 16 * operations.MiB - numTotalChunksToBeCompleted := int(math.Floor(float64(BigFileSize) / float64(gcsWriteChunkSize))) - var expectedSubstrings []string - for numChunksCompletedSoFar := 1; numChunksCompletedSoFar <= numTotalChunksToBeCompleted; numChunksCompletedSoFar++ { - expectedSubstrings = append(expectedSubstrings, fmt.Sprintf("%d bytes uploaded so far", numChunksCompletedSoFar*gcsWriteChunkSize)) - } - - operations.VerifyExpectedSubstrings(t, logString, expectedSubstrings) -} - -func TestSmallFileUploadLog(t *testing.T) { - logString := uploadFileAndReturnLogs(t, DirForSmallFileUploadLogTest, SmallFileSize) - - // The file being uploaded is too small (<16 MB) for progress logs - // to be printed. - unexpectedLogSubstrings := []string{"bytes uploaded so far"} - operations.VerifyUnexpectedSubstrings(t, logString, unexpectedLogSubstrings) -} diff --git a/tools/integration_tests/log_content/log_content_test.go b/tools/integration_tests/log_content/log_content_test.go deleted file mode 100644 index 6b3984887b..0000000000 --- a/tools/integration_tests/log_content/log_content_test.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Provides integration tests for write large files sequentially and randomly. -package log_content - -import ( - "log" - "os" - "testing" - - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" -) - -func TestMain(m *testing.M) { - setup.ParseSetUpFlags() - - // This test supports the scenario where only a testBucket has been passed. - // If a user passes a mountedDirectory, then the - // test cannot ensure that logs are generated for it, - // and thus does not support that scenario. - setup.ExitWithFailureIfMountedDirectoryIsSetOrTestBucketIsNotSet() - - // Enable tests for testBucket - setup.SetUpTestDirForTestBucketFlag() - - // Set up a log file. - logFile, err := os.CreateTemp(setup.TestDir(), "log_content_test-*.log") - if err != nil || logFile == nil { - log.Fatalf("Failed to create temp-file for logging: %v", err) - } - defer logFile.Close() - setup.SetLogFile(logFile.Name()) - - // No explicit flags need to be set. Only debugs log are to be enabled, - // which are enabled by default by static_mounting.RunTests - // and by the above call to set log-file. - flagsSet := [][]string{{}, {"--client-protocol=grpc"}} - - successCode := 0 - if successCode == 0 { - successCode = static_mounting.RunTests(flagsSet, m) - } - - os.Exit(successCode) -} diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 3b5c973187..5efce39901 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -98,7 +98,6 @@ TEST_DIR_PARALLEL=( "implicit_dir" "interrupt" "operations" - "log_content" "kernel_list_cache" "concurrent_operations" "benchmarking" @@ -129,7 +128,6 @@ TEST_DIR_PARALLEL_FOR_ZB=( "kernel_list_cache" # "list_large_dir" "local_file" - # "log_content" "log_rotation" "monitoring" "mount_timeout" From c9a273daf6028ac90dd18f35e74014763d5aa03c Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 8 Apr 2025 13:44:36 +0530 Subject: [PATCH 0332/1298] cd script changes to run zb tests only based on flag passed (#3110) * cd script changes to run zb tests only * syncing changes required with louhi * nits and improvements * formatting fixes Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> * removing redundant var * nit --------- Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> --- tools/cd_scripts/e2e_test.sh | 258 ++++++++++++++++++++++++----------- 1 file changed, 181 insertions(+), 77 deletions(-) diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 48f01d8bf7..0a4bb07acf 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -18,8 +18,26 @@ set -x # Exit immediately if a command exits with a non-zero status. set -e +# Extract the metadata parameters passed, for which we need the zone of the GCE VM +# on which the tests are supposed to run. +ZONE=$(curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/zone) +echo "Got ZONE=\"${ZONE}\" from metadata server." +# The format for the above extracted zone is projects/{project-id}/zones/{zone}, thus, from this +# need extracted zone name. +ZONE_NAME=$(basename $ZONE) +# This parameter is passed as the GCE VM metadata at the time of creation.(Logic is handled in louhi stage script) +RUN_ON_ZB_ONLY=$(gcloud compute instances describe "$HOSTNAME" --zone="$ZONE_NAME" --format='get(metadata.run-on-zb-only)') +echo "RUN_ON_ZB_ONLY flag set to : \"${RUN_ON_ZB_ONLY}\"" + +# Logging the tests being run on the active GCE VM +if [[ "$RUN_ON_ZB_ONLY" == "true" ]]; then + echo "Running integration tests for Zonal bucket only..." +else + echo "Running integration tests for non-zonal buckets only..." +fi + #details.txt file contains the release version and commit hash of the current release. -gsutil cp gs://gcsfuse-release-packages/version-detail/details.txt . +gcloud storage cp gs://gcsfuse-release-packages/version-detail/details.txt . # Writing VM instance name to details.txt (Format: release-test-) curl http://metadata.google.internal/computeMetadata/v1/instance/name -H "Metadata-Flavor: Google" >> details.txt @@ -40,14 +58,28 @@ set -e # Print commands and their arguments as they are executed. set -x +# Export the RUN_ON_ZB_ONLY variable so that it is available in the environment of the 'starterscriptuser' user. +# Since we are running the subsequent script as 'starterscriptuser' using sudo, the environment of 'starterscriptuser' +# would not automatically have access to the environment variables set by the original user (i.e. $RUN_ON_ZB_ONLY). +# By exporting this variable, we ensure that the value of RUN_ON_ZB_ONLY is passed into the 'starterscriptuser' script +# and can be used for conditional logic or decisions within that script. +export RUN_ON_ZB_ONLY='$RUN_ON_ZB_ONLY' + #Copy details.txt to starterscriptuser home directory and create logs.txt cd ~/ cp /details.txt . touch logs.txt touch logs-hns.txt +touch logs-zonal.txt +LOG_FILE='~/logs.txt' + +if [[ "$RUN_ON_ZB_ONLY" == "true" ]]; then + LOG_FILE='~/logs-zonal.txt' +fi + +echo "User: $USER" &>> ${LOG_FILE} +echo "Current Working Directory: $(pwd)" &>> ${LOG_FILE} -echo User: $USER &>> ~/logs.txt -echo Current Working Directory: $(pwd) &>> ~/logs.txt # Based on the os type in detail.txt, run the following commands for setup @@ -63,8 +95,8 @@ then sudo apt install -y fuse # download and install gcsfuse deb package - gsutil cp gs://gcsfuse-release-packages/v$(sed -n 1p details.txt)/gcsfuse_$(sed -n 1p details.txt)_${architecture}.deb . - sudo dpkg -i gcsfuse_$(sed -n 1p details.txt)_${architecture}.deb |& tee -a ~/logs.txt + gcloud storage cp gs://gcsfuse-release-packages/v$(sed -n 1p details.txt)/gcsfuse_$(sed -n 1p details.txt)_${architecture}.deb . + sudo dpkg -i gcsfuse_$(sed -n 1p details.txt)_${architecture}.deb |& tee -a ${LOG_FILE} # install wget sudo apt install -y wget @@ -98,7 +130,7 @@ else sudo yum -y install fuse #download and install gcsfuse rpm package - gsutil cp gs://gcsfuse-release-packages/v$(sed -n 1p details.txt)/gcsfuse-$(sed -n 1p details.txt)-1.${uname}.rpm . + gcloud storage cp gs://gcsfuse-release-packages/v$(sed -n 1p details.txt)/gcsfuse-$(sed -n 1p details.txt)-1.${uname}.rpm . sudo yum -y localinstall gcsfuse-$(sed -n 1p details.txt)-1.${uname}.rpm #install wget @@ -116,12 +148,12 @@ wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-${architecture}.tar.gz sudo tar -C /usr/local -xzf go_tar.tar.gz export PATH=${PATH}:/usr/local/go/bin #Write gcsfuse and go version to log file -gcsfuse --version |& tee -a ~/logs.txt -go version |& tee -a ~/logs.txt +gcsfuse --version |& tee -a ${LOG_FILE} +go version |& tee -a ${LOG_FILE} # Clone and checkout gcsfuse repo export PATH=${PATH}:/usr/local/go/bin -git clone https://github.com/googlecloudplatform/gcsfuse |& tee -a ~/logs.txt +git clone https://github.com/googlecloudplatform/gcsfuse |& tee -a ${LOG_FILE} cd gcsfuse # Installation of crcmod is working through pip only on rhel and centos. @@ -136,7 +168,7 @@ then pip3 install --require-hashes -r tools/cd_scripts/requirements.txt --user fi -git checkout $(sed -n 2p ~/details.txt) |& tee -a ~/logs.txt +git checkout $(sed -n 2p ~/details.txt) |& tee -a ${LOG_FILE} #run tests with testbucket flag set +e @@ -172,6 +204,37 @@ TEST_DIR_NON_PARALLEL=( "list_large_dir" ) +# For Zonal buckets : Test directory arrays +TEST_DIR_PARALLEL_ZONAL=( + gzip + interrupt + kernel_list_cache + local_file + log_rotation + mounting + mount_timeout + negative_stat_cache + read_cache + read_large_files + rename_dir_limit + stale_handle + write_large_files + #concurrent_operations + #explicit_dir + #implicit_dir + #list_large_dir + #log_content + #operations + #streaming_writes +) + +#For Zonal Buckets : These tests never become parallel as they are changing bucket permissions. +TEST_DIR_NON_PARALLEL_ZONAL=( + "managed_folders" + "readonly" + "readonly_creds" +) + # Create a temporary file to store the log file name. TEST_LOGS_FILE=$(mktemp) @@ -181,15 +244,16 @@ function run_non_parallel_tests() { local exit_code=0 local -n test_array=$1 local BUCKET_NAME=$2 + local zonal=$3 for test_dir_np in "${test_array[@]}" do test_path_non_parallel="./tools/integration_tests/$test_dir_np" - # To make it clear whether tests are running on a flat or HNS bucket, We kept the log file naming + # To make it clear whether tests are running on a flat or HNS or zonal bucket, We kept the log file naming # convention to include the bucket name as a suffix (e.g., package_name_bucket_name). local log_file="/tmp/${test_dir_np}_${BUCKET_NAME}.log" echo $log_file >> $TEST_LOGS_FILE # Executing integration tests - GODEBUG=asyncpreemptoff=1 go test $test_path_non_parallel -p 1 --integrationTest -v --testbucket=$BUCKET_NAME --testInstalledPackage=true -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 + GODEBUG=asyncpreemptoff=1 go test $test_path_non_parallel -p 1 --zonal=${zonal} --integrationTest -v --testbucket=$BUCKET_NAME --testInstalledPackage=true -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 exit_code_non_parallel=$? if [ $exit_code_non_parallel != 0 ]; then exit_code=$exit_code_non_parallel @@ -202,24 +266,18 @@ function run_parallel_tests() { local exit_code=0 local -n test_array=$1 local BUCKET_NAME=$2 + local zonal=$3 local pids=() - local benchmark_flags="" for test_dir_p in "${test_array[@]}" do - # Unlike regular tests,benchmark tests are not executed by default when using go test . - # The -bench flag yells go test to run the benchmark tests and report their results by - # enabling the benchmarking framework. - if [ $test_dir_p == "benchmarking" ]; then - benchmark_flags="-bench=." - fi test_path_parallel="./tools/integration_tests/$test_dir_p" # To make it clear whether tests are running on a flat or HNS bucket, We kept the log file naming # convention to include the bucket name as a suffix (e.g., package_name_bucket_name). local log_file="/tmp/${test_dir_p}_${BUCKET_NAME}.log" echo $log_file >> $TEST_LOGS_FILE # Executing integration tests - GODEBUG=asyncpreemptoff=1 go test $test_path_parallel $benchmark_flags -p 1 --integrationTest -v --testbucket=$BUCKET_NAME --testInstalledPackage=true -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & + GODEBUG=asyncpreemptoff=1 go test $test_path_parallel -p 1 --zonal=${zonal} --integrationTest -v --testbucket=$BUCKET_NAME --testInstalledPackage=true -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & pid=$! # Store the PID of the background process pids+=("$pid") # Optionally add the PID to an array for later done @@ -242,11 +300,11 @@ function run_e2e_tests_for_flat_bucket() { echo "Flat Bucket name to run tests parallelly: "$flat_bucket_name_parallel echo "Running parallel tests..." - run_parallel_tests TEST_DIR_PARALLEL "$flat_bucket_name_parallel" & + run_parallel_tests TEST_DIR_PARALLEL "$flat_bucket_name_parallel" false & parallel_tests_pid=$! echo "Running non parallel tests ..." - run_non_parallel_tests TEST_DIR_NON_PARALLEL "$flat_bucket_name_non_parallel" & + run_non_parallel_tests TEST_DIR_NON_PARALLEL "$flat_bucket_name_non_parallel" false & non_parallel_tests_pid=$! # Wait for all tests to complete. @@ -255,11 +313,9 @@ function run_e2e_tests_for_flat_bucket() { wait $non_parallel_tests_pid non_parallel_tests_exit_code=$? - if [ $non_parallel_tests_exit_code != 0 ] || [ $parallel_tests_exit_code != 0 ]; - then + if [ $non_parallel_tests_exit_code != 0 ] || [ $parallel_tests_exit_code != 0 ]; then return 1 fi - return 0 } function run_e2e_tests_for_hns_bucket(){ @@ -270,9 +326,9 @@ function run_e2e_tests_for_hns_bucket(){ echo "HNS Bucket name to run tests parallelly: "$hns_bucket_name_parallel echo "Running tests for HNS bucket" - run_parallel_tests TEST_DIR_PARALLEL "$hns_bucket_name_parallel" & + run_parallel_tests TEST_DIR_PARALLEL "$hns_bucket_name_parallel" false & parallel_tests_hns_group_pid=$! - run_non_parallel_tests TEST_DIR_NON_PARALLEL "$hns_bucket_name_non_parallel" & + run_non_parallel_tests TEST_DIR_NON_PARALLEL "$hns_bucket_name_non_parallel" false & non_parallel_tests_hns_group_pid=$! # Wait for all tests to complete. @@ -281,13 +337,36 @@ function run_e2e_tests_for_hns_bucket(){ wait $non_parallel_tests_hns_group_pid non_parallel_tests_hns_group_exit_code=$? - if [ $parallel_tests_hns_group_exit_code != 0 ] || [ $non_parallel_tests_hns_group_exit_code != 0 ]; - then + if [ $parallel_tests_hns_group_exit_code != 0 ] || [ $non_parallel_tests_hns_group_exit_code != 0 ]; then return 1 fi - return 0 } +function run_e2e_tests_for_zonal_bucket(){ + zonal_bucket_name_non_parallel=$(sed -n 3p ~/details.txt)-zonal + echo "Zonal Bucket name to run tests sequentially: "$zonal_bucket_name_non_parallel + + zonal_bucket_name_parallel=$(sed -n 3p ~/details.txt)-zonal-parallel + echo "Zonal Bucket name to run tests parallely: "$zonal_bucket_name_parallel + + echo "Running tests for Zonal bucket" + run_parallel_tests TEST_DIR_PARALLEL_ZONAL "$zonal_bucket_name_parallel" true & + parallel_tests_zonal_group_pid=$! + run_non_parallel_tests TEST_DIR_NON_PARALLEL_ZONAL "$zonal_bucket_name_non_parallel" true & + non_parallel_tests_zonal_group_pid=$! + + # Wait for all tests to complete. + wait $parallel_tests_zonal_group_pid + parallel_tests_zonal_group_exit_code=$? + wait $non_parallel_tests_zonal_group_pid + non_parallel_tests_zonal_group_exit_code=$? + + if [ $parallel_tests_zonal_group_exit_code != 0 ] || [ $non_parallel_tests_zonal_group_exit_code != 0 ]; then + return 1 + fi +} + + function run_e2e_tests_for_emulator() { ./tools/integration_tests/emulator_tests/emulator_tests.sh true > ~/logs-emulator.txt } @@ -301,6 +380,8 @@ function gather_test_logs() { if [ -f "$log_file" ]; then if [[ "$test_log_file" == *"hns"* ]]; then output_file="$HOME/logs-hns.txt" + elif [[ "$test_log_file" == *"zonal"* ]]; then + output_file="$HOME/logs-zonal.txt" else output_file="$HOME/logs.txt" fi @@ -312,53 +393,76 @@ function gather_test_logs() { done } -echo "Running integration tests for HNS bucket..." -run_e2e_tests_for_hns_bucket & -e2e_tests_hns_bucket_pid=$! - -echo "Running integration tests for FLAT bucket..." -run_e2e_tests_for_flat_bucket & -e2e_tests_flat_bucket_pid=$! - -run_e2e_tests_for_emulator & -e2e_tests_emulator_pid=$! - -wait $e2e_tests_emulator_pid -e2e_tests_emulator_status=$? - -wait $e2e_tests_flat_bucket_pid -e2e_tests_flat_bucket_status=$? - -wait $e2e_tests_hns_bucket_pid -e2e_tests_hns_bucket_status=$? - -gather_test_logs - -if [ $e2e_tests_flat_bucket_status != 0 ] -then - echo "Test failures detected in FLAT bucket." &>> ~/logs.txt -else - touch success.txt - gsutil cp success.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ -fi -gsutil cp ~/logs.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ - -if [ $e2e_tests_hns_bucket_status != 0 ]; -then - echo "Test failures detected in HNS bucket." &>> ~/logs-hns.txt -else - touch success-hns.txt - gsutil cp success-hns.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ -fi -gsutil cp ~/logs-hns.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ - -if [ $e2e_tests_emulator_status != 0 ]; -then - echo "Test failures detected in emulator based tests." &>> ~/logs-emulator.txt +if [[ "$RUN_ON_ZB_ONLY" == "true" ]]; then + echo "Started integration tests for Zonal bucket ..." + run_e2e_tests_for_zonal_bucket & + e2e_tests_zonal_bucket_pid=$! + + wait $e2e_tests_zonal_bucket_pid + e2e_tests_zonal_bucket_status=$? + + gather_test_logs + + if [ $e2e_tests_zonal_bucket_status != 0 ]; + then + echo "Test failures detected in Zonal bucket." &>> ~/logs-zonal.txt + else + touch success-zonal.txt + gcloud storage cp success-zonal.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + fi + + gcloud storage cp ~/logs-zonal.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ else - touch success-emulator.txt - gsutil cp success-emulator.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + echo "Started integration tests for HNS bucket..." + run_e2e_tests_for_hns_bucket & + e2e_tests_hns_bucket_pid=$! + + echo "Started integration tests for FLAT bucket..." + run_e2e_tests_for_flat_bucket & + e2e_tests_flat_bucket_pid=$! + + echo "Started emulator tests..." + run_e2e_tests_for_emulator & + e2e_tests_emulator_pid=$! + + wait $e2e_tests_emulator_pid + e2e_tests_emulator_status=$? + + wait $e2e_tests_flat_bucket_pid + e2e_tests_flat_bucket_status=$? + + wait $e2e_tests_hns_bucket_pid + e2e_tests_hns_bucket_status=$? + + gather_test_logs + + if [ $e2e_tests_flat_bucket_status != 0 ] + then + echo "Test failures detected in FLAT bucket." &>> ~/logs.txt + else + touch success.txt + gcloud storage cp success.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + fi + gcloud storage cp ~/logs.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + + if [ $e2e_tests_hns_bucket_status != 0 ]; + then + echo "Test failures detected in HNS bucket." &>> ~/logs-hns.txt + else + touch success-hns.txt + gcloud storage cp success-hns.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + fi + gcloud storage cp ~/logs-hns.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + + if [ $e2e_tests_emulator_status != 0 ]; + then + echo "Test failures detected in emulator based tests." &>> ~/logs-emulator.txt + else + touch success-emulator.txt + gcloud storage cp success-emulator.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + fi + + gcloud storage cp ~/logs-emulator.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ fi -gsutil cp ~/logs-emulator.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ -' +' From b8ee80f380f240839dcca180667041802367f46c Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 8 Apr 2025 14:23:21 +0530 Subject: [PATCH 0333/1298] Remove enable-otel flag (#3164) * Remove enable-otel flag --- cfg/config.go | 12 ---- cfg/params.yaml | 7 --- cmd/config_validation_test.go | 2 - cmd/root_test.go | 55 ++----------------- .../metrics_config/enable_otel_false.yml | 2 - .../metrics_config/enable_otel_true.yml | 2 - .../integration_tests/monitoring/prom_test.go | 13 +---- 7 files changed, 5 insertions(+), 88 deletions(-) delete mode 100644 cmd/testdata/metrics_config/enable_otel_false.yml delete mode 100644 cmd/testdata/metrics_config/enable_otel_true.yml diff --git a/cfg/config.go b/cfg/config.go index fbef55bc2c..1baa5a9488 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -216,8 +216,6 @@ type MetadataCacheConfig struct { type MetricsConfig struct { CloudMetricsExportIntervalSecs int64 `yaml:"cloud-metrics-export-interval-secs"` - EnableOtel bool `yaml:"enable-otel"` - PrometheusPort int64 `yaml:"prometheus-port"` StackdriverExportInterval time.Duration `yaml:"stackdriver-export-interval"` @@ -355,12 +353,6 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-nonexistent-type-cache", "", false, "Once set, if an inode is not found in GCS, a type cache entry with type NonexistentType will be created. This also means new file/dir created might not be seen. For example, if this flag is set, and metadata-cache-ttl-secs is set, then if we create the same file/node in the meantime using the same mount, since we are not refreshing the cache, it will still return nil.") - flagSet.BoolP("enable-otel", "", true, "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus.") - - if err := flagSet.MarkHidden("enable-otel"); err != nil { - return err - } - flagSet.BoolP("enable-read-stall-retry", "", false, "To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past.") if err := flagSet.MarkHidden("enable-read-stall-retry"); err != nil { @@ -688,10 +680,6 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } - if err := v.BindPFlag("metrics.enable-otel", flagSet.Lookup("enable-otel")); err != nil { - return err - } - if err := v.BindPFlag("gcs-retries.read-stall.enable", flagSet.Lookup("enable-read-stall-retry")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index c5cf3bc313..3111a30eed 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -613,13 +613,6 @@ usage: "Specifies the interval at which the metrics are uploaded to cloud monitoring" default: 0 -- config-path: "metrics.enable-otel" - flag-name: "enable-otel" - type: "bool" - usage: "Specifies whether to use OpenTelemetry for capturing and exporting metrics. If false, use OpenCensus." - default: true - hide-flag: true - - config-path: "metrics.prometheus-port" flag-name: "prometheus-port" type: "int" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index d36ef83510..ff57147478 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -806,7 +806,6 @@ func TestValidateConfigFile_MetricsConfigSuccessful(t *testing.T) { StackdriverExportInterval: 0, CloudMetricsExportIntervalSecs: 0, PrometheusPort: 0, - EnableOtel: true, }, }, { @@ -814,7 +813,6 @@ func TestValidateConfigFile_MetricsConfigSuccessful(t *testing.T) { configFile: "testdata/valid_config.yaml", expectedConfig: &cfg.MetricsConfig{ CloudMetricsExportIntervalSecs: 10, - EnableOtel: true, }, }, } diff --git a/cmd/root_test.go b/cmd/root_test.go index 9c41519914..00e63ddacf 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -908,40 +908,11 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { args []string expected *cfg.MetricsConfig }{ - { - name: "default", - args: []string{"gcsfuse", "abc", "pqr"}, - expected: &cfg.MetricsConfig{ - EnableOtel: true, - }, - }, - { - name: "enable_otel_normal", - args: []string{"gcsfuse", "--enable-otel", "abc", "pqr"}, - expected: &cfg.MetricsConfig{ - EnableOtel: true, - }, - }, - { - name: "enable_otel_false", - args: []string{"gcsfuse", "--enable-otel=false", "abc", "pqr"}, - expected: &cfg.MetricsConfig{ - EnableOtel: false, - }, - }, - { - name: "enable_otel_false", - args: []string{"gcsfuse", "--enable-otel=true", "abc", "pqr"}, - expected: &cfg.MetricsConfig{ - EnableOtel: true, - }, - }, { name: "cloud-metrics-export-interval-secs-positive", args: []string{"gcsfuse", "--cloud-metrics-export-interval-secs=10", "abc", "pqr"}, expected: &cfg.MetricsConfig{ CloudMetricsExportIntervalSecs: 10, - EnableOtel: true, }, }, { @@ -950,7 +921,6 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { expected: &cfg.MetricsConfig{ CloudMetricsExportIntervalSecs: 10 * 3600, StackdriverExportInterval: time.Duration(10) * time.Hour, - EnableOtel: true, }, }, } @@ -980,30 +950,14 @@ func TestArgsParsing_MetricsViewConfig(t *testing.T) { expected *cfg.MetricsConfig }{ { - name: "default", - cfgFile: "empty.yml", - expected: &cfg.MetricsConfig{ - EnableOtel: true, - }, - }, - { - name: "enable_otel_true", - cfgFile: "enable_otel_true.yml", - expected: &cfg.MetricsConfig{ - EnableOtel: true, - }, - }, - { - name: "enable_otel_false", - cfgFile: "enable_otel_false.yml", - expected: &cfg.MetricsConfig{ - EnableOtel: false, - }, + name: "default", + cfgFile: "empty.yml", + expected: &cfg.MetricsConfig{}, }, { name: "cloud-metrics-export-interval-secs-positive", cfgFile: "metrics_export_interval_positive.yml", - expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 100, EnableOtel: true}, + expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 100}, }, { name: "stackdriver-export-interval-positive", @@ -1011,7 +965,6 @@ func TestArgsParsing_MetricsViewConfig(t *testing.T) { expected: &cfg.MetricsConfig{ CloudMetricsExportIntervalSecs: 12 * 3600, StackdriverExportInterval: 12 * time.Hour, - EnableOtel: true, }, }, } diff --git a/cmd/testdata/metrics_config/enable_otel_false.yml b/cmd/testdata/metrics_config/enable_otel_false.yml deleted file mode 100644 index 60a9ec6010..0000000000 --- a/cmd/testdata/metrics_config/enable_otel_false.yml +++ /dev/null @@ -1,2 +0,0 @@ -metrics: - enable-otel: false diff --git a/cmd/testdata/metrics_config/enable_otel_true.yml b/cmd/testdata/metrics_config/enable_otel_true.yml deleted file mode 100644 index e5b1b20cce..0000000000 --- a/cmd/testdata/metrics_config/enable_otel_true.yml +++ /dev/null @@ -1,2 +0,0 @@ -metrics: - enable-otel: true diff --git a/tools/integration_tests/monitoring/prom_test.go b/tools/integration_tests/monitoring/prom_test.go index e01a7be894..2ce3fee8d3 100644 --- a/tools/integration_tests/monitoring/prom_test.go +++ b/tools/integration_tests/monitoring/prom_test.go @@ -78,8 +78,6 @@ type PromTest struct { // A temporary directory into which a file system may be mounted. Removed in // TearDown. mountPoint string - - enableOTEL bool } // isHNSTestRun returns true if the bucket is an HNS bucket. @@ -128,11 +126,6 @@ func (testSuite *PromTest) mount(bucketName string) error { testSuite.T().Cleanup(func() { _ = os.RemoveAll(cacheDir) }) flags := []string{fmt.Sprintf("--prometheus-port=%d", prometheusPort), "--cache-dir", cacheDir} - if testSuite.enableOTEL { - flags = append(flags, "--enable-otel=true") - } else { - flags = append(flags, "--enable-otel=false") - } args := append(flags, bucketName, testSuite.mountPoint) if err := mounting.MountGcsfuse(testSuite.gcsfusePath, args); err != nil { @@ -254,10 +247,6 @@ func (testSuite *PromTest) TestReadMetrics() { //TODO: file_cache_read_bytes_count should be added once with waitForDownload is true same as sequential for default pd, } -func TestPromOCSuite(t *testing.T) { - suite.Run(t, &PromTest{enableOTEL: false}) -} - func TestPromOTELSuite(t *testing.T) { - suite.Run(t, &PromTest{enableOTEL: true}) + suite.Run(t, new(PromTest)) } From 863d8cf8ddfaad046bc7641a27024d2efa61b368 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 8 Apr 2025 14:28:14 +0530 Subject: [PATCH 0334/1298] Exclude config.go from code coverage computation (#3165) * Exclude config.go from code coverage computation. The file is auto-generated and can be skipped from code coverage computation. Also exclude perfmetrics --- .github/.codecov.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/.codecov.yml b/.github/.codecov.yml index cf6445d4fc..0fc03a30ef 100644 --- a/.github/.codecov.yml +++ b/.github/.codecov.yml @@ -1,6 +1,22 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + ignore: - "tools" - "benchmarks" + - "perfmetrics" + - "cfg/config.go" coverage: round: down precision: 2 From 36c75243b6b00f59d4d40134fa04d8c831dd5dcb Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 9 Apr 2025 08:54:37 +0530 Subject: [PATCH 0335/1298] [Random Reader Refactoring] Initialise interfaces and req-resp struct (#3154) * initilize interface and req resp struct * Fix copyright year * Fix lint * update comment * Small refactoring * Small refactoring * review comment * small fix * small fix in warning * small fix in warning --- internal/gcsx/reader.go | 80 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 internal/gcsx/reader.go diff --git a/internal/gcsx/reader.go b/internal/gcsx/reader.go new file mode 100644 index 0000000000..21faebf0b4 --- /dev/null +++ b/internal/gcsx/reader.go @@ -0,0 +1,80 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "context" + "errors" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" +) + +// FallbackToAnotherReader is returned when data could not be retrieved +// from the current reader, indicating that the caller should attempt to fall back +// to an alternative reader. +var FallbackToAnotherReader = errors.New("fallback to another reader is required") + +// GCSReaderReq represents the request parameters needed to read a data from a GCS object. +type GCSReaderReq struct { + // Buffer is provided by jacobsa/fuse and should be filled with data from the object. + Buffer []byte + + // Offset specifies the starting position in the object from where data should be read. + Offset int64 + + // This determines GCS range request. + EndOffset int64 +} + +// ObjectRead represents the response returned as part of a ReadAt call. +// It includes the actual data read and its size. +type ObjectRead struct { + // DataBuf contains the bytes read from the object. + DataBuf []byte + + // Size indicates how many bytes were read into DataBuf. + Size int +} + +type Reader interface { + // CheckInvariants performs internal consistency checks on the reader state. + CheckInvariants() + + // ReadAt reads data into the provided byte slice starting from the specified offset. + // It returns an ObjectRead containing the data read and the number of bytes read. + // To indicate that the operation should be handled by an alternative reader, return + // the error FallbackToAnotherReader. + ReadAt(ctx context.Context, p []byte, offset int64) (ObjectRead, error) + + // Destroy is called to release any resources held by the reader. + Destroy() +} + +// ReadManager is generally used in higher-level components that need access to object metadata. +// File handle will contain a ReadManager instance and will handle read operations. +type ReadManager interface { + Reader + + // Object returns the underlying GCS object metadata associated with the reader. + Object() *gcs.MinObject +} + +// GCSReader defines an interface for reading data from a GCS object. +// This interface is intended for lower-level interactions with GCS readers. +type GCSReader interface { + // ReadAt reads data into the provided request buffer, starting from the specified offset and ending at the specified end offset. + // It returns an ObjectRead response containing the data read and any error encountered. + ReadAt(ctx context.Context, req *GCSReaderReq) (ObjectRead, error) +} From 5b9e614ec2f92f4b5be454baed991120e9318239 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 9 Apr 2025 12:17:00 +0530 Subject: [PATCH 0336/1298] Remove OpenCensus exporters (#3167) --- cfg/config.go | 12 - cfg/params.yaml | 8 - go.mod | 11 - go.sum | 431 ----------------------------------- internal/monitor/exporter.go | 208 ----------------- 5 files changed, 670 deletions(-) delete mode 100644 internal/monitor/exporter.go diff --git a/cfg/config.go b/cfg/config.go index 1baa5a9488..2df3b5b19e 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -222,8 +222,6 @@ type MetricsConfig struct { } type MonitoringConfig struct { - ExperimentalOpentelemetryCollectorAddress string `yaml:"experimental-opentelemetry-collector-address"` - ExperimentalTracingMode string `yaml:"experimental-tracing-mode"` ExperimentalTracingSamplingRatio float64 `yaml:"experimental-tracing-sampling-ratio"` @@ -379,12 +377,6 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.StringP("experimental-opentelemetry-collector-address", "", "", "Experimental: Export metrics to the OpenTelemetry collector at this address.") - - if err := flagSet.MarkDeprecated("experimental-opentelemetry-collector-address", "Experimental flag: could be dropped even in a minor release."); err != nil { - return err - } - flagSet.StringP("experimental-tracing-mode", "", "", "Experimental: specify tracing mode") if err := flagSet.MarkHidden("experimental-tracing-mode"); err != nil { @@ -700,10 +692,6 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } - if err := v.BindPFlag("monitoring.experimental-opentelemetry-collector-address", flagSet.Lookup("experimental-opentelemetry-collector-address")); err != nil { - return err - } - if err := v.BindPFlag("monitoring.experimental-tracing-mode", flagSet.Lookup("experimental-tracing-mode")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 3111a30eed..33ca097e95 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -629,14 +629,6 @@ deprecated: true deprecation-warning: "Please use --cloud-metrics-export-interval-secs instead." -- config-path: "monitoring.experimental-opentelemetry-collector-address" - flag-name: "experimental-opentelemetry-collector-address" - type: "string" - usage: "Experimental: Export metrics to the OpenTelemetry collector at this address." - default: "" - deprecated: true - deprecation-warning: "Experimental flag: could be dropped even in a minor release." - - config-path: "monitoring.experimental-tracing-mode" flag-name: "experimental-tracing-mode" type: "string" diff --git a/go.mod b/go.mod index 36a242c128..b03fe84a15 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,6 @@ require ( cloud.google.com/go/iam v1.4.2 cloud.google.com/go/secretmanager v1.14.6 cloud.google.com/go/storage v1.51.0 - contrib.go.opencensus.io/exporter/ocagent v0.7.0 - contrib.go.opencensus.io/exporter/prometheus v0.4.2 - contrib.go.opencensus.io/exporter/stackdriver v0.13.14 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 github.com/fsouza/fake-gcs-server v1.52.2 @@ -65,9 +62,7 @@ require ( cloud.google.com/go/trace v1.11.4 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect - github.com/aws/aws-sdk-go v1.55.6 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -78,15 +73,12 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect @@ -94,8 +86,6 @@ require ( github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/procfs v0.15.1 // indirect - github.com/prometheus/prometheus v0.302.1 // indirect - github.com/prometheus/statsd_exporter v0.28.0 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.12.0 // indirect @@ -110,5 +100,4 @@ require ( google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 2cb90e1c2f..ccfeff083a 100644 --- a/go.sum +++ b/go.sum @@ -1,36 +1,14 @@ cel.dev/expr v0.22.0 h1:+hFFhLPmquBImfs1BiN2PZmkr5ASse2ZOuaxIs9e4R8= cel.dev/expr v0.22.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.119.0 h1:tw7OjErMzJKbbjaEHkrt60KQrK5Wus/boCZ7tm5/RNE= cloud.google.com/go v0.119.0/go.mod h1:fwB8QLzTcNevxqi8dcpR+hoMIs3jBherGS9VUBDAW08= cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/iam v1.4.2 h1:4AckGYAYsowXeHzsn/LCKWIwSWLkdb0eGjH8wWkd27Q= cloud.google.com/go/iam v1.4.2/go.mod h1:REGlrt8vSlh4dfCJfSEcNjLGq75wW75c5aU3FLOYq34= cloud.google.com/go/kms v1.21.0 h1:x3EeWKuYwdlo2HLse/876ZrKjk2L5r7Uexfm8+p6mSI= @@ -41,32 +19,15 @@ cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3 cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY= cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM= cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/pubsub v1.47.0 h1:Ou2Qu4INnf7ykrFjGv2ntFOjVo8Nloh/+OffF4mUu9w= cloud.google.com/go/pubsub v1.47.0/go.mod h1:LaENesmga+2u0nDtLkIOILskxsfvn/BXX9Ak1NFxOs8= cloud.google.com/go/secretmanager v1.14.6 h1:/ooktIMSORaWk9gm3vf8+Mg+zSrUplJFKBztP993oL0= cloud.google.com/go/secretmanager v1.14.6/go.mod h1:0OWeM3qpJ2n71MGgNfKsgjC/9LfVTcUqXFUlGxo5PzY= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.51.0 h1:ZVZ11zCiD7b3k+cH5lQs/qcNaoSz3U9I0jgwVzqDlCw= cloud.google.com/go/storage v1.51.0/go.mod h1:YEJfu/Ki3i5oHC/7jyTgsGZwdQ8P9hqMqvpi5kRKGgc= cloud.google.com/go/trace v1.11.4 h1:LKlhVyX6I4+heP31sWvERSKZZ9cPPEZumt7b4SKVK18= cloud.google.com/go/trace v1.11.4/go.mod h1:lCSHzSPZC1TPwto7zhaRt3KtGYsXFyaErPQ18AUUeUE= -contrib.go.opencensus.io/exporter/ocagent v0.7.0 h1:BEfdCTXfMV30tLZD8c9n64V/tIZX5+9sXiuFLnrr1k8= -contrib.go.opencensus.io/exporter/ocagent v0.7.0/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= -contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= -contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= -contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4= -contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= @@ -77,29 +38,11 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= -github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= @@ -131,93 +74,43 @@ github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/ github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsouza/fake-gcs-server v1.52.2 h1:j6ne83nqHrlX5EEor7WWVIKdBsztGtwJ1J2mL+k+iio= github.com/fsouza/fake-gcs-server v1.52.2/go.mod h1:47HKyIkz6oLTes1R8vEaHLwXfzYsGfmDUk1ViHHAUsA= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= @@ -227,20 +120,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g= github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= @@ -259,59 +144,28 @@ github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3 h1:+gHfvQxomE6fI4 github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3/go.mod h1:mPvulh9VKXvo+yOlrD4VYOOYuLdZJ36wa/5QIrtXvWs= github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6 h1:XKHJmHcgU9glxk3eLPiRZT5VFSHJitVTnMj/EgIoXC4= github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6/go.mod h1:JEWKD6V8xETMW+DEv+IQVz++f8Cn8O/X0HPeDY3qNis= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/minio/crc64nvme v1.0.0 h1:MeLcBkCTD4pAoU7TciAfwsfxgkhM2u5hCe48hSEVFr0= github.com/minio/crc64nvme v1.0.0/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/minio-go/v7 v7.0.86 h1:DcgQ0AUjLJzRH6y/HrxiZ8CXarA70PAIufXHodP4s+k= github.com/minio/minio-go/v7 v7.0.86/go.mod h1:VbfO4hYwUu3Of9WqGLBZ8vl3Hxnxo4ngxK4hzQDf4x4= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA= github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= @@ -319,44 +173,15 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.12.2/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/common v0.35.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= -github.com/prometheus/common v0.37.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/prometheus/prometheus v0.302.1 h1:xqVdrwrB4WNpdgJqxsz5loqFWNUZitsK8myqLuSZ6Ag= -github.com/prometheus/prometheus v0.302.1/go.mod h1:YcyCoTbUR/TM8rY3Aoeqr0AWTu/pu1Ehh+trpX3eRzg= -github.com/prometheus/statsd_exporter v0.22.7/go.mod h1:N/TevpjkIh9ccs6nuzY3jQn9dFqnUakOjnEuMPJJJnI= -github.com/prometheus/statsd_exporter v0.28.0 h1:S3ZLyLm/hOKHYZFOF0h4zYmd0EeKyPF9R1pFBYXUgYY= -github.com/prometheus/statsd_exporter v0.28.0/go.mod h1:Lq41vNkMLfiPANmI+uHb5/rpFFUTxPXiiNpmsAYLvDI= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -364,9 +189,6 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= @@ -380,34 +202,19 @@ github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stvp/go-udp-testing v0.0.0-20201019212854-469649b16807/go.mod h1:7jxmlfBCDBXRzr0eAQJ48XC1hBu1np4CS5+cHEYfwpc= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.einride.tech/aip v0.68.1 h1:16/AfSxcQISGN5z9C5lM+0mLYXihrHbQ1onvYTr93aQ= go.einride.tech/aip v0.68.1/go.mod h1:XaFtaj4HuA3Zwk9xoBtTWgNubZ0ZZXv9BZJCkuKuWbg= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= @@ -438,260 +245,56 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.226.0 h1:9A29y1XUD+YRXfnHkO66KggxHBZWg9LsTGqm7TkUvtQ= google.golang.org/api v0.226.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf h1:114fkUG+I9ba4UmaoNZt0UtiRmBng3KJIB/E0avfYII= google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf h1:BdIVRm+fyDUn8lrZLPSlBCfM/YKDwUBYgDoLv9+DYo0= @@ -699,17 +302,9 @@ google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY= google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= @@ -721,42 +316,16 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/internal/monitor/exporter.go b/internal/monitor/exporter.go deleted file mode 100644 index 3a47496533..0000000000 --- a/internal/monitor/exporter.go +++ /dev/null @@ -1,208 +0,0 @@ -// Copyright 2021 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package monitor - -import ( - "context" - "fmt" - "net/http" - "strings" - "time" - - "contrib.go.opencensus.io/exporter/ocagent" - "contrib.go.opencensus.io/exporter/prometheus" - "contrib.go.opencensus.io/exporter/stackdriver" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "go.opencensus.io/stats/view" -) - -func startStackdriverExporter(exporterIntervalSecs int64) common.ShutdownFn { - if exporterIntervalSecs <= 0 { - logger.Info("Not starting the Stackdriver exporter since exporter-interval is not specified") - return nil - } - logger.Info("Starting Stackdriver exporter") - if stackdriverExporter, err := enableStackdriverExporter(time.Duration(exporterIntervalSecs) * time.Second); err != nil { - logger.Errorf("Unable to start stackdriver exporter: %v", err) - return nil - } else { - logger.Info("Stackdriver exporter started") - return func(_ context.Context) error { - closeStackdriverExporter(stackdriverExporter) - return nil - } - } -} - -func startPrometheusCollectorExporter(port int64) common.ShutdownFn { - if port <= 0 { - logger.Info("Not starting the Prometheus exporter since port is not specified") - return nil - } - if exporter, server, err := enablePrometheusCollectorExporter(port); err != nil { - logger.Errorf("Unable to start Prometheus exporter: %v", err) - return nil - } else { - return func(_ context.Context) error { - closePrometheusCollectorExporter(exporter, server) - return nil - } - } -} - -func startOpenTelemetryCollectorExporter(address string) common.ShutdownFn { - if address == "" { - logger.Info("Not starting the OTel exporter since collector address is not specified") - return nil - } - if ocExporter, err := enableOpenTelemetryCollectorExporter(address); err != nil { - logger.Errorf("Unable to start OC Agent exporter: %v", err) - return nil - } else { - return func(_ context.Context) error { - closeOpenTelemetryCollectorExporter(ocExporter) - return nil - } - } -} - -// SetupOpenCensusExporters starts the relevant OpenCensus exporters. -func SetupOpenCensusExporters(c *cfg.Config) common.ShutdownFn { - stackdriverShutdownFn := startStackdriverExporter(c.Metrics.CloudMetricsExportIntervalSecs) - prometheusShutdownFn := startPrometheusCollectorExporter(c.Metrics.PrometheusPort) - oTelShutdownFn := startOpenTelemetryCollectorExporter(c.Monitoring.ExperimentalOpentelemetryCollectorAddress) - return common.JoinShutdownFunc(stackdriverShutdownFn, prometheusShutdownFn, oTelShutdownFn) -} - -// enableStackdriverExporter starts to collect monitoring metrics and exports -// them to Stackdriver iff the given interval is positive. -func enableStackdriverExporter(interval time.Duration) (*stackdriver.Exporter, error) { - var err error - var stackdriverExporter *stackdriver.Exporter - if stackdriverExporter, err = stackdriver.NewExporter(stackdriver.Options{ - ReportingInterval: interval, - OnError: func(err error) { - logger.Errorf("Fail to send metric: %v", err) - }, - - // For a local metric "http_sent_bytes", the Stackdriver metric type - // would be "custom.googleapis.com/gcsfuse/http_sent_bytes", display - // name would be "Http sent bytes". - MetricPrefix: "custom.googleapis.com/gcsfuse/", - GetMetricDisplayName: func(view *view.View) string { - name := strings.ReplaceAll(view.Name, "_", " ") - if len(name) > 0 { - name = strings.ToUpper(name[:1]) + name[1:] - } - return name - }, - }); err != nil { - return nil, fmt.Errorf("create stackdriver exporter: %w", err) - } - if err = stackdriverExporter.StartMetricsExporter(); err != nil { - return nil, fmt.Errorf("start stackdriver exporter: %w", err) - } - return stackdriverExporter, nil -} - -// closeStackdriverExporter ensures all collected metrics are sent to -// Stackdriver and closes the stackdriverExporter. -func closeStackdriverExporter(stackdriverExporter *stackdriver.Exporter) { - logger.Info("Stopping Stackdriver exporter") - stackdriverExporter.StopMetricsExporter() - logger.Info("Stackdriver exporter stopped") -} - -// enableOpenTelemetryCollectorExporter starts exporting monitoring metrics to -// the OpenTelemetry Collector at the given address. -// Details: https://opentelemetry.io/docs/collector/ -func enableOpenTelemetryCollectorExporter(address string) (*ocagent.Exporter, error) { - logger.Info("Starting OpenTelemetry collector exporter") - ocExporter, err := ocagent.NewExporter( - ocagent.WithAddress(address), - ocagent.WithServiceName("gcsfuse"), - ocagent.WithReconnectionPeriod(5*time.Second), - ) - if err != nil { - return nil, fmt.Errorf("create opentelementry collector exporter: %w", err) - } - - view.RegisterExporter(ocExporter) - logger.Info("OpenTelemetry collector exporter started") - return ocExporter, nil -} - -// closeOpenTelemetryCollectorExporter ensures all collected metrics are sent to -// the OpenTelemetry Collect and closes the exporter. -func closeOpenTelemetryCollectorExporter(ocExporter *ocagent.Exporter) { - logger.Info("Stopping OpenTelemetry collector exporter") - if err := ocExporter.Stop(); err != nil { - logger.Errorf("Error while stopping OpenTelemetry collector exporter") - return - } - logger.Info("OpenTelemetry collector exporter stopped") -} - -// enablePrometheusCollectorExporter starts exporting monitoring metrics for -// the Prometheus to scrape on the given port. -func enablePrometheusCollectorExporter(port int64) (*prometheus.Exporter, *http.Server, error) { - prometheusExporter, err := prometheus.NewExporter( - prometheus.Options{ - OnError: func(err error) { - logger.Errorf("Fail to collect metric: %v", err) - }, - }, - ) - if err != nil { - return nil, nil, fmt.Errorf("create Prometheus collector exporter: %w", err) - } - - view.RegisterExporter(prometheusExporter) - - mux := http.NewServeMux() - mux.HandleFunc("/metrics", prometheusExporter.ServeHTTP) - prometheusServer := &http.Server{ - Addr: fmt.Sprintf(":%d", port), - Handler: mux, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - MaxHeaderBytes: 1 << 20, - } - - go func() { - if err := prometheusServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - logger.Errorf("Failed to start Prometheus server: %v", err) - } - }() - - logger.Info("Prometheus collector exporter started") - return prometheusExporter, prometheusServer, nil -} - -// closePrometheusCollectorExporter closes the Prometheus exporter. -func closePrometheusCollectorExporter(prometheusExporter *prometheus.Exporter, prometheusServer *http.Server) { - logger.Info("Stopping Prometheus exporter") - if prometheusServer != nil { - if err := prometheusServer.Shutdown(context.Background()); err != nil { - logger.Errorf("Failed to shutdown Prometheus server: %v", err) - } - } - - if prometheusExporter != nil { - view.UnregisterExporter(prometheusExporter) - } -} From 263ffbfa1dd13011a72e447ff5960dd996d0f2c9 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 9 Apr 2025 12:56:38 +0530 Subject: [PATCH 0337/1298] ignore rw as it is default for the mount (#3168) --- tools/mount_gcsfuse/main.go | 5 ++++- tools/mount_gcsfuse/main_test.go | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/mount_gcsfuse/main.go b/tools/mount_gcsfuse/main.go index fba1dbfefa..10ec598615 100644 --- a/tools/mount_gcsfuse/main.go +++ b/tools/mount_gcsfuse/main.go @@ -111,7 +111,10 @@ func makeGcsfuseArgs( return s == "o" }) nonBoolFlags = append(nonBoolFlags, cfg.ConfigFileFlagName) - noopOptions := []string{"user", "nouser", "auto", "noauto", "_netdev", "no_netdev"} + // 'rw' mount option is implicitly passed as CLI parameter by /etc/fstab + // entries and overrides mount options in config file. Ignoring is safe, as + // 'rw' is default, so that config file mount options take effect. + noopOptions := []string{"rw", "user", "nouser", "auto", "noauto", "_netdev", "no_netdev"} // Deal with options. for name, value := range opts { diff --git a/tools/mount_gcsfuse/main_test.go b/tools/mount_gcsfuse/main_test.go index e6f3ca99eb..dfdb82103c 100644 --- a/tools/mount_gcsfuse/main_test.go +++ b/tools/mount_gcsfuse/main_test.go @@ -109,7 +109,7 @@ func TestMakeGcsfuseArgs(t *testing.T) { // Test ignored options { name: "TestMakeGcsfuseArgs with IgnoredOptions", - opts: map[string]string{"user": "nobody", "_netdev": ""}, + opts: map[string]string{"user": "nobody", "_netdev": "", "rw": ""}, expectedFlags: []string{}, }, From 8ce0af740f27fa9f37c1230ff7b51996023e4fa9 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:30:05 +0530 Subject: [PATCH 0338/1298] e2e tests for validation grpc directpath (#3098) * upgrading glango dependencies * other upgrades * reverting * e2e tests for grpc directpath validation * dependencies changes * formatting fixes * lint fixes * go.sum changes * refactoring to reuse code * refactoring helper functions to utils package * lint fix * improvements --- .../grpc_validation/grpc_validation_test.go | 162 +++++++++++++++++ .../grpc_validation/setup_test.go | 167 ++++++++++++++++++ .../read_cache/remount_test.go | 6 +- .../util/client/storage_client.go | 29 +++ .../dynamic_mounting/dynamic_mounting.go | 33 +--- .../util/operations/file_operations.go | 15 ++ 6 files changed, 382 insertions(+), 30 deletions(-) create mode 100644 tools/integration_tests/grpc_validation/grpc_validation_test.go create mode 100644 tools/integration_tests/grpc_validation/setup_test.go diff --git a/tools/integration_tests/grpc_validation/grpc_validation_test.go b/tools/integration_tests/grpc_validation/grpc_validation_test.go new file mode 100644 index 0000000000..e231a7f161 --- /dev/null +++ b/tools/integration_tests/grpc_validation/grpc_validation_test.go @@ -0,0 +1,162 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package grpc_validation + +import ( + "fmt" + "os" + "testing" + "time" + + client_util "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type gRPCValidation struct { + suite.Suite + singleRegionBucketForGRPCSuccess string + singleRegionBucketForGRPCFailure string + multiRegionBucketForGRPCSuccess string + multiRegionBucketForGRPCFailure string +} + +// Setup involves: +// Finding out test regions. +// Creating unique test bucket names. +// Creating the test buckets. +// Test Cases: +// Test Case 1: single region test: success case , when test VM and bucket are colocated +// Test Case 2: single region test: failure case , when test VM and bucket are out-of-region +// Test Case 3: multi region test: success case , when test VM and bucket are colocated +// Test Case 4: multi region test: failure case , when test VM and bucket are out-of-region +func (g *gRPCValidation) SetupSuite() { + + // Based on the test region which is initialized in the TestMain() function, + // we will pick out the test region for: + singleRegionForGRPCSuccess := findSingleRegionForGRPCDirectPathSuccessCase(testRegion) + singleRegionForGRPCFailure := pickFailureRegionFromListOfRegions(singleRegionForGRPCSuccess, single_regions) + multiRegionForGRPCSuccess := findMultiRegionForGRPCDirectPathSuccessCase(testRegion) + multiRegionForGRPCFailure := pickFailureRegionFromListOfRegions(multiRegionForGRPCSuccess, multi_regions) + + // Set the test bucket names with unique suffix + g.singleRegionBucketForGRPCSuccess = createTestBucketName(singleRegionForGRPCSuccess) + g.singleRegionBucketForGRPCFailure = createTestBucketName(singleRegionForGRPCFailure) + g.multiRegionBucketForGRPCSuccess = createTestBucketName(multiRegionForGRPCSuccess) + g.multiRegionBucketForGRPCFailure = createTestBucketName(multiRegionForGRPCFailure) + + // Create the test buckets + if err := createTestBucket(singleRegionForGRPCSuccess, g.singleRegionBucketForGRPCSuccess); err != nil { + g.T().Fatalf("Could not create bucket in the required region, err : %v", err) + } + if err := createTestBucket(singleRegionForGRPCFailure, g.singleRegionBucketForGRPCFailure); err != nil { + g.T().Fatalf("Could not create bucket in the required region, err : %v", err) + } + if err := createTestBucket(multiRegionForGRPCSuccess, g.multiRegionBucketForGRPCSuccess); err != nil { + g.T().Fatalf("Could not create bucket in the required region, err : %v", err) + } + if err := createTestBucket(multiRegionForGRPCFailure, g.multiRegionBucketForGRPCFailure); err != nil { + g.T().Fatalf("Could not create bucket in the required region, err : %v", err) + } +} + +// Delete the test buckets created. +func (g *gRPCValidation) TearDownSuite() { + bucketsToDelete := []string{ + g.singleRegionBucketForGRPCSuccess, + g.singleRegionBucketForGRPCFailure, + g.multiRegionBucketForGRPCSuccess, + g.multiRegionBucketForGRPCFailure, + } + for _, bucket := range bucketsToDelete { + if err := client_util.DeleteBucket(ctx, client, bucket); err != nil { + g.T().Logf("Failed to delete bucket %s: %v", bucket, err) + } + } +} + +func TestGRPCValidationSuite(t *testing.T) { + suite.Run(t, new(gRPCValidation)) +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (g *gRPCValidation) TestGRPCDirectPathConnections() { + testCases := []struct { + name string + bucketName string + expectedSuccess bool + expectedLogSubstring string + }{ + { + name: "SingleRegion_Success", + bucketName: g.singleRegionBucketForGRPCSuccess, + expectedLogSubstring: fmt.Sprintf("Successfully connected over gRPC DirectPath for %s", g.singleRegionBucketForGRPCSuccess), + }, + { + name: "SingleRegion_Failure", + bucketName: g.singleRegionBucketForGRPCFailure, + expectedLogSubstring: fmt.Sprintf("Direct path connectivity unavailable for %s, reason:", g.singleRegionBucketForGRPCFailure), + }, + { + name: "MultiRegion_Success", + bucketName: g.multiRegionBucketForGRPCSuccess, + expectedLogSubstring: fmt.Sprintf("Successfully connected over gRPC DirectPath for %s", g.multiRegionBucketForGRPCSuccess), + }, + { + name: "MultiRegion_Failure", + bucketName: g.multiRegionBucketForGRPCFailure, + expectedLogSubstring: fmt.Sprintf("Direct path connectivity unavailable for %s, reason: ", g.multiRegionBucketForGRPCFailure), + }, + } + + for _, tc := range testCases { + g.T().Run(tc.name, func(t *testing.T) { + mountPoint, err := os.MkdirTemp("", "grpc_validation_test") + assert.NoError(t, err) + logFile := fmt.Sprintf("/tmp/grpc_%s_%d.txt", tc.name, time.Now().UnixNano()) + args := []string{"--client-protocol=grpc", "--log-severity=TRACE", fmt.Sprintf("--log-file=%s", logFile), tc.bucketName, mountPoint} + err = mounting.MountGcsfuse(setup.BinFile(), args) + if err != nil { + if tc.expectedSuccess { + t.Errorf("Unexpected mount failure: %v", err) + } + return + } + + defer func() { + if err := util.Unmount(mountPoint); err != nil { + t.Logf("Warning: unmount failed: %v", err) + } + os.Remove(mountPoint) + // Only remove the log file if the test succeeded + if t.Failed() { + t.Logf("Test failed, log file '%s' will not be deleted for inspection.", logFile) + } else { + os.Remove(logFile) + } + }() + success := operations.CheckLogFileForMessage(g.T(), tc.expectedLogSubstring, logFile) + require.Equal(t, true, success) + }) + } +} diff --git a/tools/integration_tests/grpc_validation/setup_test.go b/tools/integration_tests/grpc_validation/setup_test.go new file mode 100644 index 0000000000..c603d3c765 --- /dev/null +++ b/tools/integration_tests/grpc_validation/setup_test.go @@ -0,0 +1,167 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package grpc_validation + +import ( + "context" + "fmt" + "log" + "os" + "strings" + "testing" + "time" + + "cloud.google.com/go/storage" + client_util "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "go.opentelemetry.io/contrib/detectors/gcp" + "go.opentelemetry.io/otel/sdk/resource" +) + +// gRPC directPath can be established in a scenario like VM in us-central1-a and +// the bucket in us-central1. Since we are creating the buckets dynamically, we need +// to select regions and not zones. +// Note for single region bucket testing, +// We can work with 2 regions only since for testing success case, we are actually finding out +// the test region using go library. Just for failure case, we need to find out a region which +// is bound to be one of these two since continent differs. +// Example case: Test Region : us-central1 , Failure Test Region : europe-west4 +// +// Test Region : us-west1, Failure test Region : us-central1 +var single_regions = []string{ + "us-central1", + "europe-west4", +} + +// gRPC is now supported for multi region buckets as well. +// Same logic, if the test VM is in us, then failure region = eu +// If the test VM is in non-US, then failure region = us +var multi_regions = []string{ + "us", + "eu", +} +var gcp_project = "gcs-fuse-test" +var ( + ctx context.Context + client *storage.Client + testRegion string +) + +// Since gRPC directpath does not work over cloudtop, so these validation tests will be skipped +// when run on cloudtop. +var cloudtopProd = "cloudtop-prod" + +//////////////////////////////////////////////////////////////////////// +// Helper Functions +//////////////////////////////////////////////////////////////////////// + +// For both multi region buckets and single region buckets test, we need to decide failure case +// region based on the region of the VM on which the test is running. +func findTestExecutionEnvironment(ctx context.Context) (string, error) { + detectedAttrs, err := resource.New(ctx, resource.WithDetectors(gcp.NewDetector())) + if err != nil { + log.Printf("Error fetching the test environment.All tests will be skipped. Error : %v", err) + return "", err + } + attrs := detectedAttrs.Set() + if v, exists := attrs.Value("gcp.gce.instance.hostname"); exists && strings.Contains(strings.ToLower(v.AsString()), cloudtopProd) { + return cloudtopProd, nil + } + if v, exists := attrs.Value("cloud.region"); exists { + return v.AsString(), nil + } + return "", nil +} + +// For testing with single region buckets, we need to find the region for success case. +// If the input is 'us-west1-a' which is the test VM Zone, then this function returns 'us-west1' used +// creating the test buckets. +func findSingleRegionForGRPCDirectPathSuccessCase(testRegion string) string { + parts := strings.Split(testRegion, "-") + if len(parts) >= 2 { + return strings.Join(parts[:len(parts)-1], "-") //rejoin the first parts of the zone, excluding the last part. + } + return "" +} + +// For testing with multi region buckets, we need to find the region for success case. +// If the input is 'us-west1-a' which is the test VM Zone, then this function returns 'us' used +// creating the test buckets. +func findMultiRegionForGRPCDirectPathSuccessCase(testRegion string) string { + parts := strings.Split(testRegion, "-") + if len(parts) > 0 { + return parts[0] // Return the first part of the string i.e. if us-central1 then us + } + return "" +} + +// Generic function to pick a region other than the passed value to validate for failure scenario. +func pickFailureRegionFromListOfRegions(successRegion string, regions []string) string { + for _, otherRegion := range regions { + if otherRegion != successRegion { + return otherRegion + } + } + return "" +} + +// Creating test bucket name with unique suffix. +func createTestBucketName(region string) string { + epoch := time.Now().UnixNano() // Get the current Unix epoch time + return fmt.Sprintf("grpc_validation_%s_%d", region, epoch) +} + +// Based on the test case, we need to create the bucket in/out of region with the test VM. +// This has to be done dynamically at the time of test setup. +// Based on what region we pass, the test bucket will be multi region or single region. +func createTestBucket(testBucketRegion, testBucketName string) (err error) { + bucket := client.Bucket(testBucketName) + if err = bucket.Create(ctx, gcp_project, &storage.BucketAttrs{Location: testBucketRegion}); err != nil { + log.Printf("Error while creating bucket, error: %v", err) + return err + } + return nil +} + +func TestMain(m *testing.M) { + // Parse flags from the setup. + var err error + setup.ParseSetUpFlags() + if err := setup.SetUpTestDir(); err != nil { + log.Fatalf("Failed to setup GCSFuse package. Error: %v", err) + } + + // Creating a common storage client for the test + ctx = context.Background() + if client, err = client_util.CreateStorageClient(ctx); err != nil { + log.Fatalf("Creation of storage client failed with error : %v", err) + } + defer client.Close() + + testRegion, err := findTestExecutionEnvironment(ctx) + if err != nil { + log.Fatalf("Failed to retrieve test VM region: %v", err) + } + + if testRegion == cloudtopProd { + log.Println("Skipping tests due to cloudtop environment.") + os.Exit(0) + } + + // Run tests. + code := m.Run() + // Exit. + os.Exit(code) +} diff --git a/tools/integration_tests/read_cache/remount_test.go b/tools/integration_tests/read_cache/remount_test.go index aefd97ab49..6be8f69849 100644 --- a/tools/integration_tests/read_cache/remount_test.go +++ b/tools/integration_tests/read_cache/remount_test.go @@ -93,7 +93,11 @@ func (s *remountTest) TestCacheIsNotReusedOnDynamicRemount(t *testing.T) { testBucket1 := setup.TestBucket() testFileName1 := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) testBucket2 := dynamic_mounting.CreateTestBucketForDynamicMounting(ctx, storageClient) - defer dynamic_mounting.DeleteTestBucketForDynamicMounting(ctx, storageClient, testBucket2) + defer func() { + if err := client.DeleteBucket(ctx, storageClient, testBucket2); err != nil { + t.Logf("Failed to delete test bucket %s.Error : %v", testBucket1, err) + } + }() setup.SetDynamicBucketMounted(testBucket2) defer setup.SetDynamicBucketMounted("") // Introducing a sleep of 10 seconds after bucket creation to address propagation delays. diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 5ef70ae5b4..24a5e20211 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -381,3 +381,32 @@ func CopyFileInBucket(ctx context.Context, storageClient *storage.Client, srcfil log.Fatalf("Error while copying file in bucket: %v", err) } } + +func DeleteBucket(ctx context.Context, client *storage.Client, bucketName string) error { + bucket := client.Bucket(bucketName) + + // Iterate through objects and delete them + query := &storage.Query{} + it := bucket.Objects(ctx, query) + for { + objAttrs, err := it.Next() + if err == iterator.Done { + break // No more objects + } + if err != nil { + log.Fatalf("Error iterating through objects: %v", err) + } + + obj := bucket.Object(objAttrs.Name) + err = obj.Delete(ctx) + if err != nil { + log.Fatalf("Failed to delete object %s: %v", objAttrs.Name, err) + } + } + + if err := bucket.Delete(ctx); err != nil { + log.Printf("Bucket(%q).Delete: %v", bucketName, err) + return err + } + return nil +} diff --git a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go index 371ed5e6cd..02b38d5171 100644 --- a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go +++ b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go @@ -23,8 +23,8 @@ import ( "cloud.google.com/go/compute/metadata" "cloud.google.com/go/storage" - "google.golang.org/api/iterator" + client_util "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) @@ -119,33 +119,6 @@ func CreateTestBucketForDynamicMounting(ctx context.Context, client *storage.Cli return testBucketForDynamicMounting } -func DeleteTestBucketForDynamicMounting(ctx context.Context, client *storage.Client, bucketName string) { - bucket := client.Bucket(bucketName) - - // Iterate through objects and delete them - query := &storage.Query{} - it := bucket.Objects(ctx, query) - for { - objAttrs, err := it.Next() - if err == iterator.Done { - break // No more objects - } - if err != nil { - log.Fatalf("Error iterating through objects: %v", err) - } - - obj := bucket.Object(objAttrs.Name) - err = obj.Delete(ctx) - if err != nil { - log.Fatalf("Failed to delete object %s: %v", objAttrs.Name, err) - } - } - - if err := bucket.Delete(ctx); err != nil { - log.Printf("Bucket(%q).Delete: %v", bucketName, err) - } -} - func RunTests(ctx context.Context, client *storage.Client, flags [][]string, m *testing.M) (successCode int) { log.Println("Running dynamic mounting tests...") @@ -155,7 +128,9 @@ func RunTests(ctx context.Context, client *storage.Client, flags [][]string, m * log.Printf("Test log: %s\n", setup.LogFile()) - DeleteTestBucketForDynamicMounting(ctx, client, testBucketForDynamicMounting) + if err := client_util.DeleteBucket(ctx, client, testBucketForDynamicMounting); err != nil { + log.Fatalf("Failed to delete the bucket : %s. Error: %v", testBucketForDynamicMounting, err) + } return successCode } diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 99095b5d55..1f76948901 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -16,6 +16,7 @@ package operations import ( + "bufio" "bytes" "compress/gzip" "fmt" @@ -779,3 +780,17 @@ func CloseLocalFile(t *testing.T, f **os.File) error { *f = nil return err } + +func CheckLogFileForMessage(t *testing.T, expectedLog, logFile string) bool { + file, err := os.Open(logFile) + require.NoError(t, err, "Failed to open log file") + defer file.Close() + t.Logf("logfile name %s expectedLog %s", logFile, expectedLog) + scanner := bufio.NewScanner(file) + for scanner.Scan() { + if strings.Contains(scanner.Text(), expectedLog) { + return true + } + } + return false +} From 3df6c324bc80c04005c32383f60404ee4b8c4aa3 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Wed, 9 Apr 2025 19:48:08 +0530 Subject: [PATCH 0339/1298] adding grpc_validation test package to periodic test runs (#3170) --- tools/integration_tests/grpc_validation/setup_test.go | 4 ++++ tools/integration_tests/run_e2e_tests.sh | 1 + 2 files changed, 5 insertions(+) diff --git a/tools/integration_tests/grpc_validation/setup_test.go b/tools/integration_tests/grpc_validation/setup_test.go index c603d3c765..30beda9ff8 100644 --- a/tools/integration_tests/grpc_validation/setup_test.go +++ b/tools/integration_tests/grpc_validation/setup_test.go @@ -137,6 +137,10 @@ func createTestBucket(testBucketRegion, testBucketName string) (err error) { func TestMain(m *testing.M) { // Parse flags from the setup. + if setup.IsPresubmitRun() { + log.Println("Skipping test package : grpc_validation since this is a presubmit test run") + os.Exit(0) + } var err error setup.ParseSetUpFlags() if err := setup.SetUpTestDir(); err != nil { diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 5efce39901..ae966f9ba8 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -89,6 +89,7 @@ TEST_DIR_PARALLEL=( "log_rotation" "mounting" "read_cache" + "grpc_validation" "gzip" "write_large_files" "list_large_dir" From 35e31e5322ff2a0d0ca9a21f45b39f92c6a47c5d Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 9 Apr 2025 23:50:26 +0000 Subject: [PATCH 0340/1298] Fix and enable package list_large_dir in ZB e2e tests (#3155) * disable sw installations * disable all but list_large_dir tests * gcloud -> go-client in list_large_dir package (part 1) Replace testdata/create_implicit_dir.sh script with go client sdk based alternative. * add support MoveObject in util/client/client.go * add dummy replacement for update script * Revert "add support MoveObject in util/client/client.go" This reverts commit 8351649dca29a91f4d592656c89af37c3ccf215a. * reduce number of files to 1 from 12k for testing purpose * fix some build erors * temp * add logic to copy local files to gcs bucket * fix create_implicit_dirs method * [temp] disable non-implicit tests * remove debug logs * reenable non-implicit tests * put back original scale of the tests * put back original checks for the tests * cleanup * Revert "disable all but list_large_dir tests" This reverts commit 3eca8b9f028b7fe8927216b36690fbc7fcfb04d0. * Revert "disable sw installations" This reverts commit 745770850eb8f11bf84df15cde50317f2123f594. * enable list_large_dir test package for zb * address a lint error * add helpful debug logs * Replace filepath.WalkDir with filepath.Glob * remove unnecessary debug logs * fix formatting error * parallelize file copying to GCS * dont run for grpc for zonal bucket (duplicate test) * fix formatting * address self-comments * replace channels with array * fix build * remove debug logs * more code cleanup * empty commit * address some review comments * Switch from arrays to single channel acrosss all goroutines Stats associated with this change: time taken to copy all 1000 files, using arrays in goroutines = 7.929863456s time taken to copy all 1000 files, using arrays in goroutines = 6.787053469s time taken to copy all 1000 files, using arrays in goroutines = 6.375443899s time taken to copy all 1000 files, using a single buffered channel across all goroutines = 7.851011189s time taken to copy all 1000 files, using a single buffered channel across all goroutines = 6.51152162s time taken to copy all 1000 files, using a single buffered channel across all goroutines = 6.14107131s time taken to copy all 1000 files, using a single unbuffered channel across all goroutines = 7.013791204s time taken to copy all 1000 files, using a single unbuffered channel across all goroutines = 6.269052254s time taken to copy all 1000 files, using a single unbuffered channel across all goroutines = 6.056092328s time taken to copy all 12000 files, using a single unbuffered channel across all goroutines = 1m7.534551842s time taken to copy all 12000 files, using a single unbuffered channel across all goroutines = 1m5.070493206s time taken to copy all 12000 filesi, using a single unbuffered channel across all goroutines = 1m4.751596961s * fix linter error * fix a minor bug * address couple of minor comments --- ...ist_dir_with_twelve_thousand_files_test.go | 95 ++++++++++++++++++- .../list_large_dir/list_large_dir_test.go | 4 +- tools/integration_tests/run_e2e_tests.sh | 2 +- .../util/client/storage_client.go | 2 +- 4 files changed, 98 insertions(+), 5 deletions(-) diff --git a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go index cf73653cde..6071f2ff90 100644 --- a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go +++ b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go @@ -15,15 +15,20 @@ package list_large_dir import ( + "context" "fmt" "math" "os" "path" + "path/filepath" "strconv" "strings" + "sync" "testing" "time" + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" @@ -99,14 +104,80 @@ func checkIfObjNameIsCorrect(t *testing.T, objName string, prefix string, maxNum } } +func splitBucketNameAndDirPath(t *testing.T, bucketNameWithDirPath string) (bucketName, dirPathInBucket string) { + t.Helper() + + var found bool + if bucketName, dirPathInBucket, found = strings.Cut(bucketNameWithDirPath, "/"); !found { + t.Fatalf("Unexpected bucketNameWithDirPath: %q. Expected form: /", bucketNameWithDirPath) + } + return +} + +// This function is equivalent to testdata/upload_files_to_bucket.sh to replace gcloud with storage-client +// This is needed for ZB which is not supported by gcloud storage cp command yet. +func testdataUploadFilesToBucket(ctx context.Context, t *testing.T, storageClient *storage.Client, bucketNameWithDirPath, dirWith12KFiles, filesPrefix string) { + t.Helper() + + bucketName, dirPathInBucket := splitBucketNameAndDirPath(t, bucketNameWithDirPath) + + dirWith12KFilesFullPathPrefix := filepath.Join(dirWith12KFiles, filesPrefix) + matches, err := filepath.Glob(dirWith12KFilesFullPathPrefix + "*") + if err != nil { + t.Fatalf("Failed to get files of pattern %s*: %v", dirWith12KFilesFullPathPrefix, err) + } + + type copyRequest struct { + srcLocalFilePath string + dstGCSObjectPath string + } + channel := make(chan copyRequest) + + // Copy request producer. + go func() { + for _, match := range matches { + _, fileName := filepath.Split(match) + if len(fileName) > 0 { + req := copyRequest{srcLocalFilePath: match, dstGCSObjectPath: filepath.Join(dirPathInBucket, fileName)} + channel <- req + } + } + // close the channel to let the go-routines know that there is no more object to be copied. + close(channel) + }() + + // Copy request consumers. + numCopyGoroutines := 16 + var wg sync.WaitGroup + for range numCopyGoroutines { + wg.Add(1) + go func() { + defer wg.Done() + for { + copyRequest, ok := <-channel + if !ok { + break + } + client.CopyFileInBucket(ctx, storageClient, copyRequest.srcLocalFilePath, copyRequest.dstGCSObjectPath, bucketName) + } + }() + } + wg.Wait() +} + // createFilesAndUpload generates files and uploads them to the specified directory. func createFilesAndUpload(t *testing.T, dirPath string) { t.Helper() localDirPath := path.Join(os.Getenv("HOME"), directoryWithTwelveThousandFiles) operations.CreateDirectoryWithNFiles(numberOfFilesInDirectoryWithTwelveThousandFiles, localDirPath, prefixFileInDirectoryWithTwelveThousandFiles, t) + defer os.RemoveAll(localDirPath) - setup.RunScriptForTestData("testdata/upload_files_to_bucket.sh", dirPath, localDirPath, prefixFileInDirectoryWithTwelveThousandFiles) + if setup.IsZonalBucketRun() { + testdataUploadFilesToBucket(ctx, t, storageClient, dirPath, localDirPath, prefixFileInDirectoryWithTwelveThousandFiles) + } else { + setup.RunScriptForTestData("testdata/upload_files_to_bucket.sh", dirPath, localDirPath, prefixFileInDirectoryWithTwelveThousandFiles) + } } // createExplicitDirs creates empty explicit directories in the specified directory. @@ -150,6 +221,22 @@ func listDirTime(t *testing.T, dirPath string, expectExplicitDirs bool, expectIm return firstListTime, minSecondListTime } +// This function is equivalent to testdata/create_implicit_dir.sh to replace gcloud with storage-client +// This is needed for ZB which is not supported by gcloud storage cp command yet. +func testdataCreateImplicitDir(ctx context.Context, t *testing.T, storageClient *storage.Client, bucketNameWithDirPath, prefixImplicitDirInLargeDirListTest string, numberOfImplicitDirsInDirectory int) { + t.Helper() + + bucketName, dirPathInBucket := splitBucketNameAndDirPath(t, bucketNameWithDirPath) + + testFile, err := operations.CreateLocalTempFile("", false) + if err != nil { + t.Fatalf("Failed to create local file for creating copies ...") + } + for suffix := 1; suffix <= numberOfImplicitDirsInDirectory; suffix++ { + client.CopyFileInBucket(ctx, storageClient, testFile, path.Join(dirPathInBucket, fmt.Sprintf("%s%d", prefixImplicitDirInLargeDirListTest, suffix), testFile), bucketName) + } +} + // prepareTestDirectory sets up a test directory with files and required explicit and implicit directories. func prepareTestDirectory(t *testing.T, withExplicitDirs bool, withImplicitDirs bool) string { t.Helper() @@ -169,7 +256,11 @@ func prepareTestDirectory(t *testing.T, withExplicitDirs bool, withImplicitDirs } if withImplicitDirs { - setup.RunScriptForTestData("testdata/create_implicit_dir.sh", testDirPathOnBucket, prefixImplicitDirInLargeDirListTest, strconv.Itoa(numberOfImplicitDirsInDirectoryWithTwelveThousandFiles)) + if setup.IsZonalBucketRun() { + testdataCreateImplicitDir(ctx, t, storageClient, testDirPathOnBucket, prefixImplicitDirInLargeDirListTest, numberOfImplicitDirsInDirectoryWithTwelveThousandFiles) + } else { + setup.RunScriptForTestData("testdata/create_implicit_dir.sh", testDirPathOnBucket, prefixImplicitDirInLargeDirListTest, strconv.Itoa(numberOfImplicitDirsInDirectoryWithTwelveThousandFiles)) + } } return testDirPath diff --git a/tools/integration_tests/list_large_dir/list_large_dir_test.go b/tools/integration_tests/list_large_dir/list_large_dir_test.go index 7e49d325bc..5958e855e5 100644 --- a/tools/integration_tests/list_large_dir/list_large_dir_test.go +++ b/tools/integration_tests/list_large_dir/list_large_dir_test.go @@ -54,7 +54,9 @@ func TestMain(m *testing.M) { }() flags := [][]string{{"--implicit-dirs", "--stat-cache-ttl=0", "--kernel-list-cache-ttl-secs=-1"}} - if !testing.Short() { + // Don't run for grpc if -short flat is passed. + // Don't run for grpc for zonal bucket as zonal buckets by default use grpc. + if !testing.Short() && !setup.IsZonalBucketRun() { flags = append(flags, []string{"--client-protocol=grpc", "--implicit-dirs=true", "--stat-cache-ttl=0", "--kernel-list-cache-ttl-secs=-1"}) } diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index ae966f9ba8..46836780fb 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -127,7 +127,7 @@ TEST_DIR_PARALLEL_FOR_ZB=( # "implicit_dir" "interrupt" "kernel_list_cache" - # "list_large_dir" + "list_large_dir" "local_file" "log_rotation" "monitoring" diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 24a5e20211..0a99989580 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -378,7 +378,7 @@ func ClearCacheControlOnGcsObject(ctx context.Context, client *storage.Client, o func CopyFileInBucket(ctx context.Context, storageClient *storage.Client, srcfilePath, destFilePath, bucket string) { err := UploadGcsObject(ctx, storageClient, srcfilePath, bucket, destFilePath, false) if err != nil { - log.Fatalf("Error while copying file in bucket: %v", err) + log.Fatalf("Error while copying file %q to GCS object \"gs://%s/%s\" : %v", srcfilePath, bucket, destFilePath, err) } } From 2f5ef11918cfe024b4c2cd2df1dac8e7c35f8a68 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Thu, 10 Apr 2025 10:00:16 +0530 Subject: [PATCH 0341/1298] trigger shadow review on PR open (#3173) --- .github/workflows/shadow.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/shadow.yml b/.github/workflows/shadow.yml index f3a2a78cd8..91517b7b2e 100644 --- a/.github/workflows/shadow.yml +++ b/.github/workflows/shadow.yml @@ -3,7 +3,7 @@ name: "Shadow reviews" on: pull_request: types: - - review_requested + - opened branches: - master From b06814d754fd3c0105fc7f4ef3a0cc73dba9fe3e Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 10 Apr 2025 15:23:09 +0530 Subject: [PATCH 0342/1298] [Random Reader Refactoring] Convert error message consts in error variable (#3169) * Convsert error message const in err var * fix err sprintf * fix lint and linux tests * fix lint and linux tests * use %w instead of %s in error * fix lint and linux tests * use %w instead of %s in error * fix lint and linux tests * fix lint and linux tests * fix lint and linux tests * create error array in test * returning error instead of assigning it to var * update comment --- internal/cache/file/cache_handle.go | 26 +++++------- internal/cache/file/cache_handle_test.go | 46 ++++++++++------------ internal/cache/file/cache_handler.go | 4 +- internal/cache/file/cache_handler_test.go | 7 ++-- internal/cache/file/downloader/job.go | 3 +- internal/cache/file/downloader/job_test.go | 12 +++--- internal/cache/lru/lru.go | 22 +++++------ internal/cache/lru/lru_test.go | 9 ++--- internal/cache/util/util.go | 29 +++++++------- internal/cache/util/util_test.go | 26 ++++++------ internal/gcsx/random_reader.go | 7 ++-- internal/gcsx/random_reader_test.go | 2 +- 12 files changed, 88 insertions(+), 105 deletions(-) diff --git a/internal/cache/file/cache_handle.go b/internal/cache/file/cache_handle.go index a188f69ea9..bd41bab21f 100644 --- a/internal/cache/file/cache_handle.go +++ b/internal/cache/file/cache_handle.go @@ -16,7 +16,6 @@ package file import ( "context" - "errors" "fmt" "io" "os" @@ -66,11 +65,11 @@ func NewCacheHandle(localFileHandle *os.File, fileDownloadJob *downloader.Job, func (fch *CacheHandle) validateCacheHandle() error { if fch.fileHandle == nil { - return errors.New(util.InvalidFileHandleErrMsg) + return util.ErrInvalidFileHandle } if fch.fileInfoCache == nil { - return errors.New(util.InvalidFileInfoCacheErrMsg) + return util.ErrInvalidFileInfoCache } return nil @@ -82,11 +81,9 @@ func (fch *CacheHandle) shouldReadFromCache(jobStatus *downloader.JobStatus, req if jobStatus.Err != nil || jobStatus.Name == downloader.Invalid || jobStatus.Name == downloader.Failed { - err := fmt.Errorf("%s: jobStatus: %s jobError: %w", util.InvalidFileDownloadJobErrMsg, jobStatus.Name, jobStatus.Err) - return err + return fmt.Errorf("%w: jobStatus: %s jobError: %w", util.ErrInvalidFileDownloadJob, jobStatus.Name, jobStatus.Err) } else if jobStatus.Offset < requiredOffset { - err := fmt.Errorf("%s: jobOffset: %d is less than required offset: %d", util.FallbackToGCSErrMsg, jobStatus.Offset, requiredOffset) - return err + return fmt.Errorf("%w: jobOffset: %d is less than required offset: %d", util.ErrFallbackToGCS, jobStatus.Offset, requiredOffset) } return err } @@ -113,8 +110,7 @@ func (fch *CacheHandle) validateEntryInFileInfoCache(bucket gcs.Bucket, object * fileInfo = fch.fileInfoCache.LookUpWithoutChangingOrder(fileInfoKeyName) } if fileInfo == nil { - err = fmt.Errorf("%v: no entry found in file info cache for key %v", util.InvalidFileInfoCacheErrMsg, fileInfoKeyName) - return err + return fmt.Errorf("%w: no entry found in file info cache for key %v", util.ErrInvalidFileInfoCache, fileInfoKeyName) } // The generation check below is required because it may happen that file @@ -122,12 +118,10 @@ func (fch *CacheHandle) validateEntryInFileInfoCache(bucket gcs.Bucket, object * // from local cached file to `dst` buffer. fileInfoData := fileInfo.(data.FileInfo) if fileInfoData.ObjectGeneration != object.Generation { - err = fmt.Errorf("%v: generation of cached object: %v is different from required generation: %v", util.InvalidFileInfoCacheErrMsg, fileInfoData.ObjectGeneration, object.Generation) - return err + return fmt.Errorf("%w: generation of cached object: %v is different from required generation: %v", util.ErrInvalidFileInfoCache, fileInfoData.ObjectGeneration, object.Generation) } if fileInfoData.Offset < requiredOffset { - err = fmt.Errorf("%v offset of cached object: %v is less than required offset %v", util.InvalidFileInfoCacheErrMsg, fileInfoData.Offset, requiredOffset) - return err + return fmt.Errorf("%w offset of cached object: %v is less than required offset %v", util.ErrInvalidFileInfoCache, fileInfoData.Offset, requiredOffset) } return nil @@ -222,14 +216,12 @@ func (fch *CacheHandle) Read(ctx context.Context, bucket gcs.Bucket, object *gcs // Ensure that the number of bytes read into dst buffer is equal to what is // requested. It will also help catch cases where file in cache is truncated // externally to size offset + x where x < requestedNumBytes. - errMsg := fmt.Sprintf("%s, number of bytes read from file in cache: %v are not equal to requested: %v", util.ErrInReadingFileHandleMsg, n, requestedNumBytes) - return 0, false, errors.New(errMsg) + return 0, false, fmt.Errorf("%w, number of bytes read from file in cache: %v are not equal to requested: %v", util.ErrInReadingFileHandle, n, requestedNumBytes) } err = nil } if err != nil { - err = fmt.Errorf("%s: while reading from %d offset of the local file: %w", util.ErrInReadingFileHandleMsg, offset, err) - return 0, false, err + return 0, false, fmt.Errorf("%w: while reading from %d offset of the local file: %w", util.ErrInReadingFileHandle, offset, err) } // Look up of file being read in file info cache is required to update the LRU diff --git a/internal/cache/file/cache_handle_test.go b/internal/cache/file/cache_handle_test.go index 06b2f54adc..b22c12bc54 100644 --- a/internal/cache/file/cache_handle_test.go +++ b/internal/cache/file/cache_handle_test.go @@ -18,7 +18,6 @@ import ( "context" "crypto/rand" "errors" - "fmt" "io" "math" "os" @@ -176,7 +175,7 @@ func (cht *cacheHandleTest) Test_validateCacheHandle_WithNilFileHandle() { err := cht.cacheHandle.validateCacheHandle() - assert.Equal(cht.T(), util.InvalidFileHandleErrMsg, err.Error()) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileHandle)) } func (cht *cacheHandleTest) Test_validateCacheHandle_WithNilFileDownloadJob() { @@ -192,7 +191,7 @@ func (cht *cacheHandleTest) Test_validateCacheHandle_WithNilFileInfoCache() { err := cht.cacheHandle.validateCacheHandle() - assert.Equal(cht.T(), util.InvalidFileInfoCacheErrMsg, err.Error()) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileInfoCache)) } func (cht *cacheHandleTest) Test_validateCacheHandle_WithNonNilMemberAttributes() { @@ -274,7 +273,7 @@ func (cht *cacheHandleTest) Test_shouldReadFromCache_WithJobStateIsNotStarted() err := cht.cacheHandle.shouldReadFromCache(&jobStatus, requiredOffset) assert.NotNil(cht.T(), err) - assert.True(cht.T(), strings.Contains(err.Error(), util.FallbackToGCSErrMsg)) + assert.True(cht.T(), errors.Is(err, util.ErrFallbackToGCS)) } func (cht *cacheHandleTest) Test_shouldReadFromCache_WithJobStateIsFailed() { @@ -285,7 +284,7 @@ func (cht *cacheHandleTest) Test_shouldReadFromCache_WithJobStateIsFailed() { err := cht.cacheHandle.shouldReadFromCache(&jobStatus, requiredOffset) assert.NotNil(cht.T(), err) - assert.True(cht.T(), strings.Contains(err.Error(), util.InvalidFileDownloadJobErrMsg)) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileDownloadJob)) } func (cht *cacheHandleTest) Test_shouldReadFromCache_WithJobStateIsInvalid() { @@ -296,7 +295,7 @@ func (cht *cacheHandleTest) Test_shouldReadFromCache_WithJobStateIsInvalid() { err := cht.cacheHandle.shouldReadFromCache(&jobStatus, requiredOffset) assert.NotNil(cht.T(), err) - assert.True(cht.T(), strings.Contains(err.Error(), util.InvalidFileDownloadJobErrMsg)) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileDownloadJob)) } func (cht *cacheHandleTest) Test_shouldReadFromCache_WithJobStateIsCompleted() { @@ -319,7 +318,7 @@ func (cht *cacheHandleTest) Test_shouldReadFromCache_WithJobDownloadedOffsetIsLe err := cht.cacheHandle.shouldReadFromCache(&jobStatus, requiredOffset) assert.NotNil(cht.T(), err) - assert.True(cht.T(), strings.Contains(err.Error(), util.FallbackToGCSErrMsg)) + assert.True(cht.T(), errors.Is(err, util.ErrFallbackToGCS)) } func (cht *cacheHandleTest) Test_shouldReadFromCache_WithJobDownloadedOffsetSameAsRequiredOffset() { @@ -354,7 +353,7 @@ func (cht *cacheHandleTest) Test_shouldReadFromCache_WithNonNilJobStatusErr() { err := cht.cacheHandle.shouldReadFromCache(&jobStatus, requiredOffset) assert.NotNil(cht.T(), err) - assert.True(cht.T(), strings.Contains(err.Error(), util.InvalidFileDownloadJobErrMsg)) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileDownloadJob)) } func (cht *cacheHandleTest) Test_validateEntryInFileInfoCache_FileInfoPresent() { @@ -389,8 +388,7 @@ func (cht *cacheHandleTest) Test_validateEntryInFileInfoCache_FileInfoNotPresent _ = cht.cache.Erase(fileInfoKeyName) err = cht.cacheHandle.validateEntryInFileInfoCache(cht.bucket, cht.object, 0, false) - expectedErr := fmt.Errorf("%v: no entry found in file info cache for key %v", util.InvalidFileInfoCacheErrMsg, fileInfoKeyName) - assert.True(cht.T(), strings.Contains(err.Error(), expectedErr.Error())) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileInfoCache)) } func (cht *cacheHandleTest) Test_validateEntryInFileInfoCache_FileInfoGenerationChanged() { @@ -411,8 +409,7 @@ func (cht *cacheHandleTest) Test_validateEntryInFileInfoCache_FileInfoGeneration err = cht.cacheHandle.validateEntryInFileInfoCache(cht.bucket, cht.object, cht.object.Size-1, true) - expectedErr := fmt.Errorf("%v: generation of cached object: %v is different from required generation: ", util.InvalidFileInfoCacheErrMsg, fileInfo.ObjectGeneration) - assert.True(cht.T(), strings.Contains(err.Error(), expectedErr.Error())) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileInfoCache)) } func (cht *cacheHandleTest) Test_validateEntryInFileInfoCache_FileInfoOffsetLessThanRequired() { @@ -434,8 +431,7 @@ func (cht *cacheHandleTest) Test_validateEntryInFileInfoCache_FileInfoOffsetLess err = cht.cacheHandle.validateEntryInFileInfoCache(cht.bucket, cht.object, 11, true) assert.NotNil(cht.T(), err) - expectedErr := fmt.Errorf("%v offset of cached object: %v is less than required offset %v", util.InvalidFileInfoCacheErrMsg, 10, 11) - assert.Equal(cht.T(), expectedErr.Error(), err.Error()) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileInfoCache)) } func (cht *cacheHandleTest) Test_validateEntryInFileInfoCache_changeCacheOrderIsTrue() { @@ -549,7 +545,7 @@ func (cht *cacheHandleTest) Test_Read_WithNilFileHandle() { assert.NotNil(cht.T(), err) assert.Equal(cht.T(), 0, n) assert.False(cht.T(), cacheHit) - assert.Equal(cht.T(), util.InvalidFileHandleErrMsg, err.Error()) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileHandle)) } func (cht *cacheHandleTest) Test_Read_WithNilFileDownloadJobAndCacheMiss() { @@ -565,7 +561,7 @@ func (cht *cacheHandleTest) Test_Read_WithNilFileDownloadJobAndCacheMiss() { assert.NotNil(cht.T(), err) assert.Equal(cht.T(), 0, n) assert.False(cht.T(), cacheHit) - assert.True(cht.T(), strings.Contains(err.Error(), util.InvalidFileInfoCacheErrMsg)) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileInfoCache)) } func (cht *cacheHandleTest) Test_Read_WithNilFileDownloadJobAndCacheHit() { @@ -604,7 +600,7 @@ func (cht *cacheHandleTest) Test_RandomRead() { assert.Equal(cht.T(), 0, n) assert.False(cht.T(), cacheHit) assert.NotNil(cht.T(), err) - assert.True(cht.T(), strings.Contains(err.Error(), util.FallbackToGCSErrMsg)) + assert.True(cht.T(), errors.Is(err, util.ErrFallbackToGCS)) } func (cht *cacheHandleTest) Test_RandomRead_CacheForRangeReadFalse() { @@ -621,7 +617,7 @@ func (cht *cacheHandleTest) Test_RandomRead_CacheForRangeReadFalse() { assert.Equal(cht.T(), n, 0) assert.False(cht.T(), cacheHit) assert.NotNil(cht.T(), err) - assert.True(cht.T(), strings.Contains(err.Error(), util.FallbackToGCSErrMsg)) + assert.True(cht.T(), errors.Is(err, util.ErrFallbackToGCS)) } func (cht *cacheHandleTest) Test_RandomRead_CacheForRangeReadFalseButCacheHit() { @@ -733,7 +729,7 @@ func (cht *cacheHandleTest) Test_SequentialReadToRandom() { _, cacheHit, err = cht.cacheHandle.Read(context.Background(), cht.bucket, cht.object, secondReqOffset, dst) assert.NotNil(cht.T(), err) - assert.True(cht.T(), strings.Contains(err.Error(), util.FallbackToGCSErrMsg)) + assert.True(cht.T(), errors.Is(err, util.ErrFallbackToGCS)) assert.False(cht.T(), cacheHit) assert.False(cht.T(), cht.cacheHandle.isSequential) jobStatus = cht.cacheHandle.fileDownloadJob.GetStatus() @@ -782,8 +778,7 @@ func (cht *cacheHandleTest) Test_Read_FileInfoRemoved() { _, cacheHit, err = cht.cacheHandle.Read(context.Background(), cht.bucket, cht.object, 0, dst) assert.NotNil(cht.T(), err) - expectedErr := fmt.Errorf("%v: no entry found in file info cache for key %v", util.InvalidFileInfoCacheErrMsg, fileInfoKeyName) - assert.True(cht.T(), strings.Contains(err.Error(), expectedErr.Error())) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileInfoCache)) assert.False(cht.T(), cacheHit) } @@ -814,8 +809,7 @@ func (cht *cacheHandleTest) Test_Read_FileInfoGenerationChanged() { _, cacheHit, err = cht.cacheHandle.Read(context.Background(), cht.bucket, cht.object, 0, dst) assert.NotNil(cht.T(), err) - expectedErr := fmt.Errorf("%v: generation of cached object: %v is different from required generation: ", util.InvalidFileInfoCacheErrMsg, fileInfoData.ObjectGeneration) - assert.True(cht.T(), strings.Contains(err.Error(), expectedErr.Error())) + assert.True(cht.T(), errors.Is(err, util.ErrInvalidFileInfoCache)) assert.False(cht.T(), cacheHit) } @@ -875,7 +869,7 @@ func (cht *cacheHandleTest) Test_SequentialRead_Parallel_Download_True() { assert.Equal(cht.T(), downloader.Downloading, jobStatus.Name) assert.Equal(cht.T(), 0, n) assert.False(cht.T(), cacheHit) - assert.ErrorContains(cht.T(), err, util.FallbackToGCSErrMsg) + assert.True(cht.T(), errors.Is(err, util.ErrFallbackToGCS)) } func (cht *cacheHandleTest) Test_RandomRead_Parallel_Download_True() { @@ -910,7 +904,7 @@ func (cht *cacheHandleTest) Test_RandomRead_Parallel_Download_True() { assert.Equal(cht.T(), downloader.Downloading, jobStatus.Name) assert.Equal(cht.T(), 0, n) assert.False(cht.T(), cacheHit) - assert.ErrorContains(cht.T(), err, util.FallbackToGCSErrMsg) + assert.True(cht.T(), errors.Is(err, util.ErrFallbackToGCS)) } func (cht *cacheHandleTest) Test_RandomRead_CacheForRangeReadFalse_And_ParallelDownloadsEnabled() { @@ -945,5 +939,5 @@ func (cht *cacheHandleTest) Test_RandomRead_CacheForRangeReadFalse_And_ParallelD assert.Less(cht.T(), jobStatus.Offset, offset) assert.Equal(cht.T(), n, 0) assert.False(cht.T(), cacheHit) - assert.ErrorContains(cht.T(), err, util.FallbackToGCSErrMsg) + assert.True(cht.T(), errors.Is(err, util.ErrFallbackToGCS)) } diff --git a/internal/cache/file/cache_handler.go b/internal/cache/file/cache_handler.go index 1c85e86640..e56fb73c35 100644 --- a/internal/cache/file/cache_handler.go +++ b/internal/cache/file/cache_handler.go @@ -128,7 +128,7 @@ func (chr *CacheHandler) addFileInfoEntryAndCreateDownloadJob(object *gcs.MinObj filePath := util.GetDownloadPath(chr.cacheDir, util.GetObjectPath(bucket.Name(), object.Name)) _, err := os.Stat(filePath) if err != nil && os.IsNotExist(err) { - return fmt.Errorf("addFileInfoEntryAndCreateDownloadJob: %s: %s", util.FileNotPresentInCacheErrMsg, filePath) + return fmt.Errorf("addFileInfoEntryAndCreateDownloadJob: %w: %s", util.ErrFileNotPresentInCache, filePath) } // Evict object in cache if the generation of object in cache is different @@ -217,7 +217,7 @@ func (chr *CacheHandler) GetCacheHandle(object *gcs.MinObject, bucket gcs.Bucket fileInfo := chr.fileInfoCache.LookUpWithoutChangingOrder(fileInfoKeyName) if fileInfo == nil { - return nil, fmt.Errorf("addFileInfoEntryAndCreateDownloadJob: %s", util.CacheHandleNotRequiredForRandomReadErrMsg) + return nil, fmt.Errorf("addFileInfoEntryAndCreateDownloadJob: %w", util.ErrCacheHandleNotRequiredForRandomRead) } } diff --git a/internal/cache/file/cache_handler_test.go b/internal/cache/file/cache_handler_test.go index 89ab8ffb24..2da69a15c1 100644 --- a/internal/cache/file/cache_handler_test.go +++ b/internal/cache/file/cache_handler_test.go @@ -17,6 +17,7 @@ package file import ( "context" "crypto/rand" + "errors" "os" "path" "strconv" @@ -332,7 +333,7 @@ func Test_addFileInfoEntryAndCreateDownloadJob_IfLocalFileGetsDeleted(t *testing // Hence, this will return error containing util.FileNotPresentInCacheErrMsg. err = chTestArgs.cacheHandler.addFileInfoEntryAndCreateDownloadJob(chTestArgs.object, chTestArgs.bucket) - assert.ErrorContains(t, err, util.FileNotPresentInCacheErrMsg) + assert.True(t, errors.Is(err, util.ErrFileNotPresentInCache)) } func Test_addFileInfoEntryAndCreateDownloadJob_WhenJobHasCompleted(t *testing.T) { @@ -530,7 +531,7 @@ func Test_GetCacheHandle_IfLocalFileGetsDeleted(t *testing.T) { cacheHandle, err := chTestArgs.cacheHandler.GetCacheHandle(chTestArgs.object, chTestArgs.bucket, false, 0) - assert.ErrorContains(t, err, util.FileNotPresentInCacheErrMsg) + assert.True(t, errors.Is(err, util.ErrFileNotPresentInCache)) assert.Nil(t, cacheHandle) // Check file info and download job are not removed assert.True(t, isEntryInFileInfoCache(t, chTestArgs.cache, chTestArgs.object.Name, chTestArgs.bucket.Name())) @@ -577,7 +578,7 @@ func Test_GetCacheHandle_CacheForRangeRead(t *testing.T) { assert.NoError(t, err1) assert.Nil(t, cacheHandle1.validateCacheHandle()) - assert.ErrorContains(t, err2, util.CacheHandleNotRequiredForRandomReadErrMsg) + assert.True(t, errors.Is(err2, util.ErrCacheHandleNotRequiredForRandomRead)) assert.Nil(t, cacheHandle2) assert.NoError(t, err3) assert.Nil(t, cacheHandle3.validateCacheHandle()) diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index 305e7ae48d..6b230e84dd 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -22,7 +22,6 @@ import ( "io/fs" "os" "reflect" - "strings" "syscall" "github.com/googlecloudplatform/gcsfuse/v2/cfg" @@ -440,7 +439,7 @@ func (job *Job) downloadObjectAsync() { // downloading. If the entry is deleted in between which is expected // to happen at the time of eviction, then the job should be // marked Invalid instead of Failed. - if strings.Contains(err.Error(), lru.EntryNotExistErrMsg) { + if errors.Is(err, lru.ErrEntryNotExist) { job.updateStatusAndNotifySubscribers(Invalid, err) return } diff --git a/internal/cache/file/downloader/job_test.go b/internal/cache/file/downloader/job_test.go index eae1681843..dcfb0afd6e 100644 --- a/internal/cache/file/downloader/job_test.go +++ b/internal/cache/file/downloader/job_test.go @@ -81,7 +81,7 @@ func (dt *downloaderTest) initJobTest(objectName string, objectContent []byte, s } func (dt *downloaderTest) verifyInvalidError(err error) { - AssertTrue((nil == err) || (errors.Is(err, context.Canceled)) || (strings.Contains(err.Error(), lru.EntryNotExistErrMsg)), + AssertTrue((nil == err) || (errors.Is(err, context.Canceled)) || errors.Is(err, lru.ErrEntryNotExist), fmt.Sprintf("actual error:%v is not as expected", err)) } @@ -287,7 +287,7 @@ func (dt *downloaderTest) Test_updateStatusOffset_InsertNew() { err = dt.job.updateStatusOffset(10) AssertNe(nil, err) - AssertTrue(strings.Contains(err.Error(), lru.EntryNotExistErrMsg)) + AssertTrue(errors.Is(err, lru.ErrEntryNotExist)) // Confirm job's status offset AssertEq(0, dt.job.status.Offset) } @@ -313,7 +313,7 @@ func (dt *downloaderTest) Test_updateStatusOffset_Fail() { err = dt.job.updateStatusOffset(15) AssertNe(nil, err) - AssertTrue(strings.Contains(err.Error(), lru.InvalidUpdateEntrySizeErrorMsg)) + AssertTrue(errors.Is(err, lru.ErrInvalidUpdateEntrySize)) // Confirm job's status offset AssertEq(0, dt.job.status.Offset) } @@ -581,7 +581,7 @@ func (dt *downloaderTest) Test_Download_WhenAsyncFails() { // Verify that jobStatus is failed AssertEq(Failed, jobStatus.Name) AssertGe(jobStatus.Offset, 0) - AssertTrue(strings.Contains(jobStatus.Err.Error(), lru.InvalidUpdateEntrySizeErrorMsg)) + AssertTrue(errors.Is(jobStatus.Err, lru.ErrInvalidUpdateEntrySize)) // Verify callback is executed AssertTrue(callbackExecuted.Load()) } @@ -592,7 +592,7 @@ func (dt *downloaderTest) Test_Download_AlreadyFailed() { objectContent := testutil.GenerateRandomBytes(objectSize) dt.initJobTest(objectName, objectContent, DefaultSequentialReadSizeMb, uint64(objectSize), func() {}) dt.job.mu.Lock() - dt.job.status = JobStatus{Failed, fmt.Errorf(lru.InvalidUpdateEntrySizeErrorMsg), 8} + dt.job.status = JobStatus{Failed, lru.ErrInvalidUpdateEntrySize, 8} dt.job.mu.Unlock() // Requesting again from download job which is in failed state @@ -600,7 +600,7 @@ func (dt *downloaderTest) Test_Download_AlreadyFailed() { AssertEq(nil, err) AssertEq(Failed, jobStatus.Name) - AssertTrue(strings.Contains(jobStatus.Err.Error(), lru.InvalidUpdateEntrySizeErrorMsg)) + AssertTrue(errors.Is(jobStatus.Err, lru.ErrInvalidUpdateEntrySize)) } func (dt *downloaderTest) Test_Download_AlreadyInvalid() { diff --git a/internal/cache/lru/lru.go b/internal/cache/lru/lru.go index 611512a34f..84ede6021d 100644 --- a/internal/cache/lru/lru.go +++ b/internal/cache/lru/lru.go @@ -24,12 +24,12 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" ) -// Predefined error messages returned by the Cache. -const ( - InvalidEntrySizeErrorMsg = "size of the entry is more than the cache's maxSize" - InvalidEntryErrorMsg = "nil values are not supported" - InvalidUpdateEntrySizeErrorMsg = "size of entry to be updated is not same as existing size" - EntryNotExistErrMsg = "entry with given key does not exist" +// Predefined errors returned by the Cache. +var ( + ErrInvalidEntrySize = errors.New("size of the entry is more than the cache's maxSize") + ErrInvalidEntry = errors.New("nil values are not supported") + ErrInvalidUpdateEntrySize = errors.New("size of entry to be updated is not same as existing size") + ErrEntryNotExist = errors.New("entry with given key does not exist") ) // Cache is a LRU cache for any lru.ValueType indexed by string keys. @@ -149,12 +149,12 @@ func (c *Cache) Insert( key string, value ValueType) ([]ValueType, error) { if value == nil { - return nil, errors.New(InvalidEntryErrorMsg) + return nil, ErrInvalidEntry } valueSize := value.Size() if valueSize > c.maxSize { - return nil, errors.New(InvalidEntrySizeErrorMsg) + return nil, ErrInvalidEntrySize } c.mu.Lock() @@ -248,7 +248,7 @@ func (c *Cache) UpdateWithoutChangingOrder( key string, value ValueType) error { if value == nil { - return errors.New(InvalidEntryErrorMsg) + return ErrInvalidEntry } c.mu.Lock() @@ -256,11 +256,11 @@ func (c *Cache) UpdateWithoutChangingOrder( e, ok := c.index[key] if !ok { - return errors.New(EntryNotExistErrMsg) + return ErrEntryNotExist } if value.Size() != e.Value.(entry).Value.Size() { - return errors.New(InvalidUpdateEntrySizeErrorMsg) + return ErrInvalidUpdateEntrySize } e.Value = entry{key, value} diff --git a/internal/cache/lru/lru_test.go b/internal/cache/lru/lru_test.go index 80128f3613..bac7beb17c 100644 --- a/internal/cache/lru/lru_test.go +++ b/internal/cache/lru/lru_test.go @@ -17,7 +17,6 @@ package lru_test import ( "errors" "math/rand" - "strings" "sync" "testing" @@ -81,7 +80,7 @@ func (t *CacheTest) LookUpInEmptyCache() { } func (t *CacheTest) InsertNilValue() { - t.insertAndAssert("taco", nil, []int64{}, errors.New(lru.InvalidEntryErrorMsg)) + t.insertAndAssert("taco", nil, []int64{}, lru.ErrInvalidEntry) } func (t *CacheTest) LookUpUnknownKey() { @@ -155,7 +154,7 @@ func (t *CacheTest) TestWhenEntrySizeMoreThanCacheMaxSize() { t.insertAndAssert("burrito", testData{Value: 23, DataSize: 4}, []int64{}, nil) // Insert entry with size greater than maxSize of cache. - t.insertAndAssert("taco", testData{Value: 26, DataSize: MaxSize + 1}, []int64{}, errors.New(lru.InvalidEntrySizeErrorMsg)) + t.insertAndAssert("taco", testData{Value: 26, DataSize: MaxSize + 1}, []int64{}, lru.ErrInvalidEntrySize) ExpectEq(23, t.cache.LookUp("burrito").(testData).Value) } @@ -242,7 +241,7 @@ func (t *CacheTest) TestUpdateWhenKeyNotPresent() { err := t.cache.UpdateWithoutChangingOrder(key, data) ExpectNe(nil, err) - ExpectTrue(strings.Contains(err.Error(), lru.EntryNotExistErrMsg)) + ExpectTrue(errors.Is(err, lru.ErrEntryNotExist)) } func (t *CacheTest) TestUpdateWhenSizeIsDifferent() { @@ -254,7 +253,7 @@ func (t *CacheTest) TestUpdateWhenSizeIsDifferent() { err := t.cache.UpdateWithoutChangingOrder(key, newData) ExpectNe(nil, err) - ExpectTrue(strings.Contains(err.Error(), lru.InvalidUpdateEntrySizeErrorMsg)) + ExpectTrue(errors.Is(err, lru.ErrInvalidUpdateEntrySize)) } func (t *CacheTest) TestUpdateNotChangeOrder() { diff --git a/internal/cache/util/util.go b/internal/cache/util/util.go index 0b0cbe9e08..3e151b3ec6 100644 --- a/internal/cache/util/util.go +++ b/internal/cache/util/util.go @@ -23,7 +23,6 @@ import ( "os" "path" "path/filepath" - "strings" "unsafe" "github.com/googlecloudplatform/gcsfuse/v2/cfg" @@ -31,15 +30,15 @@ import ( "github.com/jacobsa/fuse/fsutil" ) -const ( - InvalidFileHandleErrMsg = "invalid file handle" - InvalidFileDownloadJobErrMsg = "invalid download job" - InvalidFileInfoCacheErrMsg = "invalid file info cache" - ErrInSeekingFileHandleMsg = "error while seeking file handle" - ErrInReadingFileHandleMsg = "error while reading file handle" - FallbackToGCSErrMsg = "read via gcs" - FileNotPresentInCacheErrMsg = "file is not present in cache" - CacheHandleNotRequiredForRandomReadErrMsg = "cacheFileForRangeRead is false, read type random read and fileInfo entry is absent" +var ( + ErrInvalidFileHandle = errors.New("invalid file handle") + ErrInvalidFileDownloadJob = errors.New("invalid download job") + ErrInvalidFileInfoCache = errors.New("invalid file info cache") + ErrInSeekingFileHandle = errors.New("error while seeking file handle") + ErrInReadingFileHandle = errors.New("error while reading file handle") + ErrFallbackToGCS = errors.New("read via gcs") + ErrFileNotPresentInCache = errors.New("file is not present in cache") + ErrCacheHandleNotRequiredForRandomRead = errors.New("cacheFileForRangeRead is false, read type random read and fileInfo entry is absent") ) const ( @@ -99,11 +98,11 @@ func GetDownloadPath(cacheDir string, objectPath string) string { // If it's invalid then we should close that cacheHandle and create new cacheHandle // for next call onwards. func IsCacheHandleInvalid(readErr error) bool { - return strings.Contains(readErr.Error(), InvalidFileHandleErrMsg) || - strings.Contains(readErr.Error(), InvalidFileDownloadJobErrMsg) || - strings.Contains(readErr.Error(), InvalidFileInfoCacheErrMsg) || - strings.Contains(readErr.Error(), ErrInSeekingFileHandleMsg) || - strings.Contains(readErr.Error(), ErrInReadingFileHandleMsg) + return errors.Is(readErr, ErrInvalidFileHandle) || + errors.Is(readErr, ErrInvalidFileDownloadJob) || + errors.Is(readErr, ErrInvalidFileInfoCache) || + errors.Is(readErr, ErrInSeekingFileHandle) || + errors.Is(readErr, ErrInReadingFileHandle) } // CreateCacheDirectoryIfNotPresentAt Creates directory at given path with diff --git a/internal/cache/util/util_test.go b/internal/cache/util/util_test.go index 6864c09416..76ac44b5dd 100644 --- a/internal/cache/util/util_test.go +++ b/internal/cache/util/util_test.go @@ -228,27 +228,27 @@ func (ut *utilTest) Test_getDownloadPath() { } func (ut *utilTest) Test_IsCacheHandleValid_True() { - errMessages := []string{ - InvalidFileHandleErrMsg + "test", - InvalidFileDownloadJobErrMsg + "test", - InvalidFileInfoCacheErrMsg + "test", - ErrInSeekingFileHandleMsg + "test", - ErrInReadingFileHandleMsg + "test", + errs := []error{ + fmt.Errorf("%w: %s", ErrInvalidFileHandle, "test"), + fmt.Errorf("%w: %s", ErrInvalidFileDownloadJob, "test"), + fmt.Errorf("%w: %s", ErrInvalidFileInfoCache, "test"), + fmt.Errorf("%w: %s", ErrInSeekingFileHandle, "test"), + fmt.Errorf("%w: %s", ErrInReadingFileHandle, "test"), } - for _, errMsg := range errMessages { - ExpectTrue(IsCacheHandleInvalid(errors.New(errMsg))) + for _, err := range errs { + ExpectTrue(IsCacheHandleInvalid(err)) } } func (ut *utilTest) Test_IsCacheHandleValid_False() { - errMessages := []string{ - FallbackToGCSErrMsg + "test", - "random error message", + errs := []error{ + fmt.Errorf("%w: %s", ErrFallbackToGCS, "test"), + fmt.Errorf("random error message"), } - for _, errMsg := range errMessages { - ExpectFalse(IsCacheHandleInvalid(errors.New(errMsg))) + for _, err := range errs { + ExpectFalse(IsCacheHandleInvalid(err)) } } diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 0b8d7f3d71..82f1aa36cf 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -20,7 +20,6 @@ import ( "io" "math" "strconv" - "strings" "time" "github.com/google/uuid" @@ -260,10 +259,10 @@ func (rr *randomReader) tryReadingFromFileCache(ctx context.Context, rr.fileCacheHandle, err = rr.fileCacheHandler.GetCacheHandle(rr.object, rr.bucket, rr.cacheFileForRangeRead, offset) if err != nil { // We fall back to GCS if file size is greater than the cache size - if strings.Contains(err.Error(), lru.InvalidEntrySizeErrorMsg) { + if errors.Is(err, lru.ErrInvalidEntrySize) { logger.Warnf("tryReadingFromFileCache: while creating CacheHandle: %v", err) return 0, false, nil - } else if strings.Contains(err.Error(), cacheutil.CacheHandleNotRequiredForRandomReadErrMsg) { + } else if errors.Is(err, cacheutil.ErrCacheHandleNotRequiredForRandomRead) { // Fall back to GCS if it is a random read, cacheFileForRangeRead is // False and there doesn't already exist file in cache. isSeq = false @@ -289,7 +288,7 @@ func (rr *randomReader) tryReadingFromFileCache(ctx context.Context, logger.Warnf("tryReadingFromFileCache: while closing fileCacheHandle: %v", err) } rr.fileCacheHandle = nil - } else if !strings.Contains(err.Error(), cacheutil.FallbackToGCSErrMsg) { + } else if !errors.Is(err, cacheutil.ErrFallbackToGCS) { err = fmt.Errorf("tryReadingFromFileCache: while reading via cache: %w", err) return } diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index e045ade8e7..7902e24887 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -907,7 +907,7 @@ func (t *RandomReaderTest) Test_ReadAt_IfCacheFileGetsDeleted() { _, err = t.rr.ReadAt(buf, 0) AssertNe(nil, err) - ExpectTrue(strings.Contains(err.Error(), util.FileNotPresentInCacheErrMsg)) + AssertTrue(errors.Is(err, util.ErrFileNotPresentInCache)) } func (t *RandomReaderTest) Test_ReadAt_IfCacheFileGetsDeletedWithCacheHandleOpen() { From 64b0727c8599294abbeb2bb3c5adc3496227c831 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 10 Apr 2025 11:16:43 +0000 Subject: [PATCH 0343/1298] Fix and enable package implicit_dir in e2e tests for rapid buckets (#3143) * disable sw upgrades and installations * disable all but implicit_dir package for zb e2e tests * Revert "disable all but implicit_dir package for zb e2e tests" This reverts commit dde2e2d432b573dfb18b064de7f2d1483e6e72db. * Revert "disable sw upgrades and installations" This reverts commit d1c8023230266d54ecc6a48596c47a0e908ed373. * Use storage-client instead of gcloud in implicit_dir package skip bucket-name from testDir * enable implicit_dir package in zb e2e tests * empty commit * address some self-review comments * remove returned error from new functions * add missed error string in the error logs * minor code shortening * minor fixes * empty commit * fix typo in tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go * self-review * empty commit --- .../implicit_dir/delete_test.go | 37 ++++++++++-- .../implicit_dir/list_test.go | 7 ++- tools/integration_tests/run_e2e_tests.sh | 2 +- .../implicit_and_explicit_dir_setup.go | 60 +++++++++++++++++++ 4 files changed, 98 insertions(+), 8 deletions(-) diff --git a/tools/integration_tests/implicit_dir/delete_test.go b/tools/integration_tests/implicit_dir/delete_test.go index d403fdfedf..53e84c1a08 100644 --- a/tools/integration_tests/implicit_dir/delete_test.go +++ b/tools/integration_tests/implicit_dir/delete_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" ) @@ -31,7 +32,11 @@ import ( func TestDeleteNonEmptyImplicitDir(t *testing.T) { testDirName := "testDeleteNonEmptyImplicitDir" testDirPath := setupTestDir(testDirName) - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) + if setup.IsZonalBucketRun() { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + } else { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) + } dirPath := path.Join(testDirPath, implicit_and_explicit_dir_setup.ImplicitDirectory) @@ -46,7 +51,11 @@ func TestDeleteNonEmptyImplicitDir(t *testing.T) { func TestDeleteNonEmptyImplicitSubDir(t *testing.T) { testDirName := "testDeleteNonEmptyImplicitSubDir" testDirPath := setupTestDir(testDirName) - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) + if setup.IsZonalBucketRun() { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + } else { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) + } subDirPath := path.Join(testDirPath, implicit_and_explicit_dir_setup.ImplicitDirectory, implicit_and_explicit_dir_setup.ImplicitSubDirectory) @@ -63,7 +72,11 @@ func TestDeleteNonEmptyImplicitSubDir(t *testing.T) { func TestDeleteImplicitDirWithExplicitSubDir(t *testing.T) { testDirName := "testDeleteImplicitDirWithExplicitSubDir" testDirPath := setupTestDir(testDirName) - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) + if setup.IsZonalBucketRun() { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + } else { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) + } explicitDirPath := path.Join(testDirPath, implicit_and_explicit_dir_setup.ImplicitDirectory, ExplicitDirInImplicitDir) @@ -84,7 +97,11 @@ func TestDeleteImplicitDirWithExplicitSubDir(t *testing.T) { func TestDeleteImplicitDirWithImplicitSubDirContainingExplicitDir(t *testing.T) { testDirName := "testDeleteImplicitDirWithImplicitSubDirContainingExplicitDir" testDirPath := setupTestDir(testDirName) - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) + if setup.IsZonalBucketRun() { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + } else { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) + } explicitDirPath := path.Join(testDirPath, implicit_and_explicit_dir_setup.ImplicitDirectory, implicit_and_explicit_dir_setup.ImplicitSubDirectory, ExplicitDirInImplicitSubDir) operations.CreateDirectoryWithNFiles(NumberOfFilesInExplicitDirInImplicitSubDir, explicitDirPath, PrefixFileInExplicitDirInImplicitSubDir, t) @@ -106,7 +123,11 @@ func TestDeleteImplicitDirWithImplicitSubDirContainingExplicitDir(t *testing.T) func TestDeleteImplicitDirInExplicitDir(t *testing.T) { testDirName := "testDeleteImplicitDirInExplicitDir" testDirPath := setupTestDir(testDirName) - implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName), t) + if setup.IsZonalBucketRun() { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + } else { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName), t) + } dirPath := path.Join(testDirPath, implicit_and_explicit_dir_setup.ExplicitDirectory, implicit_and_explicit_dir_setup.ImplicitDirectory) @@ -125,7 +146,11 @@ func TestDeleteImplicitDirInExplicitDir(t *testing.T) { func TestDeleteExplicitDirContainingImplicitSubDir(t *testing.T) { testDirName := "testDeleteExplicitDirContainingImplicitSubDir" testDirPath := setupTestDir(testDirName) - implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName), t) + if setup.IsZonalBucketRun() { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + } else { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName), t) + } dirPath := path.Join(testDirPath, implicit_and_explicit_dir_setup.ExplicitDirectory) diff --git a/tools/integration_tests/implicit_dir/list_test.go b/tools/integration_tests/implicit_dir/list_test.go index a9addd94fe..48e382ab35 100644 --- a/tools/integration_tests/implicit_dir/list_test.go +++ b/tools/integration_tests/implicit_dir/list_test.go @@ -23,6 +23,7 @@ import ( "path/filepath" "testing" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" ) @@ -39,7 +40,11 @@ func TestListImplicitObjectsFromBucket(t *testing.T) { // testBucket/dirForImplicitDirTests/testDir/explicitDirectory/fileInExplicitDir1 -- File // testBucket/dirForImplicitDirTests/testDir/explicitDirectory/fileInExplicitDir2 -- File - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) + if setup.IsZonalBucketRun() { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + } else { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) + } implicit_and_explicit_dir_setup.CreateExplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName), t) err := filepath.WalkDir(testDirPath, func(path string, dir fs.DirEntry, err error) error { diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 46836780fb..239ddb4da1 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -124,7 +124,7 @@ TEST_DIR_PARALLEL_FOR_ZB=( # "concurrent_operations" # "explicit_dir" "gzip" - # "implicit_dir" + "implicit_dir" "interrupt" "kernel_list_cache" "list_large_dir" diff --git a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go index d20879c4da..75614ba0dd 100644 --- a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go +++ b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go @@ -15,11 +15,15 @@ package implicit_and_explicit_dir_setup import ( + "context" "log" "os" "path" + "strings" "testing" + storage "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/persistent_mounting" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" @@ -74,6 +78,45 @@ func RemoveAndCheckIfDirIsDeleted(dirPath string, dirName string, t *testing.T) } } +// createTestdataObjectsUsingStorageClient is equivalent of the script tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/testdata/create_objects.sh . +// That script uses gcloud, but this function instead uses go client library. +// Note: testDirWithBucketName is of the form /. +func createTestdataObjectsUsingStorageClient(ctx context.Context, t *testing.T, storageClient *storage.Client, testDirWithBucketName string) { + t.Helper() + + idx := strings.Index(testDirWithBucketName, "/") + if idx <= 0 { + t.Fatalf("Unexpected testDirWithBucketName: %q. Expected form: /", testDirWithBucketName) + } + bucketName := testDirWithBucketName[:idx] + testDirWithoutBucketName := testDirWithBucketName[idx+1:] + + objectName := path.Join(testDirWithoutBucketName, "implicitDirectory", "fileInImplicitDir1") + err := client.CreateObjectOnGCS(ctx, storageClient, objectName, "This is from directory fileInImplicitDir1 file implicitDirectory") + if err != nil { + t.Fatalf("Failed to create GCS object %q in bucket %q: %v", objectName, bucketName, err) + } + + objectName = path.Join(testDirWithoutBucketName, "implicitDirectory/implicitSubDirectory", "fileInImplicitDir2") + err = client.CreateObjectOnGCS(ctx, storageClient, objectName, "This is from directory implicitDirectory/implicitSubDirectory file fileInImplicitDir2") + if err != nil { + t.Fatalf("Failed to create GCS object %q in bucket %q: %v", objectName, bucketName, err) + } +} + +func CreateImplicitDirectoryStructureUsingStorageClient(ctx context.Context, t *testing.T, storageClient *storage.Client, testDir string) { + t.Helper() + + // Implicit Directory Structure + // testBucket/testDir/implicitDirectory -- Dir + // testBucket/testDir/implicitDirectory/fileInImplicitDir1 -- File + // testBucket/testDir/implicitDirectory/implicitSubDirectory -- Dir + // testBucket/testDir/implicitDirectory/implicitSubDirectory/fileInImplicitDir2 -- File + + // Create implicit directory in bucket for testing. + createTestdataObjectsUsingStorageClient(ctx, t, storageClient, path.Join(setup.TestBucket(), testDir)) +} + func CreateImplicitDirectoryStructure(testDir string) { // Implicit Directory Structure // testBucket/testDir/implicitDirectory -- Dir @@ -118,3 +161,20 @@ func CreateImplicitDirectoryInExplicitDirectoryStructure(testDir string, t *test dirPathInBucket := path.Join(setup.TestBucket(), testDir, ExplicitDirectory) setup.RunScriptForTestData("../util/setup/implicit_and_explicit_dir_setup/testdata/create_objects.sh", dirPathInBucket) } + +func CreateImplicitDirectoryInExplicitDirectoryStructureUsingStorageClient(ctx context.Context, t *testing.T, storageClient *storage.Client, testDir string) { + t.Helper() + + // testBucket/testDir/explicitDirectory -- Dir + // testBucket/testDir/explictFile -- File + // testBucket/testDir/explicitDirectory/fileInExplicitDir1 -- File + // testBucket/testDir/explicitDirectory/fileInExplicitDir2 -- File + // testBucket/testDir/explicitDirectory/implicitDirectory -- Dir + // testBucket/testDir/explicitDirectory/implicitDirectory/fileInImplicitDir1 -- File + // testBucket/testDir/explicitDirectory/implicitDirectory/implicitSubDirectory -- Dir + // testBucket/testDir/explicitDirectory/implicitDirectory/implicitSubDirectory/fileInImplicitDir2 -- File + + CreateExplicitDirectoryStructure(testDir, t) + dirPathInBucket := path.Join(setup.TestBucket(), testDir, ExplicitDirectory) + createTestdataObjectsUsingStorageClient(ctx, t, storageClient, dirPathInBucket) +} From 93e58cde845c7e4a2841ffa855c0f6a3f5837ccc Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 10 Apr 2025 11:49:10 +0000 Subject: [PATCH 0344/1298] Enable e2e package concurrent_operations for ZB (#3176) --- tools/integration_tests/run_e2e_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 239ddb4da1..9fcc3d4787 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -121,7 +121,7 @@ TEST_DIR_NON_PARALLEL=( # pass for zonal buckets. TEST_DIR_PARALLEL_FOR_ZB=( "benchmarking" - # "concurrent_operations" + "concurrent_operations" # "explicit_dir" "gzip" "implicit_dir" From 161795e48fc34c183287d400177e9d126e24b9a6 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Thu, 10 Apr 2025 17:33:08 +0530 Subject: [PATCH 0345/1298] Fix: grpc_validation test suite should be skipped in presubmits (#3177) * cleanup * skip tests in presubmits --- tools/integration_tests/grpc_validation/setup_test.go | 4 ++-- tools/integration_tests/util/operations/file_operations.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/grpc_validation/setup_test.go b/tools/integration_tests/grpc_validation/setup_test.go index 30beda9ff8..b6b615233c 100644 --- a/tools/integration_tests/grpc_validation/setup_test.go +++ b/tools/integration_tests/grpc_validation/setup_test.go @@ -137,12 +137,12 @@ func createTestBucket(testBucketRegion, testBucketName string) (err error) { func TestMain(m *testing.M) { // Parse flags from the setup. + var err error + setup.ParseSetUpFlags() if setup.IsPresubmitRun() { log.Println("Skipping test package : grpc_validation since this is a presubmit test run") os.Exit(0) } - var err error - setup.ParseSetUpFlags() if err := setup.SetUpTestDir(); err != nil { log.Fatalf("Failed to setup GCSFuse package. Error: %v", err) } diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 1f76948901..3ffc6ac55f 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -785,7 +785,6 @@ func CheckLogFileForMessage(t *testing.T, expectedLog, logFile string) bool { file, err := os.Open(logFile) require.NoError(t, err, "Failed to open log file") defer file.Close() - t.Logf("logfile name %s expectedLog %s", logFile, expectedLog) scanner := bufio.NewScanner(file) for scanner.Scan() { if strings.Contains(scanner.Text(), expectedLog) { From e9b686f540c5191687d73bf33cb7dbfb1b08e05d Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Thu, 10 Apr 2025 20:34:35 +0530 Subject: [PATCH 0346/1298] Correcting the periodic test failure for grpc_validation package (#3178) * incorrect test Region being passed * adding link to reference --- .../grpc_validation/grpc_validation_test.go | 4 ++-- .../integration_tests/grpc_validation/setup_test.go | 13 +++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tools/integration_tests/grpc_validation/grpc_validation_test.go b/tools/integration_tests/grpc_validation/grpc_validation_test.go index e231a7f161..7391b1e4dd 100644 --- a/tools/integration_tests/grpc_validation/grpc_validation_test.go +++ b/tools/integration_tests/grpc_validation/grpc_validation_test.go @@ -52,9 +52,9 @@ func (g *gRPCValidation) SetupSuite() { // Based on the test region which is initialized in the TestMain() function, // we will pick out the test region for: singleRegionForGRPCSuccess := findSingleRegionForGRPCDirectPathSuccessCase(testRegion) - singleRegionForGRPCFailure := pickFailureRegionFromListOfRegions(singleRegionForGRPCSuccess, single_regions) + singleRegionForGRPCFailure := pickFailureRegionFromListOfRegions(singleRegionForGRPCSuccess, singleRegions) multiRegionForGRPCSuccess := findMultiRegionForGRPCDirectPathSuccessCase(testRegion) - multiRegionForGRPCFailure := pickFailureRegionFromListOfRegions(multiRegionForGRPCSuccess, multi_regions) + multiRegionForGRPCFailure := pickFailureRegionFromListOfRegions(multiRegionForGRPCSuccess, multiRegions) // Set the test bucket names with unique suffix g.singleRegionBucketForGRPCSuccess = createTestBucketName(singleRegionForGRPCSuccess) diff --git a/tools/integration_tests/grpc_validation/setup_test.go b/tools/integration_tests/grpc_validation/setup_test.go index b6b615233c..a0bf3127a1 100644 --- a/tools/integration_tests/grpc_validation/setup_test.go +++ b/tools/integration_tests/grpc_validation/setup_test.go @@ -40,7 +40,7 @@ import ( // Example case: Test Region : us-central1 , Failure Test Region : europe-west4 // // Test Region : us-west1, Failure test Region : us-central1 -var single_regions = []string{ +var singleRegions = []string{ "us-central1", "europe-west4", } @@ -48,11 +48,11 @@ var single_regions = []string{ // gRPC is now supported for multi region buckets as well. // Same logic, if the test VM is in us, then failure region = eu // If the test VM is in non-US, then failure region = us -var multi_regions = []string{ +var multiRegions = []string{ "us", "eu", } -var gcp_project = "gcs-fuse-test" +var gcpProject = "gcs-fuse-test" var ( ctx context.Context client *storage.Client @@ -69,6 +69,7 @@ var cloudtopProd = "cloudtop-prod" // For both multi region buckets and single region buckets test, we need to decide failure case // region based on the region of the VM on which the test is running. +// Reference for the GCP resource attribute: https://opentelemetry.io/docs/specs/semconv/attributes-registry/cloud/#cloud-availability-zone func findTestExecutionEnvironment(ctx context.Context) (string, error) { detectedAttrs, err := resource.New(ctx, resource.WithDetectors(gcp.NewDetector())) if err != nil { @@ -79,7 +80,7 @@ func findTestExecutionEnvironment(ctx context.Context) (string, error) { if v, exists := attrs.Value("gcp.gce.instance.hostname"); exists && strings.Contains(strings.ToLower(v.AsString()), cloudtopProd) { return cloudtopProd, nil } - if v, exists := attrs.Value("cloud.region"); exists { + if v, exists := attrs.Value("cloud.availability_zone"); exists { return v.AsString(), nil } return "", nil @@ -128,7 +129,7 @@ func createTestBucketName(region string) string { // Based on what region we pass, the test bucket will be multi region or single region. func createTestBucket(testBucketRegion, testBucketName string) (err error) { bucket := client.Bucket(testBucketName) - if err = bucket.Create(ctx, gcp_project, &storage.BucketAttrs{Location: testBucketRegion}); err != nil { + if err = bucket.Create(ctx, gcpProject, &storage.BucketAttrs{Location: testBucketRegion}); err != nil { log.Printf("Error while creating bucket, error: %v", err) return err } @@ -154,7 +155,7 @@ func TestMain(m *testing.M) { } defer client.Close() - testRegion, err := findTestExecutionEnvironment(ctx) + testRegion, err = findTestExecutionEnvironment(ctx) if err != nil { log.Fatalf("Failed to retrieve test VM region: %v", err) } From 5e86a6e0a2796f272e7ca2c83631f19765c25553 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 10 Apr 2025 23:05:39 +0530 Subject: [PATCH 0347/1298] fix read large file tests (#3180) --- tools/integration_tests/util/operations/file_operations.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 3ffc6ac55f..e0d4eb0eb3 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -747,7 +747,7 @@ func CreateLocalTempFile(content string, gzipCompress bool) (string, error) { // ReadAndCompare reads content from the given file paths and compares them. func ReadAndCompare(t *testing.T, filePathInMntDir string, filePathInLocalDisk string, offset int64, chunkSize int64) { t.Helper() - mountContents, err := ReadChunkFromFile(filePathInMntDir, chunkSize, offset, os.O_RDONLY) + mountContents, err := ReadChunkFromFile(filePathInMntDir, chunkSize, offset, os.O_RDONLY|syscall.O_DIRECT) if err != nil { t.Fatalf("error in read file from mounted directory :%d", err) } From be0592205af0aaa156e9558a573426387c31de2c Mon Sep 17 00:00:00 2001 From: codechanges Date: Fri, 11 Apr 2025 07:47:53 +0530 Subject: [PATCH 0348/1298] Integrate default values functionality (#3119) * Utilize default values * Change constants name * Test branch * Revert "Test branch" This reverts commit e56e56bcacde3087fbaec817fe4b2290845373cb. * Integrate buffered write handler with writer.flush (#3108) * update buffered_write_handler sync to flush object for zonal buckets * review comments * convert to table driven tests * Replace `--debug_*` with `--log-severity=trace` in e2e tests (#3124) * Replace --debug_* with --log-severity=trace in e2e This is to avoid unnecessary error logs in e2e runs. * empty commit to trigger e2e presubmit * fix a test failure * Update pull_request_template.md to ask for backward incompatible change(#3109) * Update pull_request_template.md Updating pull request template to capture any breaking change at the code-review time. Unrelated small change: Also removed the NA on b/ link since we do require bugs to be part of PRs now. Bug: b/405312149 * [testing-on-gke] Suppress unnecessary script logs (#3125) * create new inode on change in length (#3123) * create new inode on change in length * add e2e tests * Revert "add e2e tests" This reverts commit 66708cad2ad443521547aa9168419a28800fca14. * add size to other inode's Generation too * add unit test * update comment * unit tests * header fix * review comments * fix tests * revert object generation change * revert composite tests as they are not representative of current state of server. We will add integration tests in follow up PR. * review comments * Use new writer flush before reads for zonal buckets with streaming writes. (#3121) * add flush before read for zonal buckets with streaming writes * add tests for SourceGenerationAuthoritative and SyncUsingBufferedWritesHandler * improve test * improve wording * fix wording * fix indentation * fix lint * fix tests * fix lint * fix review comments. * Update internal/fs/inode/file.go * fix test file * fix test file --------- Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * Update README.md (#3136) * Update README.md Updated to add Parallel Downloads and Streaming writes in whats new section * Update README.md Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * Update README.md --------- Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> * Clean up the remnants of config-cli-parity (#3142) * Clean up the remnants of config-cli-parity * [testing-on-gke] handle no memory/cpu errors in output parsing (#3134) Add warning logs about missing cpu/memory values, and return their values as -1,-1 to indicate missing values, but don't fail the run. * fix package pass/fail log in run_e2e_tests.sh (#3148) * Update metrics documentation (#3150) * Point to metrics cloud docs on how to enable and access metrics. * Remove handle-sigterm flag. (#3145) handle-sigterm flag is a hidden flag and has been set to true for quite some time. Given that it was always hidden, it's okay to remove it without introducing any backward incompatibility. * [Random Reader Refactoring] Create hidden flag to enable new implementation (#3153) * create hidden flag for random reader refactoring * fix typo in tests * fix typo in tests * fix comment * Update GCSFuse External Benchmarks with version 2.11.1 (#3152) * skeleton update * updating data * edit: disk type * update section title : n2 benchmarking * update steps to run : fio * adding link to previous benchmark * merge notte * nits * Optimize default values on a high performance nodes (#3118) * Optimize default values * Fix comment in the function * Removed prints * Modified retry logic * Modified retry logic * Fixed typo * Change loop structure * Added constant type * Export only Optimize function * Simplify checks for machine type * Rename variable * Simplified logic to find right profile for the machine type * Renamed a few variables * Use asserts in the test case * Continue on reading error body * Restructuring tests * Refactor http query function * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor comment resolution * Minor improvements * Minor improvements * Minor improvements * Minor improvements * Minor improvements * Minor improvements * Minor improvements * Minor improvements * Minor improvements * Minor improvements * Minor improvements --------- Co-authored-by: Kislay Kishore Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Co-authored-by: Vikas Jain <615592+mustvicky@users.noreply.github.com> Co-authored-by: Mohit Kumar Yadav Co-authored-by: marcoa6 <142958842+marcoa6@users.noreply.github.com> Co-authored-by: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Co-authored-by: anushka <78717608+anushka567@users.noreply.github.com> --- cfg/constants.go | 7 +- cfg/rationalize.go | 58 +++++++++------ cfg/rationalize_test.go | 110 +++++++++++++++++++++++++--- cmd/root.go | 8 +- cmd/root_test.go | 157 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 307 insertions(+), 33 deletions(-) diff --git a/cfg/constants.go b/cfg/constants.go index 0b3c9ff7df..076878e01e 100644 --- a/cfg/constants.go +++ b/cfg/constants.go @@ -75,8 +75,11 @@ const ( AverageSizeOfNegativeStatCacheEntry uint64 = 240 // MetadataCacheTTLConfigKey is the Viper configuration key for the metadata //cache's time-to-live (TTL) in seconds. - MetadataCacheTTLConfigKey = "metadata-cache.ttl-secs" - MetadataNegativeCacheTTLConfigKey = "metadata-cache.negative-ttl-secs" + MetadataCacheTTLConfigKey = "metadata-cache.ttl-secs" + MetadataCacheStatCacheTTLConfigKey = "metadata-cache.deprecated-stat-cache-ttl" + MetadataCacheTypeCacheTTLConfigKey = "metadata-cache.deprecated-type-cache-ttl" + MetadataCacheStatCacheCapacityConfigKey = "metadata-cache.deprecated-stat-cache-capacity" + MetadataNegativeCacheTTLConfigKey = "metadata-cache.negative-ttl-secs" // StatCacheMaxSizeConfigKey is the Viper configuration key for the maximum //size of the metadata stat cache in megabytes. StatCacheMaxSizeConfigKey = "metadata-cache.stat-cache-max-size-mb" diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 40485fe4e9..a49dd797d5 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -37,40 +37,56 @@ func decodeURL(u string) (string, error) { return decodedURL.String(), nil } -// resolveMetadataCacheTTL returns the ttl to be used for stat/type cache based -// on the user flags/configs. -func resolveMetadataCacheTTL(v isSet, c *MetadataCacheConfig) { - // If metadata-cache:ttl-secs has been set, then it overrides both - // stat-cache-ttl, type-cache-tll and negative cache ttl. - if v.IsSet(MetadataNegativeCacheTTLConfigKey) { +// resolveMetadataCacheTTL calculates the ttl to be used for stat/type cache based +// on the user flags/configs or machine type based optimizations. +func resolveMetadataCacheTTL(v isSet, c *MetadataCacheConfig, optimizedFlags []string) { + optimizationAppliedToNegativeCacheTTL := isFlagPresent(optimizedFlags, MetadataNegativeCacheTTLConfigKey) + + if v.IsSet(MetadataNegativeCacheTTLConfigKey) || optimizationAppliedToNegativeCacheTTL { if c.NegativeTtlSecs == -1 { c.NegativeTtlSecs = maxSupportedTTLInSeconds } } + + // Order of precedence for setting TTL seconds + // 1. If metadata-cache:ttl-secs has been set, then it has highest precedence + // 2. If metadata-cache:stat-cache-ttl or metadata-cache:type-cache-ttl has been set or no optimization applied, then it has second highest precedence + // 3. Optimization is applied (implicit) and take care of special case of -1 which can occur even in defaults + optimizationAppliedToMetadataCacheTTL := isFlagPresent(optimizedFlags, MetadataCacheTTLConfigKey) if v.IsSet(MetadataCacheTTLConfigKey) { if c.TtlSecs == -1 { c.TtlSecs = maxSupportedTTLInSeconds } - return + } else if (v.IsSet(MetadataCacheStatCacheTTLConfigKey) || v.IsSet(MetadataCacheTypeCacheTTLConfigKey)) || (!optimizationAppliedToMetadataCacheTTL) { + c.TtlSecs = int64(math.Ceil(math.Min(c.DeprecatedStatCacheTtl.Seconds(), c.DeprecatedTypeCacheTtl.Seconds()))) + } else if c.TtlSecs == -1 { + c.TtlSecs = maxSupportedTTLInSeconds } - // Else, use deprecated stat/type cache ttl to resolve metadataCacheTTL. - c.TtlSecs = int64(math.Ceil(math.Min(c.DeprecatedStatCacheTtl.Seconds(), c.DeprecatedTypeCacheTtl.Seconds()))) } -// resolveStatCacheMaxSizeMB returns the stat-cache size in MiBs based on the -// user old and new flags/configs. -func resolveStatCacheMaxSizeMB(v isSet, c *MetadataCacheConfig) { - // If metadata-cache:stat-cache-size-mb has been set, then it overrides - // stat-cache-capacity. +// resolveStatCacheMaxSizeMB calculates the stat-cache size in MiBs based on the +// machine-type default override, user's old and new flags/configs. +func resolveStatCacheMaxSizeMB(v isSet, c *MetadataCacheConfig, optimizedFlags []string) { + // Local function to calculate size based on deprecated capacity. + calculateSizeFromCapacity := func(capacity int64) int64 { + avgTotalStatCacheEntrySize := AverageSizeOfPositiveStatCacheEntry + AverageSizeOfNegativeStatCacheEntry + return int64(util.BytesToHigherMiBs(uint64(capacity) * avgTotalStatCacheEntrySize)) + } + + // Order of precedence for setting stat cache size + // 1. If metadata-cache:stat-cache-size-mb is set it has the highest precedence + // 2. If stat-cache-capacity is set or optimization is not applied then use it to calculate stat cache size + // 3. Else handle special case of -1 for both optimized or possible default value + optimizationAppliedToStatCacheMaxSize := isFlagPresent(optimizedFlags, StatCacheMaxSizeConfigKey) if v.IsSet(StatCacheMaxSizeConfigKey) { if c.StatCacheMaxSizeMb == -1 { c.StatCacheMaxSizeMb = int64(maxSupportedStatCacheMaxSizeMB) } - return + } else if v.IsSet(MetadataCacheStatCacheCapacityConfigKey) || (!optimizationAppliedToStatCacheMaxSize) { + c.StatCacheMaxSizeMb = calculateSizeFromCapacity(c.DeprecatedStatCacheCapacity) + } else if c.StatCacheMaxSizeMb == -1 { + c.StatCacheMaxSizeMb = int64(maxSupportedStatCacheMaxSizeMB) } - // Else, use deprecated stat-cache-capacity to resolve StatCacheMaxSizeMb. - avgTotalStatCacheEntrySize := AverageSizeOfPositiveStatCacheEntry + AverageSizeOfNegativeStatCacheEntry - c.StatCacheMaxSizeMb = int64(util.BytesToHigherMiBs(uint64(c.DeprecatedStatCacheCapacity) * avgTotalStatCacheEntrySize)) } func resolveStreamingWriteConfig(w *WriteConfig) { @@ -106,7 +122,7 @@ func resolveParallelDownloadsValue(v isSet, fc *FileCacheConfig, c *Config) { } // Rationalize updates the config fields based on the values of other fields. -func Rationalize(v isSet, c *Config) error { +func Rationalize(v isSet, c *Config, optimizedFlags []string) error { var err error if c.GcsConnection.CustomEndpoint, err = decodeURL(c.GcsConnection.CustomEndpoint); err != nil { return err @@ -121,8 +137,8 @@ func Rationalize(v isSet, c *Config) error { } resolveStreamingWriteConfig(&c.Write) - resolveMetadataCacheTTL(v, &c.MetadataCache) - resolveStatCacheMaxSizeMB(v, &c.MetadataCache) + resolveMetadataCacheTTL(v, &c.MetadataCache, optimizedFlags) + resolveStatCacheMaxSizeMB(v, &c.MetadataCache, optimizedFlags) resolveCloudMetricsUploadIntervalSecs(&c.Metrics) resolveParallelDownloadsValue(v, &c.FileCache, c) diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 910ae53043..e142ee1a82 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -56,7 +56,7 @@ func TestRationalizeCustomEndpointSuccessful(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - actualErr := Rationalize(&mockIsSet{}, tc.config) + actualErr := Rationalize(&mockIsSet{}, tc.config, []string{}) if assert.NoError(t, actualErr) { assert.Equal(t, tc.expectedCustomEndpoint, tc.config.GcsConnection.CustomEndpoint) @@ -82,7 +82,7 @@ func TestRationalizeCustomEndpointUnsuccessful(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - assert.Error(t, Rationalize(&mockIsSet{}, tc.config)) + assert.Error(t, Rationalize(&mockIsSet{}, tc.config, []string{})) }) } } @@ -150,7 +150,7 @@ func TestLoggingSeverityRationalization(t *testing.T) { }, } - err := Rationalize(&mockIsSet{}, &c) + err := Rationalize(&mockIsSet{}, &c, []string{}) if assert.NoError(t, err) { assert.Equal(t, tc.expected, c.Logging.Severity) @@ -186,7 +186,7 @@ func TestRationalize_TokenURLSuccessful(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - actualErr := Rationalize(&mockIsSet{}, tc.config) + actualErr := Rationalize(&mockIsSet{}, tc.config, []string{}) if assert.NoError(t, actualErr) { assert.Equal(t, tc.expectedTokenURL, tc.config.GcsAuth.TokenUrl) @@ -212,7 +212,7 @@ func TestRationalize_TokenURLUnsuccessful(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - assert.Error(t, Rationalize(&mockIsSet{}, tc.config)) + assert.Error(t, Rationalize(&mockIsSet{}, tc.config, []string{})) }) } } @@ -304,7 +304,7 @@ func TestRationalizeMetadataCache(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - if assert.NoError(t, Rationalize(tc.flags, tc.config)) { + if assert.NoError(t, Rationalize(tc.flags, tc.config, []string{})) { assert.Equal(t, tc.expectedTTLSecs, tc.config.MetadataCache.TtlSecs) assert.Equal(t, tc.expectedStatCacheSize, tc.config.MetadataCache.StatCacheMaxSizeMb) } @@ -312,6 +312,98 @@ func TestRationalizeMetadataCache(t *testing.T) { } } +func TestRationalizeMetadataCacheWithOptimization(t *testing.T) { + testCases := []struct { + name string + flags flagSet + config *Config + expectedTTLSecs int64 + expectedNegativeTTLSecs int64 + expectedStatCacheSize int64 + }{ + { + name: "negative_ttl_flag_set", + flags: flagSet{"metadata-cache.negative-ttl-secs": true}, + config: &Config{MetadataCache: MetadataCacheConfig{NegativeTtlSecs: 44}}, + expectedNegativeTTLSecs: 44, + }, + { + name: "new_ttl_flag_set", + flags: flagSet{"metadata-cache.ttl-secs": true}, + config: &Config{MetadataCache: MetadataCacheConfig{TtlSecs: 30}}, + expectedTTLSecs: 30, + }, + { + name: "old_ttl_flags_set", + flags: flagSet{"metadata-cache.deprecated-stat-cache-ttl": true, "metadata-cache.deprecated-type-cache-ttl": true}, + config: &Config{ + MetadataCache: MetadataCacheConfig{ + DeprecatedStatCacheTtl: 10 * time.Second, + DeprecatedTypeCacheTtl: 5 * time.Second, + }, + }, + expectedTTLSecs: 5, + }, + { + name: "new_and_old_ttl_flags_set", + flags: flagSet{"metadata-cache.ttl-secs": true, "metadata-cache.deprecated-stat-cache-ttl": true, "metadata-cache.deprecated-type-cache-ttl": true}, + config: &Config{ + MetadataCache: MetadataCacheConfig{ + TtlSecs: 30, + DeprecatedStatCacheTtl: 10 * time.Second, + DeprecatedTypeCacheTtl: 5 * time.Second, + }, + }, + expectedTTLSecs: 30, + }, + { + name: "new_stat-cache-size-mb_flag_set", + flags: flagSet{"metadata-cache.stat-cache-max-size-mb": true}, + config: &Config{MetadataCache: MetadataCacheConfig{StatCacheMaxSizeMb: 100}}, + expectedTTLSecs: 0, // Assuming no change to TtlSecs in this function + expectedStatCacheSize: 100, + }, + { + name: "old_stat-cache-capacity_flag_set", + flags: flagSet{"metadata-cache.deprecated-stat-cache-capacity": true}, + config: &Config{MetadataCache: MetadataCacheConfig{DeprecatedStatCacheCapacity: 1000}}, + expectedTTLSecs: 0, + expectedStatCacheSize: 2, + }, + { + name: "new_and_old_stat-cache-capacity_flag_set", + flags: flagSet{"metadata-cache.stat-cache-max-size-mb": true, "metadata-cache.deprecated-stat-cache-capacity": true}, + config: &Config{MetadataCache: MetadataCacheConfig{StatCacheMaxSizeMb: 100, DeprecatedStatCacheCapacity: 1000}}, + expectedTTLSecs: 0, + expectedStatCacheSize: 100, + }, + { + name: "ttl_and_stat_cache_size_set_to_-1", + flags: flagSet{"metadata-cache.ttl-secs": true, "metadata-cache.stat-cache-max-size-mb": true}, + config: &Config{ + MetadataCache: MetadataCacheConfig{ + TtlSecs: -1, + NegativeTtlSecs: -1, + StatCacheMaxSizeMb: -1, + }, + }, + expectedTTLSecs: math.MaxInt64 / int64(time.Second), // Max supported ttl in seconds. + expectedNegativeTTLSecs: math.MaxInt64 / int64(time.Second), // Max supported ttl in seconds. + expectedStatCacheSize: math.MaxUint64 >> 20, // Max supported cache size in MiB. + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if assert.NoError(t, Rationalize(tc.flags, tc.config, []string{"metadata-cache.negative-ttl-secs", "metadata-cache.ttl-secs", "metadata-cache.stat-cache-max-size-mb", "metadata-cache.deprecated-stat-cache-capacity", "metadata-cache.deprecated-stat-cache-ttl", "metadata-cache.deprecated-type-cache-ttl"})) { + assert.Equal(t, tc.expectedTTLSecs, tc.config.MetadataCache.TtlSecs) + assert.Equal(t, tc.expectedNegativeTTLSecs, tc.config.MetadataCache.NegativeTtlSecs) + assert.Equal(t, tc.expectedStatCacheSize, tc.config.MetadataCache.StatCacheMaxSizeMb) + } + }) + } +} + func TestRationalize_WriteConfig(t *testing.T) { testCases := []struct { name string @@ -369,7 +461,7 @@ func TestRationalize_WriteConfig(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - actualErr := Rationalize(&mockIsSet{}, tc.config) + actualErr := Rationalize(&mockIsSet{}, tc.config, []string{}) if assert.NoError(t, actualErr) { assert.Equal(t, tc.expectedCreateEmptyFile, tc.config.Write.CreateEmptyFile) @@ -424,7 +516,7 @@ func TestRationalizeMetricsConfig(t *testing.T) { t.Run(tc.name, func(t *testing.T) { t.Parallel() - if assert.NoError(t, Rationalize(&mockIsSet{}, tc.config)) { + if assert.NoError(t, Rationalize(&mockIsSet{}, tc.config, []string{})) { assert.Equal(t, tc.expected, tc.config.Metrics.CloudMetricsExportIntervalSecs) } }) @@ -479,7 +571,7 @@ func TestRationalize_ParallelDownloadsConfig(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - err := Rationalize(tc.flags, tc.config) + err := Rationalize(tc.flags, tc.config, []string{}) if assert.NoError(t, err) { assert.Equal(t, tc.expectedParallelDownloads, tc.config.FileCache.EnableParallelDownloads) diff --git a/cmd/root.go b/cmd/root.go index 8b817de5b7..8f677f6160 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -86,7 +86,13 @@ of Cloud Storage FUSE, see https://cloud.google.com/storage/docs/gcs-fuse.`, if cfgErr = cfg.ValidateConfig(v, &configObj); cfgErr != nil { return } - if cfgErr = cfg.Rationalize(v, &configObj); cfgErr != nil { + + var optimizedFlags []string + if optimizedFlags, cfgErr = cfg.Optimize(&configObj, v); cfgErr != nil { + return + } + + if cfgErr = cfg.Rationalize(v, &configObj, optimizedFlags); cfgErr != nil { return } } diff --git a/cmd/root_test.go b/cmd/root_test.go index 00e63ddacf..04e89f1fe5 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -193,6 +193,58 @@ func TestArgsParsing_MountOptions(t *testing.T) { } } +// Lets test for ImplicitDirs which is goverened by implicit-dirs flags +func TestArgsParsing_ImplicitDirsFlag(t *testing.T) { + tests := []struct { + name string + args []string + expectedImplicit bool + }{ + { + name: "normal", + args: []string{"gcsfuse", "--implicit-dirs", "abc", "pqr"}, + expectedImplicit: true, + }, + { + name: "default", + args: []string{"gcsfuse", "abc", "pqr"}, + expectedImplicit: false, + }, + { + name: "normal_false", + args: []string{"gcsfuse", "--implicit-dirs=false", "abc", "pqr"}, + expectedImplicit: false, + }, + { + name: "default true on high performance machine", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "abc", "pqr"}, + expectedImplicit: true, + }, + { + name: "default overriden on high performance machine", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--implicit-dirs=false", "abc", "pqr"}, + expectedImplicit: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var gotImplicit bool + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { + gotImplicit = cfg.ImplicitDirs + return nil + }) + require.Nil(t, err) + cmd.SetArgs(convertToPosixArgs(tc.args, cmd)) + + err = cmd.Execute() + + if assert.NoError(t, err) { + assert.Equal(t, tc.expectedImplicit, gotImplicit) + } + }) + } +} func TestArgsParsing_WriteConfigFlags(t *testing.T) { tests := []struct { name string @@ -275,6 +327,22 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedWriteGlobalMaxBlocks: math.MaxInt64, expectedWriteMaxBlocksPerFile: 10, }, + { + name: "Test high performance config values.", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "abc", "pqr"}, + expectedEnableStreamingWrites: true, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: math.MaxInt64, + }, + { + name: "Test high performance config values with enable-streaming-writes flag overriden.", + args: []string{"gcsfuse", "--enable-streaming-writes=false", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, + }, } for _, tc := range tests { @@ -681,6 +749,44 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { }, }, }, + { + name: "high performance defaults with rename dir options", + args: []string{"gcsfuse", "--dir-mode=777", "--machine-type=a3-highgpu-8g", "--file-mode=666", "abc", "pqr"}, + expectedConfig: &cfg.Config{ + FileSystem: cfg.FileSystemConfig{ + DirMode: 0777, + DisableParallelDirops: false, + FileMode: 0666, + FuseOptions: []string{}, + Gid: -1, + IgnoreInterrupts: true, + KernelListCacheTtlSecs: 0, + RenameDirLimit: 200000, + TempDir: "", + PreconditionErrors: true, + Uid: -1, + }, + }, + }, + { + name: "high performance defaults with overriden rename dir options", + args: []string{"gcsfuse", "--dir-mode=777", "--machine-type=a3-highgpu-8g", "--rename-dir-limit=15000", "--file-mode=666", "abc", "pqr"}, + expectedConfig: &cfg.Config{ + FileSystem: cfg.FileSystemConfig{ + DirMode: 0777, + DisableParallelDirops: false, + FileMode: 0666, + FuseOptions: []string{}, + Gid: -1, + IgnoreInterrupts: true, + KernelListCacheTtlSecs: 0, + RenameDirLimit: 15000, + TempDir: "", + PreconditionErrors: true, + Uid: -1, + }, + }, + }, { name: "default", args: []string{"gcsfuse", "abc", "pqr"}, @@ -1027,6 +1133,57 @@ func TestArgsParsing_MetadataCacheFlags(t *testing.T) { }, }, }, + { + name: "high performance default config values", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "abc", "pqr"}, + expectedConfig: &cfg.Config{ + MetadataCache: cfg.MetadataCacheConfig{ + DeprecatedStatCacheCapacity: 20460, + DeprecatedStatCacheTtl: 60 * time.Second, + DeprecatedTypeCacheTtl: 60 * time.Second, + EnableNonexistentTypeCache: false, + ExperimentalMetadataPrefetchOnMount: "disabled", + StatCacheMaxSizeMb: 1024, + TtlSecs: 9223372036, + NegativeTtlSecs: 0, + TypeCacheMaxSizeMb: 128, + }, + }, + }, + { + name: "high performance default config values obey customer flags", + args: []string{"gcsfuse", "--stat-cache-capacity=2000", "--stat-cache-ttl=2m", "--type-cache-ttl=1m20s", "--enable-nonexistent-type-cache", "--experimental-metadata-prefetch-on-mount=async", "--stat-cache-max-size-mb=15", "--metadata-cache-ttl-secs=25", "--metadata-cache-negative-ttl-secs=20", "--type-cache-max-size-mb=30", "abc", "pqr"}, + expectedConfig: &cfg.Config{ + MetadataCache: cfg.MetadataCacheConfig{ + DeprecatedStatCacheCapacity: 2000, + DeprecatedStatCacheTtl: 2 * time.Minute, + DeprecatedTypeCacheTtl: 80 * time.Second, + EnableNonexistentTypeCache: true, + ExperimentalMetadataPrefetchOnMount: "async", + StatCacheMaxSizeMb: 15, + TtlSecs: 25, + NegativeTtlSecs: 20, + TypeCacheMaxSizeMb: 30, + }, + }, + }, + { + name: "high performance default config values use deprecated flags", + args: []string{"gcsfuse", "--stat-cache-capacity=2000", "--stat-cache-ttl=2m", "--type-cache-ttl=4m", "--enable-nonexistent-type-cache", "--experimental-metadata-prefetch-on-mount=async", "--metadata-cache-negative-ttl-secs=20", "--type-cache-max-size-mb=30", "abc", "pqr"}, + expectedConfig: &cfg.Config{ + MetadataCache: cfg.MetadataCacheConfig{ + DeprecatedStatCacheCapacity: 2000, + DeprecatedStatCacheTtl: 2 * time.Minute, + DeprecatedTypeCacheTtl: 4 * time.Minute, + EnableNonexistentTypeCache: true, + ExperimentalMetadataPrefetchOnMount: "async", + StatCacheMaxSizeMb: 4, + TtlSecs: 120, + NegativeTtlSecs: 20, + TypeCacheMaxSizeMb: 30, + }, + }, + }, } for _, tc := range tests { From 1449ff2621595b55b2d450d3ee55775c636c5d7d Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 11 Apr 2025 05:07:31 +0000 Subject: [PATCH 0349/1298] Fix and enable package explicit_dir in zb e2e tests (#3144) * use storage-client instead of gcloud in explicit_dir package * enable explicit_dir package for ZB e2e tests * address self-review comments * Remove outdated utilities * empty commit * Revert "Remove outdated utilities" This reverts commit 5491d6a9fc976a0f22ca229be150f4b089b64c1c. * put back non-ZB call * empty commit * Address self-review comments use common utility * address couple of review comments * empty commit --- .../explicit_dir/explicit_dir_test.go | 24 +++++++++++-------- .../explicit_dir/list_test.go | 9 +++++-- .../implicit_dir/delete_test.go | 6 +++++ .../implicit_dir/list_test.go | 1 + ...ist_dir_with_twelve_thousand_files_test.go | 15 ++---------- tools/integration_tests/run_e2e_tests.sh | 2 +- .../util/operations/string_operations.go | 10 ++++++++ .../implicit_and_explicit_dir_setup.go | 24 +++++++++---------- 8 files changed, 52 insertions(+), 39 deletions(-) diff --git a/tools/integration_tests/explicit_dir/explicit_dir_test.go b/tools/integration_tests/explicit_dir/explicit_dir_test.go index 85b0b729ac..4fa4eaad29 100644 --- a/tools/integration_tests/explicit_dir/explicit_dir_test.go +++ b/tools/integration_tests/explicit_dir/explicit_dir_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -29,18 +29,22 @@ import ( const DirForExplicitDirTests = "dirForExplicitDirTests" +var ( + storageClient *storage.Client + ctx context.Context +) + func TestMain(m *testing.M) { setup.ParseSetUpFlags() - var storageClient *storage.Client // Create storage client before running tests. - ctx := context.Background() - storageClient, err := client.CreateStorageClient(ctx) - if err != nil { - log.Printf("Error creating storage client: %v\n", err) - os.Exit(1) - } - - defer storageClient.Close() + ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() // These tests will not run on HNS buckets because the "--implicit-dirs=false" flag does not function similarly to how it does on FLAT buckets. // Note that HNS buckets do not have the concept of implicit directories. diff --git a/tools/integration_tests/explicit_dir/list_test.go b/tools/integration_tests/explicit_dir/list_test.go index 4421ef1ba4..1cbac8f12a 100644 --- a/tools/integration_tests/explicit_dir/list_test.go +++ b/tools/integration_tests/explicit_dir/list_test.go @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -38,7 +38,12 @@ func TestListOnlyExplicitObjectsFromBucket(t *testing.T) { // testBucket/dirForExplicitDirTests/explicitDirectory/fileInExplicitDir1 -- File // testBucket/dirForExplicitDirTests/explicitDirectory/fileInExplicitDir2 -- File - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(DirForExplicitDirTests) + // TODO: Remove the condition and keep the storage-client flow for non-ZB too. + if setup.IsZonalBucketRun() { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, DirForExplicitDirTests) + } else { + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(DirForExplicitDirTests) + } implicit_and_explicit_dir_setup.CreateExplicitDirectoryStructure(DirForExplicitDirTests, t) err := filepath.WalkDir(testDir, func(path string, dir fs.DirEntry, err error) error { diff --git a/tools/integration_tests/implicit_dir/delete_test.go b/tools/integration_tests/implicit_dir/delete_test.go index 53e84c1a08..94f676dee9 100644 --- a/tools/integration_tests/implicit_dir/delete_test.go +++ b/tools/integration_tests/implicit_dir/delete_test.go @@ -32,6 +32,7 @@ import ( func TestDeleteNonEmptyImplicitDir(t *testing.T) { testDirName := "testDeleteNonEmptyImplicitDir" testDirPath := setupTestDir(testDirName) + // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { @@ -51,6 +52,7 @@ func TestDeleteNonEmptyImplicitDir(t *testing.T) { func TestDeleteNonEmptyImplicitSubDir(t *testing.T) { testDirName := "testDeleteNonEmptyImplicitSubDir" testDirPath := setupTestDir(testDirName) + // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { @@ -72,6 +74,7 @@ func TestDeleteNonEmptyImplicitSubDir(t *testing.T) { func TestDeleteImplicitDirWithExplicitSubDir(t *testing.T) { testDirName := "testDeleteImplicitDirWithExplicitSubDir" testDirPath := setupTestDir(testDirName) + // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { @@ -97,6 +100,7 @@ func TestDeleteImplicitDirWithExplicitSubDir(t *testing.T) { func TestDeleteImplicitDirWithImplicitSubDirContainingExplicitDir(t *testing.T) { testDirName := "testDeleteImplicitDirWithImplicitSubDirContainingExplicitDir" testDirPath := setupTestDir(testDirName) + // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { @@ -123,6 +127,7 @@ func TestDeleteImplicitDirWithImplicitSubDirContainingExplicitDir(t *testing.T) func TestDeleteImplicitDirInExplicitDir(t *testing.T) { testDirName := "testDeleteImplicitDirInExplicitDir" testDirPath := setupTestDir(testDirName) + // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { @@ -146,6 +151,7 @@ func TestDeleteImplicitDirInExplicitDir(t *testing.T) { func TestDeleteExplicitDirContainingImplicitSubDir(t *testing.T) { testDirName := "testDeleteExplicitDirContainingImplicitSubDir" testDirPath := setupTestDir(testDirName) + // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { diff --git a/tools/integration_tests/implicit_dir/list_test.go b/tools/integration_tests/implicit_dir/list_test.go index 48e382ab35..660c4f8e0b 100644 --- a/tools/integration_tests/implicit_dir/list_test.go +++ b/tools/integration_tests/implicit_dir/list_test.go @@ -40,6 +40,7 @@ func TestListImplicitObjectsFromBucket(t *testing.T) { // testBucket/dirForImplicitDirTests/testDir/explicitDirectory/fileInExplicitDir1 -- File // testBucket/dirForImplicitDirTests/testDir/explicitDirectory/fileInExplicitDir2 -- File + // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { diff --git a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go index 6071f2ff90..7f8326d297 100644 --- a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go +++ b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go @@ -104,22 +104,11 @@ func checkIfObjNameIsCorrect(t *testing.T, objName string, prefix string, maxNum } } -func splitBucketNameAndDirPath(t *testing.T, bucketNameWithDirPath string) (bucketName, dirPathInBucket string) { - t.Helper() - - var found bool - if bucketName, dirPathInBucket, found = strings.Cut(bucketNameWithDirPath, "/"); !found { - t.Fatalf("Unexpected bucketNameWithDirPath: %q. Expected form: /", bucketNameWithDirPath) - } - return -} - -// This function is equivalent to testdata/upload_files_to_bucket.sh to replace gcloud with storage-client // This is needed for ZB which is not supported by gcloud storage cp command yet. func testdataUploadFilesToBucket(ctx context.Context, t *testing.T, storageClient *storage.Client, bucketNameWithDirPath, dirWith12KFiles, filesPrefix string) { t.Helper() - bucketName, dirPathInBucket := splitBucketNameAndDirPath(t, bucketNameWithDirPath) + bucketName, dirPathInBucket := operations.SplitBucketNameAndDirPath(t, bucketNameWithDirPath) dirWith12KFilesFullPathPrefix := filepath.Join(dirWith12KFiles, filesPrefix) matches, err := filepath.Glob(dirWith12KFilesFullPathPrefix + "*") @@ -226,7 +215,7 @@ func listDirTime(t *testing.T, dirPath string, expectExplicitDirs bool, expectIm func testdataCreateImplicitDir(ctx context.Context, t *testing.T, storageClient *storage.Client, bucketNameWithDirPath, prefixImplicitDirInLargeDirListTest string, numberOfImplicitDirsInDirectory int) { t.Helper() - bucketName, dirPathInBucket := splitBucketNameAndDirPath(t, bucketNameWithDirPath) + bucketName, dirPathInBucket := operations.SplitBucketNameAndDirPath(t, bucketNameWithDirPath) testFile, err := operations.CreateLocalTempFile("", false) if err != nil { diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 9fcc3d4787..1484f7c716 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -122,7 +122,7 @@ TEST_DIR_NON_PARALLEL=( TEST_DIR_PARALLEL_FOR_ZB=( "benchmarking" "concurrent_operations" - # "explicit_dir" + "explicit_dir" "gzip" "implicit_dir" "interrupt" diff --git a/tools/integration_tests/util/operations/string_operations.go b/tools/integration_tests/util/operations/string_operations.go index d0dfca0285..41c789bfbd 100644 --- a/tools/integration_tests/util/operations/string_operations.go +++ b/tools/integration_tests/util/operations/string_operations.go @@ -45,3 +45,13 @@ func GetRandomName(t *testing.T) string { } return id.String() } + +func SplitBucketNameAndDirPath(t *testing.T, bucketNameWithDirPath string) (bucketName, dirPathInBucket string) { + t.Helper() + + var found bool + if bucketName, dirPathInBucket, found = strings.Cut(bucketNameWithDirPath, "/"); !found { + t.Fatalf("Unexpected bucketNameWithDirPath: %q. Expected form: /", bucketNameWithDirPath) + } + return +} diff --git a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go index 75614ba0dd..dccdc1d4c8 100644 --- a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go +++ b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go @@ -19,7 +19,6 @@ import ( "log" "os" "path" - "strings" "testing" storage "cloud.google.com/go/storage" @@ -78,26 +77,21 @@ func RemoveAndCheckIfDirIsDeleted(dirPath string, dirName string, t *testing.T) } } -// createTestdataObjectsUsingStorageClient is equivalent of the script tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/testdata/create_objects.sh . +// testdataCreateObjects is equivalent of the script tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/testdata/create_objects.sh . // That script uses gcloud, but this function instead uses go client library. // Note: testDirWithBucketName is of the form /. -func createTestdataObjectsUsingStorageClient(ctx context.Context, t *testing.T, storageClient *storage.Client, testDirWithBucketName string) { +func testdataCreateObjects(ctx context.Context, t *testing.T, storageClient *storage.Client, testDirWithBucketName string) { t.Helper() - idx := strings.Index(testDirWithBucketName, "/") - if idx <= 0 { - t.Fatalf("Unexpected testDirWithBucketName: %q. Expected form: /", testDirWithBucketName) - } - bucketName := testDirWithBucketName[:idx] - testDirWithoutBucketName := testDirWithBucketName[idx+1:] + bucketName, testDirWithoutBucketName := operations.SplitBucketNameAndDirPath(t, testDirWithBucketName) - objectName := path.Join(testDirWithoutBucketName, "implicitDirectory", "fileInImplicitDir1") + objectName := path.Join(testDirWithoutBucketName, ImplicitDirectory, FileInImplicitDirectory) err := client.CreateObjectOnGCS(ctx, storageClient, objectName, "This is from directory fileInImplicitDir1 file implicitDirectory") if err != nil { t.Fatalf("Failed to create GCS object %q in bucket %q: %v", objectName, bucketName, err) } - objectName = path.Join(testDirWithoutBucketName, "implicitDirectory/implicitSubDirectory", "fileInImplicitDir2") + objectName = path.Join(testDirWithoutBucketName, path.Join(ImplicitDirectory, ImplicitSubDirectory), FileInImplicitSubDirectory) err = client.CreateObjectOnGCS(ctx, storageClient, objectName, "This is from directory implicitDirectory/implicitSubDirectory file fileInImplicitDir2") if err != nil { t.Fatalf("Failed to create GCS object %q in bucket %q: %v", objectName, bucketName, err) @@ -114,7 +108,7 @@ func CreateImplicitDirectoryStructureUsingStorageClient(ctx context.Context, t * // testBucket/testDir/implicitDirectory/implicitSubDirectory/fileInImplicitDir2 -- File // Create implicit directory in bucket for testing. - createTestdataObjectsUsingStorageClient(ctx, t, storageClient, path.Join(setup.TestBucket(), testDir)) + testdataCreateObjects(ctx, t, storageClient, path.Join(setup.TestBucket(), testDir)) } func CreateImplicitDirectoryStructure(testDir string) { @@ -157,7 +151,9 @@ func CreateImplicitDirectoryInExplicitDirectoryStructure(testDir string, t *test // testBucket/testDir/explicitDirectory/implicitDirectory/implicitSubDirectory -- Dir // testBucket/testDir/explicitDirectory/implicitDirectory/implicitSubDirectory/fileInImplicitDir2 -- File + // CreateExplicitDirectoryStructure writes files using GCSFuse. CreateExplicitDirectoryStructure(testDir, t) + dirPathInBucket := path.Join(setup.TestBucket(), testDir, ExplicitDirectory) setup.RunScriptForTestData("../util/setup/implicit_and_explicit_dir_setup/testdata/create_objects.sh", dirPathInBucket) } @@ -174,7 +170,9 @@ func CreateImplicitDirectoryInExplicitDirectoryStructureUsingStorageClient(ctx c // testBucket/testDir/explicitDirectory/implicitDirectory/implicitSubDirectory -- Dir // testBucket/testDir/explicitDirectory/implicitDirectory/implicitSubDirectory/fileInImplicitDir2 -- File + // CreateExplicitDirectoryStructure writes files using GCSFuse. CreateExplicitDirectoryStructure(testDir, t) + dirPathInBucket := path.Join(setup.TestBucket(), testDir, ExplicitDirectory) - createTestdataObjectsUsingStorageClient(ctx, t, storageClient, dirPathInBucket) + testdataCreateObjects(ctx, t, storageClient, dirPathInBucket) } From 8129f49c152b8a5a93917654c0d196333d25f242 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 11 Apr 2025 05:13:30 +0000 Subject: [PATCH 0350/1298] Fix and enable e2e test package "operations" for ZB (#3172) * first commit of gargnitin/fix-zb-e2e/operations/v1 * fix validateObjectAttributes for zonal buckets * enable e2e test package operations for ZB * add more debug information * ignore storage-class not matching RAPID in zonal bucket test --- .../operations/write_test.go | 21 ++++++++++++++++--- tools/integration_tests/run_e2e_tests.sh | 2 +- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/tools/integration_tests/operations/write_test.go b/tools/integration_tests/operations/write_test.go index 2e72ba3e65..43b868bea6 100644 --- a/tools/integration_tests/operations/write_test.go +++ b/tools/integration_tests/operations/write_test.go @@ -68,7 +68,10 @@ func validateObjectAttributes(attr1, attr2 *storage.ObjectAttrs, t *testing.T) { const componentCount = 0 const sizeBeforeOperation = int64(len(tempFileContent)) const sizeAfterOperation = sizeBeforeOperation + int64(len(appendContent)) - const storageClass = "STANDARD" + storageClass := "STANDARD" + if setup.IsZonalBucketRun() { + storageClass = "RAPID" + } if attr1.ContentType != contentType || attr2.ContentType != contentType { t.Errorf("Expected content type: %s, Got: %s, %s", contentType, attr1.ContentType, attr2.ContentType) @@ -98,10 +101,22 @@ func validateObjectAttributes(attr1, attr2 *storage.ObjectAttrs, t *testing.T) { t.Error("Expected CRC32 attributes to be non 0") } if attr1.MediaLink == "" || attr2.MediaLink == "" { - t.Errorf("Expected media link to be non empty") + if setup.IsZonalBucketRun() { + t.Logf("media link is empty, but it is a known limitation in RAPID/zonal buckets.") + } else { + t.Errorf("Expected media link to be non empty") + } } if attr1.StorageClass != storageClass || attr2.StorageClass != storageClass { - t.Errorf("Expected storage class ") + if setup.IsZonalBucketRun() { + if attr1.StorageClass != attr2.StorageClass { + t.Errorf("Expected storage classes to be same (%q) for both, but found attr1.StorageClass = %q (bucketName = %q) != attr2.StorageClass = %q (bucketName = %q)", storageClass, attr1.StorageClass, attr1.Bucket, attr2.StorageClass, attr2.Bucket) + } else { + t.Logf("Expected storage class to be %q for both, but found StorageClass = %q for both (buckets = %q, %q).", storageClass, attr1.StorageClass, attr1.Bucket, attr2.Bucket) + } + } else { + t.Errorf("Expected storage class to be %q, but found attr1.StorageClass = %q (bucketName = %q), attr2.StorageClass = %q (bucketName = %q)", storageClass, attr1.StorageClass, attr1.Bucket, attr2.StorageClass, attr2.Bucket) + } } attr1MTime, _ := time.Parse(time.RFC3339Nano, attr1.Metadata[gcs.MtimeMetadataKey]) attr2MTime, _ := time.Parse(time.RFC3339Nano, attr2.Metadata[gcs.MtimeMetadataKey]) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 1484f7c716..6ad36a17e5 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -134,7 +134,7 @@ TEST_DIR_PARALLEL_FOR_ZB=( "mount_timeout" "mounting" "negative_stat_cache" - # "operations" + "operations" "read_cache" "read_large_files" "rename_dir_limit" From 875fb2d6f8a40004e40e0436e895e2dac6891955 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Fri, 11 Apr 2025 12:02:03 +0530 Subject: [PATCH 0351/1298] close file command should not exit (#3181) --- internal/fs/stale_file_handle_common_test.go | 2 +- .../common_failure_test.go | 2 +- .../disabled_kernel_list_cache_test.go | 4 +-- .../finite_kernel_list_cache_test.go | 4 +-- ...inite_kernel_list_cache_delete_dir_test.go | 8 ++--- .../infinite_kernel_list_cache_test.go | 34 +++++++++---------- .../local_file/remove_dir.go | 6 ++-- .../integration_tests/local_file/sym_link.go | 2 +- .../local_file/unlinked_file.go | 8 ++--- .../list_empty_managed_folders_test.go | 2 +- .../managed_folders/test_helper.go | 4 +-- .../operations/copy_dir_test.go | 2 +- .../operations/delete_file_test.go | 2 +- .../operations/move_file_test.go | 2 +- .../integration_tests/operations/read_test.go | 2 +- .../operations/write_test.go | 4 +-- .../cache_file_for_range_read_false_test.go | 4 +-- .../concurrent_read_same_file_test.go | 2 +- .../rename_dir_limit/move_dir_test.go | 2 +- .../stale_file_handle_common_test.go | 2 +- ...ile_handle_streaming_writes_common_test.go | 2 +- ...le_streaming_writes_empty_gcs_file_test.go | 4 +-- .../streaming_writes/symlink_file_test.go | 2 +- .../util/client/gcs_helper.go | 2 +- .../util/client/storage_client.go | 2 +- .../util/operations/dir_operations.go | 2 +- .../util/operations/file_operations.go | 13 ++++--- .../implicit_and_explicit_dir_setup.go | 2 +- 28 files changed, 63 insertions(+), 64 deletions(-) diff --git a/internal/fs/stale_file_handle_common_test.go b/internal/fs/stale_file_handle_common_test.go index 00fba5e849..b1c1c3e138 100644 --- a/internal/fs/stale_file_handle_common_test.go +++ b/internal/fs/stale_file_handle_common_test.go @@ -133,7 +133,7 @@ func (t *staleFileHandleCommon) TestFileDeletedLocallySyncAndCloseDoNotThrowErro assert.NoError(t.T(), err) operations.SyncFile(t.f1, t.T()) - operations.CloseFile(t.f1) + operations.CloseFileShouldNotThrowError(t.T(), t.f1) // Make f1 nil, so that another attempt is not taken in TearDown to close the // file. diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go index 15c578daf9..4150f8c1b3 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go @@ -275,7 +275,7 @@ func (t *commonFailureTestSuite) TestStreamingWritesBwhResetsWhenFileHandlesAreO operations.CloseFileShouldThrowError(t.T(), t.fh1) // Opening new file handle and writing to file succeeds when file handles in O_RDONLY mode are open. t.writingAfterBwhReinitializationSucceeds() - operations.CloseFileShouldNotThrowError(fh2, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), fh2) // Close and validate object content found on GCS. CloseFileAndValidateContentFromGCS(t.ctx, t.storageClient, t.fh1, testDirName, FileName1, string(t.data), t.T()) } diff --git a/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go index f3756293b1..e05e7539c8 100644 --- a/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go @@ -53,9 +53,9 @@ func (s *disabledKernelListCacheTest) TestKernelListCache_AlwaysCacheMiss(t *tes operations.CreateDirectory(targetDir, t) // Create test data f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) diff --git a/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go index 9f10f8e27b..e499017ab1 100644 --- a/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go @@ -56,9 +56,9 @@ func (s *finiteKernelListCacheTest) TestKernelListCache_CacheHitWithinLimit_Cach operations.CreateDirectory(targetDir, t) // Create test data f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go index 8905676c0b..c6d88122e2 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go @@ -49,9 +49,9 @@ func (s *infiniteKernelListCacheDeleteDirTest) TestKernelListCache_ListAndDelete operations.CreateDirectory(targetDir, t) // Create test data f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) // (a) First read served from GCS, kernel will cache the dir response. f, err := os.Open(targetDir) @@ -77,9 +77,9 @@ func (s *infiniteKernelListCacheDeleteDirTest) TestKernelListCache_DeleteAndList operations.CreateDirectory(targetDir, t) // Create test data f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) err := os.RemoveAll(targetDir) assert.NoError(t, err) diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go index ea740f1862..2f6196a7c3 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go @@ -54,9 +54,9 @@ func (s *infiniteKernelListCacheTest) TestKernelListCache_AlwaysCacheHit(t *test operations.CreateDirectory(targetDir, t) // Create test data f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) @@ -96,9 +96,9 @@ func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnAdditionOfF operations.CreateDirectory(targetDir, t) // Create test data f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) @@ -147,9 +147,9 @@ func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnDeletionOfF operations.CreateDirectory(targetDir, t) // Create test data f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) @@ -191,9 +191,9 @@ func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnFileRename( operations.CreateDirectory(targetDir, t) // Create test data f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) @@ -257,11 +257,11 @@ func (s *infiniteKernelListCacheTest) TestKernelListCache_EvictCacheEntryOfOnlyD operations.CreateDirectory(subDir, t) // Create test files f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(subDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) f3 := operations.CreateFile(path.Join(subDir, "file3.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f3) + operations.CloseFileShouldNotThrowError(t, f3) // Initial read of parent directory (caches results) f, err := os.Open(targetDir) require.NoError(t, err) @@ -324,9 +324,9 @@ func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnAdditionOfD operations.CreateDirectory(targetDir, t) // Create test data f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) require.NoError(t, err) @@ -367,9 +367,9 @@ func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnDeletionOfD operations.CreateDirectory(targetDir, t) // Create test data f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) err := os.Mkdir(path.Join(targetDir, "sub_dir"), setup.DirPermission_0755) require.NoError(t, err) // First read, kernel will cache the dir response. @@ -412,9 +412,9 @@ func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnDirectoryRe operations.CreateDirectory(targetDir, t) // Create test data f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f1) + operations.CloseFileShouldNotThrowError(t, f1) f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFile(f2) + operations.CloseFileShouldNotThrowError(t, f2) err := os.Mkdir(path.Join(targetDir, "sub_dir"), setup.DirPermission_0755) require.NoError(t, err) // First read, kernel will cache the dir response. diff --git a/tools/integration_tests/local_file/remove_dir.go b/tools/integration_tests/local_file/remove_dir.go index 35ddb0070f..8a04eda16a 100644 --- a/tools/integration_tests/local_file/remove_dir.go +++ b/tools/integration_tests/local_file/remove_dir.go @@ -45,7 +45,7 @@ func (t *CommonLocalFileTestSuite) TestRmDirOfDirectoryContainingGCSAndLocalFile // Validate writing content to unlinked local file does not throw error. operations.WriteWithoutClose(fh2, FileContents, t.T()) // Validate flush file does not throw error and does not create object on GCS. - operations.CloseFileShouldNotThrowError(fh2, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), fh2) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile, t.T()) // Validate synced files are also deleted. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, syncedFile, t.T()) @@ -67,9 +67,9 @@ func (t *CommonLocalFileTestSuite) TestRmDirOfDirectoryContainingOnlyLocalFiles( // Verify rmDir operation succeeds. operations.ValidateNoFileOrDirError(t.T(), path.Join(testDirPath, ExplicitDirName)) // Close the local files and validate they are not present on GCS. - operations.CloseFileShouldNotThrowError(fh1, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), fh1) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile1, t.T()) - operations.CloseFileShouldNotThrowError(fh2, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), fh2) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, localFile2, t.T()) // Validate directory is also deleted. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, ExplicitDirName, t.T()) diff --git a/tools/integration_tests/local_file/sym_link.go b/tools/integration_tests/local_file/sym_link.go index 6110ebdbdd..440d29b8db 100644 --- a/tools/integration_tests/local_file/sym_link.go +++ b/tools/integration_tests/local_file/sym_link.go @@ -52,7 +52,7 @@ func (t *localFileTestSuite) TestReadSymlinkForDeletedLocalFile() { filePath, symlink, fh := createAndVerifySymLink(t.T()) // Remove filePath and then close the fileHandle to avoid syncing to GCS. operations.RemoveFile(filePath) - operations.CloseFileShouldNotThrowError(fh, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), fh) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) // Reading symlink should fail. diff --git a/tools/integration_tests/local_file/unlinked_file.go b/tools/integration_tests/local_file/unlinked_file.go index fe2df21a9a..27e64fce57 100644 --- a/tools/integration_tests/local_file/unlinked_file.go +++ b/tools/integration_tests/local_file/unlinked_file.go @@ -34,7 +34,7 @@ func (t *CommonLocalFileTestSuite) TestStatOnUnlinkedLocalFile() { operations.ValidateNoFileOrDirError(t.T(), path.Join(testDirPath, FileName1)) // Close the file and validate that file is not created on GCS. - operations.CloseFileShouldNotThrowError(fh, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), fh) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } @@ -60,7 +60,7 @@ func (t *CommonLocalFileTestSuite) TestReadDirContainingUnlinkedLocalFiles() { CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testDirName, FileName2, "", t.T()) // Verify unlinked file is not written to GCS. - operations.CloseFileShouldNotThrowError(fh3, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), fh3) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName3, t.T()) } @@ -76,7 +76,7 @@ func (t *CommonLocalFileTestSuite) TestWriteOnUnlinkedLocalFileSucceeds() { operations.WriteWithoutClose(fh, FileContents, t.T()) // Validate flush file does not throw error. - operations.CloseFileShouldNotThrowError(fh, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), fh) // Validate unlinked file is not written to GCS. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } @@ -95,7 +95,7 @@ func (t *CommonLocalFileTestSuite) TestSyncOnUnlinkedLocalFile() { operations.SyncFile(fh, t.T()) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) // Close the local file and validate it is not present on GCS. - operations.CloseFileShouldNotThrowError(fh, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), fh) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) } diff --git a/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go b/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go index 557b110ead..714893e0c5 100644 --- a/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go +++ b/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go @@ -73,7 +73,7 @@ func createDirectoryStructureForEmptyManagedFoldersTest(t *testing.T) { client.CreateManagedFoldersInBucket(ctx, controlClient, path.Join(testDir, EmptyManagedFolder2), bucket) operations.CreateDirectory(path.Join(setup.MntDir(), TestDirForEmptyManagedFoldersTest, SimulatedFolder), t) f := operations.CreateFile(path.Join(setup.MntDir(), TestDirForEmptyManagedFoldersTest, File), setup.FilePermission_0600, t) - operations.CloseFile(f) + operations.CloseFileShouldNotThrowError(t, f) } //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/managed_folders/test_helper.go b/tools/integration_tests/managed_folders/test_helper.go index 61cb5b5bb3..de364ee1bf 100644 --- a/tools/integration_tests/managed_folders/test_helper.go +++ b/tools/integration_tests/managed_folders/test_helper.go @@ -117,7 +117,7 @@ func createDirectoryStructureForNonEmptyManagedFolders(ctx context.Context, stor log.Fatalf("Failed to clean up test directory: %v", err) } f := operations.CreateFile(path.Join("/tmp", FileInNonEmptyManagedFoldersTest), setup.FilePermission_0600, t) - defer operations.CloseFile(f) + defer operations.CloseFileShouldNotThrowError(t, f) managedFolder1 := path.Join(testDir, ManagedFolder1) managedFolder2 := path.Join(testDir, ManagedFolder2) simulatedFolderNonEmptyManagedFoldersTest := path.Join(testDir, SimulatedFolderNonEmptyManagedFoldersTest) @@ -254,7 +254,7 @@ func moveAndCheckErrForViewPermission(src, dest string, t *testing.T) { func createFileForTest(filePath string, t *testing.T) { file, err := os.Create(filePath) - defer operations.CloseFile(file) + defer operations.CloseFileShouldNotThrowError(t, file) if err != nil { t.Errorf("Error in creating local file, %v", err) } diff --git a/tools/integration_tests/operations/copy_dir_test.go b/tools/integration_tests/operations/copy_dir_test.go index 0728a59d92..80eb98437f 100644 --- a/tools/integration_tests/operations/copy_dir_test.go +++ b/tools/integration_tests/operations/copy_dir_test.go @@ -59,7 +59,7 @@ func createSrcDirectoryWithObjects(dirPath string, t *testing.T) string { } // Closing file at the end - defer operations.CloseFile(file) + defer operations.CloseFileShouldNotThrowError(t, file) return dirPath } diff --git a/tools/integration_tests/operations/delete_file_test.go b/tools/integration_tests/operations/delete_file_test.go index 5cf56ba3f6..716f1e6126 100644 --- a/tools/integration_tests/operations/delete_file_test.go +++ b/tools/integration_tests/operations/delete_file_test.go @@ -48,7 +48,7 @@ func createFile(filePath string, t *testing.T) { } // Closing file at the end - operations.CloseFile(file) + operations.CloseFileShouldNotThrowError(t, file) } // Remove testBucket/A.txt diff --git a/tools/integration_tests/operations/move_file_test.go b/tools/integration_tests/operations/move_file_test.go index f397b8b946..4e16fc3657 100644 --- a/tools/integration_tests/operations/move_file_test.go +++ b/tools/integration_tests/operations/move_file_test.go @@ -42,7 +42,7 @@ func createSrcDirectoryAndFile(dirPath string, filePath string, t *testing.T) { } // Closing file at the end. - defer operations.CloseFile(file) + defer operations.CloseFileShouldNotThrowError(t, file) err = operations.WriteFile(file.Name(), MoveFileContent) if err != nil { diff --git a/tools/integration_tests/operations/read_test.go b/tools/integration_tests/operations/read_test.go index 9e22f64c39..f31bb127aa 100644 --- a/tools/integration_tests/operations/read_test.go +++ b/tools/integration_tests/operations/read_test.go @@ -39,7 +39,7 @@ func TestReadAfterWrite(t *testing.T) { } // Closing file at the end - operations.CloseFile(tmpFile) + operations.CloseFileShouldNotThrowError(t, tmpFile) fileName := tmpFile.Name() diff --git a/tools/integration_tests/operations/write_test.go b/tools/integration_tests/operations/write_test.go index 43b868bea6..c0dc0886b9 100644 --- a/tools/integration_tests/operations/write_test.go +++ b/tools/integration_tests/operations/write_test.go @@ -177,7 +177,7 @@ func TestWriteAtRandom(t *testing.T) { t.Errorf("WriteString-Random: %v", err) } // Closing file at the end - operations.CloseFile(f) + operations.CloseFileShouldNotThrowError(t, f) setup.CompareFileContents(t, fileName, "line 1\nline 5\n") // Validate that extended object attributes are non nil/ non-empty. @@ -231,7 +231,7 @@ func TestWriteAtFileOperationsDoesNotChangeObjectAttributes(t *testing.T) { t.Errorf("Could not open file %s after creation.", fileName) } operations.WriteAt(tempFileContent+appendContent, 0, fh, t) - operations.CloseFile(fh) + operations.CloseFileShouldNotThrowError(t, fh) attr2 := validateExtendedObjectAttributesNonEmpty(path.Join(DirForOperationTests, tempFileName), t) // Validate object attributes are as expected. diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go index 56d1f2f286..6f7f7b2525 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go @@ -101,9 +101,9 @@ func (s *cacheFileForRangeReadFalseTest) TestReadIsTreatedNonSequentialAfterFile randomReadChunkCount := fileSizeSameAsCacheCapacity / chunkSizeToRead readTillChunk := randomReadChunkCount / 2 fh1 := operations.OpenFile(path.Join(testDirPath, testFileNames[0]), t) - defer operations.CloseFile(fh1) + defer operations.CloseFileShouldNotThrowError(t, fh1) fh2 := operations.OpenFile(path.Join(testDirPath, testFileNames[1]), t) - defer operations.CloseFile(fh2) + defer operations.CloseFileShouldNotThrowError(t, fh2) // Use file handle 1 to read file 1 partially. expectedOutcome[0] = readFileBetweenOffset(t, fh1, 0, int64(readTillChunk*chunkSizeToRead)) diff --git a/tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go b/tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go index 496b7ba57e..72cb581cee 100644 --- a/tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go +++ b/tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go @@ -53,7 +53,7 @@ func readAndCompare(t *testing.T, filePathInMntDir string, filePathInLocalDisk s if err != nil { t.Fatalf("error in opening file from mounted directory :%d", err) } - defer operations.CloseFile(mountedFile) + defer operations.CloseFileShouldNotThrowError(t, mountedFile) // Perform 5 reads on each file. numberOfReads := 5 diff --git a/tools/integration_tests/rename_dir_limit/move_dir_test.go b/tools/integration_tests/rename_dir_limit/move_dir_test.go index 19b4bb0389..8680849b35 100644 --- a/tools/integration_tests/rename_dir_limit/move_dir_test.go +++ b/tools/integration_tests/rename_dir_limit/move_dir_test.go @@ -78,7 +78,7 @@ func createSrcDirectoryWithObjectsForMoveDirTest(dirPath string, t *testing.T) { } // Closing file at the end - defer operations.CloseFile(file) + defer operations.CloseFileShouldNotThrowError(t, file) err = operations.WriteFile(file.Name(), SrcMoveFileContent) if err != nil { diff --git a/tools/integration_tests/stale_handle/stale_file_handle_common_test.go b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go index e05a159bf9..1a787d98ba 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_common_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go @@ -64,5 +64,5 @@ func (s *staleFileHandleCommon) TestFileDeletedLocallySyncAndCloseDoNotThrowErro operations.WriteWithoutClose(s.f1, Content2, s.T()) operations.SyncFile(s.f1, s.T()) - operations.CloseFile(s.f1) + operations.CloseFileShouldNotThrowError(s.T(), s.f1) } diff --git a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go index 89adba4ea9..834c25c3be 100644 --- a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go +++ b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go @@ -71,7 +71,7 @@ func (t *staleFileHandleStreamingWritesCommon) TestFileDeletedLocallySyncAndClos assert.NoError(t.T(), err) operations.SyncFile(t.f1, t.T()) - operations.CloseFileShouldNotThrowError(t.f1, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), t.f1) } func (t *staleFileHandleStreamingWritesCommon) TestClosingFileHandleForClobberedFileReturnsStaleFileHandleError() { diff --git a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go index aceca19ff9..71c321b317 100644 --- a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go +++ b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go @@ -61,7 +61,7 @@ func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestFirstWriteToClobberedFi operations.ValidateStaleNFSFileHandleError(t.T(), err) operations.SyncFile(t.f1, t.T()) - operations.CloseFileShouldNotThrowError(t.f1, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), t.f1) } func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestWriteOnRenamedFileThrowsStaleFileHandleError() { @@ -78,7 +78,7 @@ func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestWriteOnRenamedFileThrow operations.ValidateStaleNFSFileHandleError(t.T(), err) // Sync and Close succeeds. operations.SyncFile(t.f1, t.T()) - operations.CloseFileShouldNotThrowError(t.f1, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), t.f1) ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, t.data, t.T()) } diff --git a/tools/integration_tests/streaming_writes/symlink_file_test.go b/tools/integration_tests/streaming_writes/symlink_file_test.go index 3ffcec37fe..fe5d7768ba 100644 --- a/tools/integration_tests/streaming_writes/symlink_file_test.go +++ b/tools/integration_tests/streaming_writes/symlink_file_test.go @@ -56,7 +56,7 @@ func (t *defaultMountCommonTest) TestReadSymlinkForDeletedLocalFileFails() { assert.Error(t.T(), err) // Remove filePath and then close the fileHandle to avoid syncing to GCS. operations.RemoveFile(t.filePath) - operations.CloseFileShouldNotThrowError(t.f1, t.T()) + operations.CloseFileShouldNotThrowError(t.T(), t.f1) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) // Reading symlink should fail. _, err = os.Stat(symlink) diff --git a/tools/integration_tests/util/client/gcs_helper.go b/tools/integration_tests/util/client/gcs_helper.go index 226229549f..8c49b6ae3d 100644 --- a/tools/integration_tests/util/client/gcs_helper.go +++ b/tools/integration_tests/util/client/gcs_helper.go @@ -96,7 +96,7 @@ func ValidateObjectChunkFromGCS(ctx context.Context, storageClient *storage.Clie func CloseFileAndValidateContentFromGCS(ctx context.Context, storageClient *storage.Client, fh *os.File, testDirName, fileName, content string, t *testing.T) { - operations.CloseFileShouldNotThrowError(fh, t) + operations.CloseFileShouldNotThrowError(t, fh) ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, fileName, content, t) } diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 0a99989580..77d2579264 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -213,7 +213,7 @@ func DownloadObjectFromGCS(gcsFile string, destFileName string, t *testing.T) er } }() f := operations.CreateFile(destFileName, setup.FilePermission_0600, t) - defer operations.CloseFile(f) + defer operations.CloseFileShouldNotThrowError(t, f) rc, err := storageClient.Bucket(bucket).Object(gcsFile).NewReader(ctx) if err != nil { diff --git a/tools/integration_tests/util/operations/dir_operations.go b/tools/integration_tests/util/operations/dir_operations.go index a9a73167e3..c4c0c81019 100644 --- a/tools/integration_tests/util/operations/dir_operations.go +++ b/tools/integration_tests/util/operations/dir_operations.go @@ -106,7 +106,7 @@ func CreateDirectoryWithNFiles(numberOfFiles int, dirPath string, prefix string, } // Closing file at the end. - CloseFile(file) + CloseFileShouldNotThrowError(t, file) } } diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index e0d4eb0eb3..24277e45a3 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -180,6 +180,7 @@ func CloseFiles(t *testing.T, files []*os.File) { } } +// Deprecated: please use CloseFileShouldNotThrowError instead. func CloseFile(file *os.File) { if err := file.Close(); err != nil { log.Fatalf("error in closing: %v", err) @@ -601,10 +602,9 @@ func WriteAt(content string, offset int64, fh *os.File, t testing.TB) { } } -func CloseFileShouldNotThrowError(file *os.File, t *testing.T) { - if err := file.Close(); err != nil { - t.Fatalf("file.Close() for file %s: %v", file.Name(), err) - } +func CloseFileShouldNotThrowError(t testing.TB, file *os.File) { + err := file.Close() + assert.NoError(t, err) } func CloseFileShouldThrowError(t *testing.T, file *os.File) { @@ -630,11 +630,10 @@ func SyncFileShouldThrowError(t *testing.T, file *os.File) { } } -func CreateFileWithContent(filePath string, filePerms os.FileMode, - content string, t testing.TB) { +func CreateFileWithContent(filePath string, filePerms os.FileMode, content string, t testing.TB) { fh := CreateFile(filePath, filePerms, t) WriteAt(content, 0, fh, t) - CloseFile(fh) + CloseFileShouldNotThrowError(t, fh) } // CreateFileOfSize creates a file of given size with random data. diff --git a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go index dccdc1d4c8..cd4adefb0c 100644 --- a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go +++ b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go @@ -138,7 +138,7 @@ func CreateExplicitDirectoryStructure(testDir string, t *testing.T) { } // Closing file at the end. - defer operations.CloseFile(file) + defer operations.CloseFileShouldNotThrowError(t, file) } func CreateImplicitDirectoryInExplicitDirectoryStructure(testDir string, t *testing.T) { From ddf1659feb4add4452570c413fdd3e86b675c802 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 11 Apr 2025 12:32:54 +0530 Subject: [PATCH 0352/1298] [Random Reader Refactoring] Initialize file cache reader (#3174) * initilize file cache reader * add setup teardown in test * small fix * lint fix * remove comment * update test name * add const * review comments * move const * rename struct * remove unnecessary const --- internal/gcsx/file_cache_reader.go | 51 +++++++++++++++ internal/gcsx/file_cache_reader_test.go | 83 +++++++++++++++++++++++++ internal/gcsx/random_reader_test.go | 4 -- internal/gcsx/reader.go | 16 ++--- 4 files changed, 142 insertions(+), 12 deletions(-) create mode 100644 internal/gcsx/file_cache_reader.go create mode 100644 internal/gcsx/file_cache_reader_test.go diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go new file mode 100644 index 0000000000..82ac8c85ea --- /dev/null +++ b/internal/gcsx/file_cache_reader.go @@ -0,0 +1,51 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" +) + +type FileCacheReader struct { + Reader + obj *gcs.MinObject + bucket gcs.Bucket + + // fileCacheHandler is used to get file cache handle and read happens using that. + // This will be nil if the file cache is disabled. + fileCacheHandler *file.CacheHandler + + // cacheFileForRangeRead is also valid for cache workflow, if true, object content + // will be downloaded for random reads as well too. + cacheFileForRangeRead bool + + // fileCacheHandle is used to read from the cached location. It is created on the fly + // using fileCacheHandler for the given object and bucket. + fileCacheHandle *file.CacheHandle + + metricHandle common.MetricHandle +} + +func NewFileCacheReader(o *gcs.MinObject, bucket gcs.Bucket, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle) FileCacheReader { + return FileCacheReader{ + obj: o, + bucket: bucket, + fileCacheHandler: fileCacheHandler, + cacheFileForRangeRead: cacheFileForRangeRead, + metricHandle: metricHandle, + } +} diff --git a/internal/gcsx/file_cache_reader_test.go b/internal/gcsx/file_cache_reader_test.go new file mode 100644 index 0000000000..3366e89f4f --- /dev/null +++ b/internal/gcsx/file_cache_reader_test.go @@ -0,0 +1,83 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "os" + "path" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +const ( + TestObject = "testObject" + sequentialReadSizeInMb = 22 + sequentialReadSizeInBytes = sequentialReadSizeInMb * MB + CacheMaxSize = 2 * sequentialReadSizeInMb * util.MiB +) + +type FileCacheReaderTest struct { + suite.Suite + object *gcs.MinObject + mockBucket *storage.TestifyMockBucket + cacheDir string + jobManager *downloader.JobManager + cacheHandler *file.CacheHandler +} + +func TestFileCacheReaderTestSuite(t *testing.T) { + suite.Run(t, new(FileCacheReaderTest)) +} + +func (t *FileCacheReaderTest) SetupTestSuite() { + t.object = &gcs.MinObject{ + Name: TestObject, + Size: 17, + Generation: 1234, + } + t.mockBucket = new(storage.TestifyMockBucket) + t.cacheDir = path.Join(os.Getenv("HOME"), "test_cache_dir") + lruCache := lru.NewCache(CacheMaxSize) + t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{EnableCrc: false}, nil) + t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) +} + +func (t *FileCacheReaderTest) TearDownSuite() { + err := os.RemoveAll(t.cacheDir) + if err != nil { + t.T().Logf("Failed to clean up test cache directory '%s': %v", t.cacheDir, err) + } +} + +func (t *FileCacheReaderTest) TestNewFileCacheReader() { + reader := NewFileCacheReader(t.object, t.mockBucket, t.cacheHandler, true, nil) + + assert.NotNil(t.T(), reader) + assert.Equal(t.T(), t.object, reader.obj) + assert.Equal(t.T(), t.mockBucket, reader.bucket) + assert.Equal(t.T(), t.cacheHandler, reader.fileCacheHandler) + assert.True(t.T(), reader.cacheFileForRangeRead) + assert.Nil(t.T(), reader.metricHandle) + assert.Nil(t.T(), reader.fileCacheHandle) +} diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index 7902e24887..f8a4a6ac4b 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -146,10 +146,6 @@ func rangeLimitIs(expected uint64) (m Matcher) { // Boilerplate //////////////////////////////////////////////////////////////////////// -const sequentialReadSizeInMb = 22 -const sequentialReadSizeInBytes = sequentialReadSizeInMb * MB -const CacheMaxSize = 2 * sequentialReadSizeInMb * util.MiB - type RandomReaderTest struct { object *gcs.MinObject bucket storage.MockBucket diff --git a/internal/gcsx/reader.go b/internal/gcsx/reader.go index 21faebf0b4..354b2e66ae 100644 --- a/internal/gcsx/reader.go +++ b/internal/gcsx/reader.go @@ -26,8 +26,8 @@ import ( // to an alternative reader. var FallbackToAnotherReader = errors.New("fallback to another reader is required") -// GCSReaderReq represents the request parameters needed to read a data from a GCS object. -type GCSReaderReq struct { +// GCSReaderRequest represents the request parameters needed to read a data from a GCS object. +type GCSReaderRequest struct { // Buffer is provided by jacobsa/fuse and should be filled with data from the object. Buffer []byte @@ -38,9 +38,9 @@ type GCSReaderReq struct { EndOffset int64 } -// ObjectRead represents the response returned as part of a ReadAt call. +// ReaderResponse represents the response returned as part of a ReadAt call. // It includes the actual data read and its size. -type ObjectRead struct { +type ReaderResponse struct { // DataBuf contains the bytes read from the object. DataBuf []byte @@ -53,10 +53,10 @@ type Reader interface { CheckInvariants() // ReadAt reads data into the provided byte slice starting from the specified offset. - // It returns an ObjectRead containing the data read and the number of bytes read. + // It returns an ReaderResponse containing the data read and the number of bytes read. // To indicate that the operation should be handled by an alternative reader, return // the error FallbackToAnotherReader. - ReadAt(ctx context.Context, p []byte, offset int64) (ObjectRead, error) + ReadAt(ctx context.Context, p []byte, offset int64) (ReaderResponse, error) // Destroy is called to release any resources held by the reader. Destroy() @@ -75,6 +75,6 @@ type ReadManager interface { // This interface is intended for lower-level interactions with GCS readers. type GCSReader interface { // ReadAt reads data into the provided request buffer, starting from the specified offset and ending at the specified end offset. - // It returns an ObjectRead response containing the data read and any error encountered. - ReadAt(ctx context.Context, req *GCSReaderReq) (ObjectRead, error) + // It returns an ReaderResponse response containing the data read and any error encountered. + ReadAt(ctx context.Context, req *GCSReaderRequest) (ReaderResponse, error) } From 55ac19b9c031429e350136c087576f02d2ea2168 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 11 Apr 2025 15:17:09 +0530 Subject: [PATCH 0353/1298] Add flag to guard metric renames (#3140) * Add flag to guard metric renames * The hidden flag allows one to switch between the existing metric renames and the new proposed ones. * This flag is not supposed to be exposed to the end user but will allow to decouple GKE with GCSFuse metrics. --- cfg/config.go | 12 ++++++++++++ cfg/params.yaml | 7 +++++++ cmd/root_test.go | 7 +++++++ 3 files changed, 26 insertions(+) diff --git a/cfg/config.go b/cfg/config.go index 2df3b5b19e..990be0b360 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -219,6 +219,8 @@ type MetricsConfig struct { PrometheusPort int64 `yaml:"prometheus-port"` StackdriverExportInterval time.Duration `yaml:"stackdriver-export-interval"` + + UseNewNames bool `yaml:"use-new-names"` } type MonitoringConfig struct { @@ -481,6 +483,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.IntP("metadata-cache-ttl-secs", "", 60, "The ttl value in seconds to be used for expiring items in metadata-cache. It can be set to -1 for no-ttl, 0 for no cache and > 0 for ttl-controlled metadata-cache. Any value set below -1 will throw an error.") + flagSet.BoolP("metrics-use-new-names", "", false, "Use the new metric names.") + + if err := flagSet.MarkHidden("metrics-use-new-names"); err != nil { + return err + } + flagSet.StringSliceP("o", "", []string{}, "Additional system-specific mount options. Multiple options can be passed as comma separated. For readonly, use --o ro") flagSet.StringP("only-dir", "", "", "Mount only a specific directory within the bucket. See docs/mounting for more information") @@ -832,6 +840,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("metrics.use-new-names", flagSet.Lookup("metrics-use-new-names")); err != nil { + return err + } + if err := v.BindPFlag("file-system.fuse-options", flagSet.Lookup("o")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 33ca097e95..1ec47498fa 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -629,6 +629,13 @@ deprecated: true deprecation-warning: "Please use --cloud-metrics-export-interval-secs instead." +- config-path: "metrics.use-new-names" + flag-name: "metrics-use-new-names" + type: "bool" + usage: "Use the new metric names." + default: false + hide-flag: true + - config-path: "monitoring.experimental-tracing-mode" flag-name: "experimental-tracing-mode" type: "string" diff --git a/cmd/root_test.go b/cmd/root_test.go index 04e89f1fe5..64498474dd 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1029,6 +1029,13 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { StackdriverExportInterval: time.Duration(10) * time.Hour, }, }, + { + name: "use_new_metric_names", + args: []string{"gcsfuse", "--metrics-use-new-names=true", "abc", "pqr"}, + expected: &cfg.MetricsConfig{ + UseNewNames: true, + }, + }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { From e3b0376aa2cdcda2e9b72ea38804dd9bdfea9032 Mon Sep 17 00:00:00 2001 From: codechanges Date: Wed, 16 Apr 2025 14:35:14 +0530 Subject: [PATCH 0354/1298] Make default value of disable-autoconfig false (#3206) --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/root_test.go | 72 ++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 990be0b360..9ec5dffad4 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -315,7 +315,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.StringP("dir-mode", "", "0755", "Permissions bits for directories, in octal.") - flagSet.BoolP("disable-autoconfig", "", false, "Disable optimizing configuration automatically for a machine") + flagSet.BoolP("disable-autoconfig", "", true, "Disable optimizing configuration automatically for a machine") if err := flagSet.MarkHidden("disable-autoconfig"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 1ec47498fa..1c16b1c14f 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -75,7 +75,7 @@ flag-name: "disable-autoconfig" type: "bool" usage: "Disable optimizing configuration automatically for a machine" - default: false + default: true hide-flag: true - config-path: "enable-atomic-rename-object" diff --git a/cmd/root_test.go b/cmd/root_test.go index 64498474dd..a1e805f106 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -216,8 +216,13 @@ func TestArgsParsing_ImplicitDirsFlag(t *testing.T) { expectedImplicit: false, }, { - name: "default true on high performance machine", - args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "abc", "pqr"}, + name: "default false on high performance machine with autoconfig disabled", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=true", "abc", "pqr"}, + expectedImplicit: false, + }, + { + name: "default true on high performance machine with autoconfig enabled", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "abc", "pqr"}, expectedImplicit: true, }, { @@ -328,12 +333,21 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedWriteMaxBlocksPerFile: 10, }, { - name: "Test high performance config values.", - args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "abc", "pqr"}, + name: "Test high performance config values with autoconfig enabled.", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "abc", "pqr"}, expectedEnableStreamingWrites: true, expectedWriteBlockSizeMB: 32 * util.MiB, expectedWriteGlobalMaxBlocks: math.MaxInt64, }, + { + name: "Test high performance config values with autoconfig disabled.", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=true", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, + }, { name: "Test high performance config values with enable-streaming-writes flag overriden.", args: []string{"gcsfuse", "--enable-streaming-writes=false", "abc", "pqr"}, @@ -750,8 +764,8 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { }, }, { - name: "high performance defaults with rename dir options", - args: []string{"gcsfuse", "--dir-mode=777", "--machine-type=a3-highgpu-8g", "--file-mode=666", "abc", "pqr"}, + name: "high performance defaults with rename dir options with autoconfig enabled", + args: []string{"gcsfuse", "--dir-mode=777", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "--file-mode=666", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileSystem: cfg.FileSystemConfig{ DirMode: 0777, @@ -768,9 +782,28 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { }, }, }, + { + name: "high performance defaults with rename dir options with autoconfig disabled", + args: []string{"gcsfuse", "--dir-mode=777", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=true", "--file-mode=666", "abc", "pqr"}, + expectedConfig: &cfg.Config{ + FileSystem: cfg.FileSystemConfig{ + DirMode: 0777, + DisableParallelDirops: false, + FileMode: 0666, + FuseOptions: []string{}, + Gid: -1, + IgnoreInterrupts: true, + KernelListCacheTtlSecs: 0, + RenameDirLimit: 0, + TempDir: "", + PreconditionErrors: true, + Uid: -1, + }, + }, + }, { name: "high performance defaults with overriden rename dir options", - args: []string{"gcsfuse", "--dir-mode=777", "--machine-type=a3-highgpu-8g", "--rename-dir-limit=15000", "--file-mode=666", "abc", "pqr"}, + args: []string{"gcsfuse", "--dir-mode=777", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "--rename-dir-limit=15000", "--file-mode=666", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileSystem: cfg.FileSystemConfig{ DirMode: 0777, @@ -1141,8 +1174,25 @@ func TestArgsParsing_MetadataCacheFlags(t *testing.T) { }, }, { - name: "high performance default config values", - args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "abc", "pqr"}, + name: "high performance default config values with autoconfig disabled", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=true", "abc", "pqr"}, + expectedConfig: &cfg.Config{ + MetadataCache: cfg.MetadataCacheConfig{ + DeprecatedStatCacheCapacity: 20460, + DeprecatedStatCacheTtl: 60 * time.Second, + DeprecatedTypeCacheTtl: 60 * time.Second, + EnableNonexistentTypeCache: false, + ExperimentalMetadataPrefetchOnMount: "disabled", + StatCacheMaxSizeMb: 32, + TtlSecs: 60, + NegativeTtlSecs: 5, + TypeCacheMaxSizeMb: 4, + }, + }, + }, + { + name: "high performance default config values with autoconfig enabled", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "abc", "pqr"}, expectedConfig: &cfg.Config{ MetadataCache: cfg.MetadataCacheConfig{ DeprecatedStatCacheCapacity: 20460, @@ -1159,7 +1209,7 @@ func TestArgsParsing_MetadataCacheFlags(t *testing.T) { }, { name: "high performance default config values obey customer flags", - args: []string{"gcsfuse", "--stat-cache-capacity=2000", "--stat-cache-ttl=2m", "--type-cache-ttl=1m20s", "--enable-nonexistent-type-cache", "--experimental-metadata-prefetch-on-mount=async", "--stat-cache-max-size-mb=15", "--metadata-cache-ttl-secs=25", "--metadata-cache-negative-ttl-secs=20", "--type-cache-max-size-mb=30", "abc", "pqr"}, + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "--stat-cache-capacity=2000", "--stat-cache-ttl=2m", "--type-cache-ttl=1m20s", "--enable-nonexistent-type-cache", "--experimental-metadata-prefetch-on-mount=async", "--stat-cache-max-size-mb=15", "--metadata-cache-ttl-secs=25", "--metadata-cache-negative-ttl-secs=20", "--type-cache-max-size-mb=30", "abc", "pqr"}, expectedConfig: &cfg.Config{ MetadataCache: cfg.MetadataCacheConfig{ DeprecatedStatCacheCapacity: 2000, @@ -1176,7 +1226,7 @@ func TestArgsParsing_MetadataCacheFlags(t *testing.T) { }, { name: "high performance default config values use deprecated flags", - args: []string{"gcsfuse", "--stat-cache-capacity=2000", "--stat-cache-ttl=2m", "--type-cache-ttl=4m", "--enable-nonexistent-type-cache", "--experimental-metadata-prefetch-on-mount=async", "--metadata-cache-negative-ttl-secs=20", "--type-cache-max-size-mb=30", "abc", "pqr"}, + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "--stat-cache-capacity=2000", "--stat-cache-ttl=2m", "--type-cache-ttl=4m", "--enable-nonexistent-type-cache", "--experimental-metadata-prefetch-on-mount=async", "--metadata-cache-negative-ttl-secs=20", "--type-cache-max-size-mb=30", "abc", "pqr"}, expectedConfig: &cfg.Config{ MetadataCache: cfg.MetadataCacheConfig{ DeprecatedStatCacheCapacity: 2000, From cdd396d744e2dd5c54ed5402649fb3e9fec2241e Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 16 Apr 2025 15:15:57 +0530 Subject: [PATCH 0355/1298] add unfinalized object operations tests (#3204) * add unfinalized object operations tests * review comments --- internal/fs/stale_file_handle_common_test.go | 4 +- ...ile_handle_streaming_writes_common_test.go | 2 +- ...handle_streaming_writes_local_file_test.go | 6 +- ...andle_streaming_writes_synced_file_test.go | 4 +- .../fs/stale_file_handle_synced_file_test.go | 12 +- .../local_file/create_file.go | 2 +- .../integration_tests/local_file/read_dir.go | 2 +- tools/integration_tests/run_e2e_tests.sh | 1 + .../stale_file_handle_common_test.go | 4 +- .../stale_file_handle_synced_file_test.go | 12 +- ...ile_handle_streaming_writes_common_test.go | 2 +- ...le_streaming_writes_empty_gcs_file_test.go | 6 +- .../streaming_writes/rename_file_test.go | 2 +- .../unfinalized_object/setup_test.go | 54 ++++++ .../unfinalized_object_operations_test.go | 173 ++++++++++++++++++ .../util/client/gcs_helper.go | 17 ++ .../util/client/storage_client.go | 16 ++ .../util/operations/validation_helper.go | 5 +- 18 files changed, 293 insertions(+), 31 deletions(-) create mode 100644 tools/integration_tests/unfinalized_object/setup_test.go create mode 100644 tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go diff --git a/internal/fs/stale_file_handle_common_test.go b/internal/fs/stale_file_handle_common_test.go index b1c1c3e138..b5d218ef6e 100644 --- a/internal/fs/stale_file_handle_common_test.go +++ b/internal/fs/stale_file_handle_common_test.go @@ -105,9 +105,9 @@ func (t *staleFileHandleCommon) TestClobberedFileSyncAndCloseThrowsStaleFileHand err = t.f1.Sync() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) err = t.f1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) // Make f1 nil, so that another attempt is not taken in TearDown to close the // file. t.f1 = nil diff --git a/internal/fs/stale_file_handle_streaming_writes_common_test.go b/internal/fs/stale_file_handle_streaming_writes_common_test.go index aa2a0808c5..04b70e1164 100644 --- a/internal/fs/stale_file_handle_streaming_writes_common_test.go +++ b/internal/fs/stale_file_handle_streaming_writes_common_test.go @@ -73,7 +73,7 @@ func (t *staleFileHandleStreamingWritesCommon) TestWriteFileSyncFileClobberedFlu err = t.f1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) // Make f1 nil, so that another attempt is not taken in TearDown to close the // file. t.f1 = nil diff --git a/internal/fs/stale_file_handle_streaming_writes_local_file_test.go b/internal/fs/stale_file_handle_streaming_writes_local_file_test.go index 66dea4cbc5..4dd0e27801 100644 --- a/internal/fs/stale_file_handle_streaming_writes_local_file_test.go +++ b/internal/fs/stale_file_handle_streaming_writes_local_file_test.go @@ -53,11 +53,11 @@ func (t *staleFileHandleStreamingWritesLocalFile) TestClobberedWriteFileSyncAndC _, err = t.f1.WriteAt(data, 0) - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) err = t.f1.Sync() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) err = t.f1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) // Make f1 nil, so that another attempt is not taken in TearDown to close the // file. t.f1 = nil diff --git a/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go b/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go index 2d0aea1ea8..33878ef6ea 100644 --- a/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go +++ b/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go @@ -55,7 +55,7 @@ func (t *staleFileHandleStreamingWritesSyncedFile) TestWriteToClobberedFileThrow _, err = t.f1.WriteAt(data, 0) - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) err = t.f1.Sync() assert.NoError(t.T(), err) err = t.f1.Close() @@ -79,7 +79,7 @@ func (t *staleFileHandleStreamingWritesSyncedFile) TestRenameFileWriteThrowsStal _, err = t.f1.WriteAt(data, 0) - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) err = t.f1.Sync() assert.NoError(t.T(), err) err = t.f1.Close() diff --git a/internal/fs/stale_file_handle_synced_file_test.go b/internal/fs/stale_file_handle_synced_file_test.go index 95f1c56826..8f997b50ec 100644 --- a/internal/fs/stale_file_handle_synced_file_test.go +++ b/internal/fs/stale_file_handle_synced_file_test.go @@ -53,7 +53,7 @@ func (t *staleFileHandleSyncedFile) TestClobberedFileReadThrowsStaleFileHandleEr buffer := make([]byte, 6) _, err := t.f1.Read(buffer) - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) // Validate that object is updated with new content. contents, err := storageutil.ReadObject(ctx, bucket, "foo") assert.NoError(t.T(), err) @@ -66,7 +66,7 @@ func (t *staleFileHandleSyncedFile) TestClobberedFileFirstWriteThrowsStaleFileHa _, err := t.f1.Write([]byte("taco")) - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) // Attempt to sync to file should not result in error as we first check if the // content has been dirtied before clobbered check in Sync flow. err = t.f1.Sync() @@ -92,9 +92,9 @@ func (t *staleFileHandleSyncedFile) TestRenamedFileSyncThrowsStaleFileHandleErro err = t.f1.Sync() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) err = t.f1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) // Make f1 nil, so that another attempt is not taken in TearDown to close the // file. t.f1 = nil @@ -118,9 +118,9 @@ func (t *staleFileHandleSyncedFile) TestFileDeletedRemotelySyncAndCloseThrowsSta err = t.f1.Sync() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) err = t.f1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) // Make f1 nil, so that another attempt is not taken in TearDown to close the // file. t.f1 = nil diff --git a/tools/integration_tests/local_file/create_file.go b/tools/integration_tests/local_file/create_file.go index 3ec50282fe..828392df36 100644 --- a/tools/integration_tests/local_file/create_file.go +++ b/tools/integration_tests/local_file/create_file.go @@ -55,7 +55,7 @@ func (t *CommonLocalFileTestSuite) TestCreateNewFileWhenSameFileExistsOnGCS() { operations.WriteWithoutClose(fh, FileContents, t.T()) // Validate closing local file throws error. err := fh.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) // Ensure that the content on GCS is not overwritten. ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, FileName1, GCSFileContent, t.T()) } diff --git a/tools/integration_tests/local_file/read_dir.go b/tools/integration_tests/local_file/read_dir.go index fe13de6f52..945a2bfc1c 100644 --- a/tools/integration_tests/local_file/read_dir.go +++ b/tools/integration_tests/local_file/read_dir.go @@ -175,7 +175,7 @@ func (t *CommonLocalFileTestSuite) TestReadDirWithSameNameLocalAndGCSFile() { // Validate closing local file throws error. err = fh1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) } func (t *CommonLocalFileTestSuite) TestConcurrentReadDirAndCreationOfLocalFiles_DoesNotThrowError() { diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 6ad36a17e5..f01f1d5a92 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -141,6 +141,7 @@ TEST_DIR_PARALLEL_FOR_ZB=( "stale_handle" # "streaming_writes" "write_large_files" + "unfinalized_object" ) # Subset of TEST_DIR_NON_PARALLEL, diff --git a/tools/integration_tests/stale_handle/stale_file_handle_common_test.go b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go index 1a787d98ba..de464b300b 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_common_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go @@ -47,9 +47,9 @@ func (s *staleFileHandleCommon) TestClobberedFileSyncAndCloseThrowsStaleFileHand err = s.f1.Sync() - operations.ValidateStaleNFSFileHandleError(s.T(), err) + operations.ValidateESTALEError(s.T(), err) err = s.f1.Close() - operations.ValidateStaleNFSFileHandleError(s.T(), err) + operations.ValidateESTALEError(s.T(), err) } func (s *staleFileHandleCommon) TestFileDeletedLocallySyncAndCloseDoNotThrowError() { diff --git a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go index f856d68c19..f7102b3dd1 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go @@ -60,7 +60,7 @@ func (s *staleFileHandleSyncedFile) TestClobberedFileReadThrowsStaleFileHandleEr buffer := make([]byte, GCSFileSize) _, err = s.f1.Read(buffer) - operations.ValidateStaleNFSFileHandleError(s.T(), err) + operations.ValidateESTALEError(s.T(), err) } func (s *staleFileHandleSyncedFile) TestClobberedFileFirstWriteThrowsStaleFileHandleError() { @@ -70,7 +70,7 @@ func (s *staleFileHandleSyncedFile) TestClobberedFileFirstWriteThrowsStaleFileHa _, err = s.f1.WriteString(Content) - operations.ValidateStaleNFSFileHandleError(s.T(), err) + operations.ValidateESTALEError(s.T(), err) // Attempt to sync to file should not result in error as we first check if the // content has been dirtied before clobbered check in Sync flow. operations.SyncFile(s.f1, s.T()) @@ -88,9 +88,9 @@ func (s *staleFileHandleSyncedFile) TestRenamedFileSyncAndCloseThrowsStaleFileHa err = s.f1.Sync() - operations.ValidateStaleNFSFileHandleError(s.T(), err) + operations.ValidateESTALEError(s.T(), err) err = s.f1.Close() - operations.ValidateStaleNFSFileHandleError(s.T(), err) + operations.ValidateESTALEError(s.T(), err) } func (s *staleFileHandleSyncedFile) TestFileDeletedRemotelySyncAndCloseThrowsStaleFileHandleError() { @@ -107,9 +107,9 @@ func (s *staleFileHandleSyncedFile) TestFileDeletedRemotelySyncAndCloseThrowsSta err = s.f1.Sync() - operations.ValidateStaleNFSFileHandleError(s.T(), err) + operations.ValidateESTALEError(s.T(), err) err = s.f1.Close() - operations.ValidateStaleNFSFileHandleError(s.T(), err) + operations.ValidateESTALEError(s.T(), err) } //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go index 834c25c3be..3c334f3b21 100644 --- a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go +++ b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go @@ -86,6 +86,6 @@ func (t *staleFileHandleStreamingWritesCommon) TestClosingFileHandleForClobbered // Closing the file/writer returns stale NFS file handle error. err = t.f1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, FileContents, t.T()) } diff --git a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go index 71c321b317..1de7c08b8a 100644 --- a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go +++ b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go @@ -59,7 +59,7 @@ func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestFirstWriteToClobberedFi // Attempt first write to the file. _, err = t.f1.WriteAt([]byte(t.data), 0) - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) operations.SyncFile(t.f1, t.T()) operations.CloseFileShouldNotThrowError(t.T(), t.f1) } @@ -75,7 +75,7 @@ func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestWriteOnRenamedFileThrow // Attempt to write to file should give stale NFS file handle erorr. _, err = t.f1.WriteAt([]byte(t.data), int64(n)) - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) // Sync and Close succeeds. operations.SyncFile(t.f1, t.T()) operations.CloseFileShouldNotThrowError(t.T(), t.f1) @@ -98,7 +98,7 @@ func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestFileDeletedRemotelyWrit // Closing the file/writer returns stale NFS file handle error. err = t.f1.Close() - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) } diff --git a/tools/integration_tests/streaming_writes/rename_file_test.go b/tools/integration_tests/streaming_writes/rename_file_test.go index 02d8e84a06..89302a86e9 100644 --- a/tools/integration_tests/streaming_writes/rename_file_test.go +++ b/tools/integration_tests/streaming_writes/rename_file_test.go @@ -79,7 +79,7 @@ func (t *defaultMountCommonTest) TestAfterRenameWriteFailsWithStaleNFSFileHandle _, err = t.f1.WriteAt(data, operations.MiB*4) - operations.ValidateStaleNFSFileHandleError(t.T(), err) + operations.ValidateESTALEError(t.T(), err) // Verify the new object contents. ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, string(data), t.T()) require.NoError(t.T(), t.f1.Close()) diff --git a/tools/integration_tests/unfinalized_object/setup_test.go b/tools/integration_tests/unfinalized_object/setup_test.go new file mode 100644 index 0000000000..c5c3205c7a --- /dev/null +++ b/tools/integration_tests/unfinalized_object/setup_test.go @@ -0,0 +1,54 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package unfinalized_object + +import ( + "log" + "os" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +const ( + testDirName = "UnfinalizedObjectTest" +) + +var ( + mountFunc func([]string) error +) + +//////////////////////////////////////////////////////////////////////// +// TestMain +//////////////////////////////////////////////////////////////////////// + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + // To run mountedDirectory tests, we need both testBucket and mountedDirectory + // flags to be set, as operations tests validates content from the bucket. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + setup.RunTestsForMountedDirectoryFlag(m) + } + + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() + os.Exit(successCode) +} diff --git a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go new file mode 100644 index 0000000000..462ea090bd --- /dev/null +++ b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go @@ -0,0 +1,173 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package unfinalized_object + +import ( + "context" + "log" + "path" + "syscall" + "testing" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type unfinalizedObjectOperations struct { + flags []string + storageClient *storage.Client + ctx context.Context + testDirPath string + fileName string + suite.Suite +} + +func (t *unfinalizedObjectOperations) SetupTest() { + t.testDirPath = client.SetupTestDirectory(t.ctx, t.storageClient, testDirName) + t.fileName = path.Base(t.T().Name()) + setup.GenerateRandomString(5) +} + +func (t *unfinalizedObjectOperations) TeardownTest() {} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCreatedOutsideOfMountReports0Size() { + var size int64 = operations.MiB + writer := client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), size) + + statRes, err := operations.StatFile(path.Join(t.testDirPath, t.fileName)) + + require.NoError(t.T(), err) + assert.Equal(t.T(), t.fileName, (*statRes).Name()) + assert.EqualValues(t.T(), 0, (*statRes).Size()) + // After object is finalized, correct size should be reported. + err = writer.Close() + require.NoError(t.T(), err) + statRes, err = operations.StatFile(path.Join(t.testDirPath, t.fileName)) + require.NoError(t.T(), err) + assert.EqualValues(t.T(), size, (*statRes).Size()) +} + +func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCreatedFromSameMountReportsCorrectSize() { + size := operations.MiB + // Create un-finalized object via same mount. + fh := operations.CreateFile(path.Join(t.testDirPath, t.fileName), setup.FilePermission_0600, t.T()) + operations.WriteWithoutClose(fh, setup.GenerateRandomString(size), t.T()) + operations.SyncFile(fh, t.T()) + + statRes, err := operations.StatFile(path.Join(t.testDirPath, t.fileName)) + + require.NoError(t.T(), err) + assert.Equal(t.T(), t.fileName, (*statRes).Name()) + assert.EqualValues(t.T(), size, (*statRes).Size()) + // Write more data to the object and finalize. + operations.WriteWithoutClose(fh, setup.GenerateRandomString(size), t.T()) + err = fh.Close() + require.NoError(t.T(), err) + // After object is finalized, correct size should be reported. + statRes, err = operations.StatFile(path.Join(t.testDirPath, t.fileName)) + require.NoError(t.T(), err) + assert.EqualValues(t.T(), 2*size, (*statRes).Size()) +} + +func (t *unfinalizedObjectOperations) TestOverWritingUnfinalizedObjectsReturnsESTALE() { + var size int64 = operations.MiB + _ = client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), size) + fh := operations.OpenFile(path.Join(t.testDirPath, t.fileName), t.T()) + + // Overwrite unfinalized object. + operations.WriteWithoutClose(fh, setup.GenerateRandomString(int(size)), t.T()) + err := fh.Close() + + operations.ValidateESTALEError(t.T(), err) +} + +func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCanBeRenamedIfCreatedFromSameMount() { + size := operations.MiB + content := setup.GenerateRandomString(size) + newFileName := "new" + t.fileName + // Create un-finalized object via same mount. + fh := operations.CreateFile(path.Join(t.testDirPath, t.fileName), setup.FilePermission_0600, t.T()) + operations.WriteWithoutClose(fh, content, t.T()) + operations.SyncFile(fh, t.T()) + + err := operations.RenameFile(path.Join(t.testDirPath, t.fileName), path.Join(t.testDirPath, newFileName)) + + require.NoError(t.T(), err) + client.ValidateObjectNotFoundErrOnGCS(t.ctx, t.storageClient, testDirName, t.fileName, t.T()) + client.ValidateObjectContentsFromGCS(t.ctx, t.storageClient, testDirName, newFileName, content, t.T()) + // validate writing to the renamed file via stale file handle returns ESTALE error. + _, err = fh.Write([]byte(content)) + operations.ValidateESTALEError(t.T(), err) +} + +func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCantBeRenamedIfCreatedFromDifferentMount() { + var size int64 = operations.MiB + _ = client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), size) + + // Overwrite unfinalized object. + err := operations.RenameFile(path.Join(t.testDirPath, t.fileName), path.Join(t.testDirPath, "New"+t.fileName)) + + require.Error(t.T(), err) + assert.ErrorContains(t.T(), err, syscall.EIO.Error()) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestUnfinalizedObjectOperationTest(t *testing.T) { + ts := &unfinalizedObjectOperations{ctx: context.Background()} + // Create storage client before running tests. + closeStorageClient := client.CreateStorageClientWithCancel(&ts.ctx, &ts.storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + t.Errorf("closeStorageClient failed: %v", err) + } + }() + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + suite.Run(t, ts) + return + } + + // Define flag set to run the tests. + flagsSet := [][]string{ + {"--enable-streaming-writes=true", "--metadata-cache-ttl-secs=0"}, + } + + // Run tests. + for _, flags := range flagsSet { + ts.flags = flags + setup.MountGCSFuseWithGivenMountFunc(ts.flags, mountFunc) + log.Printf("Running tests with flags: %s", ts.flags) + suite.Run(t, ts) + setup.SaveGCSFuseLogFileInCaseOfFailure(t) + setup.UnmountGCSFuseAndDeleteLogFile(setup.MntDir()) + } +} diff --git a/tools/integration_tests/util/client/gcs_helper.go b/tools/integration_tests/util/client/gcs_helper.go index 8c49b6ae3d..c1d988c850 100644 --- a/tools/integration_tests/util/client/gcs_helper.go +++ b/tools/integration_tests/util/client/gcs_helper.go @@ -26,6 +26,8 @@ import ( "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) const ( @@ -163,3 +165,18 @@ func GetCRCFromGCS(objectPath string, ctx context.Context, storageClient *storag } return attr.CRC32C, nil } + +func CreateUnfinalizedObject(ctx context.Context, t *testing.T, client *storage.Client, object string, size int64) *storage.Writer { + writer, err := AppendableWriter(ctx, client, object, storage.Conditions{}) + require.NoError(t, err) + + bytesWritten, err := writer.Write([]byte(setup.GenerateRandomString(int(size)))) + require.NoError(t, err) + assert.EqualValues(t, size, bytesWritten) + + flushOffset, err := writer.Flush() + require.NoError(t, err) + assert.Equal(t, size, flushOffset) + + return writer +} diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 77d2579264..2fe710db4b 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -410,3 +410,19 @@ func DeleteBucket(ctx context.Context, client *storage.Client, bucketName string } return nil } + +func AppendableWriter(ctx context.Context, client *storage.Client, object string, precondition storage.Conditions) (*storage.Writer, error) { + bucket, object := setup.GetBucketAndObjectBasedOnTypeOfMount(object) + + o := client.Bucket(bucket).Object(object) + if !reflect.DeepEqual(precondition, storage.Conditions{}) { + o = o.If(precondition) + } + + // Upload an object with storage.Writer. + wc, err := NewWriter(ctx, o, client) + if err != nil { + return nil, fmt.Errorf("failed to open writer for object %q: %w", o.ObjectName(), err) + } + return wc, nil +} diff --git a/tools/integration_tests/util/operations/validation_helper.go b/tools/integration_tests/util/operations/validation_helper.go index e01c0edac9..f7b8b1481c 100644 --- a/tools/integration_tests/util/operations/validation_helper.go +++ b/tools/integration_tests/util/operations/validation_helper.go @@ -26,6 +26,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func ValidateNoFileOrDirError(t *testing.T, path string) { @@ -46,8 +47,8 @@ func ValidateObjectNotFoundErr(ctx context.Context, t *testing.T, bucket gcs.Buc assert.True(t, errors.As(err, ¬FoundErr)) } -func ValidateStaleNFSFileHandleError(t *testing.T, err error) { - assert.NotEqual(t, nil, err) +func ValidateESTALEError(t *testing.T, err error) { + require.Error(t, err) assert.Regexp(t, syscall.ESTALE.Error(), err.Error()) } From 501bd1810881781cd4477801816ccea3fa767798 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 16 Apr 2025 15:16:09 +0530 Subject: [PATCH 0356/1298] add zb to jax tests (#3205) --- .../ml_tests/checkpoint/Jax/run_checkpoints.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh b/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh index 3f6bf84458..b39135c228 100755 --- a/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh +++ b/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh @@ -86,21 +86,27 @@ source .venv/bin/activate # Install JAX dependencies. pip install -r ./perfmetrics/scripts/ml_tests/checkpoint/Jax/requirements.txt -# Run tests in parallel on flat and hns bucket. +ZONE=$(curl -s -H "Metadata-Flavor: Google" http://metadata/computeMetadata/v1/instance/zone | cut -d'/' -f4) +# Run tests in parallel on flat, hns and zonal bucket. FLAT_BUCKET_NAME="jax-emulated-checkpoint-flat-${architecture}" HNS_BUCKET_NAME="jax-emulated-checkpoint-hns-${architecture}" +ZONAL_BUCKET_NAME="jax-emulated-checkpoint-zonal-${ZONE}-${architecture}" mount_gcsfuse_and_run_test "${FLAT_BUCKET_NAME}" & flat_pid=$! mount_gcsfuse_and_run_test "${HNS_BUCKET_NAME}" & hns_pid=$! +mount_gcsfuse_and_run_test "${ZONAL_BUCKET_NAME}" & +zonal_pid=$! -# Wait for both processes to finish and check exit codes +# Wait for all processes to finish and check exit codes wait "$flat_pid" flat_status=$? wait "$hns_pid" hns_status=$? +wait "$zonal_pid" +zonal_status=$? -if [[ "$flat_status" -ne 0 ]] || [[ "$hns_status" -ne 0 ]]; then +if [[ "$flat_status" -ne 0 ]] || [[ "$hns_status" -ne 0 ]] || [[ "$zonal_status" -ne 0 ]]; then echo "Checkpoint tests failed" exit 1 else From 4ceddd5ace270c0bca17bd48713b9730fb79395b Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 17 Apr 2025 12:40:44 +0530 Subject: [PATCH 0357/1298] Start running streaming_writes package for ZB. (#3199) * fix tests for streaming writes zb * revent changes and add a helper method in operations package. * skip zonal object read tests * Add todo * Add streaming_writes package to ZB list * fix lint * fix test name * fix review comments --- tools/integration_tests/run_e2e_tests.sh | 2 +- .../default_mount_empty_gcs_file_test.go | 2 +- .../default_mount_local_file_test.go | 7 +++-- .../streaming_writes/default_mount_test.go | 15 +++++++++ .../streaming_writes/read_file_test.go | 31 +++++++++---------- .../streaming_writes/rename_file_test.go | 26 +++++++--------- .../streaming_writes/symlink_file_test.go | 20 ++++++------ .../util/operations/file_operations.go | 10 ++++++ 8 files changed, 66 insertions(+), 47 deletions(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index f01f1d5a92..30e68eb5a4 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -139,7 +139,7 @@ TEST_DIR_PARALLEL_FOR_ZB=( "read_large_files" "rename_dir_limit" "stale_handle" - # "streaming_writes" + "streaming_writes" "write_large_files" "unfinalized_object" ) diff --git a/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go b/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go index d7bdaf21f3..edfc8d525e 100644 --- a/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go @@ -43,7 +43,7 @@ func (t *defaultMountEmptyGCSFile) createEmptyGCSFile() { CreateObjectInGCSTestDir(ctx, storageClient, testDirName, t.fileName, "", t.T()) ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, "", t.T()) t.filePath = path.Join(testDirPath, t.fileName) - t.f1 = operations.OpenFile(t.filePath, t.T()) + t.f1 = operations.OpenFileWithODirect(t.T(), t.filePath) } // Executes all tests that run with single streamingWrites configuration for empty GCS Files. diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go index f2f742a228..d92eccbe83 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -15,10 +15,12 @@ package streaming_writes import ( + "path" "testing" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/stretchr/testify/suite" ) @@ -39,8 +41,9 @@ func (t *defaultMountLocalFile) SetupSubTest() { func (t *defaultMountLocalFile) createLocalFile() { t.fileName = FileName1 + setup.GenerateRandomString(5) - // Create a local file. - t.filePath, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, t.fileName, t.T()) + t.filePath = path.Join(testDirPath, t.fileName) + // Create a local file with O_DIRECT. + t.f1 = operations.OpenFileWithODirect(t.T(), t.filePath) } // Executes all tests that run with single streamingWrites configuration for localFiles. diff --git a/tools/integration_tests/streaming_writes/default_mount_test.go b/tools/integration_tests/streaming_writes/default_mount_test.go index a28df27e0d..5aad67cf91 100644 --- a/tools/integration_tests/streaming_writes/default_mount_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_test.go @@ -17,9 +17,11 @@ package streaming_writes import ( "os" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_suite" + "github.com/stretchr/testify/require" ) type defaultMountCommonTest struct { @@ -27,6 +29,7 @@ type defaultMountCommonTest struct { fileName string // filePath of the above file in the mounted directory. filePath string + data string test_suite.TestifySuite } @@ -38,8 +41,20 @@ func (t *defaultMountCommonTest) SetupSuite() { setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) testDirPath = setup.SetupTestDirectory(testDirName) + t.data = setup.GenerateRandomString(5 * util.MiB) } func (t *defaultMountCommonTest) TearDownSuite() { setup.UnmountGCSFuse(rootDir) } + +func (t *defaultMountCommonTest) validateReadCall(filePath string) { + _, err := os.ReadFile(filePath) + if setup.IsZonalBucketRun() { + // TODO(b/410698332): Remove skip condition once reads start working. + t.T().Skip("Skipping Zonal Bucket Read tests.") + require.NoError(t.T(), err) + } else { + require.Error(t.T(), err) + } +} diff --git a/tools/integration_tests/streaming_writes/read_file_test.go b/tools/integration_tests/streaming_writes/read_file_test.go index 175ca3e004..07f1a91f94 100644 --- a/tools/integration_tests/streaming_writes/read_file_test.go +++ b/tools/integration_tests/streaming_writes/read_file_test.go @@ -23,49 +23,46 @@ import ( "github.com/stretchr/testify/require" ) -func (t *defaultMountCommonTest) TestReadLocalFileFails() { - // Write some content to local file. - _, err := t.f1.WriteAt([]byte(FileContents), 0) +func (t *defaultMountCommonTest) TestReadFileAfterSync() { + // Write some content to the file. + _, err := t.f1.WriteAt([]byte(t.data), 0) assert.NoError(t.T(), err) + // Sync File to ensure buffers are flushed to GCS. + operations.SyncFile(t.f1, t.T()) - // Reading the local file content fails. - buf := make([]byte, len(FileContents)) - _, err = t.f1.ReadAt(buf, 0) - assert.Error(t.T(), err) + t.validateReadCall(t.f1.Name()) // Close the file and validate that the file is created on GCS. - CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data, t.T()) } func (t *defaultMountCommonTest) TestReadBeforeFileIsFlushed() { - testContent := "testContent" // Write data to file. - operations.WriteAt(testContent, 0, t.f1, t.T()) + operations.WriteAt(t.data, 0, t.f1, t.T()) // Try to read the file. _, err := t.f1.Seek(0, 0) require.NoError(t.T(), err) - buf := make([]byte, 10) + buf := make([]byte, len(t.data)) _, err = t.f1.Read(buf) require.Error(t.T(), err, "input/output error") // Validate if correct content is uploaded to GCS after read error. - CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, testContent, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data, t.T()) } func (t *defaultMountCommonTest) TestReadAfterFlush() { - testContent := "testContent" // Write data to file and flush. - operations.WriteAt(testContent, 0, t.f1, t.T()) - CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, testContent, t.T()) + operations.WriteAt(t.data, 0, t.f1, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data, t.T()) // Perform read and validate the contents. var err error t.f1, err = operations.OpenFileAsReadonly(path.Join(testDirPath, t.fileName)) require.NoError(t.T(), err) - buf := make([]byte, len(testContent)) + buf := make([]byte, len(t.data)) _, err = t.f1.Read(buf) require.NoError(t.T(), err) - require.Equal(t.T(), string(buf), testContent) + require.Equal(t.T(), string(buf), t.data) } diff --git a/tools/integration_tests/streaming_writes/rename_file_test.go b/tools/integration_tests/streaming_writes/rename_file_test.go index 89302a86e9..aac699166f 100644 --- a/tools/integration_tests/streaming_writes/rename_file_test.go +++ b/tools/integration_tests/streaming_writes/rename_file_test.go @@ -23,9 +23,9 @@ import ( ) func (t *defaultMountCommonTest) TestRenameBeforeFileIsFlushed() { - operations.WriteWithoutClose(t.f1, FileContents, t.T()) - operations.WriteWithoutClose(t.f1, FileContents, t.T()) - operations.VerifyStatFile(t.filePath, int64(2*len(FileContents)), FilePerms, t.T()) + operations.WriteWithoutClose(t.f1, t.data, t.T()) + operations.WriteWithoutClose(t.f1, t.data, t.T()) + operations.VerifyStatFile(t.filePath, int64(2*len(t.data)), FilePerms, t.T()) err := t.f1.Sync() require.NoError(t.T(), err) @@ -36,18 +36,16 @@ func (t *defaultMountCommonTest) TestRenameBeforeFileIsFlushed() { // Validate that move didn't throw any error. require.NoError(t.T(), err) // Verify the new object contents. - ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, FileContents+FileContents, t.T()) + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, t.data+t.data, t.T()) require.NoError(t.T(), t.f1.Close()) // Check if old object is deleted. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) } func (t *defaultMountCommonTest) TestSyncAfterRenameSucceeds() { - data, err := operations.GenerateRandomData(operations.MiB * 4) + _, err := t.f1.WriteAt([]byte(t.data), 0) require.NoError(t.T(), err) - _, err = t.f1.WriteAt(data, 0) - require.NoError(t.T(), err) - operations.VerifyStatFile(t.filePath, operations.MiB*4, FilePerms, t.T()) + operations.VerifyStatFile(t.filePath, int64(len(t.data)), FilePerms, t.T()) err = t.f1.Sync() require.NoError(t.T(), err) newFile := "new" + t.fileName @@ -59,29 +57,27 @@ func (t *defaultMountCommonTest) TestSyncAfterRenameSucceeds() { // Verify that sync succeeds after rename. require.NoError(t.T(), err) // Verify the new object contents. - ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, string(data), t.T()) + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, string(t.data), t.T()) require.NoError(t.T(), t.f1.Close()) // Check if old object is deleted. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) } func (t *defaultMountCommonTest) TestAfterRenameWriteFailsWithStaleNFSFileHandleError() { - data, err := operations.GenerateRandomData(operations.MiB * 4) - require.NoError(t.T(), err) - _, err = t.f1.WriteAt(data, 0) + _, err := t.f1.WriteAt([]byte(t.data), 0) require.NoError(t.T(), err) - operations.VerifyStatFile(t.filePath, operations.MiB*4, FilePerms, t.T()) + operations.VerifyStatFile(t.filePath, int64(len(t.data)), FilePerms, t.T()) err = t.f1.Sync() require.NoError(t.T(), err) newFile := "new" + t.fileName err = operations.RenameFile(t.filePath, path.Join(testDirPath, newFile)) require.NoError(t.T(), err) - _, err = t.f1.WriteAt(data, operations.MiB*4) + _, err = t.f1.WriteAt([]byte(t.data), int64(len(t.data))) operations.ValidateESTALEError(t.T(), err) // Verify the new object contents. - ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, string(data), t.T()) + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, string(t.data), t.T()) require.NoError(t.T(), t.f1.Close()) // Check if old object is deleted. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) diff --git a/tools/integration_tests/streaming_writes/symlink_file_test.go b/tools/integration_tests/streaming_writes/symlink_file_test.go index fe5d7768ba..3cb8384f85 100644 --- a/tools/integration_tests/streaming_writes/symlink_file_test.go +++ b/tools/integration_tests/streaming_writes/symlink_file_test.go @@ -24,36 +24,34 @@ import ( "github.com/stretchr/testify/assert" ) -func (t *defaultMountCommonTest) TestCreateSymlinkForLocalFileReadFails() { +func (t *defaultMountCommonTest) TestCreateSymlinkForLocalFileAndReadFromSymlink() { // Create Symlink. symlink := path.Join(testDirPath, setup.GenerateRandomString(5)) operations.CreateSymLink(t.filePath, symlink, t.T()) - _, err := t.f1.WriteAt([]byte(FileContents), 0) + _, err := t.f1.WriteAt([]byte(t.data), 0) assert.NoError(t.T(), err) // Verify read link. operations.VerifyReadLink(t.filePath, symlink, t.T()) - // Reading file from symlink fails. - _, err = os.ReadFile(symlink) + // Validate read file from symlink. + t.validateReadCall(symlink) - assert.Error(t.T(), err) // Close the file and validate that the file is created on GCS. - CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data, t.T()) } -func (t *defaultMountCommonTest) TestReadSymlinkForDeletedLocalFileFails() { +func (t *defaultMountCommonTest) TestReadingFromSymlinkForDeletedLocalFile() { // Create Symlink. symlink := path.Join(testDirPath, setup.GenerateRandomString(5)) operations.CreateSymLink(t.filePath, symlink, t.T()) - _, err := t.f1.WriteAt([]byte(FileContents), 0) + _, err := t.f1.WriteAt([]byte(t.data), 0) assert.NoError(t.T(), err) // Verify read link. operations.VerifyReadLink(t.filePath, symlink, t.T()) - // Reading the file from symlink fails. - _, err = os.ReadFile(symlink) + // Validate read from symlink. + t.validateReadCall(symlink) - assert.Error(t.T(), err) // Remove filePath and then close the fileHandle to avoid syncing to GCS. operations.RemoveFile(t.filePath) operations.CloseFileShouldNotThrowError(t.T(), t.f1) diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 24277e45a3..647d19df49 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -519,6 +519,16 @@ func OpenFile(filePath string, t *testing.T) (f *os.File) { return } +func OpenFileWithODirect(t *testing.T, filePath string) (f *os.File) { + t.Helper() + f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC|syscall.O_DIRECT, FilePermission_0600) + if err != nil { + require.NoError(t, err) + } + return + +} + func CreateSymLink(filePath, symlink string, t *testing.T) { err := os.Symlink(filePath, symlink) From 8a057f30c8b41266504f9034db9fce127fc80b9b Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 17 Apr 2025 12:49:29 +0530 Subject: [PATCH 0358/1298] Make MRDWrapper store copy of MinObject from inode to prevent data race. (#3201) * store copy of f.src in MRDWrapper to ensure no data-race at read. * fix comment * fix review comments --- internal/fs/inode/file.go | 4 +-- internal/fs/inode/file_test.go | 47 ++++++++++++++++++++++------------ 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index a6c5910bbb..3c39f5af02 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -160,7 +160,7 @@ func NewFileInode( globalMaxWriteBlocksSem: globalMaxBlocksSem, } var err error - f.MRDWrapper, err = gcsx.NewMultiRangeDownloaderWrapper(bucket, &f.src) + f.MRDWrapper, err = gcsx.NewMultiRangeDownloaderWrapper(bucket, &minObj) if err != nil { logger.Errorf("NewFileInode: Error in creating MRDWrapper %v", err) } @@ -905,7 +905,7 @@ func (f *FileInode) updateInodeStateAfterSync(minObj *gcs.MinObject) { // Updates the min object stored in MRDWrapper corresponding to the inode. // Should be called when minObject associated with inode is updated. func (f *FileInode) updateMRDWrapper() { - err := f.MRDWrapper.SetMinObject(&f.src) + err := f.MRDWrapper.SetMinObject(f.Source()) if err != nil { logger.Errorf("FileInode::updateMRDWrapper Error in setting minObject %v", err) } diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 1eb67db0fb..a5aefa31b6 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -442,8 +442,10 @@ func (t *FileTest) TestWriteThenSync() { // The generation should have advanced. assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) - // Validate MinObject in MRDWrapper is same as the MinObject in inode. - assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in inode and MRDWrapper points to different copy of MinObject. + assert.NotSame(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in MRDWrapper is equal to the MinObject in inode. + assert.Equal(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} @@ -529,8 +531,10 @@ func (t *FileTest) TestWriteToLocalFileThenSync() { assert.Equal(t.T(), writeTime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) + // Validate MinObject in inode and MRDWrapper points to different copy of MinObject. + assert.NotSame(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) // Validate MinObject in MRDWrapper is same as the MinObject in inode. - assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + assert.Equal(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) require.NoError(t.T(), err) @@ -590,8 +594,10 @@ func (t *FileTest) TestSyncEmptyLocalFile() { assert.Equal(t.T(), t.in.SourceGeneration().Metadata, m.MetaGeneration) assert.Equal(t.T(), t.in.SourceGeneration().Size, m.Size) assert.Equal(t.T(), uint64(0), m.Size) - // Validate MinObject in MRDWrapper is same as the MinObject in inode. - assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in inode and MRDWrapper points to different copy of MinObject. + assert.NotSame(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in MRDWrapper is equal to the MinObject in inode. + assert.Equal(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) // Validate the mtime. mtimeInBucket, ok := m.Metadata["gcsfuse_mtime"] assert.True(t.T(), ok) @@ -665,9 +671,10 @@ func (t *FileTest) TestAppendThenSync() { assert.Equal(t.T(), writeTime.UTC().Format(time.RFC3339Nano), m.Metadata["gcsfuse_mtime"]) - - // Validate MinObject in MRDWrapper is same as the MinObject in inode. - assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in inode and MRDWrapper points to different copy of MinObject. + assert.NotSame(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in MRDWrapper is equal to the MinObject in inode. + assert.Equal(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) // Read the object's contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) @@ -725,8 +732,10 @@ func (t *FileTest) TestTruncateDownwardThenSync() { // The generation should have advanced. assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) - // Validate MinObject in MRDWrapper is same as the MinObject in inode. - assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in inode and MRDWrapper points to different copy of MinObject. + assert.NotSame(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in MRDWrapper is equal to the MinObject in inode. + assert.Equal(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} @@ -795,8 +804,10 @@ func (t *FileTest) TestTruncateUpwardThenFlush() { // The generation should have advanced. assert.Less(t.T(), t.backingObj.Generation, t.in.SourceGeneration().Object) - // Validate MinObject in MRDWrapper is same as the MinObject in inode. - assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in inode and MRDWrapper points to different copy of MinObject. + assert.NotSame(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in MRDWrapper is equal to the MinObject in inode. + assert.Equal(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) // Stat the current object in the bucket. statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} @@ -1089,8 +1100,10 @@ func (t *FileTest) TestSyncFlush_Clobbered() { err = t.in.Flush(t.ctx) } - // Validate MinObject in MRDWrapper is same as the MinObject in inode. - assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in inode and MRDWrapper points to different copy of MinObject. + assert.NotSame(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in MRDWrapper is equal to the MinObject in inode. + assert.Equal(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) // Check if the error is a FileClobberedError var fcErr *gcsfuse_errors.FileClobberedError @@ -1217,8 +1230,10 @@ func (t *FileTest) TestSetMtime_ContentDirty() { statReq := &gcs.StatObjectRequest{Name: t.in.Name().GcsObjectName()} m, _, err := t.bucket.StatObject(t.ctx, statReq) - // Validate MinObject in MRDWrapper is same as the MinObject in inode. - assert.Same(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in inode and MRDWrapper points to different copy of MinObject. + assert.NotSame(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) + // Validate MinObject in MRDWrapper is equal to the MinObject in inode. + assert.Equal(t.T(), &t.in.src, t.in.MRDWrapper.GetMinObject()) require.NoError(t.T(), err) assert.NotNil(t.T(), m) From 723c2dc9d526b94554aaee210e8b25180a038b3c Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 17 Apr 2025 13:19:43 +0530 Subject: [PATCH 0359/1298] add info log when staged writes are used (#3182) --- internal/fs/inode/file.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 3c39f5af02..1a10974099 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -311,6 +311,9 @@ func (f *FileInode) ensureContent(ctx context.Context) (err error) { return err } + if f.config.Write.EnableStreamingWrites { + logger.Infof("Falling back to staged write for '%s'. Streaming write is limited to sequential writes on new/empty files.", f.name) + } tf, err := f.contentCache.NewTempFile(rc) if err != nil { err = fmt.Errorf("NewTempFile: %w", err) From a770f1d0364862d0597751c63418ebfb6c45fa48 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 17 Apr 2025 14:30:18 +0530 Subject: [PATCH 0360/1298] [Random reader refactoring] File cache reader implementation - 1 (#3202) * add helper functions * small fix * fix the log messages * add comments * add unit tests * add small fix * remove unnecessary change * small fix * unit tests * unit tests * lint fix * lint fix * small fix * added comment * added comment * added comment and remove warning * added comments * added asseration * added comments * make struct and const private * make pointer * review comments * review comments * format file * add assertion --- internal/gcsx/file_cache_reader.go | 162 +++++++++++- internal/gcsx/file_cache_reader_test.go | 246 ++++++++++++++++++- internal/gcsx/random_reader.go | 11 - internal/gcsx/random_reader_stretchr_test.go | 2 +- internal/gcsx/random_reader_test.go | 2 +- 5 files changed, 395 insertions(+), 28 deletions(-) diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index 82ac8c85ea..a5195b28b1 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -15,14 +15,27 @@ package gcsx import ( + "context" + "errors" + "fmt" + "io" + "strconv" + "time" + + "github.com/google/uuid" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" + cacheUtil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/jacobsa/fuse/fuseops" ) type FileCacheReader struct { Reader - obj *gcs.MinObject + object *gcs.MinObject bucket gcs.Bucket // fileCacheHandler is used to get file cache handle and read happens using that. @@ -40,12 +53,153 @@ type FileCacheReader struct { metricHandle common.MetricHandle } -func NewFileCacheReader(o *gcs.MinObject, bucket gcs.Bucket, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle) FileCacheReader { - return FileCacheReader{ - obj: o, +func NewFileCacheReader(o *gcs.MinObject, bucket gcs.Bucket, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle) *FileCacheReader { + return &FileCacheReader{ + object: o, bucket: bucket, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, metricHandle: metricHandle, } } + +// tryReadingFromFileCache creates the cache handle first if it doesn't exist already +// and then use that handle to read object's content which is cached in local file. +// For the successful read, it returns number of bytes read, and a boolean representing +// cacheHit as true. +// For unsuccessful read, returns cacheHit as false, in this case content +// should be read from GCS. +// And it returns non-nil error in case something unexpected happens during the execution. +// In this case, we must abort the Read operation. +// +// Important: What happens if the file in cache deleted externally? +// That means, we have fileInfo entry in the fileInfoCache for that deleted file. +// (a) If a new fileCacheHandle is created in that case it will return FileNotPresentInCache +// error, given by fileCacheHandler.GetCacheHandle(). +// (b) If there is already an open fileCacheHandle then it means there is an open +// fileHandle to file in cache. So, we will get the correct data from fileHandle +// because Linux does not delete a file until open fileHandle count for a file is zero. +func (fc *FileCacheReader) tryReadingFromFileCache(ctx context.Context, p []byte, offset int64) (int, bool, error) { + if fc.fileCacheHandler == nil { + return 0, false, nil + } + + // By default, consider read type random if the offset is non-zero. + isSequential := offset == 0 + + var handleID uint64 + if readOp, ok := ctx.Value(ReadOp).(*fuseops.ReadFileOp); ok { + handleID = uint64(readOp.Handle) + } + requestID := uuid.New() + logger.Tracef("%.13v <- FileCache(%s:/%s, offset: %d, size: %d, handle: %d)", requestID, fc.bucket.Name(), fc.object.Name, offset, len(p), handleID) + + startTime := time.Now() + var bytesRead int + var cacheHit bool + var err error + + defer func() { + executionTime := time.Since(startTime) + var requestOutput string + if err != nil { + requestOutput = fmt.Sprintf("err: %v (%v)", err, executionTime) + } else { + if fc.fileCacheHandle != nil { + isSequential = fc.fileCacheHandle.IsSequential(offset) + } + requestOutput = fmt.Sprintf("OK (isSeq: %t, cacheHit: %t) (%v)", isSequential, cacheHit, executionTime) + } + + logger.Tracef("%.13v -> %s", requestID, requestOutput) + + readType := util.Random + if isSequential { + readType = util.Sequential + } + captureFileCacheMetrics(ctx, fc.metricHandle, readType, bytesRead, cacheHit, executionTime) + }() + + // Create fileCacheHandle if not already. + if fc.fileCacheHandle == nil { + fc.fileCacheHandle, err = fc.fileCacheHandler.GetCacheHandle(fc.object, fc.bucket, fc.cacheFileForRangeRead, offset) + if err != nil { + switch { + case errors.Is(err, lru.ErrInvalidEntrySize): + logger.Warnf("tryReadingFromFileCache: while creating CacheHandle: %v", err) + return 0, false, nil + case errors.Is(err, cacheUtil.ErrCacheHandleNotRequiredForRandomRead): + // Fall back to GCS if it is a random read, cacheFileForRangeRead is + // false and there doesn't already exist file in cache. + isSequential = false + return 0, false, nil + default: + return 0, false, fmt.Errorf("tryReadingFromFileCache: GetCacheHandle failed: %w", err) + } + } + } + + bytesRead, cacheHit, err = fc.fileCacheHandle.Read(ctx, fc.bucket, fc.object, offset, p) + if err == nil { + return bytesRead, cacheHit, nil + } + + bytesRead = 0 + cacheHit = false + + if cacheUtil.IsCacheHandleInvalid(err) { + logger.Tracef("Closing cacheHandle:%p for object: %s:/%s", fc.fileCacheHandle, fc.bucket.Name(), fc.object.Name) + closeErr := fc.fileCacheHandle.Close() + if closeErr != nil { + logger.Warnf("tryReadingFromFileCache: close cacheHandle error: %v", closeErr) + } + fc.fileCacheHandle = nil + } else if !errors.Is(err, cacheUtil.ErrFallbackToGCS) { + return 0, false, fmt.Errorf("tryReadingFromFileCache: while reading via cache: %w", err) + } + err = nil + + return 0, false, nil +} + +func (fc *FileCacheReader) ReadAt(ctx context.Context, p []byte, offset int64) (ReaderResponse, error) { + var err error + readerResponse := ReaderResponse{ + DataBuf: p, + Size: 0, + } + + if offset >= int64(fc.object.Size) { + err = io.EOF + return readerResponse, err + } + + // Note: If we are reading the file for the first time and read type is sequential + // then the file cache behavior is write-through i.e. data is first read from + // GCS, cached in file and then served from that file. But the cacheHit is + // false in that case. + bytesRead, cacheHit, err := fc.tryReadingFromFileCache(ctx, p, offset) + if err != nil { + err = fmt.Errorf("ReadAt: while reading from cache: %w", err) + return readerResponse, err + } + // Data was served from cache. + if cacheHit || bytesRead == len(p) || (bytesRead < len(p) && uint64(offset)+uint64(bytesRead) == fc.object.Size) { + readerResponse.Size = bytesRead + return readerResponse, nil + } + + // The cache is unable to serve data and requires a fallback to another reader. + err = FallbackToAnotherReader + return readerResponse, err +} + +func captureFileCacheMetrics(ctx context.Context, metricHandle common.MetricHandle, readType string, readDataSize int, cacheHit bool, readLatency time.Duration) { + metricHandle.FileCacheReadCount(ctx, 1, []common.MetricAttr{ + {Key: common.ReadType, Value: readType}, + {Key: common.CacheHit, Value: strconv.FormatBool(cacheHit)}, + }) + + metricHandle.FileCacheReadBytesCount(ctx, int64(readDataSize), []common.MetricAttr{{Key: common.ReadType, Value: readType}}) + metricHandle.FileCacheReadLatency(ctx, float64(readLatency.Microseconds()), []common.MetricAttr{{Key: common.CacheHit, Value: strconv.FormatBool(cacheHit)}}) +} diff --git a/internal/gcsx/file_cache_reader_test.go b/internal/gcsx/file_cache_reader_test.go index 3366e89f4f..ca15f6fbcf 100644 --- a/internal/gcsx/file_cache_reader_test.go +++ b/internal/gcsx/file_cache_reader_test.go @@ -15,69 +15,293 @@ package gcsx import ( + "context" + "errors" + "fmt" "os" "path" "testing" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) const ( - TestObject = "testObject" + testObject = "testObject" sequentialReadSizeInMb = 22 sequentialReadSizeInBytes = sequentialReadSizeInMb * MB - CacheMaxSize = 2 * sequentialReadSizeInMb * util.MiB + cacheMaxSize = 2 * sequentialReadSizeInMb * util.MiB ) -type FileCacheReaderTest struct { +type fileCacheReaderTest struct { suite.Suite + ctx context.Context object *gcs.MinObject mockBucket *storage.TestifyMockBucket cacheDir string jobManager *downloader.JobManager cacheHandler *file.CacheHandler + reader *FileCacheReader } func TestFileCacheReaderTestSuite(t *testing.T) { - suite.Run(t, new(FileCacheReaderTest)) + suite.Run(t, new(fileCacheReaderTest)) } -func (t *FileCacheReaderTest) SetupTestSuite() { +func (t *fileCacheReaderTest) SetupTest() { t.object = &gcs.MinObject{ - Name: TestObject, + Name: testObject, Size: 17, Generation: 1234, } t.mockBucket = new(storage.TestifyMockBucket) t.cacheDir = path.Join(os.Getenv("HOME"), "test_cache_dir") - lruCache := lru.NewCache(CacheMaxSize) - t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{EnableCrc: false}, nil) + lruCache := lru.NewCache(cacheMaxSize) + t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{EnableCrc: false}, common.NewNoopMetrics()) t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) + t.reader = NewFileCacheReader(t.object, t.mockBucket, t.cacheHandler, true, common.NewNoopMetrics()) + t.ctx = context.Background() } -func (t *FileCacheReaderTest) TearDownSuite() { +func (t *fileCacheReaderTest) TearDown() { err := os.RemoveAll(t.cacheDir) if err != nil { t.T().Logf("Failed to clean up test cache directory '%s': %v", t.cacheDir, err) } } -func (t *FileCacheReaderTest) TestNewFileCacheReader() { +func (t *fileCacheReaderTest) mockNewReaderWithHandleCallForTestBucket(start uint64, limit uint64, rd gcs.StorageReader) { + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(rg *gcs.ReadObjectRequest) bool { + return rg != nil && (*rg.Range).Start == start && (*rg.Range).Limit == limit + })).Return(rd, nil).Maybe() +} + +func (t *fileCacheReaderTest) TestNewFileCacheReader() { reader := NewFileCacheReader(t.object, t.mockBucket, t.cacheHandler, true, nil) assert.NotNil(t.T(), reader) - assert.Equal(t.T(), t.object, reader.obj) + assert.Equal(t.T(), t.object, reader.object) assert.Equal(t.T(), t.mockBucket, reader.bucket) assert.Equal(t.T(), t.cacheHandler, reader.fileCacheHandler) assert.True(t.T(), reader.cacheFileForRangeRead) assert.Nil(t.T(), reader.metricHandle) assert.Nil(t.T(), reader.fileCacheHandle) } + +func (t *fileCacheReaderTest) Test_ReadAt_NilFileCacheHandlerThrowFallBackError() { + reader := NewFileCacheReader(t.object, t.mockBucket, nil, true, nil) + + readerResponse, err := reader.ReadAt(t.ctx, make([]byte, 10), 0) + + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.Zero(t.T(), readerResponse.Size) +} + +func (t *fileCacheReaderTest) Test_tryReadingFromFileCache_CacheHit() { + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockBucket.On("Name").Return("test-bucket") + buf := make([]byte, t.object.Size) + // First read will be a cache miss. + n, cacheHit, err := t.reader.tryReadingFromFileCache(t.ctx, buf, 0) + assert.NoError(t.T(), err) + assert.False(t.T(), cacheHit) + assert.Equal(t.T(), n, len(buf)) + + // Second read will be a cache hit. + n, cacheHit, err = t.reader.tryReadingFromFileCache(t.ctx, buf, 0) + + assert.NoError(t.T(), err) + assert.True(t.T(), cacheHit) + assert.Equal(t.T(), n, len(buf)) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_tryReadingFromFileCache_SequentialSubsequentReadOffsetLessThanReadChunkSize() { + t.object.Size = 20 * util.MiB + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockBucket.On("Name").Return("test-bucket") + start1 := 0 + end1 := util.MiB + require.Less(t.T(), end1, int(t.object.Size)) + // First call from offset 0 - sequential read + buf := make([]byte, end1-start1) + _, cacheHit, err := t.reader.tryReadingFromFileCache(t.ctx, buf, int64(start1)) + assert.NoError(t.T(), err) + assert.False(t.T(), cacheHit) + assert.Equal(t.T(), buf, testContent[start1:end1]) + start2 := 3*util.MiB + 4 + end2 := start2 + util.MiB + buf2 := make([]byte, end2-start2) + + // Assuming start2 offset download in progress + _, cacheHit, err = t.reader.tryReadingFromFileCache(t.ctx, buf2, int64(start2)) + + assert.NoError(t.T(), err) + assert.True(t.T(), cacheHit) + assert.Equal(t.T(), buf2, testContent[start2:end2]) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_SequentialRangeRead() { + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockBucket.On("Name").Return("test-bucket") + start := 0 + end := 10 + require.Less(t.T(), end, int(t.object.Size)) + buf := make([]byte, end-start) + + readerResponse, err := t.reader.ReadAt(t.ctx, buf, int64(start)) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent[start:end]) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCacheForRangeReadIsFalse() { + t.reader.cacheFileForRangeRead = false + start := 5 + end := 10 + t.mockBucket.On("Name").Return("test-bucket") + buf := make([]byte, end-start) + readerResponse, err := t.reader.ReadAt(t.ctx, buf, int64(start)) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.Zero(t.T(), readerResponse.Size) + job := t.jobManager.CreateJobIfNotExists(t.object, t.mockBucket) + jobStatus := job.GetStatus() + assert.True(t.T(), jobStatus.Name == downloader.NotStarted) + + readerResponse, err = t.reader.ReadAt(t.ctx, buf, int64(start)) + + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.Zero(t.T(), readerResponse.Size) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCacheForRangeReadIsTrue() { + t.reader.cacheFileForRangeRead = true + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + start := 5 + end := 10 + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + // Mock for download job's NewReader call + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockBucket.On("Name").Return("test-bucket") + buf := make([]byte, end-start) + + readerResponse, err := t.reader.ReadAt(t.ctx, buf, int64(start)) + + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.Zero(t.T(), readerResponse.Size) + job := t.jobManager.GetJob(t.object.Name, t.mockBucket.Name()) + assert.True(t.T(), job == nil || job.GetStatus().Name == downloader.Downloading) + assert.NotNil(t.T(), t.reader.fileCacheHandle) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffsetMoreThanReadChunkSize() { + t.object.Size = 20 * util.MiB + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + // Mock for download job's NewReader call + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockBucket.On("Name").Return("test-bucket") + start1 := 0 + end1 := util.MiB + require.Less(t.T(), end1, int(t.object.Size)) + // First call from offset 0 - sequential read + buf := make([]byte, end1-start1) + readerResponse, err := t.reader.ReadAt(t.ctx, buf, int64(start1)) + // Served from file cache + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent[start1:end1]) + start2 := 16*util.MiB + 4 + end2 := start2 + util.MiB + buf2 := make([]byte, end2-start2) + + readerResponse, err = t.reader.ReadAt(t.ctx, buf2, int64(start2)) + + // Assuming a download with a start offset of start2 is in progress, a fallback to another reader will be required. + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.Zero(t.T(), readerResponse.Size) + job := t.jobManager.GetJob(t.object.Name, t.mockBucket.Name()) + assert.True(t.T(), job == nil || job.GetStatus().Name == downloader.Downloading) + assert.NotNil(t.T(), t.reader.fileCacheHandle) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffsetLessThanPrevious() { + t.object.Size = 20 * util.MiB + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockBucket.On("Name").Return("test-bucket") + start1 := 0 + end1 := util.MiB + require.Less(t.T(), end1, int(t.object.Size)) + // First call from offset 0 - sequential read + buf := make([]byte, end1-start1) + readerResponse, err := t.reader.ReadAt(t.ctx, buf, int64(start1)) + // Served from file cache + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent[start1:end1]) + start2 := 16*util.MiB + 4 + end2 := start2 + util.MiB + buf2 := make([]byte, end2-start2) + // Assuming a download with a start offset of start2 is in progress, a fallback to another reader will be required. + readerResponse, err = t.reader.ReadAt(t.ctx, buf2, int64(start2)) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.Zero(t.T(), readerResponse.Size) + // Assuming start3 offset is downloaded + start3 := 4 * util.MiB + end3 := start3 + util.MiB + buf3 := make([]byte, end3-start3) + + readerResponse, err = t.reader.ReadAt(t.ctx, buf3, int64(start3)) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent[start3:end3]) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_CacheMissDueToInvalidJob() { + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rc1 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rc1) + t.mockBucket.On("Name").Return("test-bucket") + buf := make([]byte, t.object.Size) + readerResponse, err := t.reader.ReadAt(t.ctx, buf, 0) + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent) + job := t.jobManager.GetJob(t.object.Name, t.mockBucket.Name()) + if job != nil { + jobStatus := job.GetStatus().Name + assert.True(t.T(), jobStatus == downloader.Downloading || jobStatus == downloader.Completed, fmt.Sprintf("the actual status is %v", jobStatus)) + } + err = t.reader.fileCacheHandler.InvalidateCache(t.object.Name, t.mockBucket.Name()) + assert.NoError(t.T(), err) + + readerResponse, err = t.reader.ReadAt(t.ctx, buf, 0) + + // As job is invalidated Need to get served from GCS reader + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.Zero(t.T(), readerResponse.Size) + t.mockBucket.AssertExpectations(t.T()) +} diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 82f1aa36cf..a40c2c8196 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -19,7 +19,6 @@ import ( "fmt" "io" "math" - "strconv" "time" "github.com/google/uuid" @@ -297,16 +296,6 @@ func (rr *randomReader) tryReadingFromFileCache(ctx context.Context, return } -func captureFileCacheMetrics(ctx context.Context, metricHandle common.MetricHandle, readType string, readDataSize int, cacheHit bool, readLatency time.Duration) { - metricHandle.FileCacheReadCount(ctx, 1, []common.MetricAttr{ - {Key: common.ReadType, Value: readType}, - {Key: common.CacheHit, Value: strconv.FormatBool(cacheHit)}, - }) - - metricHandle.FileCacheReadBytesCount(ctx, int64(readDataSize), []common.MetricAttr{{Key: common.ReadType, Value: readType}}) - metricHandle.FileCacheReadLatency(ctx, float64(readLatency.Microseconds()), []common.MetricAttr{{Key: common.CacheHit, Value: strconv.FormatBool(cacheHit)}}) -} - func (rr *randomReader) ReadAt( ctx context.Context, p []byte, diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 2c1a86269c..298da89e0d 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -72,7 +72,7 @@ func (t *RandomReaderStretchrTest) SetupTest() { t.mockBucket = new(storage.TestifyMockBucket) t.cacheDir = path.Join(os.Getenv("HOME"), "cache/dir") - lruCache := lru.NewCache(CacheMaxSize) + lruCache := lru.NewCache(cacheMaxSize) t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{ EnableCrc: false, }, nil) diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index f8a4a6ac4b..f4565a54ae 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -175,7 +175,7 @@ func (t *RandomReaderTest) SetUp(ti *TestInfo) { t.bucket = storage.NewMockBucket(ti.MockController, "bucket") t.cacheDir = path.Join(os.Getenv("HOME"), "cache/dir") - lruCache := lru.NewCache(CacheMaxSize) + lruCache := lru.NewCache(cacheMaxSize) t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{ EnableCrc: false, }, common.NewNoopMetrics()) From 1937ff7a7749a2992265736c6d95541f97a2b615 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Fri, 18 Apr 2025 14:04:09 +0530 Subject: [PATCH 0361/1298] temp fix (#3210) --- tools/integration_tests/run_e2e_tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 30e68eb5a4..e88d91dcd2 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -89,7 +89,7 @@ TEST_DIR_PARALLEL=( "log_rotation" "mounting" "read_cache" - "grpc_validation" + # "grpc_validation" "gzip" "write_large_files" "list_large_dir" From 6d7f86ec87756bc2c7b14d52a338b4470c9653d8 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Fri, 18 Apr 2025 08:48:04 +0000 Subject: [PATCH 0362/1298] Add Recommended Sample YAMLsfor major AI/ML workflows for GKE Clusters --- samples/gke-csi-yaml/README.md | 28 +++++++++++++ samples/gke-csi-yaml/checkpointing-pod.yaml | 24 +++++++++++ samples/gke-csi-yaml/checkpointing-pv.yaml | 46 +++++++++++++++++++++ samples/gke-csi-yaml/serving-pod.yaml | 24 +++++++++++ samples/gke-csi-yaml/serving-pv.yaml | 44 ++++++++++++++++++++ samples/gke-csi-yaml/training-pod.yaml | 23 +++++++++++ samples/gke-csi-yaml/training-pv.yaml | 44 ++++++++++++++++++++ 7 files changed, 233 insertions(+) create mode 100644 samples/gke-csi-yaml/README.md create mode 100644 samples/gke-csi-yaml/checkpointing-pod.yaml create mode 100644 samples/gke-csi-yaml/checkpointing-pv.yaml create mode 100644 samples/gke-csi-yaml/serving-pod.yaml create mode 100644 samples/gke-csi-yaml/serving-pv.yaml create mode 100644 samples/gke-csi-yaml/training-pod.yaml create mode 100644 samples/gke-csi-yaml/training-pv.yaml diff --git a/samples/gke-csi-yaml/README.md b/samples/gke-csi-yaml/README.md new file mode 100644 index 0000000000..024e12e1cf --- /dev/null +++ b/samples/gke-csi-yaml/README.md @@ -0,0 +1,28 @@ +This folder contains sample YAML files for GKE GCSFuse CSI driver with recommendations for GPUs/TPUs. + +This folder contains sample Pod and Pod Volume Yaml config file for the following workflows +1. Training +2. Serving or Inference +3. Checkpointing + +For Jit Cache workflows, use the Checkpointing workflow yaml configs. + +For e.g. if you want to set up the serving workload. + +First, deploy the PVC/PV first (this is required because the GKE pod webhook inspect the PV volume attributes for additional optimizations like injection of additional containers) +e.g. +``` +kubectl apply -f serving-pv.yaml +``` + +Then, deploy the pod spec that accesses the PVC +e.g. +``` +kubectl apply -f serving-pod.yaml +``` + +Note: +* The service account needs to be created before use. +* Replace placeholders with your actual values. + +Read https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-storage-fuse-csi-driver-perf#best-practices-for-performance-tuning for more tuning details. \ No newline at end of file diff --git a/samples/gke-csi-yaml/checkpointing-pod.yaml b/samples/gke-csi-yaml/checkpointing-pod.yaml new file mode 100644 index 0000000000..4fe86b5c68 --- /dev/null +++ b/samples/gke-csi-yaml/checkpointing-pod.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Pod +metadata: + name: gcs-fuse-csi-example-pod + namespace: + annotations: + gke-gcsfuse/volumes: "true" + +spec: + containers: + # Add your workload container spec + ... + volumeMounts: + - name: checkpoint-bucket-vol + mountPath: /checkpoint-data + serviceAccountName: + volumes: + #RAM disk file cache if L-SSD not available. Uncomment out to use# + # - name: gke-gcsfuse-cache # gcsfuse file cache backed by RAM Disk + # emptyDir: + # medium: Memory + - name: checkpoint-bucket-vol + persistentVolumeClaim: + claimName: checkpoint-bucket-pvc \ No newline at end of file diff --git a/samples/gke-csi-yaml/checkpointing-pv.yaml b/samples/gke-csi-yaml/checkpointing-pv.yaml new file mode 100644 index 0000000000..3571773ba2 --- /dev/null +++ b/samples/gke-csi-yaml/checkpointing-pv.yaml @@ -0,0 +1,46 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: checkpoint-bucket-pv +spec: + accessModes: + - ReadWriteMany + capacity: + storage: 64Gi + persistentVolumeReclaimPolicy: Retain + storageClassName: gcsfuse-sc # dummy storage class + claimRef: + namespace: + name: checkpoint-bucket-pvc + mountOptions: + - implicit-dirs #set because most prefer ease of use + - metadata-cache:negative-ttl-secs:0 # disable negative cache + - metadata-cache:ttl-secs:-1 #no expiry + - metadata-cache:stat-cache-max-size-mb:-1 #unlimited + - metadata-cache:type-cache-max-size-mb:-1 #unlimited + - file-cache:max-size-mb:-1 #unlimited + - file-cache:cache-file-for-range-read:true + - file-cache:enable-parallel-downloads:true + - read_ahead_kb=1024 + - write:enable-streaming-writes:true + csi: + driver: gcsfuse.csi.storage.gke.io + volumeHandle: # unique bucket name + volumeAttributes: + skipCSIBucketAccessCheck: "true" + gcsfuseMetadataPrefetchOnMount: "true" + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: checkpoint-bucket-pvc + namespace: +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 64Gi + volumeName: checkpoint-bucket-pv + storageClassName: gcsfuse-sc # dummy storage class \ No newline at end of file diff --git a/samples/gke-csi-yaml/serving-pod.yaml b/samples/gke-csi-yaml/serving-pod.yaml new file mode 100644 index 0000000000..4338755ef8 --- /dev/null +++ b/samples/gke-csi-yaml/serving-pod.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Pod +metadata: + name: gcs-fuse-csi-example-pod + namespace: + annotations: + gke-gcsfuse/volumes: "true" + +spec: + containers: + # Your workload container spec + ... + volumeMounts: + - name: serving-bucket-vol + mountPath: /serving-data + serviceAccountName: + volumes: + #RAM disk file cache for best performance of Parallel Download. Can use L-SSD if not available memory + - name: gke-gcsfuse-cache # gcsfuse file cache backed by RAM Disk + emptyDir: + medium: Memory + - name: serving-bucket-vol + persistentVolumeClaim: + claimName: serving-bucket-pvc \ No newline at end of file diff --git a/samples/gke-csi-yaml/serving-pv.yaml b/samples/gke-csi-yaml/serving-pv.yaml new file mode 100644 index 0000000000..e238d8fe49 --- /dev/null +++ b/samples/gke-csi-yaml/serving-pv.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: serving-bucket-pv +spec: + accessModes: + - ReadWriteMany + capacity: + storage: 64Gi + persistentVolumeReclaimPolicy: Retain + storageClassName: gcsfuse-sc # dummy storage class + claimRef: + namespace: + name: serving-bucket-pvc + mountOptions: + - implicit-dirs #set because most prefer ease of use + - metadata-cache:negative-ttl-secs:0 # disable negative cache + - metadata-cache:ttl-secs:-1 #no expiry + - metadata-cache:stat-cache-max-size-mb:-1 #unlimited + - metadata-cache:type-cache-max-size-mb:-1 #unlimited + - file-cache:max-size-mb:-1 #unlimited + - file-cache:cache-file-for-range-read:true + - file-cache:enable-parallel-downloads:true + - read_ahead_kb=1024 + csi: + driver: gcsfuse.csi.storage.gke.io + volumeHandle: # unique bucket name + volumeAttributes: + skipCSIBucketAccessCheck: "true" + gcsfuseMetadataPrefetchOnMount: "true" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: serving-bucket-pvc + namespace: +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 64Gi + volumeName: serving-bucket-pv + storageClassName: gcsfuse-sc # dummy storage class \ No newline at end of file diff --git a/samples/gke-csi-yaml/training-pod.yaml b/samples/gke-csi-yaml/training-pod.yaml new file mode 100644 index 0000000000..e26407c0f3 --- /dev/null +++ b/samples/gke-csi-yaml/training-pod.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Pod +metadata: + name: gcs-fuse-csi-example-pod + namespace: + annotations: + gke-gcsfuse/volumes: "true" +spec: + containers: + # Your workload container spec + ... + volumeMounts: + - name: training-bucket-vol + mountPath: /training-data + serviceAccountName: + volumes: + #RAM disk file cache if L-SSD not available. Uncomment out to use# + # - name: gke-gcsfuse-cache # gcsfuse file cache backed by RAM Disk + # emptyDir: + # medium: Memory + - name: training-bucket-vol + persistentVolumeClaim: + claimName: training-bucket-pvc \ No newline at end of file diff --git a/samples/gke-csi-yaml/training-pv.yaml b/samples/gke-csi-yaml/training-pv.yaml new file mode 100644 index 0000000000..1bffd5c188 --- /dev/null +++ b/samples/gke-csi-yaml/training-pv.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: training-bucket-pv +spec: + accessModes: + - ReadWriteMany + capacity: + storage: 64Gi + persistentVolumeReclaimPolicy: Retain + storageClassName: gcsfuse-sc # dummy storage class + claimRef: + namespace: + name: training-bucket-pvc + mountOptions: + - implicit-dirs #set because most prefer ease of use + - metadata-cache:negative-ttl-secs:0 #disabled + - metadata-cache:ttl-secs:-1 #unlimited + - metadata-cache:stat-cache-max-size-mb:-1 #unlimited + - metadata-cache:type-cache-max-size-mb:-1 #unlimited + # if enabling the file cache, uncomment out to use # + # - file-cache:max-size-mb:-1 # only for GPUs + # - file-cache:cache-file-for-range-read:true + # - read_ahead_kb=1024 + csi: + driver: gcsfuse.csi.storage.gke.io + volumeHandle: # unique bucket name + volumeAttributes: + skipCSIBucketAccessCheck: "true" + gcsfuseMetadataPrefetchOnMount: "true" +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: training-bucket-pvc + namespace: +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 64Gi + volumeName: training-bucket-pv + storageClassName: gcsfuse-sc # dummy storage class \ No newline at end of file From d7b3a6a4002cc92b821e58c9fb562f536aa9da82 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Fri, 18 Apr 2025 08:54:59 +0000 Subject: [PATCH 0363/1298] Update Readme instructions --- samples/gke-csi-yaml/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/gke-csi-yaml/README.md b/samples/gke-csi-yaml/README.md index 024e12e1cf..12bfb8e62b 100644 --- a/samples/gke-csi-yaml/README.md +++ b/samples/gke-csi-yaml/README.md @@ -1,13 +1,13 @@ This folder contains sample YAML files for GKE GCSFuse CSI driver with recommendations for GPUs/TPUs. -This folder contains sample Pod and Pod Volume Yaml config file for the following workflows +The following workflow samples are added 1. Training 2. Serving or Inference 3. Checkpointing -For Jit Cache workflows, use the Checkpointing workflow yaml configs. +For JAX Jit Cache workflows, use the Checkpointing workflow yaml configs. -For e.g. if you want to set up the serving workload. +For e.g. To set up the serving workload. First, deploy the PVC/PV first (this is required because the GKE pod webhook inspect the PV volume attributes for additional optimizations like injection of additional containers) e.g. @@ -23,6 +23,6 @@ kubectl apply -f serving-pod.yaml Note: * The service account needs to be created before use. -* Replace placeholders with your actual values. +* Replace placeholders with actual values. Read https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-storage-fuse-csi-driver-perf#best-practices-for-performance-tuning for more tuning details. \ No newline at end of file From 92f3d1bcc1ff295f9fbc01e62cf5294a30ccf3d3 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Fri, 18 Apr 2025 09:01:50 +0000 Subject: [PATCH 0364/1298] Add Newline at end of YAML files --- samples/gke-csi-yaml/checkpointing-pod.yaml | 2 +- samples/gke-csi-yaml/checkpointing-pv.yaml | 2 +- samples/gke-csi-yaml/serving-pod.yaml | 2 +- samples/gke-csi-yaml/serving-pv.yaml | 2 +- samples/gke-csi-yaml/training-pod.yaml | 2 +- samples/gke-csi-yaml/training-pv.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/samples/gke-csi-yaml/checkpointing-pod.yaml b/samples/gke-csi-yaml/checkpointing-pod.yaml index 4fe86b5c68..a031857dc1 100644 --- a/samples/gke-csi-yaml/checkpointing-pod.yaml +++ b/samples/gke-csi-yaml/checkpointing-pod.yaml @@ -21,4 +21,4 @@ spec: # medium: Memory - name: checkpoint-bucket-vol persistentVolumeClaim: - claimName: checkpoint-bucket-pvc \ No newline at end of file + claimName: checkpoint-bucket-pvc diff --git a/samples/gke-csi-yaml/checkpointing-pv.yaml b/samples/gke-csi-yaml/checkpointing-pv.yaml index 3571773ba2..ffd26af5c5 100644 --- a/samples/gke-csi-yaml/checkpointing-pv.yaml +++ b/samples/gke-csi-yaml/checkpointing-pv.yaml @@ -43,4 +43,4 @@ spec: requests: storage: 64Gi volumeName: checkpoint-bucket-pv - storageClassName: gcsfuse-sc # dummy storage class \ No newline at end of file + storageClassName: gcsfuse-sc # dummy storage class diff --git a/samples/gke-csi-yaml/serving-pod.yaml b/samples/gke-csi-yaml/serving-pod.yaml index 4338755ef8..75683ae4a0 100644 --- a/samples/gke-csi-yaml/serving-pod.yaml +++ b/samples/gke-csi-yaml/serving-pod.yaml @@ -21,4 +21,4 @@ spec: medium: Memory - name: serving-bucket-vol persistentVolumeClaim: - claimName: serving-bucket-pvc \ No newline at end of file + claimName: serving-bucket-pvc diff --git a/samples/gke-csi-yaml/serving-pv.yaml b/samples/gke-csi-yaml/serving-pv.yaml index e238d8fe49..e7d4198f31 100644 --- a/samples/gke-csi-yaml/serving-pv.yaml +++ b/samples/gke-csi-yaml/serving-pv.yaml @@ -41,4 +41,4 @@ spec: requests: storage: 64Gi volumeName: serving-bucket-pv - storageClassName: gcsfuse-sc # dummy storage class \ No newline at end of file + storageClassName: gcsfuse-sc # dummy storage class diff --git a/samples/gke-csi-yaml/training-pod.yaml b/samples/gke-csi-yaml/training-pod.yaml index e26407c0f3..149434dab6 100644 --- a/samples/gke-csi-yaml/training-pod.yaml +++ b/samples/gke-csi-yaml/training-pod.yaml @@ -20,4 +20,4 @@ spec: # medium: Memory - name: training-bucket-vol persistentVolumeClaim: - claimName: training-bucket-pvc \ No newline at end of file + claimName: training-bucket-pvc diff --git a/samples/gke-csi-yaml/training-pv.yaml b/samples/gke-csi-yaml/training-pv.yaml index 1bffd5c188..98d4be721a 100644 --- a/samples/gke-csi-yaml/training-pv.yaml +++ b/samples/gke-csi-yaml/training-pv.yaml @@ -41,4 +41,4 @@ spec: requests: storage: 64Gi volumeName: training-bucket-pv - storageClassName: gcsfuse-sc # dummy storage class \ No newline at end of file + storageClassName: gcsfuse-sc # dummy storage class From 0d723ff06797679bffa1d02dc48f85540bc491a3 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 18 Apr 2025 10:25:03 +0000 Subject: [PATCH 0365/1298] Clean up buckets on e2e failure (#3190) * Cleanup buckets on e2e script exit * Remove old outdated clean_up code * add comments for documentation * fix to changes * improve readability * fix a typo and other minor corrections * empty commit * address a review comment * address another review comment * address another review comment * more code cleanup --- tools/integration_tests/run_e2e_tests.sh | 86 +++++++++++++----------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index e88d91dcd2..10db9de8f7 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -156,6 +156,28 @@ TEST_DIR_NON_PARALLEL_FOR_ZB=( # Create a temporary file to store the log file name. TEST_LOGS_FILE=$(mktemp) +# Delete contents of the buckets (and then the buckets themselves) whose names are in the passed file. +# Args: +function delete_buckets_listed_in_file() { + local bucketNamesFile="${@}" + if test -f "${bucketNamesFile}"; then + cat "${bucketNamesFile}" | while read bucket; do + # Only if bucket-name is non-empty and contains + # something other than spaces. + if [ -n "${bucket}" ] && [ -n "${bucket// }" ]; then + # Delete the bucket and its contents. + if ! gcloud -q storage rm -r --verbosity=none gs://${bucket} ; then + >&2 echo "Failed to delete bucket ${bucket} !" + fi + fi + done + # At the end, delete the bucket-names file itself. + rm "${bucketNamesFile}" + else + echo "file ${bucketNamesFile} not found !" + fi +} + function upgrade_gcloud_version() { sudo apt-get update # Upgrade gcloud version. @@ -319,41 +341,42 @@ function run_e2e_tests_for_flat_bucket() { bucketPrefix="golang-grpc-test-gcsfuse-np-e2e-tests-" bucket_name_non_parallel=$(create_bucket $bucketPrefix) echo "Bucket name for non parallel tests: "$bucket_name_non_parallel + echo ${bucket_name_non_parallel}>>"${bucketNamesFile}" bucketPrefix="golang-grpc-test-gcsfuse-p-e2e-tests-" bucket_name_parallel=$(create_bucket $bucketPrefix) echo "Bucket name for parallel tests: "$bucket_name_parallel + echo ${bucket_name_parallel}>>"${bucketNamesFile}" echo "Running parallel tests..." run_parallel_tests TEST_DIR_PARALLEL $bucket_name_parallel & parallel_tests_pid=$! - echo "Running non parallel tests ..." - run_non_parallel_tests TEST_DIR_NON_PARALLEL $bucket_name_non_parallel & - non_parallel_tests_pid=$! - - # Wait for all tests to complete. - wait $parallel_tests_pid - parallel_tests_exit_code=$? - wait $non_parallel_tests_pid - non_parallel_tests_exit_code=$? + echo "Running non parallel tests ..." + run_non_parallel_tests TEST_DIR_NON_PARALLEL $bucket_name_non_parallel & + non_parallel_tests_pid=$! - flat_buckets=("$bucket_name_parallel" "$bucket_name_non_parallel") - clean_up flat_buckets + # Wait for all tests to complete. + wait $parallel_tests_pid + parallel_tests_exit_code=$? + wait $non_parallel_tests_pid + non_parallel_tests_exit_code=$? - if [ $non_parallel_tests_exit_code != 0 ] || [ $parallel_tests_exit_code != 0 ]; - then - return 1 - fi - return 0 + if [ $non_parallel_tests_exit_code != 0 ] || [ $parallel_tests_exit_code != 0 ]; + then + return 1 + fi + return 0 } function run_e2e_tests_for_hns_bucket(){ hns_bucket_name_parallel_group=$(create_hns_bucket) echo "Hns Bucket Created: "$hns_bucket_name_parallel_group + echo ${hns_bucket_name_parallel_group}>>"${bucketNamesFile}" hns_bucket_name_non_parallel_group=$(create_hns_bucket) echo "Hns Bucket Created: "$hns_bucket_name_non_parallel_group + echo ${hns_bucket_name_non_parallel_group}>>"${bucketNamesFile}" echo "Running tests for HNS bucket" run_parallel_tests TEST_DIR_PARALLEL "$hns_bucket_name_parallel_group" & @@ -367,9 +390,6 @@ function run_e2e_tests_for_hns_bucket(){ wait $non_parallel_tests_hns_group_pid non_parallel_tests_hns_group_exit_code=$? - hns_buckets=("$hns_bucket_name_parallel_group" "$hns_bucket_name_non_parallel_group") - clean_up hns_buckets - if [ $parallel_tests_hns_group_exit_code != 0 ] || [ $non_parallel_tests_hns_group_exit_code != 0 ]; then return 1 @@ -380,9 +400,11 @@ function run_e2e_tests_for_hns_bucket(){ function run_e2e_tests_for_zonal_bucket(){ zonal_bucket_name_parallel_group=$(create_zonal_bucket) echo "Zonal Bucket Created for parallel tests: "$zonal_bucket_name_parallel_group + echo ${zonal_bucket_name_parallel_group}>>"${bucketNamesFile}" zonal_bucket_name_non_parallel_group=$(create_zonal_bucket) echo "Zonal Bucket Created for non-parallel tests: "$zonal_bucket_name_non_parallel_group + echo ${zonal_bucket_name_non_parallel_group}>>"${bucketNamesFile}" echo "Running tests for ZONAL bucket" run_parallel_tests TEST_DIR_PARALLEL_FOR_ZB "$zonal_bucket_name_parallel_group" true & @@ -396,9 +418,6 @@ function run_e2e_tests_for_zonal_bucket(){ wait $non_parallel_tests_zonal_group_pid non_parallel_tests_zonal_group_exit_code=$? - zonal_buckets=("$zonal_bucket_name_parallel_group" "$zonal_bucket_name_non_parallel_group") - clean_up zonal_buckets - if [ $parallel_tests_zonal_group_exit_code != 0 ] || [ $non_parallel_tests_zonal_group_exit_code != 0 ]; then return 1 @@ -430,24 +449,13 @@ function run_e2e_tests_for_emulator() { ./tools/integration_tests/emulator_tests/emulator_tests.sh $RUN_E2E_TESTS_ON_PACKAGE } -#commenting it so cleanup and failure check happens for both -#set -e - -function clean_up() { - # Cleanup - # Delete bucket after testing. - local -n buckets=$1 - for bucket in "${buckets[@]}" - do - # Empty bucket name may cause deletions of all the buckets. - if [ "$bucket" != "" ]; - then - gcloud alpha storage rm --recursive gs://$bucket 2>&1 | grep "ERROR" - fi - done -} - function main(){ + # The name of a file containing the names of all the + # buckets to be cleaned-up while exiting this program. + bucketNamesFile=$(realpath ./bucketNames)"-"$(tr -dc 'a-z0-9' < /dev/urandom | head -c $RANDOM_STRING_LENGTH) + # Delete all these buckets when the program exits. + trap "delete_buckets_listed_in_file ${bucketNamesFile}" EXIT + set -e upgrade_gcloud_version From 2c13628561b2b1be20d4d9b957cbd6c8d1234921 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Fri, 18 Apr 2025 11:23:12 +0000 Subject: [PATCH 0366/1298] Review comments --- samples/gke-csi-yaml/README.md | 2 +- samples/gke-csi-yaml/checkpointing-pod.yaml | 2 +- samples/gke-csi-yaml/serving-pod.yaml | 2 +- samples/gke-csi-yaml/training-pod.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/gke-csi-yaml/README.md b/samples/gke-csi-yaml/README.md index 12bfb8e62b..a6b950d12b 100644 --- a/samples/gke-csi-yaml/README.md +++ b/samples/gke-csi-yaml/README.md @@ -9,7 +9,7 @@ For JAX Jit Cache workflows, use the Checkpointing workflow yaml configs. For e.g. To set up the serving workload. -First, deploy the PVC/PV first (this is required because the GKE pod webhook inspect the PV volume attributes for additional optimizations like injection of additional containers) +Deploy the PVC/PV first (this is required because the GKE pod webhook inspects the PV volume attributes for additional optimizations like injection of additional containers) e.g. ``` kubectl apply -f serving-pv.yaml diff --git a/samples/gke-csi-yaml/checkpointing-pod.yaml b/samples/gke-csi-yaml/checkpointing-pod.yaml index a031857dc1..19dc7032cf 100644 --- a/samples/gke-csi-yaml/checkpointing-pod.yaml +++ b/samples/gke-csi-yaml/checkpointing-pod.yaml @@ -15,7 +15,7 @@ spec: mountPath: /checkpoint-data serviceAccountName: volumes: - #RAM disk file cache if L-SSD not available. Uncomment out to use# + # RAM disk file cache if L-SSD not available. Uncomment to use # - name: gke-gcsfuse-cache # gcsfuse file cache backed by RAM Disk # emptyDir: # medium: Memory diff --git a/samples/gke-csi-yaml/serving-pod.yaml b/samples/gke-csi-yaml/serving-pod.yaml index 75683ae4a0..31417d21af 100644 --- a/samples/gke-csi-yaml/serving-pod.yaml +++ b/samples/gke-csi-yaml/serving-pod.yaml @@ -15,7 +15,7 @@ spec: mountPath: /serving-data serviceAccountName: volumes: - #RAM disk file cache for best performance of Parallel Download. Can use L-SSD if not available memory + # RAM disk file cache for best performance of Parallel Download. Can use L-SSD if not available memory - name: gke-gcsfuse-cache # gcsfuse file cache backed by RAM Disk emptyDir: medium: Memory diff --git a/samples/gke-csi-yaml/training-pod.yaml b/samples/gke-csi-yaml/training-pod.yaml index 149434dab6..38086b269a 100644 --- a/samples/gke-csi-yaml/training-pod.yaml +++ b/samples/gke-csi-yaml/training-pod.yaml @@ -14,7 +14,7 @@ spec: mountPath: /training-data serviceAccountName: volumes: - #RAM disk file cache if L-SSD not available. Uncomment out to use# + # RAM disk file cache if L-SSD not available. Uncomment to use # - name: gke-gcsfuse-cache # gcsfuse file cache backed by RAM Disk # emptyDir: # medium: Memory From 0bc407435ee7b08014843f95fbb8851a6003a42b Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 18 Apr 2025 17:39:41 +0530 Subject: [PATCH 0367/1298] Update README.md --- samples/gke-csi-yaml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/gke-csi-yaml/README.md b/samples/gke-csi-yaml/README.md index a6b950d12b..98acd96107 100644 --- a/samples/gke-csi-yaml/README.md +++ b/samples/gke-csi-yaml/README.md @@ -25,4 +25,4 @@ Note: * The service account needs to be created before use. * Replace placeholders with actual values. -Read https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-storage-fuse-csi-driver-perf#best-practices-for-performance-tuning for more tuning details. \ No newline at end of file +Read https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-storage-fuse-csi-driver-perf#best-practices-for-performance-tuning for more tuning details. From efc357525805465a299309546bf7c3ab01c0d037 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Sat, 19 Apr 2025 12:00:05 +0530 Subject: [PATCH 0368/1298] Enable Read Stall Retry by default (#3126) --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/config_validation_test.go | 4 ++-- cmd/root_test.go | 2 +- cmd/testdata/valid_config.yaml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 9ec5dffad4..9c8b9a2d3a 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -353,7 +353,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-nonexistent-type-cache", "", false, "Once set, if an inode is not found in GCS, a type cache entry with type NonexistentType will be created. This also means new file/dir created might not be seen. For example, if this flag is set, and metadata-cache-ttl-secs is set, then if we create the same file/node in the meantime using the same mount, since we are not refreshing the cache, it will still return nil.") - flagSet.BoolP("enable-read-stall-retry", "", false, "To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past.") + flagSet.BoolP("enable-read-stall-retry", "", true, "To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past.") if err := flagSet.MarkHidden("enable-read-stall-retry"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 1c16b1c14f..9aa5872472 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -401,7 +401,7 @@ usage: >- To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past. - default: false + default: true hide-flag: true - config-path: "gcs-retries.read-stall.initial-req-timeout" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index ff57147478..ae00658559 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -712,7 +712,7 @@ func TestValidateConfigFile_GCSRetries(t *testing.T) { MaxRetrySleep: 30 * time.Second, Multiplier: 2, ReadStall: cfg.ReadStallGcsRetriesConfig{ - Enable: false, + Enable: true, MinReqTimeout: 1500 * time.Millisecond, MaxReqTimeout: 1200 * time.Second, InitialReqTimeout: 20 * time.Second, @@ -732,7 +732,7 @@ func TestValidateConfigFile_GCSRetries(t *testing.T) { MaxRetrySleep: 30 * time.Second, Multiplier: 2, ReadStall: cfg.ReadStallGcsRetriesConfig{ - Enable: true, + Enable: false, MinReqTimeout: 10 * time.Second, MaxReqTimeout: 200 * time.Second, InitialReqTimeout: 20 * time.Second, diff --git a/cmd/root_test.go b/cmd/root_test.go index a1e805f106..997c2fefe0 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1278,7 +1278,7 @@ func TestArgParsing_GCSRetries(t *testing.T) { MaxRetrySleep: 30 * time.Second, Multiplier: 2, ReadStall: cfg.ReadStallGcsRetriesConfig{ - Enable: false, + Enable: true, InitialReqTimeout: 20 * time.Second, MinReqTimeout: 1500 * time.Millisecond, MaxReqTimeout: 1200 * time.Second, diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 08ab5940fb..ca30720bc2 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -35,7 +35,7 @@ gcs-connection: gcs-retries: chunk-transfer-timeout-secs: 20 read-stall: - enable: true + enable: false min-req-timeout: 10s max-req-timeout: 200s initial-req-timeout: 20s From 6cdac4c65a4f925a52ea9bceb076d2783be990af Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 21 Apr 2025 10:57:57 +0530 Subject: [PATCH 0369/1298] [Random reader refactoring] File cache reader implementation - 2 (#3209) * add unit tests * add unit tests * removing unnecessary changes * removing coverage file * removing buf var * small fix * small fix * lint fix * remove unnecessary file * fix comments * small fix * remove unused variable * trigger kokoro * remove log statement * small fix * review comment --- internal/cache/util/util.go | 2 - internal/cache/util/util_test.go | 1 - internal/gcsx/file_cache_reader.go | 6 + internal/gcsx/file_cache_reader_test.go | 193 +++++++++++++++++-- internal/gcsx/random_reader.go | 22 +-- internal/gcsx/random_reader_stretchr_test.go | 20 +- internal/gcsx/random_reader_test.go | 15 +- internal/gcsx/temp_file.go | 2 +- 8 files changed, 211 insertions(+), 50 deletions(-) diff --git a/internal/cache/util/util.go b/internal/cache/util/util.go index 3e151b3ec6..dec74d2fd3 100644 --- a/internal/cache/util/util.go +++ b/internal/cache/util/util.go @@ -34,7 +34,6 @@ var ( ErrInvalidFileHandle = errors.New("invalid file handle") ErrInvalidFileDownloadJob = errors.New("invalid download job") ErrInvalidFileInfoCache = errors.New("invalid file info cache") - ErrInSeekingFileHandle = errors.New("error while seeking file handle") ErrInReadingFileHandle = errors.New("error while reading file handle") ErrFallbackToGCS = errors.New("read via gcs") ErrFileNotPresentInCache = errors.New("file is not present in cache") @@ -101,7 +100,6 @@ func IsCacheHandleInvalid(readErr error) bool { return errors.Is(readErr, ErrInvalidFileHandle) || errors.Is(readErr, ErrInvalidFileDownloadJob) || errors.Is(readErr, ErrInvalidFileInfoCache) || - errors.Is(readErr, ErrInSeekingFileHandle) || errors.Is(readErr, ErrInReadingFileHandle) } diff --git a/internal/cache/util/util_test.go b/internal/cache/util/util_test.go index 76ac44b5dd..ebc59a2bad 100644 --- a/internal/cache/util/util_test.go +++ b/internal/cache/util/util_test.go @@ -232,7 +232,6 @@ func (ut *utilTest) Test_IsCacheHandleValid_True() { fmt.Errorf("%w: %s", ErrInvalidFileHandle, "test"), fmt.Errorf("%w: %s", ErrInvalidFileDownloadJob, "test"), fmt.Errorf("%w: %s", ErrInvalidFileInfoCache, "test"), - fmt.Errorf("%w: %s", ErrInSeekingFileHandle, "test"), fmt.Errorf("%w: %s", ErrInReadingFileHandle, "test"), } diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index a5195b28b1..870f75b655 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -33,6 +33,12 @@ import ( "github.com/jacobsa/fuse/fuseops" ) +const ( + // ReadOp ("readOp") is the value used in read context to store pointer to the read operation. + ReadOp = "readOp" + MiB = 1 << 20 +) + type FileCacheReader struct { Reader object *gcs.MinObject diff --git a/internal/gcsx/file_cache_reader_test.go b/internal/gcsx/file_cache_reader_test.go index ca15f6fbcf..a8632858da 100644 --- a/internal/gcsx/file_cache_reader_test.go +++ b/internal/gcsx/file_cache_reader_test.go @@ -15,9 +15,11 @@ package gcsx import ( + "bytes" "context" "errors" "fmt" + "io" "os" "path" "testing" @@ -41,8 +43,8 @@ import ( const ( testObject = "testObject" sequentialReadSizeInMb = 22 - sequentialReadSizeInBytes = sequentialReadSizeInMb * MB - cacheMaxSize = 2 * sequentialReadSizeInMb * util.MiB + sequentialReadSizeInBytes = sequentialReadSizeInMb * MiB + cacheMaxSize = 2 * sequentialReadSizeInMb * MiB ) type fileCacheReaderTest struct { @@ -85,7 +87,13 @@ func (t *fileCacheReaderTest) TearDown() { func (t *fileCacheReaderTest) mockNewReaderWithHandleCallForTestBucket(start uint64, limit uint64, rd gcs.StorageReader) { t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(rg *gcs.ReadObjectRequest) bool { return rg != nil && (*rg.Range).Start == start && (*rg.Range).Limit == limit - })).Return(rd, nil).Maybe() + })).Return(rd, nil).Once() +} + +func getReadCloser(content []byte) io.ReadCloser { + r := bytes.NewReader(content) + rc := io.NopCloser(r) + return rc } func (t *fileCacheReaderTest) TestNewFileCacheReader() { @@ -105,7 +113,26 @@ func (t *fileCacheReaderTest) Test_ReadAt_NilFileCacheHandlerThrowFallBackError( readerResponse, err := reader.ReadAt(t.ctx, make([]byte, 10), 0) - assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader), "expected %v error got %v", FallbackToAnotherReader, err) + assert.Zero(t.T(), readerResponse.Size) +} + +func (t *fileCacheReaderTest) Test_ReadAt_FileSizeIsGreaterThanCacheSize() { + t.object.Size = cacheMaxSize + 5 + t.mockBucket.On("Name").Return("test-bucket") + + readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader), "expected %v error got %v", FallbackToAnotherReader, err) + assert.Zero(t.T(), readerResponse.Size) +} + +func (t *fileCacheReaderTest) Test_ReadAt_OffsetGreaterThanFileSizeWillReturnEOF() { + offset := t.object.Size + 10 + + readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, 10), int64(offset)) + + assert.True(t.T(), errors.Is(err, io.EOF), "expected %v error got %v", io.EOF, err) assert.Zero(t.T(), readerResponse.Size) } @@ -182,7 +209,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCa t.mockBucket.On("Name").Return("test-bucket") buf := make([]byte, end-start) readerResponse, err := t.reader.ReadAt(t.ctx, buf, int64(start)) - assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader), "expected %v error got %v", FallbackToAnotherReader, err) assert.Zero(t.T(), readerResponse.Size) job := t.jobManager.CreateJobIfNotExists(t.object, t.mockBucket) jobStatus := job.GetStatus() @@ -190,7 +217,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCa readerResponse, err = t.reader.ReadAt(t.ctx, buf, int64(start)) - assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader), "expected %v error got %v", FallbackToAnotherReader, err) assert.Zero(t.T(), readerResponse.Size) t.mockBucket.AssertExpectations(t.T()) } @@ -208,12 +235,11 @@ func (t *fileCacheReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCa readerResponse, err := t.reader.ReadAt(t.ctx, buf, int64(start)) - assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader), "expected %v error got %v", FallbackToAnotherReader, err) assert.Zero(t.T(), readerResponse.Size) job := t.jobManager.GetJob(t.object.Name, t.mockBucket.Name()) assert.True(t.T(), job == nil || job.GetStatus().Name == downloader.Downloading) assert.NotNil(t.T(), t.reader.fileCacheHandle) - t.mockBucket.AssertExpectations(t.T()) } func (t *fileCacheReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffsetMoreThanReadChunkSize() { @@ -239,7 +265,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffset readerResponse, err = t.reader.ReadAt(t.ctx, buf2, int64(start2)) // Assuming a download with a start offset of start2 is in progress, a fallback to another reader will be required. - assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader), "expected %v error got %v", FallbackToAnotherReader, err) assert.Zero(t.T(), readerResponse.Size) job := t.jobManager.GetJob(t.object.Name, t.mockBucket.Name()) assert.True(t.T(), job == nil || job.GetStatus().Name == downloader.Downloading) @@ -267,7 +293,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffset buf2 := make([]byte, end2-start2) // Assuming a download with a start offset of start2 is in progress, a fallback to another reader will be required. readerResponse, err = t.reader.ReadAt(t.ctx, buf2, int64(start2)) - assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader), "expected %v error got %v", FallbackToAnotherReader, err) assert.Zero(t.T(), readerResponse.Size) // Assuming start3 offset is downloaded start3 := 4 * util.MiB @@ -301,7 +327,152 @@ func (t *fileCacheReaderTest) Test_ReadAt_CacheMissDueToInvalidJob() { readerResponse, err = t.reader.ReadAt(t.ctx, buf, 0) // As job is invalidated Need to get served from GCS reader - assert.True(t.T(), errors.Is(err, FallbackToAnotherReader)) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader), "expected %v error got %v", FallbackToAnotherReader, err) + assert.Zero(t.T(), readerResponse.Size) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInvalidJob() { + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + // First successful read with cache + rd1 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd1) + t.mockBucket.On("Name").Return("test-bucket") + readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + assert.NoError(t.T(), err) + assert.Equal(t.T(), testContent, readerResponse.DataBuf) + job := t.jobManager.GetJob(t.object.Name, t.mockBucket.Name()) + if job != nil { + jobStatus := job.GetStatus().Name + assert.True(t.T(), jobStatus == downloader.Downloading || jobStatus == downloader.Completed, fmt.Sprintf("the actual status is %v", jobStatus)) + } + assert.NotNil(t.T(), t.reader.fileCacheHandle) + // Invalidate the cache to simulate cache miss + err = t.reader.fileCacheHandler.InvalidateCache(t.object.Name, t.mockBucket.Name()) + assert.NoError(t.T(), err) + readerResponse, err = t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader), "expected %v error got %v", FallbackToAnotherReader, err) + assert.Zero(t.T(), readerResponse.Size) + assert.Nil(t.T(), t.reader.fileCacheHandle) + rd2 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd2) + + readerResponse, err = t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent) + assert.NotNil(t.T(), t.reader.fileCacheHandle) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInvalidFileHandleAfterThenCacheHitWithNewFileCacheHandle() { + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockBucket.On("Name").Return("test-bucket") + readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent) + assert.NotNil(t.T(), t.reader.fileCacheHandle) + err = t.reader.fileCacheHandle.Close() + assert.NoError(t.T(), err) + readerResponse, err = t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader), "expected %v error got %v", FallbackToAnotherReader, err) + assert.Zero(t.T(), readerResponse.Size) + assert.Nil(t.T(), t.reader.fileCacheHandle) + + readerResponse, err = t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + + // Reading from file cache with new file cache handle. + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent) + assert.NotNil(t.T(), t.reader.fileCacheHandle) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_IfCacheFileGetsDeleted() { + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockBucket.On("Name").Return("test-bucket") + readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent) + assert.NotNil(t.T(), t.reader.fileCacheHandle) + err = t.reader.fileCacheHandle.Close() + assert.NoError(t.T(), err) + t.reader.fileCacheHandle = nil + // Delete the local cache file. + filePath := util.GetDownloadPath(t.cacheDir, util.GetObjectPath(t.mockBucket.Name(), t.object.Name)) + err = os.Remove(filePath) + assert.NoError(t.T(), err) + + readerResponse, err = t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + + assert.True(t.T(), errors.Is(err, util.ErrFileNotPresentInCache)) + assert.Zero(t.T(), readerResponse.Size) +} + +func (t *fileCacheReaderTest) Test_ReadAt_IfCacheFileGetsDeletedWithCacheHandleOpen() { + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockBucket.On("Name").Return("test-bucket") + readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent) + assert.NotNil(t.T(), t.reader.fileCacheHandle) + // Delete the local cache file. + filePath := util.GetDownloadPath(t.cacheDir, util.GetObjectPath(t.mockBucket.Name(), t.object.Name)) + err = os.Remove(filePath) + assert.NoError(nil, err) + + // Read via cache only, as we have old fileHandle open and linux + // doesn't delete the file until the fileHandle count for the file is zero. + readerResponse, err = t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_FailedJobNextReadCreatesNewJobAndCacheHit() { + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + // First NewReaderWithReadHandle call fails, simulating a failed attempt to read from GCS. + // This triggers a fallback to GCS reader. + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(nil, errors.New("")).Once() + t.mockBucket.On("Name").Return("test-bucket") + // First ReadAt call: + // - Should result in a FallbackToAnotherReader error. + // - No data should be returned. + // - The job should be marked as failed (if jobManager is functioning correctly). + readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + assert.True(t.T(), errors.Is(err, FallbackToAnotherReader), "expected %v error got %v", FallbackToAnotherReader, err) assert.Zero(t.T(), readerResponse.Size) + job := t.jobManager.GetJob(t.object.Name, t.mockBucket.Name()) + assert.True(t.T(), job == nil || job.GetStatus().Name == downloader.Failed) + rc := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rc) + // Second ReadAt call: The file cache should be populated as a result of this successful read. + readerResponse, err = t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent) + assert.NotNil(t.T(), t.reader.fileCacheHandle) + + // Third ReadAt call: Should be served directly from the file cache. + readerResponse, err = t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent) + assert.NotNil(t.T(), t.reader.fileCacheHandle) t.mockBucket.AssertExpectations(t.T()) } + +func (t *fileCacheReaderTest) Test_ReadAt_NegativeOffsetShouldThrowError() { + t.mockBucket.On("Name").Return("test-bucket") + + readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, 10), -1) + + assert.Error(t.T(), err) + assert.Zero(t.T(), readerResponse.Size) +} diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index a40c2c8196..1f616115c4 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -34,27 +34,21 @@ import ( "golang.org/x/net/context" ) -// MB is 1 Megabyte. (Silly comment to make the lint warning go away) -const MB = 1 << 20 - // Min read size in bytes for random reads. // We will not send a request to GCS for less than this many bytes (unless the // end of the object comes first). -const minReadSize = MB +const minReadSize = MiB // Max read size in bytes for random reads. // If the average read size (between seeks) is below this number, reads will // optimised for random access. // We will skip forwards in a GCS response at most this many bytes. -// About 6 MB of data is buffered anyway, so 8 MB seems like a good round number. -const maxReadSize = 8 * MB +// About 6 MiB of data is buffered anyway, so 8 MiB seems like a good round number. +const maxReadSize = 8 * MiB // Minimum number of seeks before evaluating if the read pattern is random. const minSeeksForRandom = 2 -// "readOp" is the value used in read context to store pointer to the read operation. -const ReadOp = "readOp" - // TODO(b/385826024): Revert timeout to an appropriate value const TimeoutForMultiRangeRead = time.Hour @@ -335,7 +329,7 @@ func (rr *randomReader) ReadAt( // concurrent reads, often by only a few 128kB fuse read requests. The aim is to // re-use GCS connection and avoid throwing away already read data. // For parallel sequential reads to a single file, not throwing away the connections - // is a 15-20x improvement in throughput: 150-200 MB/s instead of 10 MB/s. + // is a 15-20x improvement in throughput: 150-200 MiB/s instead of 10 MiB/s. if rr.reader != nil && rr.start < offset && offset-rr.start < maxReadSize { bytesToSkip := offset - rr.start discardedBytes, copyError := io.CopyN(io.Discard, rr.reader, int64(bytesToSkip)) @@ -523,20 +517,20 @@ func (rr *randomReader) getReadInfo( // GCS requests are expensive. Prefer to issue read requests defined by // sequentialReadSizeMb flag. Sequential reads will simply sip from the fire house // with each call to ReadAt. In practice, GCS will fill the TCP buffers - // with about 6 MB of data. Requests from outside GCP will be charged + // with about 6 MiB of data. Requests from outside GCP will be charged // about 6MB of egress data, even if less data is read. Inside GCP // regions, GCS egress is free. This logic should limit the number of // GCS read requests, which are not free. // But if we notice random read patterns after a minimum number of seeks, // optimise for random reads. Random reads will read data in chunks of - // (average read size in bytes rounded up to the next MB). + // (average read size in bytes rounded up to the next MiB). end = int64(rr.object.Size) if rr.seeks >= minSeeksForRandom { rr.readType = util.Random averageReadBytes := rr.totalReadBytes / rr.seeks if averageReadBytes < maxReadSize { - randomReadSize := int64(((averageReadBytes / MB) + 1) * MB) + randomReadSize := int64(((averageReadBytes / MiB) + 1) * MiB) if randomReadSize < minReadSize { randomReadSize = minReadSize } @@ -552,7 +546,7 @@ func (rr *randomReader) getReadInfo( // To avoid overloading GCS and to have reasonable latencies, we will only // fetch data of max size defined by sequentialReadSizeMb. - maxSizeToReadFromGCS := int64(rr.sequentialReadSizeMb * MB) + maxSizeToReadFromGCS := int64(rr.sequentialReadSizeMb * MiB) if end-start > maxSizeToReadFromGCS { end = start + maxSizeToReadFromGCS } diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 298da89e0d..87247f7e62 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -88,7 +88,7 @@ func (t *RandomReaderStretchrTest) TearDownTest() { } func (t *RandomReaderStretchrTest) Test_ReadInfo() { - t.object.Size = 10 * MB + t.object.Size = 10 * MiB testCases := []struct { name string start int64 @@ -130,9 +130,9 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo_Sequential() { start int64 objectSize uint64 }{ - {"10MBObject", 10 * MB, 0, 10 * MB}, - {"ReadSizeGreaterThanObjectSize", 10 * MB, int64(t.object.Size - 1), 10 * MB}, - {"ObjectSizeGreaterThanReadSize", int64(sequentialReadSizeInBytes), 0, 50 * MB}, + {"10MBObject", 10 * MiB, 0, 10 * MiB}, + {"ReadSizeGreaterThanObjectSize", 10 * MiB, int64(t.object.Size - 1), 10 * MiB}, + {"ObjectSizeGreaterThanReadSize", int64(sequentialReadSizeInBytes), 0, 50 * MiB}, } for _, tc := range testCases { @@ -157,14 +157,14 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo_Random() { totalReadBytes uint64 }{ // TotalReadByte is 10MB, so average is 10/2 = 5MB >1MB and <8MB - {"RangeBetween1And8MB", 6 * MB, 0, 50 * MB, 10 * MB}, + {"RangeBetween1And8MB", 6 * MiB, 0, 50 * MiB, 10 * MiB}, // TotalReadByte is 1MB, so average is 1/2 = 0.5MB which is <1MB - {"ReadSizeLessThan1MB", minReadSize, 0, 50 * MB, 1 * MB}, + {"ReadSizeLessThan1MB", minReadSize, 0, 50 * MiB, 1 * MiB}, // TotalReadByte is 1MB, so average is 10/2 = 5MB which is <8MB - {"ReadSizeLessThan8MB", 6 * MB, 0, 50 * MB, 10 * MB}, + {"ReadSizeLessThan8MB", 6 * MiB, 0, 50 * MiB, 10 * MiB}, // TotalReadByte is 1MB, so average is 20/2 = 10MB which is >8MB - {"ReadSizeGreaterThan8MB", sequentialReadSizeInBytes, 0, 50 * MB, 20 * MB}, - {"ReadSizeGreaterThanObjectSize", 5 * MB, 5*MB - 1, 5 * MB, 2 * MB}, + {"ReadSizeGreaterThan8MB", sequentialReadSizeInBytes, 0, 50 * MiB, 20 * MiB}, + {"ReadSizeGreaterThanObjectSize", 5 * MiB, 5*MiB - 1, 5 * MiB, 2 * MiB}, } for _, tc := range testCases { @@ -201,7 +201,7 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { name: "ZonalBucketRandomReadLargerThan8MB", readType: testutil.Random, start: 0, - end: 9 * MB, + end: 9 * MiB, bucketType: gcs.BucketType{Zonal: true}, readerType: RangeReader, }, diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index f4565a54ae..ac7bb76734 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -15,7 +15,6 @@ package gcsx import ( - "bytes" "errors" "fmt" "io" @@ -190,12 +189,6 @@ func (t *RandomReaderTest) TearDown() { t.rr.Destroy() } -func getReadCloser(content []byte) io.ReadCloser { - r := bytes.NewReader(content) - rc := io.NopCloser(r) - return rc -} - func (t *RandomReaderTest) mockNewReaderWithHandleCallForTestBucket(start uint64, limit uint64, rd gcs.StorageReader) { ExpectCall(t.bucket, "NewReaderWithReadHandle")( Any(), AllOf(rangeStartIs(start), rangeLimitIs(limit))). @@ -424,7 +417,7 @@ func (t *RandomReaderTest) DoesntPropagateCancellationAfterReturning() { } func (t *RandomReaderTest) UpgradesReadsToObjectSize() { - const objectSize = 2 * MB + const objectSize = 2 * MiB t.object.Size = objectSize const readSize = 10 @@ -460,7 +453,7 @@ func (t *RandomReaderTest) UpgradesReadsToObjectSize() { func (t *RandomReaderTest) UpgradeReadsToAverageSize() { t.object.Size = 1 << 40 - const totalReadBytes = 6 * MB + const totalReadBytes = 6 * MiB const numReads = 2 const avgReadBytes = totalReadBytes / numReads @@ -536,9 +529,9 @@ func (t *RandomReaderTest) UpgradesSequentialReads_ExistingReader() { func (t *RandomReaderTest) UpgradesSequentialReads_NoExistingReader() { t.object.Size = 1 << 40 - const readSize = 1 * MB + const readSize = 1 * MiB // Set up the custom randomReader. - rr := NewRandomReader(t.object, t.bucket, readSize/MB, nil, false, common.NewNoopMetrics(), nil) + rr := NewRandomReader(t.object, t.bucket, readSize/MiB, nil, false, common.NewNoopMetrics(), nil) t.rr.wrapped = rr.(*randomReader) // Simulate a previous exhausted reader that ended at the offset from which diff --git a/internal/gcsx/temp_file.go b/internal/gcsx/temp_file.go index b4e3b15ce1..fd61c4330e 100644 --- a/internal/gcsx/temp_file.go +++ b/internal/gcsx/temp_file.go @@ -326,7 +326,7 @@ func minInt64(a int64, b int64) int64 { } const ( - minCopyLength = 64 * 1024 * 1024 // 64 MB + minCopyLength = 64 * 1024 * 1024 // 64 MiB ) func (tf *tempFile) ensure(limit int64) error { From 80e822789504745874c6f3ee57ad73a4dfbf4154 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 21 Apr 2025 08:42:06 +0000 Subject: [PATCH 0370/1298] Move some packages to non-parallel in ZB e2e (#3212) Move following packages from parallel to non-parallel because their random failures when they are run in parallel with other packages. - concurrent_operations - list_large_dir --- tools/integration_tests/run_e2e_tests.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 10db9de8f7..af9eb90077 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -121,13 +121,11 @@ TEST_DIR_NON_PARALLEL=( # pass for zonal buckets. TEST_DIR_PARALLEL_FOR_ZB=( "benchmarking" - "concurrent_operations" "explicit_dir" "gzip" "implicit_dir" "interrupt" "kernel_list_cache" - "list_large_dir" "local_file" "log_rotation" "monitoring" @@ -148,8 +146,10 @@ TEST_DIR_PARALLEL_FOR_ZB=( # but only those tests which currently # pass for zonal buckets. TEST_DIR_NON_PARALLEL_FOR_ZB=( - "readonly" + "concurrent_operations" + "list_large_dir" "managed_folders" + "readonly" "readonly_creds" ) From 6f0c9c88ff92743559544757975881cce64301dd Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 22 Apr 2025 11:14:41 +0530 Subject: [PATCH 0371/1298] [Random Reader Refactoring] File cache reader destroy method implementation (#3217) * implement destroy method * rebase * small fix * add one more unit test * small fix * small fix --- internal/gcsx/file_cache_reader.go | 11 +++++++++++ internal/gcsx/file_cache_reader_test.go | 23 +++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index 870f75b655..a61c044fef 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -209,3 +209,14 @@ func captureFileCacheMetrics(ctx context.Context, metricHandle common.MetricHand metricHandle.FileCacheReadBytesCount(ctx, int64(readDataSize), []common.MetricAttr{{Key: common.ReadType, Value: readType}}) metricHandle.FileCacheReadLatency(ctx, float64(readLatency.Microseconds()), []common.MetricAttr{{Key: common.CacheHit, Value: strconv.FormatBool(cacheHit)}}) } + +func (fc *FileCacheReader) Destroy() { + if fc.fileCacheHandle != nil { + logger.Tracef("Closing cacheHandle:%p for object: %s:/%s", fc.fileCacheHandle, fc.bucket.Name(), fc.object.Name) + err := fc.fileCacheHandle.Close() + if err != nil { + logger.Warnf("fc.Destroy(): while closing cacheFileHandle: %v", err) + } + fc.fileCacheHandle = nil + } +} diff --git a/internal/gcsx/file_cache_reader_test.go b/internal/gcsx/file_cache_reader_test.go index a8632858da..4ba4501b0d 100644 --- a/internal/gcsx/file_cache_reader_test.go +++ b/internal/gcsx/file_cache_reader_test.go @@ -476,3 +476,26 @@ func (t *fileCacheReaderTest) Test_ReadAt_NegativeOffsetShouldThrowError() { assert.Error(t.T(), err) assert.Zero(t.T(), readerResponse.Size) } + +func (t *fileCacheReaderTest) Test_Destroy_NonNilCacheHandle() { + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockBucket.On("Name").Return("test-bucket") + readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + assert.NoError(t.T(), err) + assert.Equal(t.T(), readerResponse.DataBuf, testContent) + assert.NotNil(t.T(), t.reader.fileCacheHandle) + + t.reader.Destroy() + + assert.Nil(t.T(), t.reader.fileCacheHandle) +} + +func (t *fileCacheReaderTest) Test_Destroy_NilCacheHandle() { + t.reader.fileCacheHandler = nil + + t.reader.Destroy() + + assert.Nil(nil, t.reader.fileCacheHandle) +} From ea6ba309503fdfa5be3e93286772d36fbb32fed2 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 22 Apr 2025 07:04:34 +0000 Subject: [PATCH 0372/1298] Changes in mount_timeout package for ZB E2E (#3211) * add tests for ZB for mount_timeout package * fixing build errors * add a helpful debug log * fix some more build issues * fix build and run failures * add/update comments * fix some cosmetic issues * cosmetic cleanup * fix lint issues * fetch exact vm zone * code cleanup * rename same/cross region to same/cross zone * fix lint errors --- .../gcsfuse_mount_timeout_test.go | 146 ++++++++++++++---- .../mount_timeout/mount_timeout_test.go | 46 ++++-- 2 files changed, 150 insertions(+), 42 deletions(-) diff --git a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go index b05a917a1e..cff2461812 100644 --- a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go +++ b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go @@ -35,34 +35,64 @@ const ( ) func TestMountTimeout(t *testing.T) { - if os.Getenv("TEST_ENV") == testEnvGCEUSCentral { - // Set strict region based timeout values if testing environment is GCE VM in us-central. - timeout := RegionWiseTimeouts{ - multiRegionUSTimeout: multiRegionUSExpectedMountTime, - multiRegionAsiaTimeout: multiRegionAsiaExpectedMountTime, - dualRegionUSTimeout: dualRegionUSExpectedMountTime, - dualRegionAsiaTimeout: dualRegionAsiaExpectedMountTime, - singleRegionUSCentralTimeout: singleRegionUSCentralExpectedMountTime, - singleRegionAsiaEastTimeout: singleRegionAsiaEastExpectedMountTime, + if setup.IsZonalBucketRun() { + zone := os.Getenv("TEST_ENV") + switch zone { + case testEnvZoneGCEUSCentral1A: + // Set strict zone-based config values. + config := ZBMountTimeoutTestCaseConfig{ + sameZoneZonalBucket: zonalUSCentral1ABucket, + crossZoneZonalBucket: zonalUSWest4ABucket, + sameZoneMountTimeout: zonalSameZoneExpectedMountTime, + crossZoneMountTimeout: zonalCrossZoneExpectedMountTime, + } + t.Log("Running tests with region based timeout values since the GCE VM is located in us-central...\n") + suite.Run(t, &ZBMountTimeoutTest{config: config}) + case testEnvZoneGCEUSWEST4A: + // Set strict zone-based config values. + config := ZBMountTimeoutTestCaseConfig{ + sameZoneZonalBucket: zonalUSWest4ABucket, + crossZoneZonalBucket: zonalUSCentral1ABucket, + sameZoneMountTimeout: relaxedExpectedMountTime, + crossZoneMountTimeout: relaxedExpectedMountTime, + } + t.Logf("Running tests with relaxed timeout of %f sec for all scenarios since the GCE VM is not located in us-central...\n", relaxedExpectedMountTime.Seconds()) + suite.Run(t, &ZBMountTimeoutTest{config: config}) + default: + // Skip the tests if the testing environment is not GCE VM. + t.Logf("Skipping tests since the testing environment (%q) is not a ZB supported region...\n", zone) + t.Skip() } - t.Log("Running tests with region based timeout values since the GCE VM is located in us-central...\n") - suite.Run(t, &MountTimeoutTest{timeouts: timeout}) - } else if os.Getenv("TEST_ENV") == testEnvGCENonUSCentral { - // Set common relaxed timeout values if testing environment is GCE VM not in us-central. - timeout := RegionWiseTimeouts{ - multiRegionUSTimeout: relaxedExpectedMountTime, - multiRegionAsiaTimeout: relaxedExpectedMountTime, - dualRegionUSTimeout: relaxedExpectedMountTime, - dualRegionAsiaTimeout: relaxedExpectedMountTime, - singleRegionUSCentralTimeout: relaxedExpectedMountTime, - singleRegionAsiaEastTimeout: relaxedExpectedMountTime, - } - t.Logf("Running tests with relaxed timeout of %f sec for all scenarios since the GCE VM is not located in us-central...\n", relaxedExpectedMountTime.Seconds()) - suite.Run(t, &MountTimeoutTest{timeouts: timeout}) } else { - // Skip the tests if the testing environment is not GCE VM. - t.Log("Skipping tests since the testing environment is not GCE VM...\n") - t.Skip() + if os.Getenv("TEST_ENV") == testEnvGCEUSCentral { + // Set strict region based timeout values if testing environment is GCE VM in us-central. + timeout := RegionWiseTimeouts{ + multiRegionUSTimeout: multiRegionUSExpectedMountTime, + multiRegionAsiaTimeout: multiRegionAsiaExpectedMountTime, + dualRegionUSTimeout: dualRegionUSExpectedMountTime, + dualRegionAsiaTimeout: dualRegionAsiaExpectedMountTime, + singleRegionUSCentralTimeout: singleRegionUSCentralExpectedMountTime, + singleRegionAsiaEastTimeout: singleRegionAsiaEastExpectedMountTime, + } + t.Log("Running tests with region based timeout values since the GCE VM is located in us-central...\n") + suite.Run(t, &NonZBMountTimeoutTest{timeouts: timeout}) + } else if os.Getenv("TEST_ENV") == testEnvGCENonUSCentral { + // Set common relaxed timeout values if testing environment is GCE VM not in us-central. + timeout := RegionWiseTimeouts{ + multiRegionUSTimeout: relaxedExpectedMountTime, + multiRegionAsiaTimeout: relaxedExpectedMountTime, + dualRegionUSTimeout: relaxedExpectedMountTime, + dualRegionAsiaTimeout: relaxedExpectedMountTime, + singleRegionUSCentralTimeout: relaxedExpectedMountTime, + singleRegionAsiaEastTimeout: relaxedExpectedMountTime, + } + t.Logf("Running tests with relaxed timeout of %f sec for all scenarios since the GCE VM is not located in us-central...\n", relaxedExpectedMountTime.Seconds()) + suite.Run(t, &NonZBMountTimeoutTest{timeouts: timeout}) + } else { + // Skip the tests if the testing environment is not GCE VM. + t.Log("Skipping tests since the testing environment is not GCE VM...\n") + t.Skip() + } } } @@ -75,6 +105,13 @@ type RegionWiseTimeouts struct { singleRegionAsiaEastTimeout time.Duration } +type ZBMountTimeoutTestCaseConfig struct { + sameZoneZonalBucket string + crossZoneZonalBucket string + sameZoneMountTimeout time.Duration + crossZoneMountTimeout time.Duration +} + type MountTimeoutTest struct { suite.Suite // Path to the gcsfuse binary. @@ -82,10 +119,19 @@ type MountTimeoutTest struct { // A temporary directory into which a file system may be mounted. Removed in // TearDown. - dir string + dir string +} + +type NonZBMountTimeoutTest struct { + MountTimeoutTest timeouts RegionWiseTimeouts } +type ZBMountTimeoutTest struct { + MountTimeoutTest + config ZBMountTimeoutTestCaseConfig +} + func (testSuite *MountTimeoutTest) SetupTest() { var err error testSuite.gcsfusePath = path.Join(gBuildDir, "bin/gcsfuse") @@ -99,6 +145,19 @@ func (testSuite *MountTimeoutTest) TearDownTest() { assert.NoError(testSuite.T(), err) } +func (testSuite *NonZBMountTimeoutTest) SetupTest() { + testSuite.MountTimeoutTest.SetupTest() +} +func (testSuite *NonZBMountTimeoutTest) TearDownTest() { + testSuite.MountTimeoutTest.TearDownTest() +} +func (testSuite *ZBMountTimeoutTest) SetupTest() { + testSuite.MountTimeoutTest.SetupTest() +} +func (testSuite *ZBMountTimeoutTest) TearDownTest() { + testSuite.MountTimeoutTest.TearDownTest() +} + // mountOrTimeout mounts the bucket with the given client protocol. If the time taken // exceeds the expected for the particular test case , an error is thrown and test will fail. func (testSuite *MountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientProtocol string, expectedMountTime time.Duration) error { @@ -127,7 +186,14 @@ func (testSuite *MountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientPr return nil } -func (testSuite *MountTimeoutTest) TestMountMultiRegionUSBucketWithTimeout() { +func (testSuite *NonZBMountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientProtocol string, expectedMountTime time.Duration) error { + return testSuite.MountTimeoutTest.mountOrTimeout(bucketName, mountDir, clientProtocol, expectedMountTime) +} +func (testSuite *ZBMountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientProtocol string, expectedMountTime time.Duration) error { + return testSuite.MountTimeoutTest.mountOrTimeout(bucketName, mountDir, clientProtocol, expectedMountTime) +} + +func (testSuite *NonZBMountTimeoutTest) TestMountMultiRegionUSBucketWithTimeout() { testCases := []struct { name string clientProtocol cfg.Protocol @@ -153,7 +219,7 @@ func (testSuite *MountTimeoutTest) TestMountMultiRegionUSBucketWithTimeout() { } } -func (testSuite *MountTimeoutTest) TestMountMultiRegionAsiaBucketWithTimeout() { +func (testSuite *NonZBMountTimeoutTest) TestMountMultiRegionAsiaBucketWithTimeout() { testCases := []struct { name string clientProtocol cfg.Protocol @@ -179,7 +245,7 @@ func (testSuite *MountTimeoutTest) TestMountMultiRegionAsiaBucketWithTimeout() { } } -func (testSuite *MountTimeoutTest) TestMountDualRegionUSBucketWithTimeout() { +func (testSuite *NonZBMountTimeoutTest) TestMountDualRegionUSBucketWithTimeout() { testCases := []struct { name string clientProtocol cfg.Protocol @@ -205,7 +271,7 @@ func (testSuite *MountTimeoutTest) TestMountDualRegionUSBucketWithTimeout() { } } -func (testSuite *MountTimeoutTest) TestMountDualRegionAsiaBucketWithTimeout() { +func (testSuite *NonZBMountTimeoutTest) TestMountDualRegionAsiaBucketWithTimeout() { testCases := []struct { name string clientProtocol cfg.Protocol @@ -231,7 +297,7 @@ func (testSuite *MountTimeoutTest) TestMountDualRegionAsiaBucketWithTimeout() { } } -func (testSuite *MountTimeoutTest) TestMountSingleRegionUSBucketWithTimeout() { +func (testSuite *NonZBMountTimeoutTest) TestMountSingleRegionUSBucketWithTimeout() { testCases := []struct { name string clientProtocol cfg.Protocol @@ -257,7 +323,7 @@ func (testSuite *MountTimeoutTest) TestMountSingleRegionUSBucketWithTimeout() { } } -func (testSuite *MountTimeoutTest) TestMountSingleRegionAsiaBucketWithTimeout() { +func (testSuite *NonZBMountTimeoutTest) TestMountSingleRegionAsiaBucketWithTimeout() { testCases := []struct { name string clientProtocol cfg.Protocol @@ -282,3 +348,17 @@ func (testSuite *MountTimeoutTest) TestMountSingleRegionAsiaBucketWithTimeout() assert.NoError(testSuite.T(), err) } } + +func (testSuite *ZBMountTimeoutTest) TestMountSameZoneZonalBucketWithTimeout() { + setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, "SameZoneZonalBucket")) + + err := testSuite.mountOrTimeout(testSuite.config.sameZoneZonalBucket, testSuite.dir, cfg.GRPC, testSuite.config.sameZoneMountTimeout) + assert.NoError(testSuite.T(), err) +} + +func (testSuite *ZBMountTimeoutTest) TestMountCrossZoneZonalBucketWithTimeout() { + setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, "CrossZoneZonalBucket")) + + err := testSuite.mountOrTimeout(testSuite.config.crossZoneZonalBucket, testSuite.dir, cfg.GRPC, testSuite.config.crossZoneMountTimeout) + assert.NoError(testSuite.T(), err) +} diff --git a/tools/integration_tests/mount_timeout/mount_timeout_test.go b/tools/integration_tests/mount_timeout/mount_timeout_test.go index 727e37125a..8b5dae6684 100644 --- a/tools/integration_tests/mount_timeout/mount_timeout_test.go +++ b/tools/integration_tests/mount_timeout/mount_timeout_test.go @@ -40,6 +40,7 @@ var gBuildDir string var gFusermountPath string const ( + // Constants specific to non-ZB E2E runs. testEnvGCEUSCentral string = "gce-us-central" testEnvGCENonUSCentral string = "gce-non-us-central" testEnvNonGCE string = "non-gce" @@ -55,8 +56,17 @@ const ( dualRegionUSExpectedMountTime time.Duration = 4500 * time.Millisecond dualRegionAsiaExpectedMountTime time.Duration = 6250 * time.Millisecond singleRegionUSCentralExpectedMountTime time.Duration = 2500 * time.Millisecond - relaxedExpectedMountTime time.Duration = 8000 * time.Millisecond - logfilePathPrefix string = "/tmp/gcsfuse_mount_timeout_" + // Constants specific to ZB E2E runs. + testEnvZoneGCEUSCentral1A string = "gce-zone-us-central1-a" + testEnvZoneGCEUSWEST4A string = "gce-zone-us-west4a-a" + testEnvZoneGCEOther string = "gce-zone-other" + zonalUSCentral1ABucket string = "mount_timeout_test_bucket_zb_usc1a" + zonalUSWest4ABucket string = "mount_timeout_test_bucket_zb_usw4a" + zonalSameZoneExpectedMountTime time.Duration = 2500 * time.Millisecond + zonalCrossZoneExpectedMountTime time.Duration = 5000 * time.Millisecond + // Commont constants. + relaxedExpectedMountTime time.Duration = 8000 * time.Millisecond + logfilePathPrefix string = "/tmp/gcsfuse_mount_timeout_" ) // findTestExecutionEnvironment determines the environment in which the tests are running. @@ -76,16 +86,34 @@ func findTestExecutionEnvironment(ctx context.Context) string { } attrs := detectedAttrs.Set() if v, exists := attrs.Value("gcp.gce.instance.hostname"); exists && strings.Contains(strings.ToLower(v.AsString()), "cloudtop-prod") { - return testEnvNonGCE - } - if v, exists := attrs.Value("cloud.region"); exists { - if strings.Contains(strings.ToLower(v.AsString()), "us-central") { - return testEnvGCEUSCentral + if !setup.IsZonalBucketRun() { + return testEnvNonGCE } else { - return testEnvGCENonUSCentral + return testEnvZoneGCEOther + } + } + if !setup.IsZonalBucketRun() { + if v, exists := attrs.Value("cloud.region"); exists { + if strings.Contains(strings.ToLower(v.AsString()), "us-central") { + return testEnvGCEUSCentral + } else { + return testEnvGCENonUSCentral + } + } + return testEnvNonGCE + } else { + if v, exists := attrs.Value("cloud.availability_zone"); exists { + switch strings.ToLower(v.AsString()) { + case "us-central1-a": + return testEnvZoneGCEUSCentral1A + case "us-west4-a": + return testEnvZoneGCEUSWEST4A + default: + return testEnvZoneGCEOther + } } + return testEnvZoneGCEOther } - return testEnvNonGCE } func TestMain(m *testing.M) { From 664171e750089f91a119ea5d679b4b6bd77ea99b Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 22 Apr 2025 12:52:55 +0530 Subject: [PATCH 0373/1298] Save gcsfuse logs for streaming writes package for easier debugging. (#3218) * save gcsfuse logs for streaming writes package * Trigger CI build --- tools/integration_tests/streaming_writes/buffer_size_test.go | 1 + tools/integration_tests/streaming_writes/default_mount_test.go | 1 + tools/integration_tests/util/setup/setup.go | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/integration_tests/streaming_writes/buffer_size_test.go b/tools/integration_tests/streaming_writes/buffer_size_test.go index db204b45cb..7a6d1913c6 100644 --- a/tools/integration_tests/streaming_writes/buffer_size_test.go +++ b/tools/integration_tests/streaming_writes/buffer_size_test.go @@ -58,6 +58,7 @@ func TestWritesWithDifferentConfig(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { setup.MountGCSFuseWithGivenMountFunc(tc.flags, mountFunc) + defer setup.SaveGCSFuseLogFileInCaseOfFailure(t) defer setup.UnmountGCSFuse(rootDir) testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. diff --git a/tools/integration_tests/streaming_writes/default_mount_test.go b/tools/integration_tests/streaming_writes/default_mount_test.go index 5aad67cf91..35a6e68726 100644 --- a/tools/integration_tests/streaming_writes/default_mount_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_test.go @@ -46,6 +46,7 @@ func (t *defaultMountCommonTest) SetupSuite() { func (t *defaultMountCommonTest) TearDownSuite() { setup.UnmountGCSFuse(rootDir) + setup.SaveGCSFuseLogFileInCaseOfFailure(t.T()) } func (t *defaultMountCommonTest) validateReadCall(filePath string) { diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 301d9968cb..34456a9df0 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -259,7 +259,7 @@ func SaveLogFileAsArtifact(logFile, artifactName string) { // KOKORO artifacts directory if test ran on KOKORO // or saves to TestDir if test ran on local. func SaveGCSFuseLogFileInCaseOfFailure(tb testing.TB) { - if !tb.Failed() { + if !tb.Failed() || MountedDirectory() != "" { return } SaveLogFileAsArtifact(LogFile(), GCSFuseLogFilePrefix+strings.ReplaceAll(tb.Name(), "/", "_")+GenerateRandomString(5)) From 6101f30e2c92f829d186ba1b5ae01169e44748ed Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:12:31 +0530 Subject: [PATCH 0374/1298] Update documentation for custom-endpoint flag (#3220) * update descriptio of flag * formating * small fix --- cfg/config.go | 2 +- cfg/params.yaml | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 9c8b9a2d3a..b07b63f202 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -277,7 +277,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("create-empty-file", "", false, "For a new file, it creates an empty file in Cloud Storage bucket as a hold.") - flagSet.StringP("custom-endpoint", "", "", "Specifies an alternative custom endpoint for fetching data. Should only be used for testing. The custom endpoint must support the equivalent resources and operations as the GCS JSON endpoint, https://storage.googleapis.com/storage/v1. If a custom endpoint is not specified, GCSFuse uses the global GCS JSON API endpoint, https://storage.googleapis.com/storage/v1.") + flagSet.StringP("custom-endpoint", "", "", "Specifies an alternative custom endpoint for fetching data. The custom endpoint must support the equivalent resources and operations as the GCS JSON endpoint, https://storage.googleapis.com/storage/v1. If a custom endpoint is not specified, GCSFuse uses the global GCS JSON API endpoint, https://storage.googleapis.com/storage/v1.") flagSet.BoolP("debug_fs", "", false, "This flag is unused.") diff --git a/cfg/params.yaml b/cfg/params.yaml index 9aa5872472..caf08bbfaf 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -293,11 +293,10 @@ flag-name: "custom-endpoint" type: "string" usage: >- - Specifies an alternative custom endpoint for fetching data. Should only be - used for testing. The custom endpoint must support the equivalent resources - and operations as the GCS JSON endpoint, + Specifies an alternative custom endpoint for fetching data. The custom endpoint + must support the equivalent resources and operations as the GCS JSON endpoint, https://storage.googleapis.com/storage/v1. If a custom endpoint is not - specified, GCSFuse uses the global GCS JSON API endpoint, + specified, GCSFuse uses the global GCS JSON API endpoint, https://storage.googleapis.com/storage/v1. default: "" From 9576679c5d3f4e356b17f00649bf64e7ce995f39 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 22 Apr 2025 15:27:21 +0530 Subject: [PATCH 0375/1298] Update troubleshooting doc for git clone issue (#3224) * git clone is slow * Update docs/troubleshooting.md Co-authored-by: Kislay Kishore * Update docs/troubleshooting.md Co-authored-by: Kislay Kishore * include reason --------- Co-authored-by: Kislay Kishore --- docs/troubleshooting.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index db19a79d8d..96928b261e 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -168,3 +168,15 @@ To allow users other than the mounting user to access the bucket, use the `-o al Use the `--uid` and `--gid` flags to specify the correct user and group IDs for access. Please note that GCSFuse does not support using `chmod` or similar commands to manage file access. For more detailed information, refer to the [Permissions and Ownership](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/semantics.md#permissions-and-ownership). + + +### Cloning GitHub repository inside mounted bucket is extremely slow +Ensure your GCS bucket mount configuration does not include `o=sync` or `o=dirsync`. \ +During a Git clone, Git doesn’t just fetch object data, it builds out the entire .git directory structure, including initializing config, refs, hooks, and other internals. As part of this setup: + +- Git repeatedly writes and updates .git/config, especially when setting remotes, branches, and defaults. +- Each update uses Git’s lock-write-rename-delete pattern to ensure consistency. + +While using the mount configuration `o=sync,o=dirsync`, all modifications to the config file incur a network call due to enforced synchronous writes, resulting in +performance bottleneck. \ +**Note** : There is no impact of disabling this mount configuration on the user workflow, since we avoid flushing data to GCS on sync( happens multiple times during the course of a Git clone ) , but only on close(), thus ensuring data persistence. \ No newline at end of file From 97c230167bfde45cdb893e389de393dbe082bd65 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 22 Apr 2025 10:22:18 +0000 Subject: [PATCH 0376/1298] Randomize file/dir names in tools/integration_tests/operations/parallel_dirops_test (#3226) * wip * fix build error * cosmetic changes * randomize testDir name, and add new test * remove the just-added new test --- .../operations/parallel_dirops_test.go | 149 +++++++++++------- 1 file changed, 95 insertions(+), 54 deletions(-) diff --git a/tools/integration_tests/operations/parallel_dirops_test.go b/tools/integration_tests/operations/parallel_dirops_test.go index a94b0cd83c..609a44b09e 100644 --- a/tools/integration_tests/operations/parallel_dirops_test.go +++ b/tools/integration_tests/operations/parallel_dirops_test.go @@ -30,41 +30,74 @@ import ( "github.com/stretchr/testify/assert" ) -// createDirectoryStructureForParallelDiropsTest creates the following files and +type testDirStrucure struct { + testDir string + explicitDir1Name string + file1InExplicitDir1Name string + file2InExplicitDir1Name string + explicitDir2Name string + file1InExplicitDir2Name string + file1Name string + file2Name string +} + +// createDirStructure creates the following files and // directory structure. // bucket // -// file1.txt -// file2.txt -// explicitDir1/file1.txt -// explicitDir1/file2.txt -// explicitDir2/file1.txt +// file1Name +// file2Name +// explicitDir1Name/file1InExplicitDir1Name +// explicitDir1Name/file2InExplicitDir1Name +// explicitDir2Name/file1InExplicitDir2Name // // Also returns the path to test directory. -func createDirectoryStructureForParallelDiropsTest(t *testing.T) string { - testDir := setup.SetupTestDirectory(DirForOperationTests) - setup.CleanUpDir(testDir) +func createDirStructure(t *testing.T) testDirStrucure { + var tds testDirStrucure + tds.testDir = setup.SetupTestDirectory(DirForOperationTests + "-" + setup.GenerateRandomString(5)) // Create explicitDir1 structure - explicitDir1 := path.Join(testDir, "explicitDir1") + tds.explicitDir1Name = "explicitDir1-" + setup.GenerateRandomString(5) + explicitDir1 := path.Join(tds.testDir, tds.explicitDir1Name) operations.CreateDirectory(explicitDir1, t) - filePath1 := path.Join(explicitDir1, "file1.txt") + tds.file1InExplicitDir1Name = "file1-" + setup.GenerateRandomString(5) + ".txt" + filePath1 := path.Join(explicitDir1, tds.file1InExplicitDir1Name) operations.CreateFileOfSize(5, filePath1, t) - filePath2 := path.Join(explicitDir1, "file2.txt") + tds.file2InExplicitDir1Name = "file2-" + setup.GenerateRandomString(5) + ".txt" + filePath2 := path.Join(explicitDir1, tds.file2InExplicitDir1Name) operations.CreateFileOfSize(10, filePath2, t) // Create explicitDir2 structure - explicitDir2 := path.Join(testDir, "explicitDir2") + tds.explicitDir2Name = "explicitDir2-" + setup.GenerateRandomString(5) + explicitDir2 := path.Join(tds.testDir, tds.explicitDir2Name) operations.CreateDirectory(explicitDir2, t) - filePath1 = path.Join(explicitDir2, "file1.txt") + tds.file1InExplicitDir2Name = "file1-" + setup.GenerateRandomString(5) + ".txt" + filePath1 = path.Join(explicitDir2, tds.file1InExplicitDir2Name) operations.CreateFileOfSize(11, filePath1, t) - filePath1 = path.Join(testDir, "file1.txt") + tds.file1Name = "file1-" + setup.GenerateRandomString(5) + ".txt" + filePath1 = path.Join(tds.testDir, tds.file1Name) operations.CreateFileOfSize(5, filePath1, t) - filePath2 = path.Join(testDir, "file2.txt") + tds.file2Name = "file2-" + setup.GenerateRandomString(5) + ".txt" + filePath2 = path.Join(tds.testDir, tds.file2Name) operations.CreateFileOfSize(3, filePath2, t) - return testDir + return tds +} + +// deleteDirStructure deletes the following files and +// directory structure. +// bucket +// +// file1Name +// file2Name +// explicitDir1Name/file1InExplicitDir1Name +// explicitDir1Name/file2InExplicitDir1Name +// explicitDir2Name/file1InExplicitDir2Name +// +// Also returns the path to test directory. +func deleteDirStructure(tds testDirStrucure) { + setup.CleanUpDir(tds.testDir) } // lookUpFileStat performs a lookup for the given file path and returns the FileInfo and error. @@ -77,12 +110,13 @@ func lookUpFileStat(wg *sync.WaitGroup, filePath string, result *os.FileInfo, er func TestParallelLookUpsForSameFile(t *testing.T) { // Create directory structure for testing. - testDir := createDirectoryStructureForParallelDiropsTest(t) + tds := createDirStructure(t) + defer deleteDirStructure(tds) var stat1, stat2 os.FileInfo var err1, err2 error // Parallel lookups of file just under mount. - filePath := path.Join(testDir, "file1.txt") + filePath := path.Join(tds.testDir, tds.file1Name) wg := sync.WaitGroup{} wg.Add(2) go lookUpFileStat(&wg, filePath, &stat1, &err1) @@ -98,7 +132,7 @@ func TestParallelLookUpsForSameFile(t *testing.T) { assert.Contains(t, filePath, stat2.Name()) // Parallel lookups of file under a directory in mount. - filePath = path.Join(testDir, "explicitDir1/file2.txt") + filePath = path.Join(tds.testDir, tds.explicitDir1Name, tds.file2InExplicitDir1Name) wg.Add(2) go lookUpFileStat(&wg, filePath, &stat1, &err1) go lookUpFileStat(&wg, filePath, &stat2, &err2) @@ -115,7 +149,8 @@ func TestParallelLookUpsForSameFile(t *testing.T) { func TestParallelReadDirs(t *testing.T) { // Create directory structure for testing. - testDir := createDirectoryStructureForParallelDiropsTest(t) + tds := createDirStructure(t) + defer deleteDirStructure(tds) readDirFunc := func(wg *sync.WaitGroup, dirPath string, dirEntries *[]os.DirEntry, err *error) { defer wg.Done() *dirEntries, *err = os.ReadDir(dirPath) @@ -124,7 +159,7 @@ func TestParallelReadDirs(t *testing.T) { var err1, err2 error // Parallel readDirs of explicit dir under mount. - dirPath := path.Join(testDir, "explicitDir1") + dirPath := path.Join(tds.testDir, tds.explicitDir1Name) wg := sync.WaitGroup{} wg.Add(2) go readDirFunc(&wg, dirPath, &dirEntries1, &err1) @@ -137,14 +172,14 @@ func TestParallelReadDirs(t *testing.T) { assert.NoError(t, err2) assert.Equal(t, 2, len(dirEntries1)) assert.Equal(t, 2, len(dirEntries2)) - assert.Contains(t, "file1.txt", dirEntries1[0].Name()) - assert.Contains(t, "file2.txt", dirEntries1[1].Name()) - assert.Contains(t, "file1.txt", dirEntries2[0].Name()) - assert.Contains(t, "file2.txt", dirEntries2[1].Name()) + assert.Contains(t, tds.file1InExplicitDir1Name, dirEntries1[0].Name()) + assert.Contains(t, tds.file2InExplicitDir1Name, dirEntries1[1].Name()) + assert.Contains(t, tds.file1InExplicitDir1Name, dirEntries2[0].Name()) + assert.Contains(t, tds.file2InExplicitDir1Name, dirEntries2[1].Name()) // Parallel readDirs of a directory and its parent directory. - dirPath = path.Join(testDir, "explicitDir1") - parentDirPath := testDir + dirPath = path.Join(tds.testDir, tds.explicitDir1Name) + parentDirPath := tds.testDir wg = sync.WaitGroup{} wg.Add(2) go readDirFunc(&wg, dirPath, &dirEntries1, &err1) @@ -156,17 +191,18 @@ func TestParallelReadDirs(t *testing.T) { assert.NoError(t, err2) assert.Equal(t, 2, len(dirEntries1)) assert.Equal(t, 4, len(dirEntries2)) - assert.Contains(t, "file1.txt", dirEntries1[0].Name()) - assert.Contains(t, "file2.txt", dirEntries1[1].Name()) - assert.Contains(t, "explicitDir1", dirEntries2[0].Name()) - assert.Contains(t, "explicitDir2", dirEntries2[1].Name()) - assert.Contains(t, "file1.txt", dirEntries2[2].Name()) - assert.Contains(t, "file2.txt", dirEntries2[3].Name()) + assert.Contains(t, tds.file1InExplicitDir1Name, dirEntries1[0].Name()) + assert.Contains(t, tds.file2InExplicitDir1Name, dirEntries1[1].Name()) + assert.Contains(t, tds.explicitDir1Name, dirEntries2[0].Name()) + assert.Contains(t, tds.explicitDir2Name, dirEntries2[1].Name()) + assert.Contains(t, tds.file1Name, dirEntries2[2].Name()) + assert.Contains(t, tds.file2Name, dirEntries2[3].Name()) } func TestParallelLookUpAndDeleteSameDir(t *testing.T) { // Create directory structure for testing. - testDir := createDirectoryStructureForParallelDiropsTest(t) + tds := createDirStructure(t) + defer deleteDirStructure(tds) deleteFunc := func(wg *sync.WaitGroup, dirPath string, err *error) { defer wg.Done() *err = os.RemoveAll(dirPath) @@ -175,7 +211,7 @@ func TestParallelLookUpAndDeleteSameDir(t *testing.T) { var lookUpErr, deleteErr error // Parallel lookup and deletion of explicit dir under mount. - dirPath := path.Join(testDir, "explicitDir1") + dirPath := path.Join(tds.testDir, tds.explicitDir1Name) wg := sync.WaitGroup{} wg.Add(2) go lookUpFileStat(&wg, dirPath, &statInfo, &lookUpErr) @@ -188,7 +224,7 @@ func TestParallelLookUpAndDeleteSameDir(t *testing.T) { // Assert either dir is looked up first or deleted first if lookUpErr == nil { assert.NotNil(t, statInfo, "statInfo should not be nil when lookUpErr is nil") - assert.Contains(t, statInfo.Name(), "explicitDir1") + assert.Contains(t, statInfo.Name(), tds.explicitDir1Name) assert.True(t, statInfo.IsDir(), "The created path should be a directory") } else { assert.True(t, os.IsNotExist(lookUpErr)) @@ -197,13 +233,14 @@ func TestParallelLookUpAndDeleteSameDir(t *testing.T) { func TestParallelLookUpsForDifferentFiles(t *testing.T) { // Create directory structure for testing. - testDir := createDirectoryStructureForParallelDiropsTest(t) + tds := createDirStructure(t) + defer deleteDirStructure(tds) var stat1, stat2 os.FileInfo var err1, err2 error // Parallel lookups of two files just under mount. - filePath1 := path.Join(testDir, "file1.txt") - filePath2 := path.Join(testDir, "file2.txt") + filePath1 := path.Join(tds.testDir, tds.file1Name) + filePath2 := path.Join(tds.testDir, tds.file2Name) wg := sync.WaitGroup{} wg.Add(2) go lookUpFileStat(&wg, filePath1, &stat1, &err1) @@ -220,8 +257,8 @@ func TestParallelLookUpsForDifferentFiles(t *testing.T) { assert.Contains(t, filePath2, stat2.Name()) // Parallel lookups of two files under a directory in mount. - filePath1 = path.Join(testDir, "explicitDir1", "file1.txt") - filePath2 = path.Join(testDir, "explicitDir1", "file2.txt") + filePath1 = path.Join(tds.testDir, tds.explicitDir1Name, tds.file1InExplicitDir1Name) + filePath2 = path.Join(tds.testDir, tds.explicitDir1Name, tds.file2InExplicitDir1Name) wg = sync.WaitGroup{} wg.Add(2) go lookUpFileStat(&wg, filePath1, &stat1, &err1) @@ -239,7 +276,8 @@ func TestParallelLookUpsForDifferentFiles(t *testing.T) { func TestParallelReadDirAndMkdirInsideSameDir(t *testing.T) { // Create directory structure for testing. - testDir := createDirectoryStructureForParallelDiropsTest(t) + tds := createDirStructure(t) + defer deleteDirStructure(tds) readDirFunc := func(wg *sync.WaitGroup, dirPath string, dirEntries *[]os.DirEntry, err *error) { defer wg.Done() *err = filepath.WalkDir(dirPath, func(path string, d fs.DirEntry, err error) error { @@ -255,10 +293,10 @@ func TestParallelReadDirAndMkdirInsideSameDir(t *testing.T) { var readDirErr, mkdirErr error // Parallel readDirs and mkdir inside the same directory. - newDirPath := path.Join(testDir, "newDir") + newDirPath := path.Join(tds.testDir, "newDir") wg := sync.WaitGroup{} wg.Add(2) - go readDirFunc(&wg, testDir, &dirEntries, &readDirErr) + go readDirFunc(&wg, tds.testDir, &dirEntries, &readDirErr) go mkdirFunc(&wg, newDirPath, &mkdirErr) wg.Wait() @@ -278,7 +316,8 @@ func TestParallelReadDirAndMkdirInsideSameDir(t *testing.T) { func TestParallelLookUpAndDeleteSameFile(t *testing.T) { // Create directory structure for testing. - testDir := createDirectoryStructureForParallelDiropsTest(t) + tds := createDirStructure(t) + defer deleteDirStructure(tds) deleteFileFunc := func(wg *sync.WaitGroup, filePath string, err *error) { defer wg.Done() *err = os.Remove(filePath) @@ -287,7 +326,7 @@ func TestParallelLookUpAndDeleteSameFile(t *testing.T) { var lookUpErr, deleteErr error // Parallel lookup and deletion of a file. - filePath := path.Join(testDir, "explicitDir1", "file1.txt") + filePath := path.Join(tds.testDir, tds.explicitDir1Name, tds.file1InExplicitDir1Name) wg := sync.WaitGroup{} wg.Add(2) @@ -303,7 +342,7 @@ func TestParallelLookUpAndDeleteSameFile(t *testing.T) { if lookUpErr == nil { assert.NotNil(t, fileInfo, "fileInfo should not be nil when lookUpErr is nil") assert.Equal(t, int64(5), fileInfo.Size()) - assert.Contains(t, fileInfo.Name(), "file1.txt") + assert.Contains(t, fileInfo.Name(), tds.file1InExplicitDir1Name) assert.False(t, fileInfo.IsDir(), "The created path should not be a directory") } else { assert.True(t, os.IsNotExist(lookUpErr)) @@ -312,7 +351,8 @@ func TestParallelLookUpAndDeleteSameFile(t *testing.T) { func TestParallelLookUpAndRenameSameFile(t *testing.T) { // Create directory structure for testing. - testDir := createDirectoryStructureForParallelDiropsTest(t) + tds := createDirStructure(t) + defer deleteDirStructure(tds) renameFunc := func(wg *sync.WaitGroup, oldFilePath string, newFilePath string, err *error) { defer wg.Done() *err = os.Rename(oldFilePath, newFilePath) @@ -321,8 +361,8 @@ func TestParallelLookUpAndRenameSameFile(t *testing.T) { var lookUpErr, renameErr error // Parallel lookup and rename of a file. - filePath := path.Join(testDir, "explicitDir1", "file1.txt") - newFilePath := path.Join(testDir, "newFile.txt") + filePath := path.Join(tds.testDir, tds.explicitDir1Name, tds.file1InExplicitDir1Name) + newFilePath := path.Join(tds.testDir, "newFile.txt") wg := sync.WaitGroup{} wg.Add(2) go lookUpFileStat(&wg, filePath, &fileInfo, &lookUpErr) @@ -340,7 +380,7 @@ func TestParallelLookUpAndRenameSameFile(t *testing.T) { if lookUpErr == nil { assert.NotNil(t, fileInfo, "fileInfo should not be nil when lookUpErr is nil") assert.Equal(t, int64(5), fileInfo.Size()) - assert.Contains(t, fileInfo.Name(), "file1.txt") + assert.Contains(t, fileInfo.Name(), tds.file1InExplicitDir1Name) assert.False(t, fileInfo.IsDir(), "The created path should not be a directory") } else { assert.True(t, os.IsNotExist(lookUpErr)) @@ -349,7 +389,8 @@ func TestParallelLookUpAndRenameSameFile(t *testing.T) { func TestParallelLookUpAndMkdirSameDir(t *testing.T) { // Create directory structure for testing. - testDir := createDirectoryStructureForParallelDiropsTest(t) + tds := createDirStructure(t) + defer deleteDirStructure(tds) mkdirFunc := func(wg *sync.WaitGroup, dirPath string, err *error) { defer wg.Done() *err = os.Mkdir(dirPath, setup.DirPermission_0755) @@ -358,7 +399,7 @@ func TestParallelLookUpAndMkdirSameDir(t *testing.T) { var statInfo os.FileInfo var lookUpErr, mkdirErr error - dirPath := path.Join(testDir, "newDir") + dirPath := path.Join(tds.testDir, "newDir") var wg sync.WaitGroup wg.Add(2) From ec65696876d82c1409345dd1dec7fd31ff7ef6b2 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:52:02 +0530 Subject: [PATCH 0377/1298] [Random reader refactoring] Range reader implementation - 1 (#3219) * adding destroy and checkinvariants method * remove unit tests * add unit tests * updating unit test * small fix * adding teardown * add test for checkinvariants * add test for checkinvariants * small fix * remove coverage file * remove duplicate test * adding one more unit test * review comments * small ifx * adding destroy call in tear down of file cache reader * updated comment --- internal/gcsx/client_readers/range_reader.go | 83 +++++++++ .../gcsx/client_readers/range_reader_test.go | 169 ++++++++++++++++++ internal/gcsx/file_cache_reader_test.go | 1 + 3 files changed, 253 insertions(+) create mode 100644 internal/gcsx/client_readers/range_reader.go create mode 100644 internal/gcsx/client_readers/range_reader_test.go diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go new file mode 100644 index 0000000000..16981c618d --- /dev/null +++ b/internal/gcsx/client_readers/range_reader.go @@ -0,0 +1,83 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "fmt" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" +) + +type RangeReader struct { + // TODO: Add additional fields as needed. + gcsx.Reader + start int64 + limit int64 + + // If non-nil, an in-flight read request and a function for cancelling it. + // + // INVARIANT: (reader == nil) == (cancel == nil) + reader gcs.StorageReader + + // Stores the handle associated with the previously closed newReader instance. + // This will be used while making the new connection to bypass auth and metadata + // checks. + readHandle []byte + cancel func() +} + +func NewRangeReader() *RangeReader { + return &RangeReader{ + start: -1, + limit: -1, + } +} + +func (rr *RangeReader) CheckInvariants() { + // INVARIANT: (reader == nil) == (cancel == nil) + if (rr.reader == nil) != (rr.cancel == nil) { + panic(fmt.Sprintf("Mismatch: %v vs. %v", rr.reader == nil, rr.cancel == nil)) + } + + // INVARIANT: start <= limit + if !(rr.start <= rr.limit) { + panic(fmt.Sprintf("Unexpected range: [%d, %d)", rr.start, rr.limit)) + } + + // INVARIANT: limit < 0 implies reader != nil + if rr.limit < 0 && rr.reader != nil { + panic(fmt.Sprintf("Unexpected non-nil reader with limit == %d", rr.limit)) + } +} + +func (rr *RangeReader) Destroy() { + // Close out the reader, if we have one. + if rr.reader != nil { + rr.closeReader() + rr.reader = nil + rr.cancel = nil + } +} + +// closeReader fetches the readHandle before closing the reader instance. +func (rr *RangeReader) closeReader() { + rr.readHandle = rr.reader.ReadHandle() + err := rr.reader.Close() + if err != nil { + logger.Warnf("error while closing reader: %v", err) + } +} diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go new file mode 100644 index 0000000000..d502239340 --- /dev/null +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -0,0 +1,169 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "bytes" + "io" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" + testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +const fakeHandleData = "fake-handle" + +type rangeReaderTest struct { + suite.Suite + rangeReader *RangeReader +} + +func TestRangeReaderTestSuite(t *testing.T) { + suite.Run(t, new(rangeReaderTest)) +} + +func (t *rangeReaderTest) SetupTest() { + t.rangeReader = NewRangeReader() +} + +func (t *rangeReaderTest) TearDown() { + t.rangeReader.Destroy() +} + +func getReadCloser(content []byte) io.ReadCloser { + r := bytes.NewReader(content) + rc := io.NopCloser(r) + return rc +} + +func getReader() *fake.FakeReader { + testContent := testutil.GenerateRandomBytes(2) + return &fake.FakeReader{ + ReadCloser: getReadCloser(testContent), + Handle: []byte(fakeHandleData), + } +} + +func (t *rangeReaderTest) Test_NewRangeReader() { + // The setup instantiates rangeReader with NewRangeReader. + assert.Equal(t.T(), int64(-1), t.rangeReader.start) + assert.Equal(t.T(), int64(-1), t.rangeReader.limit) +} + +func (t *rangeReaderTest) Test_CheckInvariants() { + tests := []struct { + name string + setup func() *RangeReader + shouldPanic bool + }{ + { + name: "valid no reader", + setup: func() *RangeReader { + return &RangeReader{ + start: 0, + limit: 10, + reader: nil, + cancel: nil, + } + }, + shouldPanic: false, + }, + { + name: "reader without cancel", + setup: func() *RangeReader { + t.rangeReader.reader = getReader() + return &RangeReader{ + start: 0, + limit: 10, + reader: t.rangeReader.reader, + cancel: nil, + } + }, + shouldPanic: true, + }, + { + name: "cancel without reader", + setup: func() *RangeReader { + return &RangeReader{ + start: 0, + limit: 10, + reader: nil, + cancel: func() {}, + } + }, + shouldPanic: true, + }, + { + name: "invalid range", + setup: func() *RangeReader { + return &RangeReader{ + start: 20, + limit: 10, + reader: nil, + cancel: nil, + } + }, + shouldPanic: true, + }, + { + name: "negative limit with valid reader", + setup: func() *RangeReader { + t.rangeReader.reader = getReader() + return &RangeReader{ + start: -10, + limit: -5, + reader: t.rangeReader.reader, + cancel: func() {}, + } + }, + shouldPanic: true, + }, + { + name: "negative limit with nil reader", + setup: func() *RangeReader { + return &RangeReader{ + start: -10, + limit: -5, + reader: nil, + cancel: nil, + } + }, + shouldPanic: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func() { + rr := tt.setup() + if tt.shouldPanic { + assert.Panics(t.T(), func() { rr.CheckInvariants() }, "Expected panic") + } else { + assert.NotPanics(t.T(), func() { rr.CheckInvariants() }, "Expected no panic") + } + }) + } +} + +func (t *rangeReaderTest) Test_Destroy_NonNilReader() { + t.rangeReader.reader = getReader() + + t.rangeReader.Destroy() + + assert.Nil(t.T(), t.rangeReader.Reader) + assert.Nil(t.T(), t.rangeReader.cancel) + assert.Equal(t.T(), []byte(fakeHandleData), t.rangeReader.readHandle) +} diff --git a/internal/gcsx/file_cache_reader_test.go b/internal/gcsx/file_cache_reader_test.go index 4ba4501b0d..d4f39ff13b 100644 --- a/internal/gcsx/file_cache_reader_test.go +++ b/internal/gcsx/file_cache_reader_test.go @@ -82,6 +82,7 @@ func (t *fileCacheReaderTest) TearDown() { if err != nil { t.T().Logf("Failed to clean up test cache directory '%s': %v", t.cacheDir, err) } + t.reader.Destroy() } func (t *fileCacheReaderTest) mockNewReaderWithHandleCallForTestBucket(start uint64, limit uint64, rd gcs.StorageReader) { From 85b4a8de33512622bd253d66cc59cf23bd14dd2b Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 22 Apr 2025 21:38:52 +0530 Subject: [PATCH 0378/1298] TPC support with HNS buckets (#3225) * add support for hns tpc * small fix * Exit to prevent the following code from executing for TPC. * add log statement * review comments * small fix * fix error log * small fix * add var name in double quote --- internal/storage/storage_handle.go | 10 ++--- tools/integration_tests/run_e2e_tests.sh | 47 ++++++++++++++++++++---- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index d97737d681..5c09be7cac 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -19,6 +19,7 @@ import ( "net/http" "os" "strconv" + "strings" "time" "cloud.google.com/go/storage" @@ -268,12 +269,9 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien var controlClient *control.StorageControlClient var clientOpts []option.ClientOption - // TODO: We will implement an additional check for the HTTP control client protocol once the Go SDK supports HTTP. // Control-client is needed for folder APIs and for getting storage-layout of the bucket. - // GetStorageLayout API is not supported for storage-testbench and for TPC, both of which are identified by non-nil custom-endpoint. - // Change this check once TPC(custom-endpoint) supports gRPC. - // TODO: Enable creation of control-client for preprod endpoint. - if clientConfig.EnableHNS && clientConfig.CustomEndpoint == "" { + // GetStorageLayout API is not supported for storage-testbench, which are identified by custom-endpoint containing localhost. + if clientConfig.EnableHNS && !strings.Contains(clientConfig.CustomEndpoint, "localhost") { clientOpts, err = createClientOptionForGRPCClient(&clientConfig, false) if err != nil { return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) @@ -282,6 +280,8 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien if err != nil { return nil, fmt.Errorf("could not create StorageControl Client: %w", err) } + } else { + logger.Infof("Skipping storage control client creation because custom-endpoint %q was passed, which is assumed to be a storage testbench server because of 'localhost' in it.", clientConfig.CustomEndpoint) } sh = &storageClient{ diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index af9eb90077..07f16c64d4 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -425,24 +425,31 @@ function run_e2e_tests_for_zonal_bucket(){ return 0 } -function run_e2e_tests_for_tpc_and_exit() { +function run_e2e_tests_for_tpc() { + local bucket=$1 + if [ "$bucket" == "" ]; + then + echo "Bucket name is required" + return 1 + fi + # Clean bucket before testing. - gcloud storage rm -r gs://gcsfuse-e2e-tests-tpc/** + gcloud --verbosity=error storage rm -r gs://"$bucket"/* # Run Operations e2e tests in TPC to validate all the functionality. - GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... --testOnTPCEndPoint=$RUN_TEST_ON_TPC_ENDPOINT $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=false -p 1 --integrationTest -v --testbucket=gcsfuse-e2e-tests-tpc --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT + GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... --testOnTPCEndPoint=$RUN_TEST_ON_TPC_ENDPOINT $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=false -p 1 --integrationTest -v --testbucket="$bucket" --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT exit_code=$? set -e # Delete data after testing. - gcloud storage rm -r gs://gcsfuse-e2e-tests-tpc/** + gcloud --verbosity=error storage rm -r gs://"$bucket"/* if [ $exit_code != 0 ]; then - echo "The tests failed." + return 1 fi - exit $exit_code + return 0 } function run_e2e_tests_for_emulator() { @@ -479,8 +486,32 @@ function main(){ fi else # Run tpc test and exit in case RUN_TEST_ON_TPC_ENDPOINT is true. - if [ $RUN_TEST_ON_TPC_ENDPOINT == true ]; then - run_e2e_tests_for_tpc_and_exit + if [ "$RUN_TEST_ON_TPC_ENDPOINT" == true ]; then + # Run tests for flat bucket + run_e2e_tests_for_tpc gcsfuse-e2e-tests-tpc & + e2e_tests_tpc_flat_bucket_pid=$! + # Run tests for hns bucket + run_e2e_tests_for_tpc gcsfuse-e2e-tests-tpc-hns & + e2e_tests_tpc_hns_bucket_pid=$! + + wait $e2e_tests_tpc_flat_bucket_pid + e2e_tests_tpc_flat_bucket_status=$? + + wait $e2e_tests_tpc_hns_bucket_pid + e2e_tests_tpc_hns_bucket_status=$? + + if [ $e2e_tests_tpc_flat_bucket_status != 0 ]; + then + echo "The e2e tests for flat bucket failed.." + exit 1 + fi + if [ $e2e_tests_tpc_hns_bucket_status != 0 ]; + then + echo "The e2e tests for hns bucket failed.." + exit 1 + fi + # Exit to prevent the following code from executing for TPC. + exit 0 fi run_e2e_tests_for_hns_bucket & From 619f6a2ee2910bf505da151b7d37c7dc3ce9e648 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Wed, 23 Apr 2025 10:08:33 +0530 Subject: [PATCH 0379/1298] Fix: Handle Intermittent SSH Connection Errors for long haul test VMs (#3215) * adding retries for ssh * Update perfmetrics/scripts/continuous_test/ml_tests/run_and_manage_test.sh Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> * Update perfmetrics/scripts/continuous_test/ml_tests/run_and_manage_test.sh Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> * do not wait after last attempt failed --------- Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> --- .../ml_tests/run_and_manage_test.sh | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/perfmetrics/scripts/continuous_test/ml_tests/run_and_manage_test.sh b/perfmetrics/scripts/continuous_test/ml_tests/run_and_manage_test.sh index 7b9f8e9a93..b92b2ba5dc 100755 --- a/perfmetrics/scripts/continuous_test/ml_tests/run_and_manage_test.sh +++ b/perfmetrics/scripts/continuous_test/ml_tests/run_and_manage_test.sh @@ -36,10 +36,29 @@ function initialize_ssh_key () { echo "Delete existing ssh keys " # This is required to avoid issue: https://github.com/kyma-project/test-infra/issues/93 for i in $(sudo gcloud compute os-login ssh-keys list | grep -v FINGERPRINT); do sudo gcloud compute os-login ssh-keys remove --key $i; done - - # Requires running first ssh command with --quiet option to initialize keys. - # Otherwise it prompts for yes and no. - sudo gcloud compute ssh $VM_NAME --zone $ZONE_NAME --internal-ip --quiet --command "echo 'Running from VM'" + delay=1 + max_delay=10 + attempt=1 + max_attempts=5 + while true; do + echo "Attempting SSH connection (attempt $attempt)..." + # Requires running first ssh command with --quiet option to initialize keys. + # Otherwise it prompts for yes or no. + if sudo gcloud compute ssh "$VM_NAME" --zone "$ZONE_NAME" --internal-ip --quiet --command "echo 'Running from VM'"; then + return 0 + fi + if [ "$attempt" -ge "$max_attempts" ]; then + echo "All SSH connection attempts failed." + exit 1 + fi + echo "SSH connection failed. Waiting $delay seconds before retrying..." + sleep $delay + delay=$((delay * 2)) + if [ "$delay" -gt "$max_delay" ]; then + delay="$max_delay" + fi + attempt=$((attempt + 1)) + done } function delete_existing_vm_and_create_new () { From 56b41c8cf8c2016f8c7f8af1950631a9c01768bb Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 23 Apr 2025 10:02:08 +0000 Subject: [PATCH 0380/1298] [testing-on-gke] Support for rapid buckets (#3127) * Download fio output from zonal buckets using gcsfuse This is bypass the problem that gcloud storage cp is not supported for zonal buckets yet. For this support, a new environment var option `zonal` has been introduced. When `zonal` is passed as true, the outputs from fio workloads are downloaded using gcsfuse rather than gcloud. By default, `zonal` is false. It should be set to true of even one of your fio workloaded uses a zonal bucket. * address self-review comments * self-review comments * Throw error on DLIO workload(s) with zonal bucket(s) * fix typo * address review comments * minor fixes * minor fixes --- .../testing_on_gke/examples/fio/parse_logs.py | 9 +- .../testing_on_gke/examples/run-gke-tests.sh | 116 +++++++++++++++++- .../examples/utils/parse_logs_common.py | 7 ++ 3 files changed, 123 insertions(+), 9 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py index cb08e3d608..c7862bf518 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py @@ -339,10 +339,11 @@ def writeRecordsToCsvOutputFile(output: dict, output_file_path: str): args = parse_arguments() ensure_directory_exists(_LOCAL_LOGS_LOCATION) - fioWorkloads = fio_workload.ParseTestConfigForFioWorkloads( - args.workload_config - ) - downloadFioOutputs(fioWorkloads, args.instance_id) + if not args.predownloaded_output_files: + fioWorkloads = fio_workload.ParseTestConfigForFioWorkloads( + args.workload_config + ) + downloadFioOutputs(fioWorkloads, args.instance_id) mash_installed = is_mash_installed() if not mash_installed: diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index 4c062d8f21..4b6d29701b 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -65,6 +65,7 @@ readonly DEFAULT_POD_WAIT_TIME_IN_SECONDS=300 # 1 week readonly DEFAULT_POD_TIMEOUT_IN_SECONDS=604800 readonly DEFAULT_FORCE_UPDATE_GCSFUSE_CODE=false +readonly DEFAULT_ZONAL=false function printHelp() { echo "Usage guide: " @@ -96,6 +97,7 @@ function printHelp() { echo "workload_config=" echo "output_dir=" echo "force_update_gcsfuse_code=" + echo "zonal= " echo "" echo "" echo "" @@ -235,6 +237,13 @@ else export output_dir="${gke_testing_dir}"/examples fi +if test -z "${zonal}"; then + echo "env var zonal not set, so assuming ${DEFAULT_ZONAL} for it." + export zonal=${DEFAULT_ZONAL} +elif [[ ${zonal} != "true" && "${zonal}" != "false" ]]; then + exitWithError "env var zonal should be set as false, or true, but received: ${zonal}" +fi + function printRunParameters() { echo "Running $0 with following parameters:" echo "" @@ -264,6 +273,7 @@ function printRunParameters() { echo "workload_config=\"${workload_config}\"" echo "output_dir=\"${output_dir}\"" echo "force_update_gcsfuse_code=\"${force_update_gcsfuse_code}\"" + echo "zonal=\"${zonal}\"" echo "" echo "" echo "" @@ -467,9 +477,20 @@ function ensureGkeCluster() { echo "Internally changing machine-type from ${machine_type} to ${existing_machine_type} ..." machine_type=${existing_machine_type} fi - gcloud container clusters update ${cluster_name} --project=${project_id} --location=${zone} --workload-pool=${project_id}.svc.id.goog + cluster_updation_command="gcloud container clusters update ${cluster_name} --project=${project_id} --location=${zone}" + ${cluster_updation_command} --workload-pool=${project_id}.svc.id.goog + # Separating in two update calls as gcloud doesn't support updating these + # two fields in a single call. + if ${zonal}; then + ${cluster_updation_command} --private-ipv6-google-access-type=bidirectional + fi else - gcloud container clusters create ${cluster_name} --project=${project_id} --zone "${zone}" --workload-pool=${project_id}.svc.id.goog --machine-type "${machine_type}" --image-type "COS_CONTAINERD" --num-nodes ${num_nodes} --ephemeral-storage-local-ssd count=${num_ssd} --network-performance-configs=total-egress-bandwidth-tier=TIER_1 --workload-metadata=GKE_METADATA --enable-gvnic + # Create a new cluster + cluster_creation_args="--project=${project_id} --zone \"${zone}\" --workload-pool=${project_id}.svc.id.goog --machine-type \"${machine_type}\" --image-type \"COS_CONTAINERD\" --num-nodes ${num_nodes} --ephemeral-storage-local-ssd count=${num_ssd} --network-performance-configs=total-egress-bandwidth-tier=TIER_1 --workload-metadata=GKE_METADATA --enable-gvnic \"${extra_args_for_cluster_creation}\"" + if ${zonal}; then + cluster_creation_args+=" --private-ipv6-google-access-type=bidirectional" + fi + gcloud container clusters create ${cluster_name} ${cluster_creation_args} fi } @@ -609,14 +630,14 @@ function createCustomCsiDriverIfNeeded() { } function deleteAllHelmCharts() { - printf "\nDeleting all existing helm charts ...\n\n" + printf "Deleting all existing helm charts ...\n\n" helm ls --namespace=${appnamespace} | tr -s '\t' ' ' | cut -d' ' -f1 | tail -n +2 | while read helmchart; do helm uninstall ${helmchart} --namespace=${appnamespace}; done } function deleteAllPods() { deleteAllHelmCharts - printf "\nDeleting all existing pods ...\n\n" + printf "Deleting all existing pods ...\n\n" kubectl get pods --namespace=${appnamespace} | tail -n +2 | cut -d' ' -f1 | while read podname; do kubectl delete pods/${podname} --namespace=${appnamespace} --grace-period=0 --force || true; done } @@ -679,6 +700,7 @@ function waitTillAllPodsComplete() { if test -d "${csi_src_dir}"; then message+="csi_src_dir=\"${csi_src_dir}\" " fi + message+=" zonal=${zonal} " message+="pod_wait_time_in_seconds=${pod_wait_time_in_seconds} pod_timeout_in_seconds=${pod_timeout_in_seconds} workload_config=\"${workload_config}\" cluster_name=${cluster_name} output_dir=\"${output_dir}\" $0 \n" message+="\nbut remember that this will reset the start-timer for pod timeout.\n\n" message+="\nTo ssh to any specific pod, use the following command: \n" @@ -698,10 +720,84 @@ function waitTillAllPodsComplete() { done } +# Download all the fio workload outputs for the current instance-id from the +# given bucket and file-size. +function downloadFioOutputsFromBucket() { + local bucket=$1 + local fileSize=$2 + local mountpath=$3 + + mkdir -pv $mountpath + fusermount -uz $mountpath 2>/dev/null || true + echo "Mounting bucket \"${bucket}\" to ${mountpath} ... " + + cd $gcsfuse_src_dir + if ! go run $gcsfuse_src_dir --implicit-dirs $bucket $mountpath > /dev/null ; then + # If fails to mount this bucket, + # Return to original directory before exiting.. + cd - >/dev/null + + exitWithError "Failed to mount bucket ${bucket} to ${mountpath}." + fi + + # Return to original directory. + cd - >/dev/null + + # If the given bucket has the fio outputs for the given instance-id, then + # copy/download them locally to the appropriate folder. + src_dir="${mountpath}/fio-output/${instance_id}" + dst_dir="${gcsfuse_src_dir}/perfmetrics/scripts/testing_on_gke/bin/fio-logs/${instance_id}/${fileSize}" + if test -d "${src_dir}" ; then + mkdir -pv "${dst_dir}" + echo "Copying files from \"${src_dir}\" to \"${dst_dir}/\" ... " + cp -rfvu "${src_dir}"/* "${dst_dir}"/ + fi + + echo " Unmounting \"${bucket}\" from \"${mountpath}\" ... " + fusermount -uz "${mountpath}" || true +} + +function downloadFioOutputsFromAllBucketsInWorkloadConfig() { + local mountpath=$(realpath mounted) + # Using jquery, find out all the relevant buckets for non-disabled fio + # workloads in the workload-config file and download fio outputs for them all. + cat ${workload_config} | jq 'select(.TestConfig.workloadConfig.workloads[].fioWorkload != null)' | jq -r '.TestConfig.workloadConfig.workloads[] | [.bucket, .fioWorkload.fileSize] | @csv' | grep -v " " | sort | uniq | while read bucket_size_combo; do + workload_bucket=$(echo ${bucket_size_combo} | cut -d, -f1 | tr -d \") + workload_filesize=$(echo ${bucket_size_combo} | cut -d, -f2 | tr -d \") + if [[ "${workload_bucket}" != "" && "${workload_filesize}" != "" ]]; then + downloadFioOutputsFromBucket ${workload_bucket} ${workload_filesize} ${mountpath} + fi + done + # Clean-up + fusermount -uz ${mountpath} || true + rm -rf ${mountpath} +} + +function areThereAnyDLIOWorkloads() { + lines=$(cat ${workload_config} | jq 'select(.TestConfig.workloadConfig.workloads[].dlioWorkload != null)' | jq -r '.TestConfig.workloadConfig.workloads[] | [.bucket, .dlioWorkload.numFilesTrain, .dlioWorkload.recordLength] | @csv' | grep -v " " | sort | uniq) + while read bucket_numFilesTrain_recordLength_combo; do + workload_bucket=$(echo ${bucket_numFilesTrain_recordLength_combo} | cut -d, -f1 | tr -d \") + workload_numFileTrain=$(echo ${bucket_numFilesTrain_recordLength_combo} | cut -d, -f2 | tr -d \") + workload_recordLength=$(echo ${bucket_numFilesTrain_recordLength_combo} | cut -d, -f3 | tr -d \") + if [[ "${workload_bucket}" != "" && "${workload_numFileTrain}" != "" && "${workload_recordLength}" != "" ]]; then + return 0 + fi + done <<< "${lines}" # It's necessary to pass lines this way to while + # to avoid creating a subshell for while-execution, to + # ensure that the above return statement works in the same shell. + + return 1 +} + function fetchAndParseFioOutputs() { printf "\nFetching and parsing fio outputs ...\n\n" cd "${gke_testing_dir}"/examples/fio - python3 parse_logs.py --project-number=${project_number} --workload-config "${workload_config}" --instance-id ${instance_id} --output-file "${output_dir}"/fio/output.csv --project-id=${project_id} --cluster-name=${cluster_name} --namespace-name=${appnamespace} + parse_logs_args="--project-number=${project_number} --workload-config ${workload_config} --instance-id ${instance_id} --output-file ${output_dir}/fio/output.csv --project-id=${project_id} --cluster-name=${cluster_name} --namespace-name=${appnamespace}" + if ${zonal}; then + python3 parse_logs.py ${parse_logs_args} --predownloaded-output-files + else + python3 parse_logs.py ${parse_logs_args} + fi cd - } @@ -720,6 +816,10 @@ installDependencies if test -z ${only_parse} || ! ${only_parse} ; then validateMachineConfig ${machine_type} ${num_nodes} ${num_ssd} + if ${zonal} && $(areThereAnyDLIOWorkloads); then + exitWithError "DLIO workloads are not supported with zonal buckets as of now." + fi + # GCP configuration ensureGcpAuthsAndConfig ensureGkeCluster @@ -746,6 +846,12 @@ waitTillAllPodsComplete # clean-up after run deleteAllPods +# Download fio outputs from all buckets using gcsfuse because zonal buckets don't work with gcloud storage cp. +if ${zonal}; then + printf "\nDownloading all fio outputs using gcsfuse mount as there are zonal buckets involved ...\n\n" + downloadFioOutputsFromAllBucketsInWorkloadConfig +fi + # parse outputs fetchAndParseFioOutputs fetchAndParseDlioOutputs diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py b/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py index 500b888076..5e336a6cb0 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py @@ -112,4 +112,11 @@ def parse_arguments() -> object: help="File path of the output metrics (in CSV format)", default="output.csv", ) + parser.add_argument( + "--predownloaded-output-files", + help="If true, output files will not be downloaded. False by default.", + required=False, + default=False, + action="store_true", + ) return parser.parse_args() From adb45ab1f2afa24bd7fb458bb4c8aa2f5e757649 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:48:59 +0530 Subject: [PATCH 0381/1298] upgrade go sdk version to 1.52.0 (#3232) --- go.mod | 36 ++++----- go.sum | 76 +++++++++---------- internal/storage/bucket_handle.go | 4 + .../util/client/storage_client.go | 1 + 4 files changed, 61 insertions(+), 56 deletions(-) diff --git a/go.mod b/go.mod index b03fe84a15..af4c4b05e2 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,9 @@ go 1.24.0 require ( cloud.google.com/go/compute/metadata v0.6.0 - cloud.google.com/go/iam v1.4.2 + cloud.google.com/go/iam v1.5.0 cloud.google.com/go/secretmanager v1.14.6 - cloud.google.com/go/storage v1.51.0 + cloud.google.com/go/storage v1.52.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 github.com/fsouza/fake-gcs-server v1.52.2 @@ -38,25 +38,25 @@ require ( go.opentelemetry.io/otel/sdk v1.35.0 go.opentelemetry.io/otel/sdk/metric v1.35.0 go.opentelemetry.io/otel/trace v1.35.0 - golang.org/x/net v0.37.0 - golang.org/x/oauth2 v0.28.0 - golang.org/x/sync v0.12.0 - golang.org/x/sys v0.31.0 - golang.org/x/text v0.23.0 + golang.org/x/net v0.39.0 + golang.org/x/oauth2 v0.29.0 + golang.org/x/sync v0.13.0 + golang.org/x/sys v0.32.0 + golang.org/x/text v0.24.0 golang.org/x/time v0.11.0 - google.golang.org/api v0.226.0 - google.golang.org/grpc v1.71.0 - google.golang.org/protobuf v1.36.5 + google.golang.org/api v0.229.0 + google.golang.org/grpc v1.71.1 + google.golang.org/protobuf v1.36.6 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 ) require ( cel.dev/expr v0.22.0 // indirect - cloud.google.com/go v0.119.0 // indirect - cloud.google.com/go/auth v0.15.0 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect - cloud.google.com/go/longrunning v0.6.5 // indirect + cloud.google.com/go v0.120.0 // indirect + cloud.google.com/go/auth v0.16.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect + cloud.google.com/go/longrunning v0.6.6 // indirect cloud.google.com/go/monitoring v1.24.0 // indirect cloud.google.com/go/pubsub v1.47.0 // indirect cloud.google.com/go/trace v1.11.4 // indirect @@ -75,7 +75,7 @@ require ( github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect @@ -96,8 +96,8 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.36.0 // indirect + golang.org/x/crypto v0.37.0 // indirect google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect ) diff --git a/go.sum b/go.sum index ccfeff083a..5096761592 100644 --- a/go.sum +++ b/go.sum @@ -1,30 +1,30 @@ cel.dev/expr v0.22.0 h1:+hFFhLPmquBImfs1BiN2PZmkr5ASse2ZOuaxIs9e4R8= cel.dev/expr v0.22.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.119.0 h1:tw7OjErMzJKbbjaEHkrt60KQrK5Wus/boCZ7tm5/RNE= -cloud.google.com/go v0.119.0/go.mod h1:fwB8QLzTcNevxqi8dcpR+hoMIs3jBherGS9VUBDAW08= -cloud.google.com/go/auth v0.15.0 h1:Ly0u4aA5vG/fsSsxu98qCQBemXtAtJf+95z9HK+cxps= -cloud.google.com/go/auth v0.15.0/go.mod h1:WJDGqZ1o9E9wKIL+IwStfyn/+s59zl4Bi+1KQNVXLZ8= -cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74z6cBk9Rw6M= -cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc= +cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA= +cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= +cloud.google.com/go/auth v0.16.0 h1:Pd8P1s9WkcrBE2n/PhAwKsdrR35V3Sg2II9B+ndM3CU= +cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= +cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= +cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -cloud.google.com/go/iam v1.4.2 h1:4AckGYAYsowXeHzsn/LCKWIwSWLkdb0eGjH8wWkd27Q= -cloud.google.com/go/iam v1.4.2/go.mod h1:REGlrt8vSlh4dfCJfSEcNjLGq75wW75c5aU3FLOYq34= +cloud.google.com/go/iam v1.5.0 h1:QlLcVMhbLGOjRcGe6VTGGTyQib8dRLK2B/kYNV0+2xs= +cloud.google.com/go/iam v1.5.0/go.mod h1:U+DOtKQltF/LxPEtcDLoobcsZMilSRwR7mgNL7knOpo= cloud.google.com/go/kms v1.21.0 h1:x3EeWKuYwdlo2HLse/876ZrKjk2L5r7Uexfm8+p6mSI= cloud.google.com/go/kms v1.21.0/go.mod h1:zoFXMhVVK7lQ3JC9xmhHMoQhnjEDZFoLAr5YMwzBLtk= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= -cloud.google.com/go/longrunning v0.6.5 h1:sD+t8DO8j4HKW4QfouCklg7ZC1qC4uzVZt8iz3uTW+Q= -cloud.google.com/go/longrunning v0.6.5/go.mod h1:Et04XK+0TTLKa5IPYryKf5DkpwImy6TluQ1QTLwlKmY= +cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR65Rbw= +cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM= cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= cloud.google.com/go/pubsub v1.47.0 h1:Ou2Qu4INnf7ykrFjGv2ntFOjVo8Nloh/+OffF4mUu9w= cloud.google.com/go/pubsub v1.47.0/go.mod h1:LaENesmga+2u0nDtLkIOILskxsfvn/BXX9Ak1NFxOs8= cloud.google.com/go/secretmanager v1.14.6 h1:/ooktIMSORaWk9gm3vf8+Mg+zSrUplJFKBztP993oL0= cloud.google.com/go/secretmanager v1.14.6/go.mod h1:0OWeM3qpJ2n71MGgNfKsgjC/9LfVTcUqXFUlGxo5PzY= -cloud.google.com/go/storage v1.51.0 h1:ZVZ11zCiD7b3k+cH5lQs/qcNaoSz3U9I0jgwVzqDlCw= -cloud.google.com/go/storage v1.51.0/go.mod h1:YEJfu/Ki3i5oHC/7jyTgsGZwdQ8P9hqMqvpi5kRKGgc= +cloud.google.com/go/storage v1.52.0 h1:ROpzMW/IwipKtatA69ikxibdzQSiXJrY9f6IgBa9AlA= +cloud.google.com/go/storage v1.52.0/go.mod h1:4wrBAbAYUvYkbrf19ahGm4I5kDQhESSqN3CGEkMGvOY= cloud.google.com/go/trace v1.11.4 h1:LKlhVyX6I4+heP31sWvERSKZZ9cPPEZumt7b4SKVK18= cloud.google.com/go/trace v1.11.4/go.mod h1:lCSHzSPZC1TPwto7zhaRt3KtGYsXFyaErPQ18AUUeUE= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -118,8 +118,8 @@ github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.5 h1:VgzTY2jogw3xt39CusEnFJWm7rlsq5yL5q9XdLOuP5g= -github.com/googleapis/enterprise-certificate-proxy v0.3.5/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= +github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= +github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= @@ -229,8 +229,8 @@ go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk= go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg= go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= @@ -247,8 +247,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -259,27 +259,27 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= +golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -288,8 +288,8 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.226.0 h1:9A29y1XUD+YRXfnHkO66KggxHBZWg9LsTGqm7TkUvtQ= -google.golang.org/api v0.226.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY= +google.golang.org/api v0.229.0 h1:p98ymMtqeJ5i3lIBMj5MpR9kzIIgzpHHh8vQ+vgAzx8= +google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -297,17 +297,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf h1:114fkUG+I9ba4UmaoNZt0UtiRmBng3KJIB/E0avfYII= google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= -google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf h1:BdIVRm+fyDUn8lrZLPSlBCfM/YKDwUBYgDoLv9+DYo0= -google.golang.org/genproto/googleapis/api v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:jbe3Bkdp+Dh2IrslsFCklNhweNTBgSYanP1UXhJDhKg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf h1:dHDlF3CWxQkefK9IJx+O8ldY0gLygvrlYRBNbPqDWuY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I= +google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e h1:UdXH7Kzbj+Vzastr5nVfccbmFsmYNygVLSPk1pEfDoY= +google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= -google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= +google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -317,8 +317,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index fb9908daf7..4e8bc79e13 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -197,6 +197,8 @@ func (bh *bucketHandle) CreateObject(ctx context.Context, req *gcs.CreateObjectR } // All objects in zonal buckets must be appendable. wc.Append = bh.BucketType().Zonal + // FinalizeOnClose should be true for all writes for now. + wc.FinalizeOnClose = true // Copy the contents to the writer. if _, err = io.Copy(wc, req.Contents); err != nil { @@ -231,6 +233,8 @@ func (bh *bucketHandle) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cr wc.ProgressFunc = callBack // All objects in zonal buckets must be appendable. wc.Append = bh.BucketType().Zonal + // FinalizeOnClose should be true for all writes for now. + wc.FinalizeOnClose = true return wc, nil } diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 2fe710db4b..1f902a880a 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -132,6 +132,7 @@ func ReadChunkFromGCS(ctx context.Context, client *storage.Client, object string // extends support to zonal buckets. func NewWriter(ctx context.Context, o *storage.ObjectHandle, client *storage.Client) (wc *storage.Writer, err error) { wc = o.NewWriter(ctx) + wc.FinalizeOnClose = true // Changes specific to zonal bucket var attrs *storage.BucketAttrs From 33121199373d3210682a35281771a4b9f43874e0 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Wed, 23 Apr 2025 18:24:35 +0530 Subject: [PATCH 0382/1298] fix: use local rand.NewSource() in each invocation as NewSource() is not safe for concurrent use. (#3235) use local rand.NewSource() in each invocation as NewSource() is not safe for concurrent use. --- tools/integration_tests/util/setup/setup.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 34456a9df0..79041b4246 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -43,7 +43,6 @@ var mountedDirectory = flag.String("mountedDirectory", "", "The GCSFuse mounted var integrationTest = flag.Bool("integrationTest", false, "Run tests only when the flag value is true.") var testInstalledPackage = flag.Bool("testInstalledPackage", false, "[Optional] Run tests on the package pre-installed on the host machine. By default, integration tests build a new package to run the tests.") var testOnTPCEndPoint = flag.Bool("testOnTPCEndPoint", false, "Run tests on TPC endpoint only when the flag value is true.") -var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano())) const ( FilePermission_0600 = 0600 @@ -219,6 +218,7 @@ func ExecuteTest(m *testing.M) (successCode int) { } func GenerateRandomString(length int) string { + seededRand := rand.New(rand.NewSource(time.Now().UnixNano())) b := make([]byte, length) for i := range b { b[i] = Charset[seededRand.Intn(len(Charset))] From 9c541233c32092dee47e2e69e640661d7e96dc16 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Wed, 23 Apr 2025 20:47:47 +0530 Subject: [PATCH 0383/1298] Ensure correct size of min object in fileInode and Random Reader (#3198) * fix min object size in fileInode and Random Reader * fix review comments * fix space * update tests * fix review comments * add comment for size update --- internal/fs/handle/file.go | 2 ++ internal/fs/inode/file.go | 5 ++++- .../fs/inode/file_streaming_writes_test.go | 18 ++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 73bfcf127d..8fe9fb76fe 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -194,6 +194,8 @@ func (fh *FileHandle) tryEnsureReader(ctx context.Context, sequentialReadSizeMb // can use it. Otherwise we must throw it away. if fh.reader != nil { if fh.reader.Object().Generation == fh.inode.SourceGeneration().Object { + // Update reader object size to source object size. + fh.reader.Object().Size = fh.inode.SourceGeneration().Size return } fh.reader.Destroy() diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 1a10974099..05c09dfdf6 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -390,7 +390,7 @@ func (f *FileInode) SourceGenerationIsAuthoritative() bool { // Equivalent to the generation returned by f.Source(). // -// LOCKS_REQUIRED(f) +// LOCKS_REQUIRED(f.mu) func (f *FileInode) SourceGeneration() (g Generation) { g.Size = f.src.Size g.Object = f.src.Generation @@ -676,6 +676,9 @@ func (f *FileInode) SyncPendingBufferedWrites() error { if err != nil { return fmt.Errorf("f.bwh.Sync(): %w", err) } + // Update the f.src.Size to the current Size of object + // on GCS after flushing pending buffers. + f.src.Size = uint64(f.bwh.WriteFileInfo().TotalSize) return nil } diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index c3904a34a9..89d1ececa2 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -182,6 +182,15 @@ func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZon assert.Equal(t.T(), "pizza", string(content)) } +func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBucketsUpdatesSrcSize() { + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0)) + assert.Equal(t.T(), uint64(0), t.in.src.Size) + + assert.NoError(t.T(), t.in.SyncPendingBufferedWrites()) + + assert.Equal(t.T(), uint64(6), t.in.src.Size) +} + // ////////////////////////////////////////////////////////////////////// // Tests (Non Zonal Bucket) // ////////////////////////////////////////////////////////////////////// @@ -207,6 +216,15 @@ func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucket operations.ValidateObjectNotFoundErr(t.ctx, t.T(), t.bucket, t.in.Name().GcsObjectName()) } +func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucketsDoesUpdateSrcSize() { + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0)) + assert.Equal(t.T(), uint64(0), t.in.src.Size) + + assert.NoError(t.T(), t.in.SyncPendingBufferedWrites()) + + assert.Equal(t.T(), uint64(6), t.in.src.Size) +} + func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempFile() { testCases := []struct { name string From fa119133393bcb4dab6f5aead512f25d83417b5d Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Wed, 23 Apr 2025 17:34:36 +0000 Subject: [PATCH 0384/1298] Sample YAMLs for GPUs and TPUs with more information --- samples/gke-csi-yaml/README.md | 28 ----------- samples/gke-csi-yaml/checkpointing-pv.yaml | 46 ------------------- samples/gke-csi-yaml/gpu/README.md | 35 ++++++++++++++ .../{ => gpu}/checkpointing-pod.yaml | 4 +- .../gke-csi-yaml/gpu/checkpointing-pv.yaml | 46 +++++++++++++++++++ .../gke-csi-yaml/{ => gpu}/serving-pod.yaml | 2 +- samples/gke-csi-yaml/gpu/serving-pv.yaml | 44 ++++++++++++++++++ .../gke-csi-yaml/{ => gpu}/training-pod.yaml | 2 +- samples/gke-csi-yaml/gpu/training-pv.yaml | 44 ++++++++++++++++++ samples/gke-csi-yaml/serving-pv.yaml | 44 ------------------ samples/gke-csi-yaml/tpu/README.md | 35 ++++++++++++++ .../gke-csi-yaml/tpu/checkpointing-pod.yaml | 23 ++++++++++ .../gke-csi-yaml/tpu/checkpointing-pv.yaml | 46 +++++++++++++++++++ samples/gke-csi-yaml/tpu/serving-pod.yaml | 24 ++++++++++ samples/gke-csi-yaml/tpu/serving-pv.yaml | 44 ++++++++++++++++++ samples/gke-csi-yaml/tpu/training-pod.yaml | 23 ++++++++++ samples/gke-csi-yaml/tpu/training-pv.yaml | 44 ++++++++++++++++++ samples/gke-csi-yaml/training-pv.yaml | 44 ------------------ 18 files changed, 412 insertions(+), 166 deletions(-) delete mode 100644 samples/gke-csi-yaml/README.md delete mode 100644 samples/gke-csi-yaml/checkpointing-pv.yaml create mode 100644 samples/gke-csi-yaml/gpu/README.md rename samples/gke-csi-yaml/{ => gpu}/checkpointing-pod.yaml (75%) create mode 100644 samples/gke-csi-yaml/gpu/checkpointing-pv.yaml rename samples/gke-csi-yaml/{ => gpu}/serving-pod.yaml (97%) create mode 100644 samples/gke-csi-yaml/gpu/serving-pv.yaml rename samples/gke-csi-yaml/{ => gpu}/training-pod.yaml (93%) create mode 100644 samples/gke-csi-yaml/gpu/training-pv.yaml delete mode 100644 samples/gke-csi-yaml/serving-pv.yaml create mode 100644 samples/gke-csi-yaml/tpu/README.md create mode 100644 samples/gke-csi-yaml/tpu/checkpointing-pod.yaml create mode 100644 samples/gke-csi-yaml/tpu/checkpointing-pv.yaml create mode 100644 samples/gke-csi-yaml/tpu/serving-pod.yaml create mode 100644 samples/gke-csi-yaml/tpu/serving-pv.yaml create mode 100644 samples/gke-csi-yaml/tpu/training-pod.yaml create mode 100644 samples/gke-csi-yaml/tpu/training-pv.yaml delete mode 100644 samples/gke-csi-yaml/training-pv.yaml diff --git a/samples/gke-csi-yaml/README.md b/samples/gke-csi-yaml/README.md deleted file mode 100644 index 98acd96107..0000000000 --- a/samples/gke-csi-yaml/README.md +++ /dev/null @@ -1,28 +0,0 @@ -This folder contains sample YAML files for GKE GCSFuse CSI driver with recommendations for GPUs/TPUs. - -The following workflow samples are added -1. Training -2. Serving or Inference -3. Checkpointing - -For JAX Jit Cache workflows, use the Checkpointing workflow yaml configs. - -For e.g. To set up the serving workload. - -Deploy the PVC/PV first (this is required because the GKE pod webhook inspects the PV volume attributes for additional optimizations like injection of additional containers) -e.g. -``` -kubectl apply -f serving-pv.yaml -``` - -Then, deploy the pod spec that accesses the PVC -e.g. -``` -kubectl apply -f serving-pod.yaml -``` - -Note: -* The service account needs to be created before use. -* Replace placeholders with actual values. - -Read https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-storage-fuse-csi-driver-perf#best-practices-for-performance-tuning for more tuning details. diff --git a/samples/gke-csi-yaml/checkpointing-pv.yaml b/samples/gke-csi-yaml/checkpointing-pv.yaml deleted file mode 100644 index ffd26af5c5..0000000000 --- a/samples/gke-csi-yaml/checkpointing-pv.yaml +++ /dev/null @@ -1,46 +0,0 @@ -apiVersion: v1 -kind: PersistentVolume -metadata: - name: checkpoint-bucket-pv -spec: - accessModes: - - ReadWriteMany - capacity: - storage: 64Gi - persistentVolumeReclaimPolicy: Retain - storageClassName: gcsfuse-sc # dummy storage class - claimRef: - namespace: - name: checkpoint-bucket-pvc - mountOptions: - - implicit-dirs #set because most prefer ease of use - - metadata-cache:negative-ttl-secs:0 # disable negative cache - - metadata-cache:ttl-secs:-1 #no expiry - - metadata-cache:stat-cache-max-size-mb:-1 #unlimited - - metadata-cache:type-cache-max-size-mb:-1 #unlimited - - file-cache:max-size-mb:-1 #unlimited - - file-cache:cache-file-for-range-read:true - - file-cache:enable-parallel-downloads:true - - read_ahead_kb=1024 - - write:enable-streaming-writes:true - csi: - driver: gcsfuse.csi.storage.gke.io - volumeHandle: # unique bucket name - volumeAttributes: - skipCSIBucketAccessCheck: "true" - gcsfuseMetadataPrefetchOnMount: "true" - ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: checkpoint-bucket-pvc - namespace: -spec: - accessModes: - - ReadWriteMany - resources: - requests: - storage: 64Gi - volumeName: checkpoint-bucket-pv - storageClassName: gcsfuse-sc # dummy storage class diff --git a/samples/gke-csi-yaml/gpu/README.md b/samples/gke-csi-yaml/gpu/README.md new file mode 100644 index 0000000000..21682333be --- /dev/null +++ b/samples/gke-csi-yaml/gpu/README.md @@ -0,0 +1,35 @@ +# GKE GCSFuse CSI Driver Sample Configurations for GPU Workloads + +This directory provides sample Kubernetes YAML configuration files for utilizing the GKE GCSFuse CSI driver, specifically optimized for workloads running on Graphics Processing Units (GPUs). + +The configurations include recommendations tailored for the following common machine learning workflows: + +1. **Model Training:** Configurations suitable for training machine learning models. +2. **Model Serving/Inference:** Configurations optimized for deploying trained models to serve predictions. +3. **Checkpointing:** Configurations designed for saving model state during training processes. These configurations are also applicable to JAX Just-In-Time (JIT) Cache workflows. + +## Deployment Instructions + +To utilize these sample configurations, follow the specified deployment order. For instance, to set up the serving workload: + +1. **Deploy the PersistentVolume (PV) and PersistentVolumeClaim (PVC):** + Apply the `*-pv.yaml` file first. This step is crucial as the GKE pod admission webhook inspects the PV's volume attributes to apply potential optimizations, such as the injection of sidecar containers, before the pod is scheduled. + ```bash + kubectl apply -f serving-pv.yaml + ``` + +2. **Deploy the Pod:** + After the PV and PVC are successfully created, deploy the pod specification that references the PVC. + ```bash + kubectl apply -f serving-pod.yaml + ``` + +## Prerequisites and Notes + +* **Service Account:** Ensure the specified Kubernetes Service Account (e.g., `` in the pod YAML) exists and possesses the necessary permissions to access the target Google Cloud Storage bucket *before* deploying the pod. +* **Placeholders:** Replace all placeholder values (e.g., ``, ``, ``) within the YAML files with your specific environment details before application. + +## Further Information + +For comprehensive details on performance tuning and best practices for the GCS FUSE CSI driver, please consult the Google Cloud documentation: +[Best practices for performance tuning](https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-storage-fuse-csi-driver-perf#best-practices-for-performance-tuning) diff --git a/samples/gke-csi-yaml/checkpointing-pod.yaml b/samples/gke-csi-yaml/gpu/checkpointing-pod.yaml similarity index 75% rename from samples/gke-csi-yaml/checkpointing-pod.yaml rename to samples/gke-csi-yaml/gpu/checkpointing-pod.yaml index 19dc7032cf..6f5f5a887e 100644 --- a/samples/gke-csi-yaml/checkpointing-pod.yaml +++ b/samples/gke-csi-yaml/gpu/checkpointing-pod.yaml @@ -15,8 +15,8 @@ spec: mountPath: /checkpoint-data serviceAccountName: volumes: - # RAM disk file cache if L-SSD not available. Uncomment to use - # - name: gke-gcsfuse-cache # gcsfuse file cache backed by RAM Disk + # In-memory (RAM Disk) for GCSFuse file cache if available. Uncomment to use. + # - name: gke-gcsfuse-cache # GCSFuse file cache backed in Memory (RAM Disk) # emptyDir: # medium: Memory - name: checkpoint-bucket-vol diff --git a/samples/gke-csi-yaml/gpu/checkpointing-pv.yaml b/samples/gke-csi-yaml/gpu/checkpointing-pv.yaml new file mode 100644 index 0000000000..ced946ee3b --- /dev/null +++ b/samples/gke-csi-yaml/gpu/checkpointing-pv.yaml @@ -0,0 +1,46 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: checkpoint-bucket-pv +spec: + accessModes: + - ReadWriteMany + capacity: + storage: 64Gi + persistentVolumeReclaimPolicy: Retain + storageClassName: gcsfuse-sc # dummy storage class + claimRef: + namespace: + name: checkpoint-bucket-pvc + mountOptions: + - implicit-dirs # Create implicit directories locally when accessed + - metadata-cache:negative-ttl-secs:0 # Disable caching for lookups of files/dirs that don't exist + - metadata-cache:ttl-secs:-1 # Keep cached metadata (file attributes, types) indefinitely time-wise + - metadata-cache:stat-cache-max-size-mb:-1 # Allow unlimited size for the file attribute (stat) cache + - metadata-cache:type-cache-max-size-mb:-1 # Allow unlimited size for the file/directory type cache + - file-cache:max-size-mb:-1 # Allow unlimited size for the file content cache + - file-cache:cache-file-for-range-read:true # Cache the entire file when any part is read sequentially + - file-cache:enable-parallel-downloads:true # Use multiple streams to download file content faster + - read_ahead_kb=1024 # Increase kernel read-ahead buffer + - write:enable-streaming-writes:true # Enable streaming writes + csi: + driver: gcsfuse.csi.storage.gke.io + volumeHandle: # Name of the GCS bucket to mount + volumeAttributes: + skipCSIBucketAccessCheck: "true" # Bypass the CSI Drivers bucket access check + gcsfuseMetadataPrefetchOnMount: "true" # Fetch GCS metadata immediately at mount time + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: checkpoint-bucket-pvc + namespace: +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 64Gi + volumeName: checkpoint-bucket-pv + storageClassName: gcsfuse-sc # dummy storage class diff --git a/samples/gke-csi-yaml/serving-pod.yaml b/samples/gke-csi-yaml/gpu/serving-pod.yaml similarity index 97% rename from samples/gke-csi-yaml/serving-pod.yaml rename to samples/gke-csi-yaml/gpu/serving-pod.yaml index 31417d21af..d3a628c58e 100644 --- a/samples/gke-csi-yaml/serving-pod.yaml +++ b/samples/gke-csi-yaml/gpu/serving-pod.yaml @@ -16,7 +16,7 @@ spec: serviceAccountName: volumes: # RAM disk file cache for best performance of Parallel Download. Can use L-SSD if not available memory - - name: gke-gcsfuse-cache # gcsfuse file cache backed by RAM Disk + - name: gke-gcsfuse-cache # gcsfuse file cache backed by RAM Disk (Memory) emptyDir: medium: Memory - name: serving-bucket-vol diff --git a/samples/gke-csi-yaml/gpu/serving-pv.yaml b/samples/gke-csi-yaml/gpu/serving-pv.yaml new file mode 100644 index 0000000000..1d7fe594a8 --- /dev/null +++ b/samples/gke-csi-yaml/gpu/serving-pv.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: serving-bucket-pv +spec: + accessModes: + - ReadWriteMany + capacity: + storage: 64Gi + persistentVolumeReclaimPolicy: Retain + storageClassName: gcsfuse-sc # dummy storage class + claimRef: + namespace: + name: serving-bucket-pvc + mountOptions: + - implicit-dirs # Create implicit directories locally when accessed + - metadata-cache:negative-ttl-secs:0 # Disable caching for lookups of files/dirs that don't exist + - metadata-cache:ttl-secs:-1 # Keep cached metadata (file attributes, types) indefinitely time-wise + - metadata-cache:stat-cache-max-size-mb:-1 # Allow unlimited size for the file attribute (stat) cache + - metadata-cache:type-cache-max-size-mb:-1 # Allow unlimited size for the file/directory type cache + - file-cache:max-size-mb:-1 # Allow unlimited size for the file content cache + - file-cache:cache-file-for-range-read:true # Cache the entire file when any part is read sequentially + - file-cache:enable-parallel-downloads:true # Use multiple streams to download file content faster + - read_ahead_kb=1024 # Increase kernel read-ahead buffer + csi: + driver: gcsfuse.csi.storage.gke.io + volumeHandle: # Name of the GCS Bucket to mount + volumeAttributes: + skipCSIBucketAccessCheck: "true" # Bypass the CSI Drivers bucket access check + gcsfuseMetadataPrefetchOnMount: "true" # Fetch GCS metadata immediately at mount time +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: serving-bucket-pvc + namespace: +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 64Gi + volumeName: serving-bucket-pv + storageClassName: gcsfuse-sc # dummy storage class diff --git a/samples/gke-csi-yaml/training-pod.yaml b/samples/gke-csi-yaml/gpu/training-pod.yaml similarity index 93% rename from samples/gke-csi-yaml/training-pod.yaml rename to samples/gke-csi-yaml/gpu/training-pod.yaml index 38086b269a..74a1b25b46 100644 --- a/samples/gke-csi-yaml/training-pod.yaml +++ b/samples/gke-csi-yaml/gpu/training-pod.yaml @@ -20,4 +20,4 @@ spec: # medium: Memory - name: training-bucket-vol persistentVolumeClaim: - claimName: training-bucket-pvc + claimName: training-bucket-pvc \ No newline at end of file diff --git a/samples/gke-csi-yaml/gpu/training-pv.yaml b/samples/gke-csi-yaml/gpu/training-pv.yaml new file mode 100644 index 0000000000..72d7763c2f --- /dev/null +++ b/samples/gke-csi-yaml/gpu/training-pv.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: training-bucket-pv +spec: + accessModes: + - ReadWriteMany + capacity: + storage: 64Gi + persistentVolumeReclaimPolicy: Retain + storageClassName: gcsfuse-sc # dummy storage class + claimRef: + namespace: + name: training-bucket-pvc + mountOptions: + - implicit-dirs # Create implicit directories locally when accessed + - metadata-cache:negative-ttl-secs:0 # Disable caching for lookups of files/dirs that don't exist + - metadata-cache:ttl-secs:-1 # Keep cached metadata (file attributes, types) indefinitely time-wise + - metadata-cache:stat-cache-max-size-mb:-1 # Allow unlimited size for the file attribute (stat) cache + - metadata-cache:type-cache-max-size-mb:-1 # Allow unlimited size for the file/directory type cache + # if enabling the file cache, uncomment out to use # + # - file-cache:max-size-mb:-1 # Allow unlimited size for the file content cache + # - file-cache:cache-file-for-range-read:true # Cache the entire file when any part is read sequentially + # - read_ahead_kb=1024 # Increase kernel read-ahead buffer + csi: + driver: gcsfuse.csi.storage.gke.io + volumeHandle: # Name of the GCS Bucket to mount + volumeAttributes: + skipCSIBucketAccessCheck: "true" # Bypass the CSI Drivers bucket access check + gcsfuseMetadataPrefetchOnMount: "true" # Fetch GCS metadata immediately at mount time +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: training-bucket-pvc + namespace: +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 64Gi + volumeName: training-bucket-pv + storageClassName: gcsfuse-sc # dummy storage class diff --git a/samples/gke-csi-yaml/serving-pv.yaml b/samples/gke-csi-yaml/serving-pv.yaml deleted file mode 100644 index e7d4198f31..0000000000 --- a/samples/gke-csi-yaml/serving-pv.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: v1 -kind: PersistentVolume -metadata: - name: serving-bucket-pv -spec: - accessModes: - - ReadWriteMany - capacity: - storage: 64Gi - persistentVolumeReclaimPolicy: Retain - storageClassName: gcsfuse-sc # dummy storage class - claimRef: - namespace: - name: serving-bucket-pvc - mountOptions: - - implicit-dirs #set because most prefer ease of use - - metadata-cache:negative-ttl-secs:0 # disable negative cache - - metadata-cache:ttl-secs:-1 #no expiry - - metadata-cache:stat-cache-max-size-mb:-1 #unlimited - - metadata-cache:type-cache-max-size-mb:-1 #unlimited - - file-cache:max-size-mb:-1 #unlimited - - file-cache:cache-file-for-range-read:true - - file-cache:enable-parallel-downloads:true - - read_ahead_kb=1024 - csi: - driver: gcsfuse.csi.storage.gke.io - volumeHandle: # unique bucket name - volumeAttributes: - skipCSIBucketAccessCheck: "true" - gcsfuseMetadataPrefetchOnMount: "true" ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: serving-bucket-pvc - namespace: -spec: - accessModes: - - ReadWriteMany - resources: - requests: - storage: 64Gi - volumeName: serving-bucket-pv - storageClassName: gcsfuse-sc # dummy storage class diff --git a/samples/gke-csi-yaml/tpu/README.md b/samples/gke-csi-yaml/tpu/README.md new file mode 100644 index 0000000000..35edf865bf --- /dev/null +++ b/samples/gke-csi-yaml/tpu/README.md @@ -0,0 +1,35 @@ +# GKE GCSFuse CSI Driver Sample Configurations for TPU Workloads + +This directory provides sample Kubernetes YAML configuration files for utilizing the GKE GCSFuse CSI driver, specifically optimized for workloads running on Tensor Processing Units (TPUs). + +The configurations include recommendations tailored for the following common machine learning workflows: + +1. **Model Training:** Configurations suitable for training machine learning models. +2. **Model Serving/Inference:** Configurations optimized for deploying trained models to serve predictions. +3. **Checkpointing:** Configurations designed for saving model state during training processes. These configurations are also applicable to JAX Just-In-Time (JIT) Cache workflows. + +## Deployment Instructions + +To utilize these sample configurations, follow the specified deployment order. For instance, to set up the serving workload: + +1. **Deploy the PersistentVolume (PV) and PersistentVolumeClaim (PVC):** + Apply the `*-pv.yaml` file first. This step is crucial as the GKE pod admission webhook inspects the PV's volume attributes to apply potential optimizations, such as the injection of sidecar containers, before the pod is scheduled. + ```bash + kubectl apply -f serving-pv.yaml + ``` + +2. **Deploy the Pod:** + After the PV and PVC are successfully created, deploy the pod specification that references the PVC. + ```bash + kubectl apply -f serving-pod.yaml + ``` + +## Prerequisites and Notes + +* **Service Account:** Ensure the specified Kubernetes Service Account (e.g., `` in the pod YAML) exists and possesses the necessary permissions to access the target Google Cloud Storage bucket *before* deploying the pod. +* **Placeholders:** Replace all placeholder values (e.g., ``, ``, ``) within the YAML files with your specific environment details before application. + +## Further Information + +For comprehensive details on performance tuning and best practices for the GCS FUSE CSI driver, please consult the Google Cloud documentation: +[Best practices for performance tuning](https://cloud.google.com/kubernetes-engine/docs/how-to/cloud-storage-fuse-csi-driver-perf#best-practices-for-performance-tuning) \ No newline at end of file diff --git a/samples/gke-csi-yaml/tpu/checkpointing-pod.yaml b/samples/gke-csi-yaml/tpu/checkpointing-pod.yaml new file mode 100644 index 0000000000..9170a7e79f --- /dev/null +++ b/samples/gke-csi-yaml/tpu/checkpointing-pod.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Pod +metadata: + name: gcs-fuse-csi-example-pod + namespace: + annotations: + gke-gcsfuse/volumes: "true" + +spec: + containers: + # Add your workload container spec + ... + volumeMounts: + - name: checkpoint-bucket-vol + mountPath: /checkpoint-data + serviceAccountName: + volumes: + - name: gke-gcsfuse-cache # GCSFuse file cache backed in Memory (RAM Disk) + emptyDir: + medium: Memory + - name: checkpoint-bucket-vol + persistentVolumeClaim: + claimName: checkpoint-bucket-pvc diff --git a/samples/gke-csi-yaml/tpu/checkpointing-pv.yaml b/samples/gke-csi-yaml/tpu/checkpointing-pv.yaml new file mode 100644 index 0000000000..e7a943bde4 --- /dev/null +++ b/samples/gke-csi-yaml/tpu/checkpointing-pv.yaml @@ -0,0 +1,46 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: checkpoint-bucket-pv +spec: + accessModes: + - ReadWriteMany + capacity: + storage: 64Gi + persistentVolumeReclaimPolicy: Retain + storageClassName: gcsfuse-sc # dummy storage class + claimRef: + namespace: + name: checkpoint-bucket-pvc + mountOptions: + - implicit-dirs # Create implicit directories locally when accessed. + - metadata-cache:negative-ttl-secs:0 # Disable caching for lookups of files/dirs that don't exist. + - metadata-cache:ttl-secs:-1 # Keep cached metadata (file attributes, types) indefinitely time-wise. + - metadata-cache:stat-cache-max-size-mb:-1 # Allow unlimited size for the file attribute (stat) cache. + - metadata-cache:type-cache-max-size-mb:-1 # Allow unlimited size for the file/directory type cache. + - file-cache:max-size-mb:-1 # Allow unlimited size for the file content cache. + - file-cache:cache-file-for-range-read:true # Cache the entire file when any part is read sequentially. + - file-cache:enable-parallel-downloads:true # Use multiple streams to download file content faster. + - read_ahead_kb=1024 # Increase kernel read-ahead buffer. + - write:enable-streaming-writes:true # Enable streaming writes. + csi: + driver: gcsfuse.csi.storage.gke.io + volumeHandle: # Name of the GCS bucket to mount. + volumeAttributes: + skipCSIBucketAccessCheck: "true" # Bypass the CSI Drivers bucket existence/access check. + gcsfuseMetadataPrefetchOnMount: "true" # Fetch GCS metadata immediately at mount time. + +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: checkpoint-bucket-pvc + namespace: +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 64Gi + volumeName: checkpoint-bucket-pv + storageClassName: gcsfuse-sc # dummy storage class diff --git a/samples/gke-csi-yaml/tpu/serving-pod.yaml b/samples/gke-csi-yaml/tpu/serving-pod.yaml new file mode 100644 index 0000000000..d3a628c58e --- /dev/null +++ b/samples/gke-csi-yaml/tpu/serving-pod.yaml @@ -0,0 +1,24 @@ +apiVersion: v1 +kind: Pod +metadata: + name: gcs-fuse-csi-example-pod + namespace: + annotations: + gke-gcsfuse/volumes: "true" + +spec: + containers: + # Your workload container spec + ... + volumeMounts: + - name: serving-bucket-vol + mountPath: /serving-data + serviceAccountName: + volumes: + # RAM disk file cache for best performance of Parallel Download. Can use L-SSD if not available memory + - name: gke-gcsfuse-cache # gcsfuse file cache backed by RAM Disk (Memory) + emptyDir: + medium: Memory + - name: serving-bucket-vol + persistentVolumeClaim: + claimName: serving-bucket-pvc diff --git a/samples/gke-csi-yaml/tpu/serving-pv.yaml b/samples/gke-csi-yaml/tpu/serving-pv.yaml new file mode 100644 index 0000000000..1d7fe594a8 --- /dev/null +++ b/samples/gke-csi-yaml/tpu/serving-pv.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: serving-bucket-pv +spec: + accessModes: + - ReadWriteMany + capacity: + storage: 64Gi + persistentVolumeReclaimPolicy: Retain + storageClassName: gcsfuse-sc # dummy storage class + claimRef: + namespace: + name: serving-bucket-pvc + mountOptions: + - implicit-dirs # Create implicit directories locally when accessed + - metadata-cache:negative-ttl-secs:0 # Disable caching for lookups of files/dirs that don't exist + - metadata-cache:ttl-secs:-1 # Keep cached metadata (file attributes, types) indefinitely time-wise + - metadata-cache:stat-cache-max-size-mb:-1 # Allow unlimited size for the file attribute (stat) cache + - metadata-cache:type-cache-max-size-mb:-1 # Allow unlimited size for the file/directory type cache + - file-cache:max-size-mb:-1 # Allow unlimited size for the file content cache + - file-cache:cache-file-for-range-read:true # Cache the entire file when any part is read sequentially + - file-cache:enable-parallel-downloads:true # Use multiple streams to download file content faster + - read_ahead_kb=1024 # Increase kernel read-ahead buffer + csi: + driver: gcsfuse.csi.storage.gke.io + volumeHandle: # Name of the GCS Bucket to mount + volumeAttributes: + skipCSIBucketAccessCheck: "true" # Bypass the CSI Drivers bucket access check + gcsfuseMetadataPrefetchOnMount: "true" # Fetch GCS metadata immediately at mount time +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: serving-bucket-pvc + namespace: +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 64Gi + volumeName: serving-bucket-pv + storageClassName: gcsfuse-sc # dummy storage class diff --git a/samples/gke-csi-yaml/tpu/training-pod.yaml b/samples/gke-csi-yaml/tpu/training-pod.yaml new file mode 100644 index 0000000000..13a8b60a56 --- /dev/null +++ b/samples/gke-csi-yaml/tpu/training-pod.yaml @@ -0,0 +1,23 @@ +apiVersion: v1 +kind: Pod +metadata: + name: gcs-fuse-csi-example-pod + namespace: + annotations: + gke-gcsfuse/volumes: "true" +spec: + containers: + # Your workload container spec + ... + volumeMounts: + - name: training-bucket-vol + mountPath: /training-data + serviceAccountName: + volumes: + # In-Memory (RAM disk) file cache if dataset fits. Uncomment to use + # - name: gke-gcsfuse-cache # gcsfuse file cache backed by RAM Disk + # emptyDir: + # medium: Memory + - name: training-bucket-vol + persistentVolumeClaim: + claimName: training-bucket-pvc \ No newline at end of file diff --git a/samples/gke-csi-yaml/tpu/training-pv.yaml b/samples/gke-csi-yaml/tpu/training-pv.yaml new file mode 100644 index 0000000000..2a98375c7a --- /dev/null +++ b/samples/gke-csi-yaml/tpu/training-pv.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: training-bucket-pv +spec: + accessModes: + - ReadWriteMany + capacity: + storage: 64Gi + persistentVolumeReclaimPolicy: Retain + storageClassName: gcsfuse-sc # dummy storage class + claimRef: + namespace: + name: training-bucket-pvc + mountOptions: + - implicit-dirs # Create implicit directories locally when accessed + - metadata-cache:negative-ttl-secs:0 # Disable caching for lookups of files/dirs that don't exist + - metadata-cache:ttl-secs:-1 # Keep cached metadata (file attributes, types) indefinitely time-wise + - metadata-cache:stat-cache-max-size-mb:-1 # Allow unlimited size for the file attribute (stat) cache + - metadata-cache:type-cache-max-size-mb:-1 # Allow unlimited size for the file/directory type cache + # if enabling the file cache, uncomment out to use # + # - file-cache:max-size-mb: # Allow DATASET_SIZE for the file content cache + # - file-cache:cache-file-for-range-read:true # Cache the entire file when any part is read sequentially + # - read_ahead_kb=1024 # Increase kernel read-ahead buffer + csi: + driver: gcsfuse.csi.storage.gke.io + volumeHandle: # Name of the GCS Bucket to mount + volumeAttributes: + skipCSIBucketAccessCheck: "true" # Bypass the CSI Drivers bucket access check + gcsfuseMetadataPrefetchOnMount: "true" # Fetch GCS metadata immediately at mount time +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: training-bucket-pvc + namespace: +spec: + accessModes: + - ReadWriteMany + resources: + requests: + storage: 64Gi + volumeName: training-bucket-pv + storageClassName: gcsfuse-sc # dummy storage class diff --git a/samples/gke-csi-yaml/training-pv.yaml b/samples/gke-csi-yaml/training-pv.yaml deleted file mode 100644 index 98d4be721a..0000000000 --- a/samples/gke-csi-yaml/training-pv.yaml +++ /dev/null @@ -1,44 +0,0 @@ -apiVersion: v1 -kind: PersistentVolume -metadata: - name: training-bucket-pv -spec: - accessModes: - - ReadWriteMany - capacity: - storage: 64Gi - persistentVolumeReclaimPolicy: Retain - storageClassName: gcsfuse-sc # dummy storage class - claimRef: - namespace: - name: training-bucket-pvc - mountOptions: - - implicit-dirs #set because most prefer ease of use - - metadata-cache:negative-ttl-secs:0 #disabled - - metadata-cache:ttl-secs:-1 #unlimited - - metadata-cache:stat-cache-max-size-mb:-1 #unlimited - - metadata-cache:type-cache-max-size-mb:-1 #unlimited - # if enabling the file cache, uncomment out to use # - # - file-cache:max-size-mb:-1 # only for GPUs - # - file-cache:cache-file-for-range-read:true - # - read_ahead_kb=1024 - csi: - driver: gcsfuse.csi.storage.gke.io - volumeHandle: # unique bucket name - volumeAttributes: - skipCSIBucketAccessCheck: "true" - gcsfuseMetadataPrefetchOnMount: "true" ---- -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: training-bucket-pvc - namespace: -spec: - accessModes: - - ReadWriteMany - resources: - requests: - storage: 64Gi - volumeName: training-bucket-pv - storageClassName: gcsfuse-sc # dummy storage class From ad174d246ef73cb8e9fd900e2595cf03066a5a23 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Wed, 23 Apr 2025 17:40:39 +0000 Subject: [PATCH 0385/1298] [nit] Adding a newline in the end of the yaml file for consistency --- samples/gke-csi-yaml/gpu/training-pod.yaml | 2 +- samples/gke-csi-yaml/tpu/training-pod.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/gke-csi-yaml/gpu/training-pod.yaml b/samples/gke-csi-yaml/gpu/training-pod.yaml index 74a1b25b46..38086b269a 100644 --- a/samples/gke-csi-yaml/gpu/training-pod.yaml +++ b/samples/gke-csi-yaml/gpu/training-pod.yaml @@ -20,4 +20,4 @@ spec: # medium: Memory - name: training-bucket-vol persistentVolumeClaim: - claimName: training-bucket-pvc \ No newline at end of file + claimName: training-bucket-pvc diff --git a/samples/gke-csi-yaml/tpu/training-pod.yaml b/samples/gke-csi-yaml/tpu/training-pod.yaml index 13a8b60a56..2daeb7ab88 100644 --- a/samples/gke-csi-yaml/tpu/training-pod.yaml +++ b/samples/gke-csi-yaml/tpu/training-pod.yaml @@ -20,4 +20,4 @@ spec: # medium: Memory - name: training-bucket-vol persistentVolumeClaim: - claimName: training-bucket-pvc \ No newline at end of file + claimName: training-bucket-pvc From 9f89c601dc0eff8f82c715bdc209be7573b02491 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Thu, 24 Apr 2025 09:25:58 +0000 Subject: [PATCH 0386/1298] Add GKE version note as well --- samples/gke-csi-yaml/gpu/README.md | 2 ++ samples/gke-csi-yaml/tpu/README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/samples/gke-csi-yaml/gpu/README.md b/samples/gke-csi-yaml/gpu/README.md index 21682333be..e0ef3a59bc 100644 --- a/samples/gke-csi-yaml/gpu/README.md +++ b/samples/gke-csi-yaml/gpu/README.md @@ -10,6 +10,8 @@ The configurations include recommendations tailored for the following common mac ## Deployment Instructions +**Note: The sample files are for GCSfuse GKE CSI driver running on GKE clusters of GKE version 1.32.2-gke.1297001 or greater.** + To utilize these sample configurations, follow the specified deployment order. For instance, to set up the serving workload: 1. **Deploy the PersistentVolume (PV) and PersistentVolumeClaim (PVC):** diff --git a/samples/gke-csi-yaml/tpu/README.md b/samples/gke-csi-yaml/tpu/README.md index 35edf865bf..be53cfa7bf 100644 --- a/samples/gke-csi-yaml/tpu/README.md +++ b/samples/gke-csi-yaml/tpu/README.md @@ -10,6 +10,8 @@ The configurations include recommendations tailored for the following common mac ## Deployment Instructions +**Note: The sample files are for GCSfuse GKE CSI driver running on GKE clusters of GKE version 1.32.2-gke.1297001 or greater.** + To utilize these sample configurations, follow the specified deployment order. For instance, to set up the serving workload: 1. **Deploy the PersistentVolume (PV) and PersistentVolumeClaim (PVC):** From 7bd897e93027f6b0a0e861bffac093ef0aa9f4b2 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 25 Apr 2025 11:07:51 +0530 Subject: [PATCH 0387/1298] Fix error handing with gcsFullReadCloser (#3245) Currently, gcsFullReadCloser's Read method returns an ErrUnexpectedEOF when the full length of the buffer that's passed into it is not filled completely. Return an EOF in such cases to have a consistent behavior. --- internal/monitor/full_read_closer.go | 14 ++++++++++---- internal/monitor/full_read_closer_test.go | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/internal/monitor/full_read_closer.go b/internal/monitor/full_read_closer.go index 16e36e0abf..abfa9f2314 100644 --- a/internal/monitor/full_read_closer.go +++ b/internal/monitor/full_read_closer.go @@ -31,11 +31,17 @@ func newGCSFullReadCloser(reader gcs.StorageReader) gcs.StorageReader { } // Read reads exactly len(buf) bytes from the wrapped StorageReader into buf. -// 1. the number of bytes copied and an ErrUnexpectedEOF if response size < buffer size -// 2. EOF only if no bytes were read. -// 3. n == len(buf) if and only if err == nil. +// 1. the number of bytes copied and an EOF if response size < buffer size +// 2. n == len(buf) if and only if err == nil. func (frc gcsFullReadCloser) Read(buf []byte) (n int, err error) { - return io.ReadFull(frc.wrapped, buf) + n, err = io.ReadFull(frc.wrapped, buf) + if err == io.ErrUnexpectedEOF { + // if an EOF is encountered before reading the full length of the buffer, + // ReadFull returns an ErrUnexpectedEOF error. This needs to be convered + // to EOF in order to have a consistent behavior (error) with and without gcsFullReadCloser. + err = io.EOF + } + return n, err } func (frc gcsFullReadCloser) ReadHandle() (rh storagev2.ReadHandle) { diff --git a/internal/monitor/full_read_closer_test.go b/internal/monitor/full_read_closer_test.go index 48f7c8c80a..f1f64f2915 100644 --- a/internal/monitor/full_read_closer_test.go +++ b/internal/monitor/full_read_closer_test.go @@ -59,7 +59,7 @@ func TestFullReaderCloser(t *testing.T) { data: []byte("0123"), bufSize: 5, expectedData: []byte("0123"), - expectedErr: io.ErrUnexpectedEOF, + expectedErr: io.EOF, }, { name: "small_buffer", From f1b23d26c20944fb6a6eaf65d4fb1ca759507d0b Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Fri, 25 Apr 2025 11:12:55 +0530 Subject: [PATCH 0388/1298] fix: Return complete object on flush writer (#3230) * update flush writer to return object for zb * update tests * update comment * handle nil writer attributes * review comments * skip rename tests --- .../bufferedwrites/buffered_write_handler.go | 26 +++++++++++------- .../buffered_write_handler_test.go | 20 ++++++++------ internal/bufferedwrites/upload_handler.go | 10 +++---- .../bufferedwrites/upload_handler_test.go | 27 ++++++++++--------- internal/fs/inode/file.go | 2 +- .../fs/inode/file_streaming_writes_test.go | 10 +++---- internal/gcsx/prefix_bucket.go | 2 +- internal/monitor/bucket.go | 6 ++--- internal/ratelimit/throttled_bucket.go | 2 +- internal/storage/bucket_handle.go | 12 ++++++--- internal/storage/caching/fast_stat_bucket.go | 16 +++++------ .../storage/caching/fast_stat_bucket_test.go | 23 +++++++--------- internal/storage/debug_bucket.go | 4 +-- internal/storage/fake/bucket.go | 11 +++++--- internal/storage/gcs/bucket.go | 2 +- internal/storage/mock/testify_mock_bucket.go | 4 +-- internal/storage/mock_bucket.go | 6 ++--- internal/storage/testify_mock_bucket.go | 4 +-- .../streaming_writes/rename_file_test.go | 13 +++++++++ 19 files changed, 113 insertions(+), 87 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 0534b7293d..ceea019f02 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -34,8 +34,11 @@ type BufferedWriteHandler interface { // the capacity is available otherwise writes to a new buffer. Write(data []byte, offset int64) (err error) - // Sync uploads all the pending full buffers to GCS. - Sync() (err error) + // Sync uploads all the pending buffers to GCS. + // Sync returns + // 1. un-finalized object created on GCS for zonal buckets. + // 2. nil object for non-zonal buckets. + Sync() (*gcs.MinObject, error) // Flush finalizes the upload. Flush() (*gcs.MinObject, error) @@ -178,12 +181,12 @@ func (wh *bufferedWriteHandlerImpl) appendBuffer(data []byte) (err error) { return } -func (wh *bufferedWriteHandlerImpl) Sync() (err error) { +func (wh *bufferedWriteHandlerImpl) Sync() (o *gcs.MinObject, err error) { // Upload current block (for both regional and zonal buckets). if wh.current != nil && wh.current.Size() != 0 { - err := wh.uploadHandler.Upload(wh.current) + err = wh.uploadHandler.Upload(wh.current) if err != nil { - return err + return nil, err } wh.current = nil } @@ -194,12 +197,12 @@ func (wh *bufferedWriteHandlerImpl) Sync() (err error) { // other operations like read. // This functionality is exclusively supported on zonal buckets. if wh.uploadHandler.bucket.BucketType().Zonal { - n, err := wh.uploadHandler.FlushPendingWrites() + o, err = wh.uploadHandler.FlushPendingWrites() if err != nil { - return err + return nil, err } - if n != wh.totalSize { - return fmt.Errorf("could not upload entire data, expected offset %d, Got %d", wh.totalSize, n) + if o.Size != uint64(wh.totalSize) { + return nil, fmt.Errorf("could not upload entire data, expected offset %d, Got %d", wh.totalSize, o.Size) } } // Release memory used by buffers. @@ -209,7 +212,10 @@ func (wh *bufferedWriteHandlerImpl) Sync() (err error) { logger.Errorf("blockPool.ClearFreeBlockChannel() failed during sync: %v", err) } err = wh.uploadHandler.UploadError() - return + if err != nil { + return nil, err + } + return o, nil } // Flush finalizes the upload. diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index b17effbce9..9a4495781d 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -288,12 +288,13 @@ func (testSuite *BufferedWriteTest) TestSync5InProgressBlocks() { } // Wait for 5 blocks to upload successfully. - err = testSuite.bwh.Sync() + o, err := testSuite.bwh.Sync() assert.NoError(testSuite.T(), err) bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) + assert.Nil(testSuite.T(), o) } func (testSuite *BufferedWriteTest) TestSyncPartialBlockTableDriven() { @@ -325,7 +326,7 @@ func (testSuite *BufferedWriteTest) TestSyncPartialBlockTableDriven() { } for _, tc := range testCases { - testSuite.T().Run(tc.name, func(t *testing.T) { + testSuite.Run(tc.name, func() { testSuite.setupTestWithBucketType(tc.bucketType) buffer, err := operations.GenerateRandomData(int64(blockSize * tc.numBlocks)) assert.NoError(testSuite.T(), err) @@ -333,10 +334,9 @@ func (testSuite *BufferedWriteTest) TestSyncPartialBlockTableDriven() { require.Nil(testSuite.T(), err) // Wait for 3 blocks to upload successfully. - err = testSuite.bwh.Sync() + o, err := testSuite.bwh.Sync() - assert.NoError(t, err) - assert.NoError(testSuite.T(), err) + require.NoError(testSuite.T(), err) bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) // Current block should also be uploaded. assert.Nil(testSuite.T(), bwhImpl.current) @@ -345,14 +345,17 @@ func (testSuite *BufferedWriteTest) TestSyncPartialBlockTableDriven() { // Read the object from back door. content, err := storageutil.ReadObject(context.Background(), bwhImpl.uploadHandler.bucket, bwhImpl.uploadHandler.objectName) if tc.bucketType.Zonal { + require.NotNil(testSuite.T(), o) + assert.EqualValues(testSuite.T(), int64(blockSize*tc.numBlocks), o.Size) require.NoError(testSuite.T(), err) assert.Equal(testSuite.T(), buffer, content) } else { + require.Nil(testSuite.T(), o) // Since the object is not finalized, the object will not be available // on GCS for non-zonal buckets. - require.Error(t, err) + require.Error(testSuite.T(), err) var notFoundErr *gcs.NotFoundError - assert.ErrorAs(t, err, ¬FoundErr) + assert.ErrorAs(testSuite.T(), err, ¬FoundErr) } }) } @@ -370,10 +373,11 @@ func (testSuite *BufferedWriteTest) TestSyncBlocksWithError() { bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) bwhImpl.uploadHandler.uploadError.Store(&errUploadFailure) - err = testSuite.bwh.Sync() + o, err := testSuite.bwh.Sync() assert.Error(testSuite.T(), err) assert.Equal(testSuite.T(), errUploadFailure, err) + assert.Nil(testSuite.T(), o) } func (testSuite *BufferedWriteTest) TestFlushWithNonZeroTruncatedLengthForEmptyObject() { diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 0eee536872..b49a113248 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -180,24 +180,24 @@ func (uh *UploadHandler) ensureWriter() error { } // FlushPendingWrites uploads any data in the write buffer. -func (uh *UploadHandler) FlushPendingWrites() (int64, error) { +func (uh *UploadHandler) FlushPendingWrites() (*gcs.MinObject, error) { uh.wg.Wait() // Writer may not have been created for empty file creation flow or for very // small writes of size less than 1 block. err := uh.ensureWriter() if err != nil { - return 0, fmt.Errorf("uh.ensureWriter() failed: %v", err) + return nil, fmt.Errorf("uh.ensureWriter() failed: %v", err) } - offset, err := uh.bucket.FlushPendingWrites(context.Background(), uh.writer) + o, err := uh.bucket.FlushPendingWrites(context.Background(), uh.writer) if err != nil { // FlushUpload already returns GCS error so no need to convert again. uh.uploadError.Store(&err) logger.Errorf("FlushUpload failed for object %s: %v", uh.objectName, err) - return 0, err + return nil, err } - return offset, nil + return o, nil } func (uh *UploadHandler) CancelUpload() { diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 4e7b0e5541..2c598a0ecc 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -165,51 +165,52 @@ func (t *UploadHandlerTest) TestFinalizeWhenFinalizeUploadFails() { func (t *UploadHandlerTest) TestFlushWithWriterAlreadyPresent() { writer := &storagemock.Writer{} - mockOffset := 10 - t.mockBucket.On("FlushPendingWrites", mock.Anything, writer).Return(mockOffset, nil) + mockObject := &gcs.MinObject{Size: 100} + t.mockBucket.On("FlushPendingWrites", mock.Anything, writer).Return(mockObject, nil) t.uh.writer = writer - offset, err := t.uh.FlushPendingWrites() + o, err := t.uh.FlushPendingWrites() require.NoError(t.T(), err) - assert.EqualValues(t.T(), mockOffset, offset) + assert.Equal(t.T(), mockObject, o) } func (t *UploadHandlerTest) TestFlushWithNoWriter() { writer := &storagemock.Writer{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) - mockOffset := 10 - t.mockBucket.On("FlushPendingWrites", mock.Anything, writer).Return(mockOffset, nil) + mockObject := &gcs.MinObject{Size: 10} + t.mockBucket.On("FlushPendingWrites", mock.Anything, writer).Return(mockObject, nil) - offset, err := t.uh.FlushPendingWrites() + o, err := t.uh.FlushPendingWrites() require.NoError(t.T(), err) - assert.EqualValues(t.T(), mockOffset, offset) + assert.Equal(t.T(), mockObject, o) } func (t *UploadHandlerTest) TestFlushWithNoWriterWhenCreateObjectWriterFails() { t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("taco")) assert.Nil(t.T(), t.uh.writer) - offset, err := t.uh.FlushPendingWrites() + o, err := t.uh.FlushPendingWrites() require.Error(t.T(), err) assert.ErrorContains(t.T(), err, "taco") assert.ErrorContains(t.T(), err, "createObjectWriter") - assert.EqualValues(t.T(), 0, offset) + assert.Nil(t.T(), o) } func (t *UploadHandlerTest) TestFlushWhenFlushPendingWritesFails() { writer := &storagemock.Writer{} t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) - t.mockBucket.On("FlushPendingWrites", mock.Anything, writer).Return(0, fmt.Errorf("taco")) + var minObj *gcs.MinObject = nil + t.mockBucket.On("FlushPendingWrites", mock.Anything, writer).Return(minObj, fmt.Errorf("taco")) - offset, err := t.uh.FlushPendingWrites() + o, err := t.uh.FlushPendingWrites() require.Error(t.T(), err) - assert.EqualValues(t.T(), 0, offset) + assert.Nil(t.T(), nil, o) assert.ErrorContains(t.T(), err, "taco") assertUploadFailureError(t.T(), t.uh) } diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 05c09dfdf6..da02db06b6 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -666,7 +666,7 @@ func (f *FileInode) SyncPendingBufferedWrites() error { if f.bwh == nil { return nil } - err := f.bwh.Sync() + _, err := f.bwh.Sync() var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { return &gcsfuse_errors.FileClobberedError{ diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 89d1ececa2..5553695f06 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -735,11 +735,11 @@ func (t *FakeBufferedWriteHandler) WriteFileInfo() bufferedwrites.WriteFileInfo } } -func (t *FakeBufferedWriteHandler) Sync() (err error) { return nil } -func (t *FakeBufferedWriteHandler) SetMtime(_ time.Time) {} -func (t *FakeBufferedWriteHandler) Truncate(_ int64) error { return nil } -func (t *FakeBufferedWriteHandler) Destroy() error { return nil } -func (t *FakeBufferedWriteHandler) Unlink() {} +func (t *FakeBufferedWriteHandler) Sync() (*gcs.MinObject, error) { return nil, nil } +func (t *FakeBufferedWriteHandler) SetMtime(_ time.Time) {} +func (t *FakeBufferedWriteHandler) Truncate(_ int64) error { return nil } +func (t *FakeBufferedWriteHandler) Destroy() error { return nil } +func (t *FakeBufferedWriteHandler) Unlink() {} func (t *FileStreamingWritesTest) TestWriteUsingBufferedWritesFails() { err := t.in.CreateBufferedOrTempWriter(t.ctx) diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index 4aa9bd222d..65a2c245cb 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -119,7 +119,7 @@ func (b *prefixBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs return } -func (b *prefixBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (offset int64, err error) { +func (b *prefixBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { return b.wrapped.FlushPendingWrites(ctx, w) } diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index b951b4884c..0d60a2c581 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -103,11 +103,11 @@ func (mb *monitoringBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (* return o, err } -func (mb *monitoringBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (int64, error) { +func (mb *monitoringBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { startTime := time.Now() - offset, err := mb.wrapped.FlushPendingWrites(ctx, w) + o, err := mb.wrapped.FlushPendingWrites(ctx, w) recordRequest(ctx, mb.metricHandle, "FlushPendingWrites", startTime) - return offset, err + return o, err } func (mb *monitoringBucket) CopyObject( diff --git a/internal/ratelimit/throttled_bucket.go b/internal/ratelimit/throttled_bucket.go index 2b7244950c..e548961210 100644 --- a/internal/ratelimit/throttled_bucket.go +++ b/internal/ratelimit/throttled_bucket.go @@ -115,7 +115,7 @@ func (b *throttledBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gc return b.wrapped.FinalizeUpload(ctx, w) } -func (b *throttledBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (int64, error) { +func (b *throttledBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { // FlushPendingWrites is not throttled to prevent permanent data loss in case the // limiter's burst size is exceeded. // Note: CreateObjectChunkWriter, a prerequisite for FlushPendingWrites, diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 4e8bc79e13..4b7e6ac27a 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -255,18 +255,24 @@ func (bh *bucketHandle) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gc return } -func (bh *bucketHandle) FlushPendingWrites(ctx context.Context, w gcs.Writer) (offset int64, err error) { +func (bh *bucketHandle) FlushPendingWrites(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { defer func() { err = gcs.GetGCSError(err) }() - offset, err = w.Flush() + _, err = w.Flush() if err != nil { err = fmt.Errorf("error in FlushPendingWrites : %w", err) return } - return offset, err + attrs := w.Attrs() // Retrieving the attributes of the created object. + // Converting attrs to type *MinObject. + o = storageutil.ObjectAttrsToMinObject(attrs) + if o == nil { + return nil, fmt.Errorf("FlushPendingWrites: nil object returned after w.Flush()") + } + return } func (bh *bucketHandle) CopyObject(ctx context.Context, req *gcs.CopyObjectRequest) (o *gcs.Object, err error) { diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index 3a9fb73dd2..a16cb0942d 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -255,22 +255,18 @@ func (b *fastStatBucket) FinalizeUpload(ctx context.Context, writer gcs.Writer) return o, err } -func (b *fastStatBucket) FlushPendingWrites(ctx context.Context, writer gcs.Writer) (int64, error) { +func (b *fastStatBucket) FlushPendingWrites(ctx context.Context, writer gcs.Writer) (*gcs.MinObject, error) { name := writer.ObjectName() - hit, o := b.cache.LookUp(name, b.clock.Now()) - if hit { - // Throw away any existing record for this object. - b.invalidate(name) - } + // Throw away any existing record for this object. + b.invalidate(name) - offset, err := b.wrapped.FlushPendingWrites(ctx, writer) + o, err := b.wrapped.FlushPendingWrites(ctx, writer) // Record the new object if err is nil. - if err == nil && hit && o != nil { - o.Size = uint64(offset) + if err == nil { b.insertMinObject(o) } - return offset, err + return o, err } // LOCKS_EXCLUDED(b.mu) diff --git a/internal/storage/caching/fast_stat_bucket_test.go b/internal/storage/caching/fast_stat_bucket_test.go index 4fb94a9ff8..90f3ed632e 100644 --- a/internal/storage/caching/fast_stat_bucket_test.go +++ b/internal/storage/caching/fast_stat_bucket_test.go @@ -293,50 +293,45 @@ func (t *FlushPendingWritesTest) WrappedFails() { writer := &storage.ObjectWriter{ Writer: &gostorage.Writer{ObjectAttrs: gostorage.ObjectAttrs{Name: name}}, } - // Expect cache Lookup. - ExpectCall(t.cache, "LookUp")(name, timeutil.TimeEq(t.clock.Now())). - WillOnce(Return(true, &gcs.MinObject{})) // Expect cache Erase. ExpectCall(t.cache, "Erase")(name) // Expect call to Wrapped method. var wrappedWriter gcs.Writer + mockObject := &gcs.MinObject{Size: 10} ExpectCall(t.wrapped, "FlushPendingWrites")(Any(), Any()). - WillOnce(DoAll(SaveArg(1, &wrappedWriter), Return(10, errors.New("taco")))) + WillOnce(DoAll(SaveArg(1, &wrappedWriter), Return(mockObject, errors.New("taco")))) // Call. - gotOffset, err := t.bucket.FlushPendingWrites(context.TODO(), writer) + gotObject, err := t.bucket.FlushPendingWrites(context.TODO(), writer) ExpectEq(writer, wrappedWriter) - ExpectEq(10, gotOffset) + ExpectEq(mockObject, gotObject) ExpectThat(err, Error(HasSubstr("taco"))) } func (t *FlushPendingWritesTest) WrappedSucceeds() { const name = "taco" - minObject := &gcs.MinObject{} writer := &storage.ObjectWriter{ Writer: &gostorage.Writer{ObjectAttrs: gostorage.ObjectAttrs{Name: name}}, } var err error - // Expect cache Lookup. - ExpectCall(t.cache, "LookUp")(name, timeutil.TimeEq(t.clock.Now())). - WillOnce(Return(true, minObject)) // Expect cache Erase. ExpectCall(t.cache, "Erase")(name) // Wrapped. + mockObject := &gcs.MinObject{Size: 10} ExpectCall(t.wrapped, "FlushPendingWrites")(Any(), Any()). - WillOnce(Return(10, nil)) + WillOnce(Return(mockObject, nil)) // Insert. var cachedMinObject *gcs.MinObject ExpectCall(t.cache, "Insert")(Any(), timeutil.TimeEq(t.clock.Now().Add(primaryCacheTTL))). WillOnce(DoAll(SaveArg(0, &cachedMinObject))) // Call - offset, err := t.bucket.FlushPendingWrites(context.TODO(), writer) + gotObject, err := t.bucket.FlushPendingWrites(context.TODO(), writer) AssertEq(nil, err) - ExpectEq(10, offset) - ExpectEq(10, cachedMinObject.Size) + ExpectEq(mockObject, gotObject) + ExpectEq(mockObject.Size, cachedMinObject.Size) } //////////////////////////////////////////////////////////////////////// diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index 9122c954f0..cf49e57c10 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -189,11 +189,11 @@ func (b *debugBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs. return } -func (b *debugBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (offset int64, err error) { +func (b *debugBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { id, desc, start := b.startRequest("FlushPendingWrites(%q)", w.ObjectName()) defer b.finishRequest(id, desc, start, &err) - offset, err = b.wrapped.FlushPendingWrites(ctx, w) + o, err = b.wrapped.FlushPendingWrites(ctx, w) return } diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index da5e0ea44e..ed987b2cf2 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -691,16 +691,21 @@ func (b *bucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.CreateObj return NewFakeObjectWriter(b, req) } -func (b *bucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (int64, error) { +func (b *bucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { b.mu.Lock() defer b.mu.Unlock() offset, err := w.Flush() if err != nil { - return 0, err + return nil, err } - return offset, nil + fakeObjectWriter, ok := w.(*FakeObjectWriter) + if !ok { + return nil, fmt.Errorf("could not type assert gcs.Writer to FakeObjectWriter") + } + fakeObjectWriter.Object.Size = uint64(offset) + return fakeObjectWriter.Object, nil } func (b *bucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { diff --git a/internal/storage/gcs/bucket.go b/internal/storage/gcs/bucket.go index 9c30476880..a0b1893646 100644 --- a/internal/storage/gcs/bucket.go +++ b/internal/storage/gcs/bucket.go @@ -108,7 +108,7 @@ type Bucket interface { // FlushPendingWrites is used for zonal buckets to flush any pending data in // the writer buffer. The object is not finalized and can be appended further. - FlushPendingWrites(ctx context.Context, writer Writer) (int64, error) + FlushPendingWrites(ctx context.Context, writer Writer) (*MinObject, error) // Copy an object to a new name, preserving all metadata. Any existing // generation of the destination name will be overwritten. diff --git a/internal/storage/mock/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go index 260d227b7c..ea131076d5 100644 --- a/internal/storage/mock/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -65,9 +65,9 @@ func (m *TestifyMockBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (* return args.Get(0).(*gcs.MinObject), args.Error(1) } -func (m *TestifyMockBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (int64, error) { +func (m *TestifyMockBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { args := m.Called(ctx, w) - return int64(args.Int(0)), args.Error(1) + return args.Get(0).(*gcs.MinObject), args.Error(1) } func (m *TestifyMockBucket) CopyObject(ctx context.Context, req *gcs.CopyObjectRequest) (*gcs.Object, error) { diff --git a/internal/storage/mock_bucket.go b/internal/storage/mock_bucket.go index c7ec9d2bea..21310cccd7 100644 --- a/internal/storage/mock_bucket.go +++ b/internal/storage/mock_bucket.go @@ -188,7 +188,7 @@ func (m *mockBucket) FinalizeUpload(p0 context.Context, p1 gcs.Writer) (o0 *gcs. return } -func (m *mockBucket) FlushPendingWrites(p0 context.Context, p1 gcs.Writer) (o0 int64, o1 error) { +func (m *mockBucket) FlushPendingWrites(p0 context.Context, p1 gcs.Writer) (o0 *gcs.MinObject, o1 error) { // Get a file name and line number for the caller. _, file, line, _ := runtime.Caller(1) @@ -204,9 +204,9 @@ func (m *mockBucket) FlushPendingWrites(p0 context.Context, p1 gcs.Writer) (o0 i panic(fmt.Sprintf("mockBucket.FlushPendingWrites: invalid return values: %v", retVals)) } - // o0 int64 (offset) + // o0 *gcs.MinObject if retVals[0] != nil { - o0 = retVals[0].(int64) + o0 = retVals[0].(*gcs.MinObject) } // o1 error if retVals[1] != nil { diff --git a/internal/storage/testify_mock_bucket.go b/internal/storage/testify_mock_bucket.go index 2b080cde10..792a7f3ac8 100644 --- a/internal/storage/testify_mock_bucket.go +++ b/internal/storage/testify_mock_bucket.go @@ -65,9 +65,9 @@ func (m *TestifyMockBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (* return args.Get(0).(*gcs.MinObject), args.Error(1) } -func (m *TestifyMockBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (int64, error) { +func (m *TestifyMockBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { args := m.Called(ctx, w) - return args.Get(0).(int64), args.Error(1) + return args.Get(0).(*gcs.MinObject), args.Error(1) } func (m *TestifyMockBucket) CopyObject(ctx context.Context, req *gcs.CopyObjectRequest) (*gcs.Object, error) { diff --git a/tools/integration_tests/streaming_writes/rename_file_test.go b/tools/integration_tests/streaming_writes/rename_file_test.go index aac699166f..4b998a8863 100644 --- a/tools/integration_tests/streaming_writes/rename_file_test.go +++ b/tools/integration_tests/streaming_writes/rename_file_test.go @@ -19,10 +19,15 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/stretchr/testify/require" ) func (t *defaultMountCommonTest) TestRenameBeforeFileIsFlushed() { + if setup.IsZonalBucketRun() { + //TODO (b/413296959): Re-enable after update inode state changes are merged. + t.T().Skip("Skipping Zonal Bucket Rename tests until inode state is fixed.") + } operations.WriteWithoutClose(t.f1, t.data, t.T()) operations.WriteWithoutClose(t.f1, t.data, t.T()) operations.VerifyStatFile(t.filePath, int64(2*len(t.data)), FilePerms, t.T()) @@ -43,6 +48,10 @@ func (t *defaultMountCommonTest) TestRenameBeforeFileIsFlushed() { } func (t *defaultMountCommonTest) TestSyncAfterRenameSucceeds() { + if setup.IsZonalBucketRun() { + //TODO (b/413296959): Re-enable after update inode state changes are merged. + t.T().Skip("Skipping Zonal Bucket Rename tests until inode state is fixed.") + } _, err := t.f1.WriteAt([]byte(t.data), 0) require.NoError(t.T(), err) operations.VerifyStatFile(t.filePath, int64(len(t.data)), FilePerms, t.T()) @@ -64,6 +73,10 @@ func (t *defaultMountCommonTest) TestSyncAfterRenameSucceeds() { } func (t *defaultMountCommonTest) TestAfterRenameWriteFailsWithStaleNFSFileHandleError() { + if setup.IsZonalBucketRun() { + //TODO (b/413296959): Re-enable after update inode state changes are merged. + t.T().Skip("Skipping Zonal Bucket Rename tests until inode state is fixed.") + } _, err := t.f1.WriteAt([]byte(t.data), 0) require.NoError(t.T(), err) operations.VerifyStatFile(t.filePath, int64(len(t.data)), FilePerms, t.T()) From 4de0bfcbc62a9f96bb91582efd472fa274c70d30 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Mon, 28 Apr 2025 10:06:27 +0530 Subject: [PATCH 0389/1298] fix: promote zonal object to generation backed inode on sync (#3246) * promote zonal object to generation backed inode on sync * remove empty line * fix test * fix review comments --- internal/fs/handle/file.go | 4 +- internal/fs/inode/file.go | 47 +++++++++++-------- .../fs/inode/file_streaming_writes_test.go | 28 +++++++---- internal/fs/inode/file_test.go | 4 +- .../streaming_writes/rename_file_test.go | 13 ----- 5 files changed, 53 insertions(+), 43 deletions(-) diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 8fe9fb76fe..bc265cd810 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -109,7 +109,9 @@ func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequen // because the inode is dirty). fh.inode.Lock() // Ensure all pending writes to Zonal Buckets are flushed before issuing a read. - err = fh.inode.SyncPendingBufferedWrites() + // Updating inode state is not required here because inode state for Zonal Buckets will + // be updated at time of BWH creation. + _, err = fh.inode.SyncPendingBufferedWrites() if err != nil { fh.inode.Unlock() err = fmt.Errorf("fh.inode.SyncPendingBufferedWrites: %w", err) diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index da02db06b6..4d0cd46fde 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -653,8 +653,8 @@ func (f *FileInode) flushUsingBufferedWriteHandler() error { if err != nil { return fmt.Errorf("f.bwh.Flush(): %w", err) } - - f.updateInodeStateAfterSync(obj) + // If we finalized the object, we need to update our state. + f.updateInodeStateAfterFlush(obj) return nil } @@ -662,24 +662,28 @@ func (f *FileInode) flushUsingBufferedWriteHandler() error { // It is a no-op when bwh is nil. // // LOCKS_REQUIRED(f.mu) -func (f *FileInode) SyncPendingBufferedWrites() error { +func (f *FileInode) SyncPendingBufferedWrites() (gcsSynced bool, err error) { if f.bwh == nil { - return nil + return } - _, err := f.bwh.Sync() + minObj, err := f.bwh.Sync() var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { - return &gcsfuse_errors.FileClobberedError{ + err = &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("f.bwh.Sync(): %w", err), } + return } if err != nil { - return fmt.Errorf("f.bwh.Sync(): %w", err) + err = fmt.Errorf("f.bwh.Sync(): %w", err) + return } - // Update the f.src.Size to the current Size of object - // on GCS after flushing pending buffers. - f.src.Size = uint64(f.bwh.WriteFileInfo().TotalSize) - return nil + // We return gcsSynced as true when we get minObject from Sync() for Zonal Buckets. + // For Non-Zonal Buckets minObj is always nil. + gcsSynced = minObj != nil + // If we flushed out object, we need to update our state. + f.updateInodeStateAfterSync(minObj) + return } // Set the mtime for this file. May involve a round trip to GCS. @@ -813,9 +817,7 @@ func (f *FileInode) Sync(ctx context.Context) (gcsSynced bool, err error) { } if f.bwh != nil { - // SyncPendingBufferedWrites does not finalize the upload, - // so return gcsSynced as false. - err = f.SyncPendingBufferedWrites() + gcsSynced, err = f.SyncPendingBufferedWrites() if err != nil { err = fmt.Errorf("could not sync what has been written so far: %w", err) } @@ -860,7 +862,7 @@ func (f *FileInode) syncUsingContent(ctx context.Context) error { } minObj := storageutil.ConvertObjToMinObject(newObj) // If we wrote out a new object, we need to update our state. - f.updateInodeStateAfterSync(minObj) + f.updateInodeStateAfterFlush(minObj) return nil } @@ -887,6 +889,16 @@ func (f *FileInode) Flush(ctx context.Context) (err error) { return f.syncUsingContent(ctx) } +func (f *FileInode) updateInodeStateAfterFlush(minObj *gcs.MinObject) { + if minObj != nil && !f.localFileCache { + // Set BWH to nil as as object has been finalized. + if f.bwh != nil { + f.bwh = nil + } + f.updateInodeStateAfterSync(minObj) + } +} + func (f *FileInode) updateInodeStateAfterSync(minObj *gcs.MinObject) { if minObj != nil && !f.localFileCache { f.src = *minObj @@ -900,12 +912,7 @@ func (f *FileInode) updateInodeStateAfterSync(minObj *gcs.MinObject) { f.content.Destroy() f.content = nil } - if f.bwh != nil { - f.bwh = nil - } } - - return } // Updates the min object stored in MRDWrapper corresponding to the inode. diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 5553695f06..1bf7e805c7 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -173,10 +173,14 @@ func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritative assert.True(t.T(), t.in.SourceGenerationIsAuthoritative()) } -func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBuckets() { +func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBucketsPromotesInodeToNonLocal() { assert.NoError(t.T(), t.in.Write(t.ctx, []byte("pizza"), 0)) - assert.NoError(t.T(), t.in.SyncPendingBufferedWrites()) + gcsSynced, err := t.in.SyncPendingBufferedWrites() + + require.NoError(t.T(), err) + assert.True(t.T(), gcsSynced) + assert.False(t.T(), t.in.IsLocal()) content, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) require.NoError(t.T(), err) assert.Equal(t.T(), "pizza", string(content)) @@ -186,8 +190,10 @@ func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZon assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0)) assert.Equal(t.T(), uint64(0), t.in.src.Size) - assert.NoError(t.T(), t.in.SyncPendingBufferedWrites()) + gcsSynced, err := t.in.SyncPendingBufferedWrites() + require.NoError(t.T(), err) + assert.True(t.T(), gcsSynced) assert.Equal(t.T(), uint64(6), t.in.src.Size) } @@ -209,20 +215,26 @@ func (t *FileStreamingWritesTest) TestSourceGenerationIsAuthoritativeReturnsFals assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) } -func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBuckets() { +func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucketsDoesNotPromoteInodeToNonLocal() { assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) - assert.NoError(t.T(), t.in.SyncPendingBufferedWrites()) + gcsSynced, err := t.in.SyncPendingBufferedWrites() + + require.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) + assert.True(t.T(), t.in.IsLocal()) operations.ValidateObjectNotFoundErr(t.ctx, t.T(), t.bucket, t.in.Name().GcsObjectName()) } -func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucketsDoesUpdateSrcSize() { +func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucketsDoesNotUpdateSrcSize() { assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0)) assert.Equal(t.T(), uint64(0), t.in.src.Size) - assert.NoError(t.T(), t.in.SyncPendingBufferedWrites()) + gcsSynced, err := t.in.SyncPendingBufferedWrites() - assert.Equal(t.T(), uint64(6), t.in.src.Size) + require.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) + assert.Equal(t.T(), uint64(0), t.in.src.Size) } func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempFile() { diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index a5aefa31b6..d0e70ae87a 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -201,8 +201,10 @@ func (t *FileTest) TestSyncPendingBufferedWritesReturnsNilAndNoOpForNonStreaming assert.Equal(t.T(), t.initialContents, string(contents)) assert.NoError(t.T(), t.in.Write(t.ctx, []byte("bar"), 0)) - assert.NoError(t.T(), t.in.SyncPendingBufferedWrites()) + gcsSynced, err := t.in.SyncPendingBufferedWrites() + require.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) contents, err = storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) require.NoError(t.T(), err) assert.Equal(t.T(), t.initialContents, string(contents)) diff --git a/tools/integration_tests/streaming_writes/rename_file_test.go b/tools/integration_tests/streaming_writes/rename_file_test.go index 4b998a8863..aac699166f 100644 --- a/tools/integration_tests/streaming_writes/rename_file_test.go +++ b/tools/integration_tests/streaming_writes/rename_file_test.go @@ -19,15 +19,10 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/stretchr/testify/require" ) func (t *defaultMountCommonTest) TestRenameBeforeFileIsFlushed() { - if setup.IsZonalBucketRun() { - //TODO (b/413296959): Re-enable after update inode state changes are merged. - t.T().Skip("Skipping Zonal Bucket Rename tests until inode state is fixed.") - } operations.WriteWithoutClose(t.f1, t.data, t.T()) operations.WriteWithoutClose(t.f1, t.data, t.T()) operations.VerifyStatFile(t.filePath, int64(2*len(t.data)), FilePerms, t.T()) @@ -48,10 +43,6 @@ func (t *defaultMountCommonTest) TestRenameBeforeFileIsFlushed() { } func (t *defaultMountCommonTest) TestSyncAfterRenameSucceeds() { - if setup.IsZonalBucketRun() { - //TODO (b/413296959): Re-enable after update inode state changes are merged. - t.T().Skip("Skipping Zonal Bucket Rename tests until inode state is fixed.") - } _, err := t.f1.WriteAt([]byte(t.data), 0) require.NoError(t.T(), err) operations.VerifyStatFile(t.filePath, int64(len(t.data)), FilePerms, t.T()) @@ -73,10 +64,6 @@ func (t *defaultMountCommonTest) TestSyncAfterRenameSucceeds() { } func (t *defaultMountCommonTest) TestAfterRenameWriteFailsWithStaleNFSFileHandleError() { - if setup.IsZonalBucketRun() { - //TODO (b/413296959): Re-enable after update inode state changes are merged. - t.T().Skip("Skipping Zonal Bucket Rename tests until inode state is fixed.") - } _, err := t.f1.WriteAt([]byte(t.data), 0) require.NoError(t.T(), err) operations.VerifyStatFile(t.filePath, int64(len(t.data)), FilePerms, t.T()) From 5063bca07964a6dd86fbf936f225076d1fb8a8b3 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 28 Apr 2025 16:09:28 +0530 Subject: [PATCH 0390/1298] [Random Reader Refactoring] Range Reader implementation - 2 (#3233) * add unit tests * remove unnecessary changes * add unit tests * remove coverage file * add more test * remove coverage file * add more unit test * remove coverage file * remove some tests to cut PR lenght * remove some tests to cut PR lenght * lint fix * passing same context to start read * small update * remove unnecessary file * use helper * small update * update tes name * adding method * add comment * add unit tests * add comments * small fixes * update comment * remove coverage file * add doc comment * small fixes * review comments * Write test in TDD format * small ifx * reuse thr function * update var name * create new context * remove unnecessary changes --- internal/gcsx/client_readers/range_reader.go | 247 +++++++++++++++++- .../gcsx/client_readers/range_reader_test.go | 204 ++++++++++++++- 2 files changed, 439 insertions(+), 12 deletions(-) diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index 16981c618d..f8c5a87918 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -15,17 +15,38 @@ package gcsx import ( + "context" + "errors" "fmt" + "io" + "math" + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" +) + +const ( + MiB = 1 << 20 + + // Max read size in bytes for random reads. + // If the average read size (between seeks) is below this number, reads will optimise for random access. + // We will skip forwards in a GCS response at most this many bytes. + maxReadSize = 8 * MiB ) type RangeReader struct { - // TODO: Add additional fields as needed. gcsx.Reader + object *gcs.MinObject + bucket gcs.Bucket + + // start is the current read offset of the reader. start int64 + + // limit is the exclusive upper bound up to which the reader can read. limit int64 // If non-nil, an in-flight read request and a function for cancelling it. @@ -38,12 +59,18 @@ type RangeReader struct { // checks. readHandle []byte cancel func() + + readType string + metricHandle common.MetricHandle } -func NewRangeReader() *RangeReader { +func NewRangeReader(object *gcs.MinObject, bucket gcs.Bucket, metricHandle common.MetricHandle) *RangeReader { return &RangeReader{ - start: -1, - limit: -1, + object: object, + bucket: bucket, + metricHandle: metricHandle, + start: -1, + limit: -1, } } @@ -81,3 +108,215 @@ func (rr *RangeReader) closeReader() { logger.Warnf("error while closing reader: %v", err) } } + +func (rr *RangeReader) ReadAt(ctx context.Context, req *gcsx.GCSReaderRequest) (gcsx.ReaderResponse, error) { + readerResponse := gcsx.ReaderResponse{ + DataBuf: req.Buffer, + Size: 0, + } + var err error + + readerResponse.Size, err = rr.readFromRangeReader(ctx, req.Buffer, req.Offset, req.EndOffset, rr.readType) + + return readerResponse, err +} + +// readFromRangeReader reads using the NewReader interface of go-sdk. It uses +// the existing reader if available, otherwise makes a call to GCS. +// Before calling this method we have to use invalidateReaderIfMisalignedOrTooSmall to get the reader start at the correct position. +func (rr *RangeReader) readFromRangeReader(ctx context.Context, p []byte, offset int64, end int64, readType string) (int, error) { + var err error + // If we don't have a reader, start a read operation. + if rr.reader == nil { + err = rr.startRead(offset, end) + if err != nil { + err = fmt.Errorf("startRead: %w", err) + return 0, err + } + } + + var n int + n, err = rr.readFull(ctx, p) + rr.start += int64(n) + + // Sanity check. + if rr.start > rr.limit { + err = fmt.Errorf("reader returned extra bytes: %d", rr.start-rr.limit) + + // Don't attempt to reuse the reader when it's malfunctioning. + rr.closeReader() + rr.reader = nil + rr.cancel = nil + rr.start = -1 + rr.limit = -1 + + return 0, err + } + + // Are we finished with this reader now? + if rr.start == rr.limit { + rr.closeReader() + rr.reader = nil + rr.cancel = nil + } + + // Handle errors. + switch { + case errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF): + // For a non-empty buffer, ReadFull returns EOF or ErrUnexpectedEOF only + // if the reader peters out early. That's fine, but it means we should + // have hit the limit above. + if rr.reader != nil { + err = fmt.Errorf("reader returned early by skipping %d bytes", rr.limit-rr.start) + return 0, err + } + + err = nil + + case err != nil: + // Propagate other errors. + err = fmt.Errorf("readFull: %w", err) + return 0, err + } + + requestedDataSize := end - offset + common.CaptureGCSReadMetrics(ctx, rr.metricHandle, readType, requestedDataSize) + + return n, err +} + +// Like io.ReadFull, but deals with the cancellation issues. +// +// REQUIRES: rr.reader != nil +func (rr *RangeReader) readFull(ctx context.Context, p []byte) (int, error) { + // Start a goroutine that will cancel the read operation we block on below if + // the calling context is cancelled, but only if this method has not already + // returned (to avoid souring the reader for the next read if this one is + // successful, since the calling context will eventually be cancelled). + readDone := make(chan struct{}) + defer close(readDone) + + go func() { + select { + case <-readDone: + return + + case <-ctx.Done(): + select { + case <-readDone: + return + + default: + rr.cancel() + } + } + }() + + return io.ReadFull(rr.reader, p) +} + +// Ensure that rr.reader is set up for a range for which [start, start+size) is +// a prefix. Irrespective of the size requested, we try to fetch more data +// from GCS defined by sequentialReadSizeMb flag to serve future read requests. +func (rr *RangeReader) startRead(start int64, end int64) error { + ctx, cancel := context.WithCancel(context.Background()) + + rc, err := rr.bucket.NewReaderWithReadHandle( + ctx, + &gcs.ReadObjectRequest{ + Name: rr.object.Name, + Generation: rr.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(start), + Limit: uint64(end), + }, + ReadCompressed: rr.object.HasContentEncodingGzip(), + ReadHandle: rr.readHandle, + }) + + // If a file handle is open locally, but the corresponding object doesn't exist + // in GCS, it indicates a file clobbering scenario. This likely occurred because: + // - The file was deleted in GCS while a local handle was still open. + // - The file content was modified leading to different generation number. + var notFoundError *gcs.NotFoundError + if errors.As(err, ¬FoundError) { + err = &gcsfuse_errors.FileClobberedError{ + Err: fmt.Errorf("NewReader: %w", err), + } + cancel() + return err + } + + if err != nil { + err = fmt.Errorf("NewReaderWithReadHandle: %w", err) + cancel() + return err + } + + rr.reader = rc + rr.cancel = cancel + rr.start = start + rr.limit = end + + requestedDataSize := end - start + common.CaptureGCSReadMetrics(ctx, rr.metricHandle, util.Sequential, requestedDataSize) + + return nil +} + +// skipBytes attempts to advance the reader position to the given offset without +// discarding the existing reader. If possible, it reads and discards data to +// maintain an active GCS connection, improving throughput for sequential reads. +func (rr *RangeReader) skipBytes(offset int64) { + // When the offset is AFTER the reader position, try to seek forward, within reason. + // This happens when the kernel page cache serves some data. It's very common for + // concurrent reads, often by only a few 128kB fuse read requests. The aim is to + // re-use GCS connection and avoid throwing away already read data. + // For parallel sequential reads to a single file, not throwing away the connections + // is a 15-20x improvement in throughput: 150-200 MiB/s instead of 10 MiB/s. + if rr.reader != nil && rr.start < offset && offset-rr.start < maxReadSize { + bytesToSkip := offset - rr.start + discardedBytes, copyError := io.CopyN(io.Discard, rr.reader, bytesToSkip) + // io.EOF is expected if the reader is shorter than the requested offset to read. + if copyError != nil && !errors.Is(copyError, io.EOF) { + logger.Warnf("Error while skipping reader bytes: %v", copyError) + } + rr.start += discardedBytes + } +} + +// invalidateReaderIfMisalignedOrTooSmall ensures that the existing reader is valid +// for the requested offset and length. If the reader is misaligned (not at the requested +// offset) or cannot serve the full request within its limit, it is closed and discarded. +// +// It attempts to skip forward to the requested offset if possible to avoid creating +// a new reader unnecessarily. If the reader is discarded due to misalignment, the method +// returns true to signal that a seek should be recorded. +// +// Parameters: +// - offset: the starting byte position of the requested read. +// - p: the buffer representing the size of the requested read. +// +// Returns: +// - true if the reader was discarded due to being misaligned (seek should be counted). +// - false otherwise. +func (rr *RangeReader) invalidateReaderIfMisalignedOrTooSmall(offset int64, p []byte) bool { + rr.skipBytes(offset) + + // If we have an existing reader, but it's positioned at the wrong place, + // clean it up and throw it away. + // We will also clean up the existing reader if it can't serve the entire request. + dataToRead := math.Min(float64(offset+int64(len(p))), float64(rr.object.Size)) + if rr.reader != nil && (rr.start != offset || int64(dataToRead) > rr.limit) { + rr.closeReader() + rr.reader = nil + rr.cancel = nil + if rr.start != offset { + // Return true to increment the seek count when discarding a reader due to incorrect positioning. + // Discarding readers that can't fulfill the entire request without this check would prevent + // the reader size from growing appropriately in random read scenarios. + return true + } + } + return false +} diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go index d502239340..db9e2bbfde 100644 --- a/internal/gcsx/client_readers/range_reader_test.go +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -16,19 +16,39 @@ package gcsx import ( "bytes" + "context" + "errors" "io" + "strings" "testing" + "testing/iotest" + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + testUtil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" ) -const fakeHandleData = "fake-handle" +const ( + fakeHandleData = "fake-handle" + testObject = "testObject" +) + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// type rangeReaderTest struct { suite.Suite + ctx context.Context + object *gcs.MinObject + mockBucket *storage.TestifyMockBucket rangeReader *RangeReader } @@ -37,29 +57,64 @@ func TestRangeReaderTestSuite(t *testing.T) { } func (t *rangeReaderTest) SetupTest() { - t.rangeReader = NewRangeReader() + t.object = &gcs.MinObject{ + Name: testObject, + Size: 17, + Generation: 1234, + } + t.mockBucket = new(storage.TestifyMockBucket) + t.rangeReader = NewRangeReader(t.object, t.mockBucket, common.NewNoopMetrics()) + t.ctx = context.Background() } func (t *rangeReaderTest) TearDown() { t.rangeReader.Destroy() } +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + func getReadCloser(content []byte) io.ReadCloser { r := bytes.NewReader(content) rc := io.NopCloser(r) return rc } -func getReader() *fake.FakeReader { - testContent := testutil.GenerateRandomBytes(2) +func getReader(size int) *fake.FakeReader { + testContent := testUtil.GenerateRandomBytes(size) return &fake.FakeReader{ ReadCloser: getReadCloser(testContent), Handle: []byte(fakeHandleData), } } +func (t *rangeReaderTest) readAt(offset int64, size int64) (gcsx.ReaderResponse, error) { + req := &gcsx.GCSReaderRequest{ + Offset: offset, + EndOffset: offset + size, + Buffer: make([]byte, size), + } + t.rangeReader.CheckInvariants() + defer t.rangeReader.CheckInvariants() + return t.rangeReader.ReadAt(t.ctx, req) +} + +func (t *rangeReaderTest) mockNewReaderWithHandleCallForTestBucket(start uint64, limit uint64, rd gcs.StorageReader) { + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(rg *gcs.ReadObjectRequest) bool { + return rg != nil && (*rg.Range).Start == start && (*rg.Range).Limit == limit + })).Return(rd, nil).Once() +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + func (t *rangeReaderTest) Test_NewRangeReader() { // The setup instantiates rangeReader with NewRangeReader. + assert.Equal(t.T(), t.object, t.rangeReader.object) + assert.Equal(t.T(), t.mockBucket, t.rangeReader.bucket) + assert.Equal(t.T(), common.NewNoopMetrics(), t.rangeReader.metricHandle) assert.Equal(t.T(), int64(-1), t.rangeReader.start) assert.Equal(t.T(), int64(-1), t.rangeReader.limit) } @@ -85,7 +140,7 @@ func (t *rangeReaderTest) Test_CheckInvariants() { { name: "reader without cancel", setup: func() *RangeReader { - t.rangeReader.reader = getReader() + t.rangeReader.reader = getReader(2) return &RangeReader{ start: 0, limit: 10, @@ -122,7 +177,7 @@ func (t *rangeReaderTest) Test_CheckInvariants() { { name: "negative limit with valid reader", setup: func() *RangeReader { - t.rangeReader.reader = getReader() + t.rangeReader.reader = getReader(2) return &RangeReader{ start: -10, limit: -5, @@ -159,7 +214,7 @@ func (t *rangeReaderTest) Test_CheckInvariants() { } func (t *rangeReaderTest) Test_Destroy_NonNilReader() { - t.rangeReader.reader = getReader() + t.rangeReader.reader = getReader(2) t.rangeReader.Destroy() @@ -167,3 +222,136 @@ func (t *rangeReaderTest) Test_Destroy_NonNilReader() { assert.Nil(t.T(), t.rangeReader.cancel) assert.Equal(t.T(), []byte(fakeHandleData), t.rangeReader.readHandle) } + +func (t *rangeReaderTest) Test_ReadAt_ReadFailsWithTimeoutError() { + content := "xxx" + r := iotest.OneByteReader(iotest.TimeoutReader(strings.NewReader(content))) + rc := &fake.FakeReader{ReadCloser: io.NopCloser(r)} + t.mockNewReaderWithHandleCallForTestBucket(0, uint64(len(content)), rc) + + readerResponse, err := t.readAt(0, int64(len(content))) + + assert.Error(t.T(), err) + assert.Contains(t.T(), err.Error(), "timeout") + assert.Zero(t.T(), readerResponse.Size) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *rangeReaderTest) Test_ReadAt_SuccessfulRead() { + offset := int64(0) + size := int64(5) + content := []byte("hello world") + r := &fake.FakeReader{ReadCloser: getReadCloser(content)} + t.mockNewReaderWithHandleCallForTestBucket(uint64(offset), uint64(offset+size), r) + + resp, err := t.readAt(offset, size) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), int(size), resp.Size) + assert.Equal(t.T(), content[:size], resp.DataBuf) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *rangeReaderTest) Test_ReadAt_PartialReadWithEOF() { + offset := int64(0) + size := int64(10) // Shorter than requested + partialReader := io.NopCloser(iotest.ErrReader(io.EOF)) // Simulates early EOF + r := &fake.FakeReader{ReadCloser: partialReader} + t.mockNewReaderWithHandleCallForTestBucket(uint64(offset), uint64(offset+size), r) + + resp, err := t.readAt(offset, size) + + assert.Error(t.T(), err) + assert.Contains(t.T(), err.Error(), "reader returned early by skipping") + assert.Zero(t.T(), resp.Size) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *rangeReaderTest) Test_ReadAt_StartReadNotFound() { + offset := int64(0) + size := int64(5) + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(nil, &gcs.NotFoundError{}).Once() + + resp, err := t.readAt(offset, size) + + var fcErr *gcsfuse_errors.FileClobberedError + assert.ErrorAs(t.T(), err, &fcErr) + assert.Zero(t.T(), resp.Size) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *rangeReaderTest) Test_ReadAt_StartReadUnexpectedError() { + offset := int64(0) + size := int64(5) + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(nil, errors.New("network error")).Once() + + resp, err := t.readAt(offset, size) + + assert.Error(t.T(), err) + assert.Zero(t.T(), resp.Size) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *rangeReaderTest) Test_invalidateReaderIfMisalignedOrTooSmall() { + tests := []struct { + name string + readerSetup func() + offset int64 + bufferSize int + expectIncreaseSeek bool + expectReaderNil bool + }{ + { + name: "InvalidateReaderDueToWrongOffset", + readerSetup: func() { + t.rangeReader.reader = &fake.FakeReader{ReadCloser: getReader(100)} + t.rangeReader.start = 50 // misaligned + t.rangeReader.limit = 1000 + }, + offset: 200, + bufferSize: 100, + expectIncreaseSeek: true, + expectReaderNil: true, + }, + { + name: "InvalidateReaderDueToTooSmall", + readerSetup: func() { + t.rangeReader.reader = &fake.FakeReader{ReadCloser: getReader(100)} + t.rangeReader.start = 200 + t.rangeReader.limit = 250 // too small + t.rangeReader.object.Size = 500 + }, + offset: 200, + bufferSize: 100, + expectIncreaseSeek: false, + expectReaderNil: true, + }, + { + name: "KeepReaderIfValid", + readerSetup: func() { + t.rangeReader.reader = &fake.FakeReader{ReadCloser: getReader(100)} + t.rangeReader.start = 200 + t.rangeReader.limit = 400 + }, + offset: 200, + bufferSize: 100, + expectIncreaseSeek: false, + expectReaderNil: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func() { + tt.readerSetup() + + result := t.rangeReader.invalidateReaderIfMisalignedOrTooSmall(tt.offset, make([]byte, tt.bufferSize)) + + assert.Equal(t.T(), tt.expectIncreaseSeek, result, "invalidateReaderIfMisalignedOrTooSmall() result") + if tt.expectReaderNil { + assert.Nil(t.T(), t.rangeReader.reader, "rangeReader.reader should be nil") + } else { + assert.NotNil(t.T(), t.rangeReader.reader, "rangeReader.reader should not be nil") + } + }) + } +} From ec952ec81be1ea0d63a171acc7d8170f64be65c7 Mon Sep 17 00:00:00 2001 From: codechanges Date: Mon, 28 Apr 2025 16:24:23 +0530 Subject: [PATCH 0391/1298] Remove enable streaming writes from high perf config (#3247) --- cfg/optimize.go | 1 - cfg/optimize_test.go | 3 +-- cmd/root_test.go | 27 +-------------------------- 3 files changed, 2 insertions(+), 29 deletions(-) diff --git a/cfg/optimize.go b/cfg/optimize.go index c11548a33f..fe88660ac7 100644 --- a/cfg/optimize.go +++ b/cfg/optimize.go @@ -77,7 +77,6 @@ var ( { name: "high-performance", overrides: map[string]flagOverride{ - "write.enable-streaming-writes": {newValue: true}, "metadata-cache.negative-ttl-secs": {newValue: 0}, "metadata-cache.ttl-secs": {newValue: -1}, "metadata-cache.stat-cache-max-size-mb": {newValue: 1024}, diff --git a/cfg/optimize_test.go b/cfg/optimize_test.go index d11e16389b..a215d0783c 100644 --- a/cfg/optimize_test.go +++ b/cfg/optimize_test.go @@ -392,8 +392,7 @@ func TestOptimize_Success(t *testing.T) { optimizedFlags, err := Optimize(cfg, isSet) require.NoError(t, err) - assert.True(t, cfg.Write.EnableStreamingWrites) - assert.True(t, isFlagPresent(optimizedFlags, "write.enable-streaming-writes")) + assert.True(t, isFlagPresent(optimizedFlags, "metadata-cache.negative-ttl-secs")) assert.EqualValues(t, 0, cfg.MetadataCache.NegativeTtlSecs) assert.EqualValues(t, -1, cfg.MetadataCache.TtlSecs) assert.EqualValues(t, 1024, cfg.MetadataCache.StatCacheMaxSizeMb) diff --git a/cmd/root_test.go b/cmd/root_test.go index 997c2fefe0..0d9a7de245 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -227,7 +227,7 @@ func TestArgsParsing_ImplicitDirsFlag(t *testing.T) { }, { name: "default overriden on high performance machine", - args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--implicit-dirs=false", "abc", "pqr"}, + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "--implicit-dirs=false", "abc", "pqr"}, expectedImplicit: false, }, } @@ -332,31 +332,6 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedWriteGlobalMaxBlocks: math.MaxInt64, expectedWriteMaxBlocksPerFile: 10, }, - { - name: "Test high performance config values with autoconfig enabled.", - args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "abc", "pqr"}, - expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, - }, - { - name: "Test high performance config values with autoconfig disabled.", - args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=true", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test high performance config values with enable-streaming-writes flag overriden.", - args: []string{"gcsfuse", "--enable-streaming-writes=false", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: 1, - }, } for _, tc := range tests { From 7b1624c3219553f262f3867ced9066c8786d4665 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 29 Apr 2025 10:12:24 +0530 Subject: [PATCH 0392/1298] un-support read operations on unfinalized objects (#3250) * unsupport read operations on unfinalized objects * lint fix * fix UT --- internal/fs/inode/file.go | 6 +- .../fs/inode/file_streaming_writes_test.go | 4 +- internal/fs/inode/file_test.go | 3 +- .../unfinalized_read_test.go | 108 ++++++++++++++++++ 4 files changed, 115 insertions(+), 6 deletions(-) create mode 100644 tools/integration_tests/unfinalized_object/unfinalized_read_test.go diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 4d0cd46fde..7a9ff36bc4 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -20,6 +20,7 @@ import ( "io" "strconv" "strings" + "syscall" "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" @@ -384,8 +385,7 @@ func (f *FileInode) Source() *gcs.MinObject { func (f *FileInode) SourceGenerationIsAuthoritative() bool { // Source generation is authoritative if: // 1. No pending writes exists on the inode (both content and bwh are nil). - // 2. The bucket is zonal and there are no pending writes in the temporary file. - return (f.content == nil && f.bwh == nil) || (f.bucket.BucketType().Zonal && f.content == nil) + return f.content == nil && f.bwh == nil } // Equivalent to the generation returned by f.Source(). @@ -546,7 +546,7 @@ func (f *FileInode) Read( // 1. Local file // 2. Empty GCS files and writes are triggered via buffered flow. if f.bwh != nil { - err = fmt.Errorf("cannot read a file when upload in progress") + err = fmt.Errorf("cannot read a file when upload in progress: %w", syscall.ENOTSUP) return } diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 1bf7e805c7..2841f4d129 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -167,10 +167,10 @@ func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritative assert.True(t.T(), t.in.SourceGenerationIsAuthoritative()) } -func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritativeReturnsTrueAfterWriteForZonalBuckets() { +func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWriteForZonalBuckets() { assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) - assert.True(t.T(), t.in.SourceGenerationIsAuthoritative()) + assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) } func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBucketsPromotesInodeToNonLocal() { diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index d0e70ae87a..e802c76bb2 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -22,6 +22,7 @@ import ( "os" "strconv" "strings" + "syscall" "testing" "time" @@ -1518,7 +1519,7 @@ func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { assert.Equal(t.T(), 0, n) require.Error(t.T(), err) - assert.Equal(t.T(), "cannot read a file when upload in progress", err.Error()) + assert.ErrorIs(t.T(), err, syscall.ENOTSUP) }) } } diff --git a/tools/integration_tests/unfinalized_object/unfinalized_read_test.go b/tools/integration_tests/unfinalized_object/unfinalized_read_test.go new file mode 100644 index 0000000000..1096d6f392 --- /dev/null +++ b/tools/integration_tests/unfinalized_object/unfinalized_read_test.go @@ -0,0 +1,108 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package unfinalized_object + +import ( + "context" + "log" + "path" + "syscall" + "testing" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type unfinalizedObjectReads struct { + flags []string + storageClient *storage.Client + ctx context.Context + testDirPath string + fileName string + suite.Suite +} + +func (t *unfinalizedObjectReads) SetupTest() { + t.testDirPath = client.SetupTestDirectory(t.ctx, t.storageClient, testDirName) + t.fileName = path.Base(t.T().Name()) + setup.GenerateRandomString(5) +} + +func (t *unfinalizedObjectReads) TeardownTest() {} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +func (t *unfinalizedObjectReads) TestUnfinalizedObjectsCantBeRead() { + var size int = operations.MiB + // Create un-finalized object via same mount. + fh := operations.CreateFile(path.Join(t.testDirPath, t.fileName), setup.FilePermission_0600, t.T()) + operations.WriteWithoutClose(fh, setup.GenerateRandomString(size), t.T()) + defer operations.CloseFileShouldNotThrowError(t.T(), fh) + + // Read un-finalized object. + content, err := operations.ReadFileSequentially(path.Join(t.testDirPath, t.fileName), util.MiB) + + require.Error(t.T(), err) + assert.ErrorContains(t.T(), err, syscall.ENOTSUP.Error()) + assert.Empty(t.T(), content, "expected empty content, but got size: %v", len(content)) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestUnfinalizedObjectReadTest(t *testing.T) { + ts := &unfinalizedObjectReads{ctx: context.Background()} + // Create storage client before running tests. + closeStorageClient := client.CreateStorageClientWithCancel(&ts.ctx, &ts.storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + t.Errorf("closeStorageClient failed: %v", err) + } + }() + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + suite.Run(t, ts) + return + } + + // Define flag set to run the tests. + flagsSet := [][]string{ + {"--enable-streaming-writes=true", "--metadata-cache-ttl-secs=-1"}, + } + + // Run tests. + for _, flags := range flagsSet { + ts.flags = flags + setup.MountGCSFuseWithGivenMountFunc(ts.flags, mountFunc) + log.Printf("Running tests with flags: %s", ts.flags) + suite.Run(t, ts) + setup.SaveGCSFuseLogFileInCaseOfFailure(t) + setup.UnmountGCSFuseAndDeleteLogFile(setup.MntDir()) + } +} From c23a94aa0526e35b036fb9afbfdb407fdf1bc021 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 29 Apr 2025 13:23:07 +0530 Subject: [PATCH 0393/1298] Revert "Enable Read Stall Retry by default (#3126)" (#3252) This reverts commit efc357525805465a299309546bf7c3ab01c0d037. --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/config_validation_test.go | 4 ++-- cmd/root_test.go | 2 +- cmd/testdata/valid_config.yaml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index b07b63f202..c83cd5834e 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -353,7 +353,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-nonexistent-type-cache", "", false, "Once set, if an inode is not found in GCS, a type cache entry with type NonexistentType will be created. This also means new file/dir created might not be seen. For example, if this flag is set, and metadata-cache-ttl-secs is set, then if we create the same file/node in the meantime using the same mount, since we are not refreshing the cache, it will still return nil.") - flagSet.BoolP("enable-read-stall-retry", "", true, "To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past.") + flagSet.BoolP("enable-read-stall-retry", "", false, "To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past.") if err := flagSet.MarkHidden("enable-read-stall-retry"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index caf08bbfaf..c5e82e29d0 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -400,7 +400,7 @@ usage: >- To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past. - default: true + default: false hide-flag: true - config-path: "gcs-retries.read-stall.initial-req-timeout" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index ae00658559..ff57147478 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -712,7 +712,7 @@ func TestValidateConfigFile_GCSRetries(t *testing.T) { MaxRetrySleep: 30 * time.Second, Multiplier: 2, ReadStall: cfg.ReadStallGcsRetriesConfig{ - Enable: true, + Enable: false, MinReqTimeout: 1500 * time.Millisecond, MaxReqTimeout: 1200 * time.Second, InitialReqTimeout: 20 * time.Second, @@ -732,7 +732,7 @@ func TestValidateConfigFile_GCSRetries(t *testing.T) { MaxRetrySleep: 30 * time.Second, Multiplier: 2, ReadStall: cfg.ReadStallGcsRetriesConfig{ - Enable: false, + Enable: true, MinReqTimeout: 10 * time.Second, MaxReqTimeout: 200 * time.Second, InitialReqTimeout: 20 * time.Second, diff --git a/cmd/root_test.go b/cmd/root_test.go index 0d9a7de245..fdd67ef6be 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1253,7 +1253,7 @@ func TestArgParsing_GCSRetries(t *testing.T) { MaxRetrySleep: 30 * time.Second, Multiplier: 2, ReadStall: cfg.ReadStallGcsRetriesConfig{ - Enable: true, + Enable: false, InitialReqTimeout: 20 * time.Second, MinReqTimeout: 1500 * time.Millisecond, MaxReqTimeout: 1200 * time.Second, diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index ca30720bc2..08ab5940fb 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -35,7 +35,7 @@ gcs-connection: gcs-retries: chunk-transfer-timeout-secs: 20 read-stall: - enable: false + enable: true min-req-timeout: 10s max-req-timeout: 200s initial-req-timeout: 20s From aba5087db1f025a79b9d7c55c9e997f612957782 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 29 Apr 2025 15:26:05 +0530 Subject: [PATCH 0394/1298] [Random Reader Refactoring] Range reader implementation - 3 (#3243) * add unit tests * remove unnecessary changes * add unit tests * remove coverage file * add more test * remove coverage file * add more unit test * remove coverage file * remove some tests to cut PR lenght * remove some tests to cut PR lenght * lint fix * passing same context to start read * small update * remove unnecessary file * use helper * small update * update tes name * adding method * add comment * add unit tests * add comments * small fixes * update comment * remove coverage file * add doc comment * small fixes * review comments * Write test in TDD format * small ifx * reuse thr function * update var name * create new context * remove unnecessary changes * add unit tests * remove unnecessary changes * add unit tests * remove coverage file * add more test * remove coverage file * add more unit test * remove coverage file * small update * remove unnecessary file * add more unit tests * add more unit tests * rebase * small fix * small fix * update comment * review comment * run test in serially * update test comment * add more tests * add assertion * making it more readable with var name * making it more readable with var name * making it more readable with var name * adding one assertion * small fix * reusing some var * reusing some var * linux test fix * adding one check for offset * append message in assertion * add more tests * use const --- internal/gcsx/client_readers/range_reader.go | 11 +- .../gcsx/client_readers/range_reader_test.go | 286 +++++++++++++++++- 2 files changed, 287 insertions(+), 10 deletions(-) diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index f8c5a87918..699e90e036 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -39,7 +39,7 @@ const ( ) type RangeReader struct { - gcsx.Reader + gcsx.GCSReader object *gcs.MinObject bucket gcs.Bucket @@ -74,7 +74,7 @@ func NewRangeReader(object *gcs.MinObject, bucket gcs.Bucket, metricHandle commo } } -func (rr *RangeReader) CheckInvariants() { +func (rr *RangeReader) checkInvariants() { // INVARIANT: (reader == nil) == (cancel == nil) if (rr.reader == nil) != (rr.cancel == nil) { panic(fmt.Sprintf("Mismatch: %v vs. %v", rr.reader == nil, rr.cancel == nil)) @@ -91,7 +91,7 @@ func (rr *RangeReader) CheckInvariants() { } } -func (rr *RangeReader) Destroy() { +func (rr *RangeReader) destroy() { // Close out the reader, if we have one. if rr.reader != nil { rr.closeReader() @@ -116,6 +116,11 @@ func (rr *RangeReader) ReadAt(ctx context.Context, req *gcsx.GCSReaderRequest) ( } var err error + if req.Offset >= int64(rr.object.Size) { + err = io.EOF + return readerResponse, err + } + readerResponse.Size, err = rr.readFromRangeReader(ctx, req.Buffer, req.Offset, req.EndOffset, rr.readType) return readerResponse, err diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go index db9e2bbfde..5e664bfda4 100644 --- a/internal/gcsx/client_readers/range_reader_test.go +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -22,6 +22,7 @@ import ( "strings" "testing" "testing/iotest" + "time" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" @@ -68,7 +69,7 @@ func (t *rangeReaderTest) SetupTest() { } func (t *rangeReaderTest) TearDown() { - t.rangeReader.Destroy() + t.rangeReader.destroy() } //////////////////////////////////////////////////////////////////////// @@ -95,8 +96,8 @@ func (t *rangeReaderTest) readAt(offset int64, size int64) (gcsx.ReaderResponse, EndOffset: offset + size, Buffer: make([]byte, size), } - t.rangeReader.CheckInvariants() - defer t.rangeReader.CheckInvariants() + t.rangeReader.checkInvariants() + defer t.rangeReader.checkInvariants() return t.rangeReader.ReadAt(t.ctx, req) } @@ -106,6 +107,34 @@ func (t *rangeReaderTest) mockNewReaderWithHandleCallForTestBucket(start uint64, })).Return(rd, nil).Once() } +//////////////////////////////////////////////////////////////////////// +// Blocking reader +//////////////////////////////////////////////////////////////////////// + +// A reader that blocks until a channel is closed, then returns an error. +type blockingReader struct { + c chan struct{} +} + +func (br *blockingReader) Read([]byte) (int, error) { + <-br.c + return 0, errors.New("blockingReader") +} + +//////////////////////////////////////////////////////////////////////// +// Counting closer +//////////////////////////////////////////////////////////////////////// + +type countingCloser struct { + io.Reader + closeCount int +} + +func (cc *countingCloser) Close() (err error) { + cc.closeCount++ + return +} + //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// @@ -205,9 +234,9 @@ func (t *rangeReaderTest) Test_CheckInvariants() { t.Run(tt.name, func() { rr := tt.setup() if tt.shouldPanic { - assert.Panics(t.T(), func() { rr.CheckInvariants() }, "Expected panic") + assert.Panics(t.T(), func() { rr.checkInvariants() }, "Expected panic") } else { - assert.NotPanics(t.T(), func() { rr.CheckInvariants() }, "Expected no panic") + assert.NotPanics(t.T(), func() { rr.checkInvariants() }, "Expected no panic") } }) } @@ -216,9 +245,9 @@ func (t *rangeReaderTest) Test_CheckInvariants() { func (t *rangeReaderTest) Test_Destroy_NonNilReader() { t.rangeReader.reader = getReader(2) - t.rangeReader.Destroy() + t.rangeReader.destroy() - assert.Nil(t.T(), t.rangeReader.Reader) + assert.Nil(t.T(), t.rangeReader.reader) assert.Nil(t.T(), t.rangeReader.cancel) assert.Equal(t.T(), []byte(fakeHandleData), t.rangeReader.readHandle) } @@ -355,3 +384,246 @@ func (t *rangeReaderTest) Test_invalidateReaderIfMisalignedOrTooSmall() { }) } } + +func (t *rangeReaderTest) Test_ReadFromRangeReader_WhenReaderReturnedMoreData() { + testCases := []struct { + name string + readHandle []byte + }{ + { + name: "GCSReturnedReadHandle", + readHandle: []byte(fakeHandleData), + }, + { + name: "GCSReturnedNoReadHandle", + readHandle: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.rangeReader.start = 0 + t.rangeReader.limit = 6 + testContent := testUtil.GenerateRandomBytes(8) + t.rangeReader.reader = &fake.FakeReader{ + ReadCloser: getReadCloser(testContent), + Handle: tc.readHandle, + } + t.rangeReader.cancel = func() {} + + n, err := t.rangeReader.readFromRangeReader(t.ctx, make([]byte, 10), 0, 10, "unhandled") + + assert.Error(t.T(), err) + assert.Zero(t.T(), n) + assert.Nil(t.T(), t.rangeReader.reader) + assert.Equal(t.T(), int64(-1), t.rangeReader.start) + assert.Equal(t.T(), int64(-1), t.rangeReader.limit) + assert.Equal(t.T(), tc.readHandle, t.rangeReader.readHandle) + }) + } +} + +func (t *rangeReaderTest) Test_ReadAt_PropagatesCancellation() { + // Set up a blocking reader + finishRead := make(chan struct{}) + blocking := &blockingReader{c: finishRead} + rc := io.NopCloser(blocking) + // Assign it to the rangeReader + t.rangeReader.reader = &fake.FakeReader{ReadCloser: rc} + t.rangeReader.start = 0 + t.rangeReader.limit = 2 + // Track cancel invocation + cancelCalled := make(chan struct{}) + t.rangeReader.cancel = func() { close(cancelCalled) } + // Controlled context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + // Channel to track read completion + readReturned := make(chan struct{}) + + go func() { + _, _ = t.rangeReader.ReadAt(ctx, &gcsx.GCSReaderRequest{ + Buffer: make([]byte, 2), + Offset: 0, + EndOffset: 2, + }) + close(readReturned) + }() + + // Wait a bit to ensure ReadAt is blocking + select { + case <-readReturned: + t.T().Fatal("Read returned early — cancellation did not propagate properly.") + case <-time.After(10 * time.Millisecond): + // OK: Still blocked + } + // Cancel the context to trigger cancellation + cancel() + // Expect rr.cancel to be called + select { + case <-cancelCalled: + // Pass + case <-time.After(100 * time.Millisecond): + t.T().Fatal("Expected rr.cancel to be called on ctx cancellation.") + } + // Unblock the reader so the read can complete + close(finishRead) + // Ensure read completes + select { + case <-readReturned: + // Pass + case <-time.After(100 * time.Millisecond): + t.T().Fatal("Expected read to return after unblocking.") + } +} + +func (t *rangeReaderTest) Test_ReadAt_DoesntPropagateCancellationAfterReturning() { + // Set up a reader that will return three bytes. + content := "xyz" + t.rangeReader.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte(content))} + t.rangeReader.start = 1 + t.rangeReader.limit = 4 + // Snoop on when cancel is called. + cancelCalled := make(chan struct{}) + t.rangeReader.cancel = func() { close(cancelCalled) } + ctx, cancel := context.WithCancel(context.Background()) + bufSize := 2 + + // Successfully read two bytes using a context whose cancellation we control. + readerResponse, err := t.rangeReader.ReadAt(ctx, &gcsx.GCSReaderRequest{ + Buffer: make([]byte, bufSize), + Offset: 0, + EndOffset: 2, + }) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), bufSize, readerResponse.Size) + assert.Equal(t.T(), content[:bufSize], string(readerResponse.DataBuf[:readerResponse.Size])) + // If we cancel the calling context now, it should not cause the underlying + // read context to be cancelled. + cancel() + select { + case <-time.After(10 * time.Millisecond): + case <-cancelCalled: + t.T().Fatal("Read context unexpectedly cancelled") + } +} + +func (t *rangeReaderTest) Test_ReadFromRangeReader_WhenAllDataFromReaderIsRead() { + testCases := []struct { + name string + readHandle []byte + }{ + { + name: "GCSReturnedReadHandle", + readHandle: []byte(fakeHandleData), + }, + { + name: "GCSReturnedNoReadHandle", + readHandle: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.rangeReader.start = 4 + t.rangeReader.limit = 10 + t.object.Size = 10 + dataSize := 6 + testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) + rc := &fake.FakeReader{ + ReadCloser: getReadCloser(testContent), + Handle: tc.readHandle, + } + t.rangeReader.reader = rc + t.rangeReader.cancel = func() {} + buf := make([]byte, dataSize) + + n, err := t.rangeReader.readFromRangeReader(t.ctx, buf, 4, 10, "unhandled") + + assert.NoError(t.T(), err) + assert.Equal(t.T(), dataSize, n) + // Verify the reader state. + assert.Nil(t.T(), t.rangeReader.reader) + assert.Nil(t.T(), t.rangeReader.cancel) + assert.Equal(t.T(), int64(10), t.rangeReader.start) + assert.Equal(t.T(), int64(10), t.rangeReader.limit) + assert.Equal(t.T(), tc.readHandle, t.rangeReader.readHandle) + }) + } +} + +func (t *rangeReaderTest) Test_ReadFromRangeReader_WhenReaderHasLessDataThanRequested() { + testCases := []struct { + name string + readHandle []byte + }{ + { + name: "GCSReturnedReadHandle", + readHandle: []byte(fakeHandleData), + }, + { + name: "GCSReturnedNoReadHandle", + readHandle: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.rangeReader.start = 0 + t.rangeReader.limit = 6 + dataSize := 6 + testContent := testUtil.GenerateRandomBytes(dataSize) + rc := &fake.FakeReader{ + ReadCloser: getReadCloser(testContent), + Handle: tc.readHandle, + } + t.rangeReader.reader = rc + t.rangeReader.cancel = func() {} + buf := make([]byte, 10) + + n, err := t.rangeReader.readFromRangeReader(t.ctx, buf, 0, 10, "unhandled") + + assert.NoError(t.T(), err) + assert.Equal(t.T(), dataSize, n) + // Verify the reader state. + assert.Nil(t.T(), t.rangeReader.reader) + assert.Nil(t.T(), t.rangeReader.cancel) + assert.Equal(t.T(), int64(dataSize), t.rangeReader.start) + assert.Equal(t.T(), int64(dataSize), t.rangeReader.limit) + assert.Equal(t.T(), tc.readHandle, t.rangeReader.readHandle) + }) + } +} + +func (t *rangeReaderTest) Test_ReadAt_ReaderNotExhausted() { + // Set up a reader that has three bytes left to give. + content := "abc" + cc := &countingCloser{ + Reader: strings.NewReader(content), + } + rc := &fake.FakeReader{ReadCloser: cc} + t.rangeReader.reader = rc + var offset int64 = 1 + t.rangeReader.start = offset + t.rangeReader.limit = 4 + t.rangeReader.cancel = func() {} + var bufSize int64 = 2 + + // Read two bytes. + resp, err := t.readAt(offset, bufSize) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), content[:bufSize], string(resp.DataBuf[:resp.Size])) + assert.Zero(t.T(), cc.closeCount) + assert.Equal(t.T(), rc, t.rangeReader.reader) + assert.Equal(t.T(), offset+bufSize, t.rangeReader.start) +} + +func (t *rangeReaderTest) Test_ReadAt_InvalidOffset() { + t.object.Size = 50 + + _, err := t.readAt(65, int64(t.object.Size)) + + assert.True(t.T(), errors.Is(err, io.EOF), "expected %v error got %v", io.EOF, err) +} From b4f20f865274d52fadf14860d9cbf50e5cd47af3 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 29 Apr 2025 18:39:15 +0530 Subject: [PATCH 0395/1298] MRD recreation in case of stream closed error (#3166) * recreate MRD in case of stream closed error * ut for mrd recreation * unit test fixes * typo * adding comment * split test * lint fix * do not log method call for getting MRD status --- .../gcsx/multi_range_downloader_wrapper.go | 28 +++++++++++-------- .../multi_range_downloader_wrapper_test.go | 26 +++++++++++++++++ internal/storage/debug_bucket.go | 5 ++++ .../fake/fake_multi_range_downloader.go | 15 ++++++++++ .../storage/gcs/multi_range_downloader.go | 1 + 5 files changed, 64 insertions(+), 11 deletions(-) diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go index 5f23fc5048..1568ed2532 100644 --- a/internal/gcsx/multi_range_downloader_wrapper.go +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -162,17 +162,23 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) ensureMultiRangeDownloader() (err if mrdWrapper.object == nil || mrdWrapper.bucket == nil { return fmt.Errorf("ensureMultiRangeDownloader error: Missing minObject or bucket") } - - if mrdWrapper.Wrapped == nil { - var mrd gcs.MultiRangeDownloader - mrd, err = mrdWrapper.bucket.NewMultiRangeDownloader(context.Background(), &gcs.MultiRangeDownloaderRequest{ - Name: mrdWrapper.object.Name, - Generation: mrdWrapper.object.Generation, - ReadCompressed: mrdWrapper.object.HasContentEncodingGzip(), - }) - if err == nil { - // Updating mrdWrapper.Wrapped only when MRD creation was successful. - mrdWrapper.Wrapped = mrd + // Create the MRD if it does not exist. + // In case the existing MRD is unusable due to closed stream, recreate the MRD. + if mrdWrapper.Wrapped == nil || mrdWrapper.Wrapped.Error() != nil { + mrdWrapper.mu.Lock() + defer mrdWrapper.mu.Unlock() + // Checking if the mrdWrapper state is same after taking the lock. + if mrdWrapper.Wrapped == nil || mrdWrapper.Wrapped.Error() != nil { + var mrd gcs.MultiRangeDownloader + mrd, err = mrdWrapper.bucket.NewMultiRangeDownloader(context.Background(), &gcs.MultiRangeDownloaderRequest{ + Name: mrdWrapper.object.Name, + Generation: mrdWrapper.object.Generation, + ReadCompressed: mrdWrapper.object.HasContentEncodingGzip(), + }) + if err == nil { + // Updating mrdWrapper.Wrapped only when MRD creation was successful. + mrdWrapper.Wrapped = mrd + } } } return diff --git a/internal/gcsx/multi_range_downloader_wrapper_test.go b/internal/gcsx/multi_range_downloader_wrapper_test.go index bcf4555add..0a1112940a 100644 --- a/internal/gcsx/multi_range_downloader_wrapper_test.go +++ b/internal/gcsx/multi_range_downloader_wrapper_test.go @@ -327,3 +327,29 @@ func (t *mrdWrapperTest) Test_EnsureMultiRangeDownloader() { }) } } + +func (t *mrdWrapperTest) Test_EnsureMultiRangeDownloader_UnusableExistingMRDTriggersRecreation() { + t.mrdWrapper.bucket = t.mockBucket + t.mrdWrapper.object = t.object + t.mrdWrapper.Wrapped = fake.NewFakeMultiRangeDownloaderWithStatusError(t.object, t.objectData, fmt.Errorf("MRD is unusable...")) + + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond)) + + err := t.mrdWrapper.ensureMultiRangeDownloader() + + assert.NoError(t.T(), err) + assert.NotNil(t.T(), t.mrdWrapper.Wrapped) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *mrdWrapperTest) Test_EnsureMultiRangeDownloader_UsableExistingMRDPreventsRecreation() { + t.mrdWrapper.bucket = t.mockBucket + t.mrdWrapper.object = t.object + t.mrdWrapper.Wrapped = fake.NewFakeMultiRangeDownloaderWithStatusError(t.object, t.objectData, nil) + + err := t.mrdWrapper.ensureMultiRangeDownloader() + + assert.NoError(t.T(), err) + assert.NotNil(t.T(), t.mrdWrapper.Wrapped) + t.mockBucket.AssertNotCalled(t.T(), "NewMultiRangeDownloader") +} diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index cf49e57c10..3cf23b9489 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -340,6 +340,11 @@ func (dmrd *debugMultiRangeDownloader) Wait() { dmrd.wrapped.Wait() } +func (dmrd *debugMultiRangeDownloader) Error() (err error) { + err = dmrd.wrapped.Error() + return +} + func (b *debugBucket) NewMultiRangeDownloader( ctx context.Context, req *gcs.MultiRangeDownloaderRequest) (mrd gcs.MultiRangeDownloader, err error) { id, desc, start := b.startRequest("NewMultiRangeDownloader(%q)", req.Name) diff --git a/internal/storage/fake/fake_multi_range_downloader.go b/internal/storage/fake/fake_multi_range_downloader.go index 7deb177da0..a3f35bae6b 100644 --- a/internal/storage/fake/fake_multi_range_downloader.go +++ b/internal/storage/fake/fake_multi_range_downloader.go @@ -31,6 +31,7 @@ type fakeMultiRangeDownloader struct { wg sync.WaitGroup err error defaultErr error + statusErr error sleepTime time.Duration // Sleep time to simulate real-world. } @@ -59,6 +60,16 @@ func NewFakeMultiRangeDownloaderWithSleepAndDefaultError(obj *gcs.MinObject, dat } } +func NewFakeMultiRangeDownloaderWithStatusError(obj *gcs.MinObject, data []byte, err error) gcs.MultiRangeDownloader { + fakeObj := createFakeObject(obj, data) + return &fakeMultiRangeDownloader{ + obj: &fakeObj, + sleepTime: 0, + defaultErr: nil, + statusErr: err, + } +} + func (fmrd *fakeMultiRangeDownloader) Add(output io.Writer, offset, length int64, callback func(int64, int64, error)) { if fmrd.defaultErr != nil { if callback != nil { @@ -123,3 +134,7 @@ func (fmrd *fakeMultiRangeDownloader) Close() error { func (fmrd *fakeMultiRangeDownloader) Wait() { fmrd.wg.Wait() } + +func (fmrd *fakeMultiRangeDownloader) Error() error { + return fmrd.statusErr +} diff --git a/internal/storage/gcs/multi_range_downloader.go b/internal/storage/gcs/multi_range_downloader.go index 911c1b2a79..ab84d4bb34 100644 --- a/internal/storage/gcs/multi_range_downloader.go +++ b/internal/storage/gcs/multi_range_downloader.go @@ -22,4 +22,5 @@ type MultiRangeDownloader interface { Add(output io.Writer, offset, length int64, callback func(int64, int64, error)) Close() error Wait() + Error() error } From 136ee956daa387edd31becbed4f4ddf97cca26be Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 1 May 2025 12:07:22 +0530 Subject: [PATCH 0396/1298] Sync file Inode at the time of BWH initialization (#3251) * park changes * fix tests * fix uploader routine startup and freeBlockCh * remove log line * Add helper method for bwh init and first sync * fs passing and unit failing * fix inode tests * make code simpler * Add comments * fix tests * fix tests * fix tests * add tests * fix tests * fix tests * fix tests * add tests * fix init of bwh in SetInodeAttr * Skip the broken test due to b/411333280 * fix review comments * create empty temp file within fs.mu lock * skip local file tests because local files are instantly promoted to synced file in streaming writes for zb * fix review comments * fix review comments * simplify * fix review comments * fix typo * fix inode unlock * refactor * refactor * refactor and fix tests * fix bwh init & reformat tests * fix unit tests * add empty commit to trigger kokoro build --- internal/bufferedwrites/upload_handler.go | 22 ++-- .../bufferedwrites/upload_handler_test.go | 2 +- internal/fs/fs.go | 68 +++++++++-- internal/fs/inode/file.go | 42 +++---- internal/fs/inode/file_mock_bucket_test.go | 4 +- .../fs/inode/file_streaming_writes_test.go | 91 ++++++++++---- internal/fs/inode/file_test.go | 113 +++++++++++------- .../streaming_writes/buffer_size_test.go | 9 +- .../default_mount_local_file_test.go | 31 +++-- .../unfinalized_object_operations_test.go | 2 + 10 files changed, 256 insertions(+), 128 deletions(-) diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index b49a113248..b4aae3ce2d 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -48,7 +48,8 @@ type UploadHandler struct { // uploadError stores atomic pointer to the error seen by uploader. uploadError atomic.Pointer[error] // CancelFunc persisted to cancel the uploads in case of unlink operation. - cancelFunc context.CancelFunc + cancelFunc context.CancelFunc + startUploader sync.Once // Parameters required for creating a new GCS chunk writer. bucket gcs.Bucket @@ -87,18 +88,14 @@ func newUploadHandler(req *CreateUploadHandlerRequest) *UploadHandler { func (uh *UploadHandler) Upload(block block.Block) error { uh.wg.Add(1) - if uh.writer == nil { - // Lazily create the object writer. - err := uh.createObjectWriter() - if err != nil { - // createObjectWriter can only fail here due to throttling, so we will not - // handle this error explicitly or fall back to temp file flow. - return fmt.Errorf("createObjectWriter failed for object %s: %w", uh.objectName, err) - } - // Start the uploader goroutine. - go uh.uploader() + err := uh.ensureWriter() + if err != nil { + return fmt.Errorf("uh.ensureWriter() failed: %v", err) } - + // Start the uploader goroutine but only once. + uh.startUploader.Do(func() { + go uh.uploader() + }) uh.uploadCh <- block return nil } @@ -126,6 +123,7 @@ func (uh *UploadHandler) UploadError() (err error) { func (uh *UploadHandler) uploader() { for currBlock := range uh.uploadCh { if uh.UploadError() != nil { + uh.freeBlocksCh <- currBlock uh.wg.Done() continue } diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 2c598a0ecc..613e57f120 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -260,7 +260,7 @@ func (t *UploadHandlerTest) TestUploadMultipleBlocksThrowsErrorInCopy() { assertUploadFailureError(t.T(), t.uh) assertAllBlocksProcessed(t.T(), t.uh) - assert.Equal(t.T(), 2, len(t.uh.freeBlocksCh)) + assert.Equal(t.T(), 4, len(t.uh.freeBlocksCh)) } func assertUploadFailureError(t *testing.T, handler *UploadHandler) { diff --git a/internal/fs/fs.go b/internal/fs/fs.go index b0945d500b..8630be08f0 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1214,6 +1214,45 @@ func (fs *fileSystem) syncFile( return nil } +// Initializes Buffered Write Handler if Eligible and synchronizes the file inode to GCS if initialization succeeds. +// Otherwise creates an empty temp writer if temp file nil. +// +// LOCKS_EXCLUDED(fs.mu) +// LOCKS_REQUIRED(f.mu) +func (fs *fileSystem) createBufferedWriteHandlerAndSyncOrTempWriter(ctx context.Context, f *inode.FileInode) error { + err := fs.initBufferedWriteHandlerAndSyncFileIfEligible(ctx, f) + if err != nil { + return err + } + err = f.CreateEmptyTempFile(ctx) + if err != nil { + return err + } + return nil +} + +// Initializes Buffered Write Handler if Eligible and synchronizes the file inode to GCS if initialization succeeds. +// +// LOCKS_EXCLUDED(fs.mu) +// LOCKS_REQUIRED(f.mu) +func (fs *fileSystem) initBufferedWriteHandlerAndSyncFileIfEligible(ctx context.Context, f *inode.FileInode) error { + initialized, err := f.InitBufferedWriteHandlerIfEligible(ctx) + if err != nil { + return err + } + if initialized { + // Calling syncFile is safe here as we have file inode lock and BWH is initialized. + // Thus sync method of BWH will be invoked. + // 1. In case of zonal bucket it creates unfinalized new generation object. + // 2. In case of non zonal bucket it's no-op as we don't have pending buffers to upload. + err = fs.syncFile(ctx, f) + if err != nil { + return err + } + } + return nil +} + // Decrement the supplied inode's lookup count, destroying it if the inode says // that it has hit zero. // @@ -1528,6 +1567,11 @@ func (fs *fileSystem) SetInodeAttributes( // Truncate files. if isFile && op.Size != nil { + // Initialize BWH if eligible and Sync file inode. + err = fs.initBufferedWriteHandlerAndSyncFileIfEligible(ctx, file) + if err != nil { + return + } err = file.Truncate(ctx, int64(*op.Size)) if err != nil { err = fmt.Errorf("truncate: %w", err) @@ -1745,19 +1789,21 @@ func (fs *fileSystem) createLocalFile(ctx context.Context, parentID fuseops.Inod } child = fs.mintInode(core) fs.localFileInodes[child.Name()] = child - // Empty file is created to be able to set attributes on the file. fileInode := child.(*inode.FileInode) - if err := fileInode.CreateBufferedOrTempWriter(ctx); err != nil { - return nil, err - } + // Use deferred locking on filesystem so that it is locked before the defer call that unlocks the mutex and it doesn't fail. + // We need to release the filesystem lock before acquiring the inode lock. fs.mu.Unlock() + defer fs.mu.Lock() + fileInode.Lock() + err = fs.createBufferedWriteHandlerAndSyncOrTempWriter(ctx, fileInode) + fileInode.Unlock() + if err != nil { + return + } parent.Lock() defer parent.Unlock() parent.InsertFileIntoTypeCache(name) - // Even though there is no action here that requires locking, adding locking - // so that the defer call that unlocks the mutex doesn't fail. - fs.mu.Lock() return child, nil } @@ -2597,11 +2643,13 @@ func (fs *fileSystem) WriteFile( in.Lock() defer in.Unlock() - // Serve the request. - if err := in.Write(ctx, op.Data, op.Offset); err != nil { - return err + err = fs.initBufferedWriteHandlerAndSyncFileIfEligible(ctx, in) + if err != nil { + return } + // Serve the request. + err = in.Write(ctx, op.Data, op.Offset) return } diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 7a9ff36bc4..53b9910f3c 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -578,12 +578,6 @@ func (f *FileInode) Write( ctx context.Context, data []byte, offset int64) error { - - err := f.initBufferedWriteHandlerIfEligible(ctx) - if err != nil { - return err - } - if f.bwh != nil { return f.writeUsingBufferedWrites(ctx, data, offset) } @@ -931,11 +925,6 @@ func (f *FileInode) Truncate( ctx context.Context, size int64) (err error) { - err = f.initBufferedWriteHandlerIfEligible(ctx) - if err != nil { - return err - } - if f.bwh != nil { return f.bwh.Truncate(size) } @@ -962,13 +951,14 @@ func (f *FileInode) CacheEnsureContent(ctx context.Context) (err error) { return } -func (f *FileInode) CreateBufferedOrTempWriter(ctx context.Context) (err error) { - err = f.initBufferedWriteHandlerIfEligible(ctx) - if err != nil { - return err - } - // Skip creating empty file when streaming writes are enabled. - if f.bwh != nil { +// CreateEmptyTempFile creates an empty file with no contents when +// streaming writes are not in enabled. +// +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) CreateEmptyTempFile(ctx context.Context) (err error) { + // Skip creating empty temp file when streaming writes are enabled + // or temp file is already created. + if f.bwh != nil || f.content != nil { return } @@ -980,16 +970,18 @@ func (f *FileInode) CreateBufferedOrTempWriter(ctx context.Context) (err error) return } -func (f *FileInode) initBufferedWriteHandlerIfEligible(ctx context.Context) error { +// Initializes Buffered Write Handler if the file inode is eligible and returns +// initialized as true when the new instance of buffered writer handler is created. +func (f *FileInode) InitBufferedWriteHandlerIfEligible(ctx context.Context) (bool, error) { // bwh already initialized, do nothing. if f.bwh != nil { - return nil + return false, nil } tempFileInUse := f.content != nil if f.src.Size != 0 || !f.config.Write.EnableStreamingWrites || tempFileInUse { // bwh should not be initialized under these conditions. - return nil + return false, nil } var latestGcsObj *gcs.Object @@ -997,7 +989,7 @@ func (f *FileInode) initBufferedWriteHandlerIfEligible(ctx context.Context) erro if !f.local { latestGcsObj, err = f.fetchLatestGcsObject(ctx) if err != nil { - return err + return false, err } } @@ -1012,10 +1004,10 @@ func (f *FileInode) initBufferedWriteHandlerIfEligible(ctx context.Context) erro ChunkTransferTimeoutSecs: f.config.GcsRetries.ChunkTransferTimeoutSecs, }) if err != nil { - return fmt.Errorf("failed to create bufferedWriteHandler: %w", err) + return false, fmt.Errorf("failed to create bufferedWriteHandler: %w", err) } f.bwh.SetMtime(f.mtimeClock.Now()) + return true, nil } - - return nil + return false, nil } diff --git a/internal/fs/inode/file_mock_bucket_test.go b/internal/fs/inode/file_mock_bucket_test.go index deb83a3bc8..49939af993 100644 --- a/internal/fs/inode/file_mock_bucket_test.go +++ b/internal/fs/inode/file_mock_bucket_test.go @@ -114,8 +114,8 @@ func (t *FileMockBucketTest) createLockedInode(fileName string, fileType string) &cfg.Config{}, semaphore.NewWeighted(math.MaxInt64)) - // Create write handler for the local inode created above. - err := t.in.CreateBufferedOrTempWriter(t.ctx) + // Create empty file for local inode created above. + err := t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) t.in.Lock() diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 2841f4d129..f245cd7f86 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -63,9 +63,17 @@ type FileStreamingWritesZonalBucketTest struct { FileStreamingWritesCommon } -//////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////// // Helper -//////////////////////////////////////////////////////////////////////// +// ////////////////////////////////////////////////////////////////////// + +func (t *FileStreamingWritesCommon) createBufferedWriteHandler() { + // Initialize BWH for local inode created above. + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) + require.NoError(t.T(), err) + assert.True(t.T(), initialized) + assert.NotNil(t.T(), t.in.bwh) +} func (t *FileStreamingWritesCommon) setupTest() { // Enabling invariant check for all tests. @@ -155,6 +163,37 @@ func (t *FileStreamingWritesCommon) createInode(fileName string, fileType string t.in.Lock() } +//////////////////////////////////////////////////////////////////////// +// Common Tests +//////////////////////////////////////////////////////////////////////// + +func (t *FileStreamingWritesCommon) TestflushUsingBufferedWriteHandlerOnZeroSizeRecreatesBwhOnInitAgain() { + t.createBufferedWriteHandler() + err := t.in.flushUsingBufferedWriteHandler() + require.NoError(t.T(), err) + assert.Nil(t.T(), t.in.bwh) + + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) + + require.NoError(t.T(), err) + assert.True(t.T(), initialized) + assert.NotNil(t.T(), t.in.bwh) +} + +func (t *FileStreamingWritesCommon) TestflushUsingBufferedWriteHandlerOnNonZeroSizeDoesNotRecreatesBwhOnInitAgain() { + t.createBufferedWriteHandler() + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0)) + err := t.in.flushUsingBufferedWriteHandler() + require.NoError(t.T(), err) + assert.Nil(t.T(), t.in.bwh) + + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) + + require.NoError(t.T(), err) + assert.False(t.T(), initialized) + assert.Nil(t.T(), t.in.bwh) +} + //////////////////////////////////////////////////////////////////////// // Tests (Zonal Bucket) //////////////////////////////////////////////////////////////////////// @@ -168,12 +207,14 @@ func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritative } func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWriteForZonalBuckets() { + t.createBufferedWriteHandler() assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) } func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBucketsPromotesInodeToNonLocal() { + t.createBufferedWriteHandler() assert.NoError(t.T(), t.in.Write(t.ctx, []byte("pizza"), 0)) gcsSynced, err := t.in.SyncPendingBufferedWrites() @@ -187,6 +228,7 @@ func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZon } func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBucketsUpdatesSrcSize() { + t.createBufferedWriteHandler() assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0)) assert.Equal(t.T(), uint64(0), t.in.src.Size) @@ -210,12 +252,14 @@ func (t *FileStreamingWritesTest) TestSourceGenerationIsAuthoritativeReturnsTrue } func (t *FileStreamingWritesTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWriteForNonZonalBuckets() { + t.createBufferedWriteHandler() assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) } func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucketsDoesNotPromoteInodeToNonLocal() { + t.createBufferedWriteHandler() assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) gcsSynced, err := t.in.SyncPendingBufferedWrites() @@ -227,6 +271,7 @@ func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucket } func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucketsDoesNotUpdateSrcSize() { + t.createBufferedWriteHandler() assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0)) assert.Equal(t.T(), uint64(0), t.in.src.Size) @@ -262,13 +307,12 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF for _, tc := range testCases { t.Run(tc.name, func() { - err := t.in.CreateBufferedOrTempWriter(t.ctx) - assert.Nil(t.T(), err) + t.createBufferedWriteHandler() assert.True(t.T(), t.in.IsLocal()) createTime := t.clock.Now() t.clock.AdvanceTime(15 * time.Minute) // Sequential Write at offset 0 - err = t.in.Write(t.ctx, []byte("taco"), 0) + err := t.in.Write(t.ctx, []byte("taco"), 0) require.Nil(t.T(), err) require.NotNil(t.T(), t.in.bwh) // validate attributes. @@ -303,13 +347,11 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF } func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { - err := t.in.CreateBufferedOrTempWriter(t.ctx) - assert.Nil(t.T(), err) + t.createBufferedWriteHandler() assert.True(t.T(), t.in.IsLocal()) createTime := t.in.mtimeClock.Now() - require.NotNil(t.T(), t.in.bwh) // Out of order write. - err = t.in.Write(t.ctx, []byte("taco"), 6) + err := t.in.Write(t.ctx, []byte("taco"), 6) require.Nil(t.T(), err) // Ensure bwh cleared and temp file created. assert.Nil(t.T(), t.in.bwh) @@ -343,6 +385,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { } func (t *FileStreamingWritesTest) TestOutOfOrderWritesOnClobberedFileThrowsError() { + t.createBufferedWriteHandler() err := t.in.Write(t.ctx, []byte("hi"), 0) require.Nil(t.T(), err) require.NotNil(t.T(), t.in.bwh) @@ -376,6 +419,7 @@ func (t *FileStreamingWritesTest) TestUnlinkLocalFileBeforeWrite() { func (t *FileStreamingWritesTest) TestUnlinkLocalFileAfterWrite() { assert.True(t.T(), t.in.IsLocal()) + t.createBufferedWriteHandler() // Write some content. err := t.in.Write(t.ctx, []byte("tacos"), 0) assert.Nil(t.T(), err) @@ -392,6 +436,7 @@ func (t *FileStreamingWritesTest) TestUnlinkLocalFileAfterWrite() { func (t *FileStreamingWritesTest) TestUnlinkEmptySyncedFile() { t.createInode(fileName, emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) + t.createBufferedWriteHandler() // Write some content to temp file. err := t.in.Write(t.ctx, []byte("tacos"), 0) assert.Nil(t.T(), err) @@ -426,6 +471,7 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndFlush() { t.createInode(fileName, emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) } + t.createBufferedWriteHandler() // Write some content to temp file. err := t.in.Write(t.ctx, []byte("tacos"), 0) assert.Nil(t.T(), err) @@ -485,10 +531,9 @@ func (t *FileStreamingWritesTest) TestFlushEmptyFile() { assert.False(t.T(), t.in.IsLocal()) } t.clock.AdvanceTime(10 * time.Second) - err := t.in.CreateBufferedOrTempWriter(t.ctx) - assert.NoError(t.T(), err) + t.createBufferedWriteHandler() - err = t.in.Flush(t.ctx) + err := t.in.Flush(t.ctx) require.Nil(t.T(), err) // Ensure bwh cleared. @@ -543,8 +588,7 @@ func (t *FileStreamingWritesTest) TestFlushClobberedFile() { t.createInode(fileName, emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) } - err := t.in.CreateBufferedOrTempWriter(t.ctx) - assert.NoError(t.T(), err) + t.createBufferedWriteHandler() t.clock.AdvanceTime(10 * time.Second) // Clobber the file. objWritten, err := storageutil.CreateObject(t.ctx, t.bucket, fileName, []byte("taco")) @@ -587,6 +631,7 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndSync() { t.createInode(fileName, emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) } + t.createBufferedWriteHandler() // Write some content to temp file. err := t.in.Write(t.ctx, []byte("tacos"), 0) assert.Nil(t.T(), err) @@ -616,6 +661,7 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndSync() { } func (t *FileStreamingWritesTest) TestSourceGenerationSizeForLocalFileIsReflected() { + t.createBufferedWriteHandler() assert.True(t.T(), t.in.IsLocal()) err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0) require.NoError(t.T(), err) @@ -630,6 +676,7 @@ func (t *FileStreamingWritesTest) TestSourceGenerationSizeForLocalFileIsReflecte func (t *FileStreamingWritesTest) TestSourceGenerationSizeForSyncedFileIsReflected() { t.createInode(fileName, emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) + t.createBufferedWriteHandler() err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0) require.NoError(t.T(), err) @@ -640,12 +687,10 @@ func (t *FileStreamingWritesTest) TestSourceGenerationSizeForSyncedFileIsReflect } func (t *FileStreamingWritesTest) TestTruncateOnFileUsingTempFileDoesNotRecreatesBWH() { - err := t.in.CreateBufferedOrTempWriter(t.ctx) - assert.NoError(t.T(), err) + t.createBufferedWriteHandler() assert.True(t.T(), t.in.IsLocal()) - require.NotNil(t.T(), t.in.bwh) // Out of order write. - err = t.in.Write(t.ctx, []byte("taco"), 2) + err := t.in.Write(t.ctx, []byte("taco"), 2) require.Nil(t.T(), err) // Ensure bwh cleared and temp file created. assert.Nil(t.T(), t.in.bwh) @@ -704,9 +749,7 @@ func (t *FileStreamingWritesTest) TestDeRegisterFileHandle() { t.Run(tc.name, func() { t.in.config = &cfg.Config{Write: *getWriteConfig()} t.in.writeHandleCount = tc.currentVal - err := t.in.initBufferedWriteHandlerIfEligible(t.ctx) - require.NoError(t.T(), err) - require.NotNil(t.T(), t.in.bwh) + t.createBufferedWriteHandler() t.in.DeRegisterFileHandle(tc.readonly) @@ -754,10 +797,8 @@ func (t *FakeBufferedWriteHandler) Destroy() error { return nil } func (t *FakeBufferedWriteHandler) Unlink() {} func (t *FileStreamingWritesTest) TestWriteUsingBufferedWritesFails() { - err := t.in.CreateBufferedOrTempWriter(t.ctx) - assert.NoError(t.T(), err) + t.createBufferedWriteHandler() assert.True(t.T(), t.in.IsLocal()) - require.NotNil(t.T(), t.in.bwh) writeErr := errors.New("write error") t.in.bwh = &FakeBufferedWriteHandler{ WriteFunc: func(data []byte, offset int64) error { @@ -765,7 +806,7 @@ func (t *FileStreamingWritesTest) TestWriteUsingBufferedWritesFails() { }, } - err = t.in.Write(context.Background(), []byte("hello"), 0) + err := t.in.Write(context.Background(), []byte("hello"), 0) require.Error(t.T(), err) assert.Regexp(t.T(), writeErr.Error(), err.Error()) diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index e802c76bb2..5d5ba4cfc4 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -157,6 +157,14 @@ func (t *FileTest) createInodeWithLocalParam(fileName string, local bool) { t.in.Lock() } +func (t *FileTest) createBufferedWriteHandler(shouldInitialize bool) { + // Initialize BWH for local inode created above. + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) + require.NoError(t.T(), err) + assert.Equal(t.T(), shouldInitialize, initialized) + assert.NotNil(t.T(), t.in.bwh) +} + //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// @@ -502,7 +510,7 @@ func (t *FileTest) TestWriteToLocalFileThenSync() { // Create a local file inode. t.createInodeWithLocalParam("test", true) // Create a temp file for the local inode created above. - err = t.in.CreateBufferedOrTempWriter(t.ctx) + err = t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) // Write some content to temp file. t.clock.AdvanceTime(time.Second) @@ -574,7 +582,7 @@ func (t *FileTest) TestSyncEmptyLocalFile() { t.createInodeWithLocalParam("test", true) creationTime := t.clock.Now() // Create a temp file for the local inode created above. - err = t.in.CreateBufferedOrTempWriter(t.ctx) + err = t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) if tc.callSync { @@ -841,7 +849,8 @@ func (t *FileTest) TestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes var attrs fuseops.InodeAttributes // Create a local file inode. t.createInodeWithLocalParam("test", true) - err = t.in.CreateBufferedOrTempWriter(t.ctx) + // Create a temp file for the local inode created above. + err = t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) // Fetch the attributes and check if the file is empty. attrs, err = t.in.Attributes(t.ctx) @@ -867,7 +876,8 @@ func (t *FileTest) TestTruncateDownwardForLocalFileShouldUpdateLocalFileAttribut var attrs fuseops.InodeAttributes // Create a local file inode. t.createInodeWithLocalParam("test", true) - err = t.in.CreateBufferedOrTempWriter(t.ctx) + // Create a temp file for the local inode created above. + err = t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) // Write some data to the local file. err = t.in.Write(t.ctx, []byte("burrito"), 0) @@ -910,16 +920,13 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.config = &cfg.Config{Write: *getWriteConfig()} - err := t.in.CreateBufferedOrTempWriter(t.ctx) - assert.Nil(t.T(), err) - assert.NotNil(t.T(), t.in.bwh) - // Fetch the attributes and check if the file is empty. attrs, err := t.in.Attributes(t.ctx) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) if tc.performWrite { + t.createBufferedWriteHandler(true) err := t.in.Write(t.ctx, []byte("hi"), 0) assert.Nil(t.T(), err) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) @@ -928,6 +935,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) } + t.createBufferedWriteHandler(!tc.performWrite) err = t.in.Truncate(t.ctx, 10) @@ -963,13 +971,14 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable t.Run(tc.name, func() { t.createInodeWithEmptyObject() t.in.config = &cfg.Config{Write: *getWriteConfig()} - assert.Nil(t.T(), t.in.bwh) + // Fetch the attributes and check if the file is empty. attrs, err := t.in.Attributes(t.ctx) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) if tc.performWrite { + t.createBufferedWriteHandler(true) err := t.in.Write(t.ctx, []byte("hi"), 0) assert.Nil(t.T(), err) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) @@ -978,6 +987,7 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) } + t.createBufferedWriteHandler(!tc.performWrite) err = t.in.Truncate(t.ctx, 10) @@ -1036,13 +1046,13 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() } t.in.config = &cfg.Config{Write: *getWriteConfig()} - assert.Nil(t.T(), t.in.bwh) // Fetch the attributes and check if the file is empty. attrs, err := t.in.Attributes(t.ctx) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) if tc.performWrite { + t.createBufferedWriteHandler(true) err := t.in.Write(t.ctx, []byte("hihello"), 0) assert.Nil(t.T(), err) assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) @@ -1051,6 +1061,7 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { require.NoError(t.T(), err) assert.Equal(t.T(), uint64(7), attrs.Size) } + t.createBufferedWriteHandler(!tc.performWrite) err = t.in.Truncate(t.ctx, tc.truncateSize) @@ -1324,7 +1335,8 @@ func (t *FileTest) TestTestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() // Create a local file inode. t.createInodeWithLocalParam("test", true) createTime := t.in.mtimeClock.Now() - err = t.in.CreateBufferedOrTempWriter(t.ctx) + // Create a temp file for the local inode created above. + err = t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) // Validate the attributes on an empty file. attrs, err = t.in.Attributes(t.ctx) @@ -1356,8 +1368,7 @@ func (t *FileTest) TestSetMtimeForLocalFileWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.config = &cfg.Config{Write: *getWriteConfig()} - err = t.in.CreateBufferedOrTempWriter(t.ctx) - assert.Nil(t.T(), err) + t.createBufferedWriteHandler(true) // Set mtime. mtime := time.Now().UTC().Add(123 * time.Second) @@ -1411,8 +1422,8 @@ func (t *FileTest) TestTestCheckInvariantsShouldNotThrowExceptionForLocalFiles() assert.NotNil(t.T(), t.in) } -func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateEmptyFile() { - err := t.in.CreateBufferedOrTempWriter(t.ctx) +func (t *FileTest) TestCreateEmptyTempFile() { + err := t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.content) @@ -1422,39 +1433,56 @@ func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateEmptyFile() { assert.Equal(t.T(), int64(0), sr.Size) } -func (t *FileTest) TestCreateBufferedOrTempWriterShouldNotCreateFileWhenStreamingWritesAreEnabled() { - t.createInodeWithLocalParam("test", true) - t.in.config = &cfg.Config{Write: *getWriteConfig()} +func (t *FileTest) TestCreateEmptyTempFileWhenBWHIsNotNil() { + testCases := []struct { + name string + isLocal bool + }{ + { + name: "ShouldNotCreateForEmptyGCSFile", + isLocal: false, + }, + { + name: "ShouldNotCreateForLocalFile", + isLocal: true, + }, + } - err := t.in.CreateBufferedOrTempWriter(t.ctx) + for _, tc := range testCases { + t.Run(tc.name, func() { + if tc.isLocal { + t.createInodeWithLocalParam("test", true) + } else { + t.createInodeWithEmptyObject() + } + t.in.config = &cfg.Config{Write: *getWriteConfig()} + t.createBufferedWriteHandler(true) - assert.Nil(t.T(), err) - assert.Nil(t.T(), t.in.content) - assert.NotNil(t.T(), t.in.bwh) + err := t.in.CreateEmptyTempFile(t.ctx) + + assert.Nil(t.T(), err) + assert.Nil(t.T(), t.in.content) + }) + } } -func (t *FileTest) TestCreateBufferedOrTempWriterShouldCreateFileForNonLocalFilesForStreamingWrites() { +func (t *FileTest) TestInitBufferedWriteHandlerIfEligibleShouldNotCreateBWHNonEmptySyncedFile() { // Enabling buffered writes. t.in.config = &cfg.Config{Write: *getWriteConfig()} - err := t.in.CreateBufferedOrTempWriter(t.ctx) + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) - assert.Nil(t.T(), err) - assert.NotNil(t.T(), t.in.content) + assert.NoError(t.T(), err) assert.Nil(t.T(), t.in.bwh) - // Validate that file size is 0. - sr, err := t.in.content.Stat() - require.NoError(t.T(), err) - assert.Equal(t.T(), int64(0), sr.Size) + assert.False(t.T(), initialized) } func (t *FileTest) TestUnlinkLocalFile() { var err error - // Create a local file inode. t.createInodeWithLocalParam("test", true) // Create a temp file for the local inode created above. - err = t.in.CreateBufferedOrTempWriter(t.ctx) + err = t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) // Unlink. @@ -1497,9 +1525,7 @@ func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.config = &cfg.Config{Write: *getWriteConfig()} - err := t.in.CreateBufferedOrTempWriter(t.ctx) - assert.Nil(t.T(), err) - assert.NotNil(t.T(), t.in.bwh) + t.createBufferedWriteHandler(true) } if tc.fileType == EmptyGCSFile { @@ -1508,6 +1534,7 @@ func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { } if tc.performWrite { + t.createBufferedWriteHandler(tc.fileType != LocalFile) err := t.in.Write(t.ctx, []byte("hi"), 0) assert.Nil(t.T(), err) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) @@ -1536,23 +1563,23 @@ func (t *FileTest) TestReadEmptyGCSFileWhenStreamingWritesAreNotInProgress() { assert.Contains(t.T(), err.Error(), "EOF") } -func (t *FileTest) TestWriteToLocalFileWithInvalidConfigWhenStreamingWritesAreEnabled() { +func (t *FileTest) TestInitBufferedWriteHandlerWithInvalidConfigWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.config = &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: true}} - assert.Nil(t.T(), t.in.bwh) - err := t.in.Write(t.ctx, []byte("hi"), 0) + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) - require.Error(t.T(), err) assert.True(t.T(), strings.Contains(err.Error(), "invalid configuration")) + assert.False(t.T(), initialized) + assert.Nil(t.T(), t.in.bwh) } func (t *FileTest) TestWriteToLocalFileWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.config = &cfg.Config{Write: *getWriteConfig()} - assert.Nil(t.T(), t.in.bwh) + t.createBufferedWriteHandler(true) err := t.in.Write(t.ctx, []byte("hi"), 0) @@ -1567,7 +1594,7 @@ func (t *FileTest) TestMultipleWritesToLocalFileWhenStreamingWritesAreEnabled() t.createInodeWithLocalParam("test", true) createTime := t.in.mtimeClock.Now() t.in.config = &cfg.Config{Write: *getWriteConfig()} - assert.Nil(t.T(), t.in.bwh) + t.createBufferedWriteHandler(true) err := t.in.Write(t.ctx, []byte("hi"), 0) assert.Nil(t.T(), err) @@ -1588,7 +1615,7 @@ func (t *FileTest) TestWriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() t.in.config = &cfg.Config{Write: *getWriteConfig()} createTime := t.in.mtimeClock.Now() - assert.Nil(t.T(), t.in.bwh) + t.createBufferedWriteHandler(true) err := t.in.Write(t.ctx, []byte("hi"), 0) @@ -1618,7 +1645,7 @@ func (t *FileTest) TestSetMtimeOnEmptyGCSFileWhenStreamingWritesAreEnabled() { func (t *FileTest) TestSetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() t.in.config = &cfg.Config{Write: *getWriteConfig()} - assert.Nil(t.T(), t.in.bwh) + t.createBufferedWriteHandler(true) // Initiate write call. err := t.in.Write(t.ctx, []byte("hi"), 0) assert.Nil(t.T(), err) diff --git a/tools/integration_tests/streaming_writes/buffer_size_test.go b/tools/integration_tests/streaming_writes/buffer_size_test.go index 7a6d1913c6..b720cb3737 100644 --- a/tools/integration_tests/streaming_writes/buffer_size_test.go +++ b/tools/integration_tests/streaming_writes/buffer_size_test.go @@ -15,6 +15,7 @@ package streaming_writes import ( + "path" "testing" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" @@ -62,7 +63,13 @@ func TestWritesWithDifferentConfig(t *testing.T) { defer setup.UnmountGCSFuse(rootDir) testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + fh := operations.CreateFile(path.Join(testDirPath, FileName1), FilePerms, t) + testDirName := GetDirName(testDirPath) + if setup.IsZonalBucketRun() { + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, FileName1, "", t) + } else { + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t) + } data, err := operations.GenerateRandomData(tc.fileSize) if err != nil { t.Fatalf("Error in generating data: %v", err) diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go index d92eccbe83..39e6315a31 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/default_mount_local_file_test.go @@ -22,24 +22,30 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_suite" "github.com/stretchr/testify/suite" ) -type defaultMountLocalFile struct { +type defaultMountCommonLocalFile struct { defaultMountCommonTest - CommonLocalFileTestSuite suite.Suite } -func (t *defaultMountLocalFile) SetupTest() { +type defaultMountRegionalBucketLocalFile struct { + CommonLocalFileTestSuite + defaultMountCommonLocalFile + test_suite.TestifySuite +} + +func (t *defaultMountCommonLocalFile) SetupTest() { t.createLocalFile() } -func (t *defaultMountLocalFile) SetupSubTest() { +func (t *defaultMountCommonLocalFile) SetupSubTest() { t.createLocalFile() } -func (t *defaultMountLocalFile) createLocalFile() { +func (t *defaultMountCommonLocalFile) createLocalFile() { t.fileName = FileName1 + setup.GenerateRandomString(5) t.filePath = path.Join(testDirPath, t.fileName) // Create a local file with O_DIRECT. @@ -48,8 +54,15 @@ func (t *defaultMountLocalFile) createLocalFile() { // Executes all tests that run with single streamingWrites configuration for localFiles. func TestDefaultMountLocalFileTest(t *testing.T) { - s := new(defaultMountLocalFile) - s.CommonLocalFileTestSuite.TestifySuite = &s.Suite - s.defaultMountCommonTest.TestifySuite = &s.Suite - suite.Run(t, s) + if setup.IsZonalBucketRun() { + s := new(defaultMountCommonLocalFile) + s.defaultMountCommonTest.TestifySuite = &s.Suite + suite.Run(t, s) + } else { + s := new(defaultMountRegionalBucketLocalFile) + s.defaultMountCommonTest.TestifySuite = &s.defaultMountCommonLocalFile.Suite + s.CommonLocalFileTestSuite.TestifySuite = &s.defaultMountCommonLocalFile.Suite + s.TestifySuite = &s.defaultMountCommonLocalFile.Suite + suite.Run(t, s) + } } diff --git a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go index 462ea090bd..69401971e8 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go @@ -94,6 +94,8 @@ func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCreatedFromSameMountR } func (t *unfinalizedObjectOperations) TestOverWritingUnfinalizedObjectsReturnsESTALE() { + // TODO(b/411333280): Enable the test once flush on unfinalized Object is fixed. + t.T().Skip("Skipping the test due to b/411333280") var size int64 = operations.MiB _ = client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), size) fh := operations.OpenFile(path.Join(t.testDirPath, t.fileName), t.T()) From e6a535cba5c7bd8ad847ad1ea794d23db563ee0c Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Thu, 1 May 2025 16:38:54 +0530 Subject: [PATCH 0397/1298] Adding simulated clock implementation for time.After method (#3254) * Simulated Clock for time.After method * header change * review comments * Review comments by kislay * review comments * review comments * minor improvements --- internal/clock/simulated_clock.go | 115 ++++++++++ internal/clock/simulated_clock_test.go | 287 +++++++++++++++++++++++++ 2 files changed, 402 insertions(+) create mode 100644 internal/clock/simulated_clock.go create mode 100644 internal/clock/simulated_clock_test.go diff --git a/internal/clock/simulated_clock.go b/internal/clock/simulated_clock.go new file mode 100644 index 0000000000..d279396632 --- /dev/null +++ b/internal/clock/simulated_clock.go @@ -0,0 +1,115 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package clock + +import ( + "sync" + "time" +) + +// afterRequest holds the information for a pending After call in SimulatedClock. +type afterRequest struct { + targetTime time.Time + ch chan time.Time +} + +// SimulatedClock is a clock that allows for manipulation of the time, +// which does not change unless AdvanceTime or SetTime is called. +// The zero value is a clock initialized to the zero time. +type SimulatedClock struct { + mu sync.RWMutex + t time.Time // GUARDED_BY(mu) + pending []*afterRequest // GUARDED_BY(mu) +} + +func NewSimulatedClock(startTime time.Time) *SimulatedClock { + return &SimulatedClock{ + t: startTime, + pending: nil, + } +} + +func (sc *SimulatedClock) Now() time.Time { + sc.mu.RLock() + defer sc.mu.RUnlock() + + return sc.t +} + +// SetTime sets the current time according to the clock. +// It also processes any pending After calls that should fire. +func (sc *SimulatedClock) SetTime(t time.Time) { + sc.mu.Lock() + defer sc.mu.Unlock() + + sc.t = t + sc.processPending() +} + +// AdvanceTime advances the current time according to the clock by the supplied duration. +// It also processes any pending After calls that should fire. +func (sc *SimulatedClock) AdvanceTime(d time.Duration) { + sc.mu.Lock() + defer sc.mu.Unlock() + + sc.t = sc.t.Add(d) + sc.processPending() +} + +// After returns a time channel and the expectedFired time is sent over the returned +// channel after the provided duration. +// If the duration is zero or negative, it sends the current simulated time immediately. +func (sc *SimulatedClock) After(d time.Duration) <-chan time.Time { + sc.mu.Lock() + defer sc.mu.Unlock() + + ch := make(chan time.Time, 1) + effectiveTargetTime := sc.t.Add(d) + + // If duration is non-positive, fire immediately with the current time. + // Or if the target time is not after the current time (e.g. d <= 0) + if !effectiveTargetTime.After(sc.t) { + ch <- sc.t // Send current time as per time.After behavior for d <= 0 + return ch + } + + // Otherwise, schedule it. + ar := &afterRequest{ + targetTime: effectiveTargetTime, + ch: ch, + } + sc.pending = append(sc.pending, ar) + + return ch +} + +// processPending checks all pending After requests and fires those +// whose target time has been reached or passed by the current simulated time. +// This method must be called with sc.mu held. +func (sc *SimulatedClock) processPending() { + var stillPending []*afterRequest + + for _, ar := range sc.pending { + // If current time sc.t is not before the targetTime (i.e., sc.t >= ar.targetTime) + if !sc.t.Before(ar.targetTime) { + ar.ch <- ar.targetTime // Send the time it was scheduled to fire + // Do not close the channel, to mimic time.After behavior + } else { + stillPending = append(stillPending, ar) + } + } + + sc.pending = stillPending +} diff --git a/internal/clock/simulated_clock_test.go b/internal/clock/simulated_clock_test.go new file mode 100644 index 0000000000..b3fe540349 --- /dev/null +++ b/internal/clock/simulated_clock_test.go @@ -0,0 +1,287 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package clock + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + // A non-zero reference time for tests + shortTestTimeout = 10 * time.Millisecond // For non-blocking channel checks + fireTestTimeout = 50 * time.Millisecond // When expecting a channel to fire +) + +func TestSimulatedClock_Now(t *testing.T) { + testCases := []struct { + name string + initialTimeSetup func(sc *SimulatedClock) + expectedTime time.Time + }{ + { + name: "InitialState_IsZeroTime", + initialTimeSetup: func(sc *SimulatedClock) {}, + expectedTime: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC), + }, + { + name: "AfterSetTime_ReturnsSetTime", + initialTimeSetup: func(sc *SimulatedClock) { + sc.SetTime(time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)) + }, + expectedTime: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC), + }, + { + name: "AfterAdvanceTime_ReturnsAdvancedTime", + initialTimeSetup: func(sc *SimulatedClock) { + sc.SetTime(time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)) + sc.AdvanceTime(time.Hour) + }, + expectedTime: time.Date(2020, time.January, 1, 13, 0, 0, 0, time.UTC), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clock := NewSimulatedClock(time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)) + tc.initialTimeSetup(clock) + + now := clock.Now() + + assert.Equal(t, tc.expectedTime, now) + }) + } +} + +func TestSimulatedClock_SetTime(t *testing.T) { + testCases := []struct { + name string + initialTimeSetup func(sc *SimulatedClock) // To set a pre-existing time if needed + timeToSet time.Time + expectedAfter time.Time + }{ + { + name: "SetFromZeroTime", + initialTimeSetup: func(sc *SimulatedClock) {}, + timeToSet: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC), + expectedAfter: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC), + }, + { + name: "OverwriteExistingTime", + initialTimeSetup: func(sc *SimulatedClock) { + sc.SetTime(time.Date(2020, time.January, 1, 11, 0, 0, 0, time.UTC)) // Start with a different time + }, + timeToSet: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC), + expectedAfter: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC), + }, + { + name: "SetToZeroTime", + initialTimeSetup: func(sc *SimulatedClock) { sc.SetTime(time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)) }, + timeToSet: time.Time{}, // Zero time + expectedAfter: time.Time{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clock := NewSimulatedClock(time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)) + tc.initialTimeSetup(clock) + + clock.SetTime(tc.timeToSet) + + assert.Equal(t, tc.expectedAfter, clock.Now()) + }) + } +} + +func TestSimulatedClock_AdvanceTime(t *testing.T) { + testCases := []struct { + name string + initialTime time.Time + advanceBy time.Duration + expectedTime time.Time + }{ + { + name: "AdvancePositiveDuration", + initialTime: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC), + advanceBy: 5 * time.Minute, + expectedTime: time.Date(2020, time.January, 1, 12, 5, 0, 0, time.UTC), + }, + { + name: "AdvanceNegativeDuration", + initialTime: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC), + advanceBy: -2 * time.Hour, + expectedTime: time.Date(2020, time.January, 1, 10, 0, 0, 0, time.UTC), + }, + { + name: "AdvanceByZeroDuration", + initialTime: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC), + advanceBy: 0, + expectedTime: time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC), + }, + { + name: "AdvanceFromZeroTime", + initialTime: time.Time{}, + advanceBy: time.Hour, + expectedTime: time.Date(1, time.January, 1, 1, 0, 0, 0, time.UTC), // zeroTime + time.Hour + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clock := NewSimulatedClock(time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)) + clock.SetTime(tc.initialTime) // Set initial time for the clock + + clock.AdvanceTime(tc.advanceBy) + + assert.Equal(t, tc.expectedTime, clock.Now()) + }) + } +} + +func TestSimulatedClock_After_ShouldFireZeroOrNegativeDuration(t *testing.T) { + testCases := []struct { + name string + afterDuration time.Duration + action func(sc *SimulatedClock) // Action to manipulate the clock after After() is called + }{ + { + name: "ZeroDuration_FiresImmediately", + afterDuration: 0, + action: func(sc *SimulatedClock) { /* No action needed for immediate fire */ }, + }, + { + name: "NegativeDuration_FiresImmediately", + afterDuration: -5 * time.Second, + action: func(sc *SimulatedClock) { /* No action needed for immediate fire */ }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clock := NewSimulatedClock(time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)) + clockTimeAtAfterCall := clock.Now() + + ch := clock.After(tc.afterDuration) + require.NotNil(t, ch, "Channel should not be nil") + + // Perform the action (if any) that might trigger the timer + tc.action(clock) + + // Fires at the same time for zero/negative duration. + expectedFireTimeOnChannel := clockTimeAtAfterCall + + select { + case actualTime := <-ch: + assert.Equal(t, expectedFireTimeOnChannel, actualTime) + + case <-time.After(fireTestTimeout): + t.Fatalf("Timeout waiting for time on channel. Expected after %v.", expectedFireTimeOnChannel) + } + }) + } +} + +func TestSimulatedClock_After_ShouldFirePositiveDuration(t *testing.T) { + testCases := []struct { + name string + afterDuration time.Duration + action func(sc *SimulatedClock) // Action to manipulate the clock after After() is called + }{ + { + name: "PositiveDuration_Fires_WhenTimeAdvancedPastDuration", + afterDuration: 10 * time.Second, + action: func(sc *SimulatedClock) { + sc.AdvanceTime(15 * time.Second) // Advance well past the duration + }, + }, + { + name: "PositiveDuration_Fires_WhenTimeSetPastDuration", + afterDuration: 10 * time.Second, + action: func(sc *SimulatedClock) { + sc.SetTime(time.Date(2020, time.January, 1, 12, 0, 15, 0, time.UTC)) // Set time well past the duration + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clock := NewSimulatedClock(time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)) + clockTimeAtAfterCall := clock.Now() + + ch := clock.After(tc.afterDuration) + require.NotNil(t, ch, "Channel should not be nil") + + // Perform the action (if any) that might trigger the timer + tc.action(clock) + // Expected fire time, time + duration. + expectedFireTimeOnChannel := clockTimeAtAfterCall.Add(tc.afterDuration) + + select { + case actualTime := <-ch: + assert.Equal(t, expectedFireTimeOnChannel, actualTime) + + case <-time.After(fireTestTimeout): + t.Fatalf("Timeout waiting for time on channel. Expected after %v.", expectedFireTimeOnChannel) + } + }) + } +} + +func TestSimulatedClock_After_ShouldNotFire(t *testing.T) { + testCases := []struct { + name string + afterDuration time.Duration + action func(sc *SimulatedClock) // Action to manipulate the clock after After() is called + }{ + { + name: "PositiveDuration_DoesNotFire_WhenTimeAdvancedBeforeDuration", + afterDuration: 10 * time.Second, + action: func(sc *SimulatedClock) { + sc.AdvanceTime(5 * time.Second) // Advance, but not enough + }, + }, + { + name: "PositiveDuration_DoesNotFire_WhenTimeSetBeforeDuration", + afterDuration: 10 * time.Second, + action: func(sc *SimulatedClock) { + sc.SetTime(time.Date(2020, time.January, 1, 12, 0, 5, 0, time.UTC)) // Set time, but not enough + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + clock := NewSimulatedClock(time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)) + + ch := clock.After(tc.afterDuration) + require.NotNil(t, ch, "Channel should not be nil") + + // Perform the action (if any) that might trigger the timer + tc.action(clock) + + select { + case receivedTime := <-ch: + t.Fatalf("Expected no time on channel, but received %v.", receivedTime) + + case <-time.After(shortTestTimeout): + // Success, nothing received + } + }) + } +} From 879fd838744b04d040ddfa467c450687d641a126 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 2 May 2025 13:12:45 +0530 Subject: [PATCH 0398/1298] [Random Reader Refactoring] MRR implementation (#3255) * adding mrd * add unit test * add tests * adding unit tests * small fixes * review comment * Delete test file * reduce sleep time --- .../gcsx/client_readers/multi_range_reader.go | 109 ++++++++ .../client_readers/multi_range_reader_test.go | 248 ++++++++++++++++++ .../gcsx/client_readers/range_reader_test.go | 2 +- internal/gcsx/file_cache_reader_test.go | 2 +- 4 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 internal/gcsx/client_readers/multi_range_reader.go create mode 100644 internal/gcsx/client_readers/multi_range_reader_test.go diff --git a/internal/gcsx/client_readers/multi_range_reader.go b/internal/gcsx/client_readers/multi_range_reader.go new file mode 100644 index 0000000000..f9546ebad2 --- /dev/null +++ b/internal/gcsx/client_readers/multi_range_reader.go @@ -0,0 +1,109 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "context" + "fmt" + "io" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" +) + +// TimeoutForMultiRangeRead is the timeout value for multi-range read operations. +// +// TODO(b/385826024): Revert timeout to an appropriate value. This value is currently a placeholder and needs to be adjusted. +const TimeoutForMultiRangeRead = time.Hour + +type MultiRangeReader struct { + gcsx.GCSReader + + object *gcs.MinObject + + // mrdWrapper points to the wrapper object within inode. + mrdWrapper *gcsx.MultiRangeDownloaderWrapper + + // boolean variable to determine if MRD is being used or not. + isMRDInUse bool + + metricHandle common.MetricHandle +} + +func NewMultiRangeReader(object *gcs.MinObject, metricHandle common.MetricHandle, mrdWrapper *gcsx.MultiRangeDownloaderWrapper) *MultiRangeReader { + return &MultiRangeReader{ + object: object, + metricHandle: metricHandle, + mrdWrapper: mrdWrapper, + } +} + +// readFromMultiRangeReader reads data from the underlying MultiRangeDownloaderWrapper. +// +// It increments the reference count of the mrdWrapper if it's not already in use. +// It then calls the Read method of the mrdWrapper with the provided parameters. +// +// Parameters: +// - ctx: The context for the read operation. It can be used to cancel the operation or set a timeout. +// - p: The byte slice to read data into. +// - offset: The starting offset for the read operation. +// - end: The ending offset for the read operation. +// - timeout: The maximum duration for the read operation. +// +// Returns: +// - int: The number of bytes read. +// - error: An error if the read operation fails. +func (mrd *MultiRangeReader) readFromMultiRangeReader(ctx context.Context, p []byte, offset, end int64, timeout time.Duration) (int, error) { + if mrd.mrdWrapper == nil { + return 0, fmt.Errorf("readFromMultiRangeReader: Invalid MultiRangeDownloaderWrapper") + } + + if !mrd.isMRDInUse { + mrd.isMRDInUse = true + mrd.mrdWrapper.IncrementRefCount() + } + + return mrd.mrdWrapper.Read(ctx, p, offset, end, timeout, mrd.metricHandle) +} + +func (mrd *MultiRangeReader) ReadAt(ctx context.Context, req *gcsx.GCSReaderRequest) (gcsx.ReaderResponse, error) { + readerResponse := gcsx.ReaderResponse{ + DataBuf: req.Buffer, + Size: 0, + } + var err error + + if req.Offset >= int64(mrd.object.Size) { + err = io.EOF + return readerResponse, err + } + + readerResponse.Size, err = mrd.readFromMultiRangeReader(ctx, req.Buffer, req.Offset, req.EndOffset, TimeoutForMultiRangeRead) + + return readerResponse, err +} + +func (mrd *MultiRangeReader) destroy() { + if mrd.isMRDInUse { + err := mrd.mrdWrapper.DecrementRefCount() + if err != nil { + logger.Errorf("randomReader::Destroy:%v", err) + } + mrd.isMRDInUse = false + } +} diff --git a/internal/gcsx/client_readers/multi_range_reader_test.go b/internal/gcsx/client_readers/multi_range_reader_test.go new file mode 100644 index 0000000000..c38075371d --- /dev/null +++ b/internal/gcsx/client_readers/multi_range_reader_test.go @@ -0,0 +1,248 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "context" + "errors" + "io" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + testUtil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +const ( + TestTimeoutForMultiRangeRead = time.Second +) + +type multiRangeReaderTest struct { + suite.Suite + ctx context.Context + object *gcs.MinObject + mockBucket *storage.TestifyMockBucket + multiRangeReader *MultiRangeReader +} + +func (t *multiRangeReaderTest) readAt(offset int64, size int64) (gcsx.ReaderResponse, error) { + req := &gcsx.GCSReaderRequest{ + Offset: offset, + EndOffset: offset + size, + Buffer: make([]byte, size), + } + return t.multiRangeReader.ReadAt(t.ctx, req) +} + +func TestMultiRangeReaderTestSuite(t *testing.T) { + suite.Run(t, new(multiRangeReaderTest)) +} + +func (t *multiRangeReaderTest) SetupTest() { + t.mockBucket = new(storage.TestifyMockBucket) + t.ctx = context.Background() + t.object = &gcs.MinObject{ + Name: "testObject", + Size: 17, + Generation: 1234, + } + t.multiRangeReader = NewMultiRangeReader(t.object, common.NewNoopMetrics(), nil) +} + +func (t *multiRangeReaderTest) TearDownTest() { + t.multiRangeReader.destroy() +} + +func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_ReadFull() { + testCases := []struct { + name string + dataSize int + extraSize int + }{ + { + name: "ReadFull", + dataSize: 100, + extraSize: 0, + }, + { + name: "ReadWithLargerBuffer", + dataSize: 100, + extraSize: 10, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.multiRangeReader.isMRDInUse = false + t.object.Size = uint64(tc.dataSize) + testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + require.NoError(t.T(), err, "Error in creating MRDWrapper") + t.multiRangeReader.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) + buf := make([]byte, tc.dataSize+tc.extraSize) + + bytesRead, err := t.multiRangeReader.readFromMultiRangeReader(t.ctx, buf, 0, int64(t.object.Size), TestTimeoutForMultiRangeRead) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), tc.dataSize, bytesRead) + assert.Equal(t.T(), testContent[:tc.dataSize], buf[:bytesRead]) + }) + } +} + +func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_TimeoutExceeded() { + t.multiRangeReader.isMRDInUse = false + dataSize := 100 + t.object.Size = uint64(dataSize) + testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + require.NoError(t.T(), err, "Error in creating MRDWrapper") + t.multiRangeReader.mrdWrapper = &fakeMRDWrapper + sleepTime := 10 * time.Millisecond + timeout := 5 * time.Millisecond + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, sleepTime)).Once() + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Once() + buf := make([]byte, dataSize) + + _, err = t.multiRangeReader.readFromMultiRangeReader(t.ctx, buf, 0, int64(t.object.Size), timeout) + + assert.Error(t.T(), err) + assert.ErrorContains(t.T(), err, "Timeout") +} + +func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_TimeoutNotExceeded() { + t.multiRangeReader.isMRDInUse = false + dataSize := 100 + t.object.Size = uint64(dataSize) + testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + require.NoError(t.T(), err, "Error in creating MRDWrapper") + t.multiRangeReader.mrdWrapper = &fakeMRDWrapper + sleepTime := 2 * time.Millisecond + timeout := 5 * time.Millisecond + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, sleepTime)).Once() + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Once() + buf := make([]byte, dataSize) + + bytesRead, err := t.multiRangeReader.readFromMultiRangeReader(t.ctx, buf, 0, int64(t.object.Size), timeout) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), dataSize, bytesRead) + assert.Equal(t.T(), testContent[:dataSize], buf[:bytesRead]) +} + +func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_NilMRDWrapper() { + t.multiRangeReader.mrdWrapper = nil + + bytesRead, err := t.multiRangeReader.readFromMultiRangeReader(t.ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), TestTimeoutForMultiRangeRead) + + assert.Error(t.T(), err) + assert.ErrorContains(t.T(), err, "readFromMultiRangeReader: Invalid MultiRangeDownloaderWrapper") + assert.Equal(t.T(), 0, bytesRead) +} + +func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_ReadChunk() { + testCases := []struct { + name string + dataSize int + start int + end int + }{ + { + name: "ReadChunk", + dataSize: 100, + start: 37, + end: 93, + }, + } + + for _, tc := range testCases { + t.object.Size = uint64(tc.dataSize) + testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + require.NoError(t.T(), err, "Error in creating MRDWrapper") + t.multiRangeReader.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) + buf := make([]byte, tc.end-tc.start) + + bytesRead, err := t.multiRangeReader.readFromMultiRangeReader(t.ctx, buf, int64(tc.start), int64(tc.end), TestTimeoutForMultiRangeRead) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), tc.end-tc.start, bytesRead) + assert.Equal(t.T(), testContent[tc.start:tc.end], buf[:bytesRead]) + } +} + +func (t *multiRangeReaderTest) Test_ReadAt_MRDRead() { + testCases := []struct { + name string + dataSize int + offset int + bytesToRead int + }{ + { + name: "ReadChunk", + dataSize: 100, + offset: 37, + bytesToRead: 43, + }, + { + name: "ReadZeroByte", + dataSize: 100, + offset: 37, + bytesToRead: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.multiRangeReader.isMRDInUse = false + t.object.Size = uint64(tc.dataSize) + testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + require.NoError(t.T(), err, "Error in creating MRDWrapper") + t.multiRangeReader.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) + + readerResponse, err := t.readAt(int64(tc.offset), int64(tc.bytesToRead)) + + t.mockBucket.AssertNotCalled(t.T(), "NewReaderWithReadHandle", mock.Anything) + assert.NoError(t.T(), err) + assert.Equal(t.T(), tc.bytesToRead, readerResponse.Size) + assert.Equal(t.T(), testContent[tc.offset:tc.offset+tc.bytesToRead], readerResponse.DataBuf[:readerResponse.Size]) + }) + } +} + +func (t *multiRangeReaderTest) Test_ReadAt_InvalidOffset() { + t.object.Size = 50 + + _, err := t.readAt(65, int64(t.object.Size)) + + assert.True(t.T(), errors.Is(err, io.EOF), "expected %v error got %v", io.EOF, err) +} diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go index 5e664bfda4..3df7de7a77 100644 --- a/internal/gcsx/client_readers/range_reader_test.go +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -68,7 +68,7 @@ func (t *rangeReaderTest) SetupTest() { t.ctx = context.Background() } -func (t *rangeReaderTest) TearDown() { +func (t *rangeReaderTest) TearDownTest() { t.rangeReader.destroy() } diff --git a/internal/gcsx/file_cache_reader_test.go b/internal/gcsx/file_cache_reader_test.go index d4f39ff13b..80112ee6fe 100644 --- a/internal/gcsx/file_cache_reader_test.go +++ b/internal/gcsx/file_cache_reader_test.go @@ -77,7 +77,7 @@ func (t *fileCacheReaderTest) SetupTest() { t.ctx = context.Background() } -func (t *fileCacheReaderTest) TearDown() { +func (t *fileCacheReaderTest) TearDownTest() { err := os.RemoveAll(t.cacheDir) if err != nil { t.T().Logf("Failed to clean up test cache directory '%s': %v", t.cacheDir, err) From 6ff9f5fab3fdd7eaded9708dae51a5b8f0ad5591 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Fri, 2 May 2025 17:22:03 +0530 Subject: [PATCH 0399/1298] fix: Use random bucket name in each CreateTestBucketForDynamicMounting call instead of using fixed random name. (#3240) * fix CreateTestBucketForDynamicMounting to not use fixed random bucket name * use metadata.ProjectIDWithContext * fix error handling. * cleanup redundant comment --- .../read_cache/remount_test.go | 5 ++- .../dynamic_mounting/dynamic_mounting.go | 36 +++++++++---------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/tools/integration_tests/read_cache/remount_test.go b/tools/integration_tests/read_cache/remount_test.go index 6be8f69849..f8a54de6c4 100644 --- a/tools/integration_tests/read_cache/remount_test.go +++ b/tools/integration_tests/read_cache/remount_test.go @@ -92,7 +92,10 @@ func (s *remountTest) TestCacheIsNotReusedOnDynamicRemount(t *testing.T) { runTestsOnlyForDynamicMount(t) testBucket1 := setup.TestBucket() testFileName1 := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) - testBucket2 := dynamic_mounting.CreateTestBucketForDynamicMounting(ctx, storageClient) + testBucket2, err := dynamic_mounting.CreateTestBucketForDynamicMounting(ctx, storageClient) + if err != nil { + t.Fatalf("Failed to create bucket for dynamic mounting test: %v", err) + } defer func() { if err := client.DeleteBucket(ctx, storageClient, testBucket2); err != nil { t.Logf("Failed to delete test bucket %s.Error : %v", testBucket1, err) diff --git a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go index 02b38d5171..3b32c0a52e 100644 --- a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go +++ b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go @@ -29,11 +29,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) -// Adding prefix `golang-grpc-test` to white list the bucket for grpc so that -// we can run the grpc related e2e test. -const PrefixBucketForDynamicMountingTest = "golang-grpc-test-gcsfuse-dynamic-mounting-test-" - -var testBucketForDynamicMounting = PrefixBucketForDynamicMountingTest + setup.GenerateRandomString(5) +const PrefixBucketForDynamicMountingTest = "gcsfuse-dynamic-mounting-test-" func MountGcsfuseWithDynamicMounting(flags []string) (err error) { defaultArg := []string{"--log-severity=trace", @@ -75,7 +71,7 @@ func runTestsOnGivenMountedTestBucket(bucketName string, flags [][]string, rootM return } -func executeTestsForDynamicMounting(flags [][]string, m *testing.M) (successCode int) { +func executeTestsForDynamicMounting(flags [][]string, createdBucket string, m *testing.M) (successCode int) { rootMntDir := setup.MntDir() // In dynamic mounting all the buckets mounted in mntDir which user has permission. @@ -89,9 +85,9 @@ func executeTestsForDynamicMounting(flags [][]string, m *testing.M) (successCode // Test on created bucket. // SetDynamicBucketMounted to the mounted bucket. - setup.SetDynamicBucketMounted(testBucketForDynamicMounting) + setup.SetDynamicBucketMounted(createdBucket) if successCode == 0 { - successCode = runTestsOnGivenMountedTestBucket(testBucketForDynamicMounting, flags, rootMntDir, m) + successCode = runTestsOnGivenMountedTestBucket(createdBucket, flags, rootMntDir, m) } // Reset SetDynamicBucketMounted to empty after tests are done. setup.SetDynamicBucketMounted("") @@ -101,10 +97,10 @@ func executeTestsForDynamicMounting(flags [][]string, m *testing.M) (successCode return } -func CreateTestBucketForDynamicMounting(ctx context.Context, client *storage.Client) (bucketName string) { - projectID, err := metadata.ProjectID() +func CreateTestBucketForDynamicMounting(ctx context.Context, client *storage.Client) (bucketName string, err error) { + projectID, err := metadata.ProjectIDWithContext(ctx) if err != nil { - log.Printf("Error in fetching project id: %v", err) + return "", fmt.Errorf("failed to get project ID of instance: %v", err) } // Create bucket handle and attributes @@ -112,24 +108,28 @@ func CreateTestBucketForDynamicMounting(ctx context.Context, client *storage.Cli Location: "us-west1", } - bucket := client.Bucket(testBucketForDynamicMounting) + bucketName = PrefixBucketForDynamicMountingTest + setup.GenerateRandomString(5) + bucket := client.Bucket(bucketName) if err := bucket.Create(ctx, projectID, storageClassAndLocation); err != nil { - log.Fatalf("DynamicBucket(%q).Create: %v", testBucketForDynamicMounting, err) + return "", fmt.Errorf("failed to create bucket: %v", err) } - return testBucketForDynamicMounting + return } func RunTests(ctx context.Context, client *storage.Client, flags [][]string, m *testing.M) (successCode int) { log.Println("Running dynamic mounting tests...") - CreateTestBucketForDynamicMounting(ctx, client) + createdBucket, err := CreateTestBucketForDynamicMounting(ctx, client) + if err != nil { + log.Fatalf("Failed to create bucket for dynamic mounting test: %v", err) + } - successCode = executeTestsForDynamicMounting(flags, m) + successCode = executeTestsForDynamicMounting(flags, createdBucket, m) log.Printf("Test log: %s\n", setup.LogFile()) - if err := client_util.DeleteBucket(ctx, client, testBucketForDynamicMounting); err != nil { - log.Fatalf("Failed to delete the bucket : %s. Error: %v", testBucketForDynamicMounting, err) + if err := client_util.DeleteBucket(ctx, client, createdBucket); err != nil { + log.Fatalf("Failed to delete the created bucket for dynamic mounting test: %s. Error: %v", createdBucket, err) } return successCode From 22649eb0bce64e6f092c37b4c9589ff2e97805d6 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 5 May 2025 08:55:56 +0000 Subject: [PATCH 0400/1298] Log both source and destination folder in RenameFolder debug log (#3263) --- internal/storage/debug_bucket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index 3cf23b9489..e98042c02a 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -300,7 +300,7 @@ func (b *debugBucket) CreateFolder(ctx context.Context, folderName string) (fold } func (b *debugBucket) RenameFolder(ctx context.Context, folderName string, destinationFolderId string) (o *gcs.Folder, err error) { - id, desc, start := b.startRequest("RenameFolder(%q)", folderName) + id, desc, start := b.startRequest("RenameFolder(%q, %q)", folderName, destinationFolderId) defer b.finishRequest(id, desc, start, &err) o, err = b.wrapped.RenameFolder(ctx, folderName, destinationFolderId) From 6850b18b0854d3ca9d5d81ec42a93d6c6788e43a Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 5 May 2025 11:09:33 +0000 Subject: [PATCH 0401/1298] Early termination in Test_ListWithMoveDir on failure in renameFolder (#3262) * Early termination in Test_ListWithMoveDir on failure in mv Switching failure in folder renames from soft-failures to asserts as they should be. * empty commit * Fatally assert in concurrent_operations tests * empty commit --- .../concurrent_listing_test.go | 108 +++++++++--------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/tools/integration_tests/concurrent_operations/concurrent_listing_test.go b/tools/integration_tests/concurrent_operations/concurrent_listing_test.go index 6c01fa58b2..a4db528bf0 100644 --- a/tools/integration_tests/concurrent_operations/concurrent_listing_test.go +++ b/tools/integration_tests/concurrent_operations/concurrent_listing_test.go @@ -94,10 +94,10 @@ func (s *concurrentListingTest) Test_OpenDirAndLookUp(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForLightOperations; i++ { f, err := os.Open(targetDir) - assert.Nil(t, err) + require.Nil(t, err) err = f.Close() - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -106,7 +106,7 @@ func (s *concurrentListingTest) Test_OpenDirAndLookUp(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForLightOperations; i++ { _, err := os.Stat(targetDir) - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -140,13 +140,13 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndLookUp(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { f, err := os.Open(targetDir) - assert.Nil(t, err) + require.Nil(t, err) _, err = f.Readdirnames(-1) - assert.Nil(t, err) + require.Nil(t, err) err = f.Close() - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -155,7 +155,7 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndLookUp(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForLightOperations; i++ { _, err := os.Stat(targetDir) - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -193,13 +193,13 @@ func (s *concurrentListingTest) Test_MultipleConcurrentReadDir(t *testing.T) { for j := 0; j < iterationsForMediumOperations; j++ { f, err := os.Open(targetDir) - assert.Nil(t, err) + require.Nil(t, err) _, err = f.Readdirnames(-1) // Read all directory entries - assert.Nil(t, err) + require.Nil(t, err) err = f.Close() - assert.Nil(t, err) + require.Nil(t, err) } }() } @@ -235,13 +235,13 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileOperations(t *testin defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { // Adjust iteration count if needed f, err := os.Open(targetDir) - assert.Nil(t, err) + require.Nil(t, err) _, err = f.Readdirnames(-1) - assert.Nil(t, err) + require.Nil(t, err) err = f.Close() - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -254,18 +254,18 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileOperations(t *testin // Create f, err := os.Create(filePath) - assert.Nil(t, err) + require.Nil(t, err) err = f.Close() - assert.Nil(t, err) + require.Nil(t, err) // Rename err = os.Rename(filePath, renamedFilePath) - assert.Nil(t, err) + require.Nil(t, err) // Delete err = os.Remove(renamedFilePath) - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -300,13 +300,13 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndDirOperations(t *testing defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { f, err := os.Open(targetDir) - assert.Nil(t, err) + require.Nil(t, err) _, err = f.Readdirnames(-1) - assert.Nil(t, err) + require.Nil(t, err) err = f.Close() - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -319,15 +319,15 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndDirOperations(t *testing // Create err := os.Mkdir(dirPath, 0755) - assert.Nil(t, err) + require.Nil(t, err) // Rename err = os.Rename(dirPath, renamedDirPath) - assert.Nil(t, err) + require.Nil(t, err) // Delete err = os.Remove(renamedDirPath) - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -362,13 +362,13 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileEdit(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { f, err := os.Open(targetDir) - assert.Nil(t, err) + require.Nil(t, err) _, err = f.Readdirnames(-1) - assert.Nil(t, err) + require.Nil(t, err) err = f.Close() - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -380,15 +380,15 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileEdit(t *testing.T) { // Create file err := os.WriteFile(filePath, []byte("Hello, world!"), setup.FilePermission_0600) - assert.Nil(t, err) + require.Nil(t, err) // Edit file (append some data) f, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, setup.FilePermission_0600) - assert.Nil(t, err) + require.Nil(t, err) _, err = f.Write([]byte("This is an edit.")) - assert.Nil(t, err) + require.Nil(t, err) err = f.Close() - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -423,13 +423,13 @@ func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { // Adjust iteration count if needed f, err := os.Open(targetDir) - assert.Nil(t, err) + require.Nil(t, err) _, err = f.Readdirnames(-1) - assert.Nil(t, err) + require.Nil(t, err) err = f.Close() - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -441,15 +441,15 @@ func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) // Create file err := os.WriteFile(filePath, []byte("Hello, world!"), setup.FilePermission_0600) - assert.Nil(t, err) + require.Nil(t, err) // Edit file (append some data) f, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, setup.FilePermission_0600) - assert.Nil(t, err) + require.Nil(t, err) _, err = f.Write([]byte("This is an edit.")) - assert.Nil(t, err) + require.Nil(t, err) err = f.Close() - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -462,15 +462,15 @@ func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) // Create err := os.Mkdir(dirPath, 0755) - assert.Nil(t, err) + require.Nil(t, err) // Rename err = os.Rename(dirPath, renamedDirPath) - assert.Nil(t, err) + require.Nil(t, err) // Delete err = os.Remove(renamedDirPath) - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -479,7 +479,7 @@ func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) defer wg.Done() for i := 0; i < iterationsForLightOperations; i++ { _, err := os.Stat(targetDir) - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -488,10 +488,10 @@ func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) defer wg.Done() for i := 0; i < iterationsForLightOperations; i++ { f, err := os.Open(targetDir) - assert.Nil(t, err) + require.Nil(t, err) err = f.Close() - assert.Nil(t, err) + require.Nil(t, err) } }() @@ -526,12 +526,12 @@ func (s *concurrentListingTest) Test_ListWithMoveFile(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { // Adjust iteration count if needed f, err := os.Open(targetDir) - assert.NoError(t, err) + require.NoError(t, err) _, err = f.Readdirnames(-1) - assert.Nil(t, err) + require.Nil(t, err) - assert.NoError(t, f.Close()) + require.NoError(t, f.Close()) } }() @@ -545,10 +545,10 @@ func (s *concurrentListingTest) Test_ListWithMoveFile(t *testing.T) { for i := 0; i < iterationsForHeavyOperations; i++ { // Adjust iteration count if needed // Move File in the target directory err = operations.Move(path.Join(testDirPath, "move_file.txt"), path.Join(targetDir, "move_file.txt")) - assert.NoError(t, err) + require.NoError(t, err) // Move File out of the target directory err = operations.Move(path.Join(targetDir, "move_file.txt"), path.Join(testDirPath, "move_file.txt")) - assert.NoError(t, err) + require.NoError(t, err) } }() @@ -586,9 +586,9 @@ func (s *concurrentListingTest) Test_ListWithMoveDir(t *testing.T) { require.NoError(t, err) _, err = f.Readdirnames(-1) - assert.Nil(t, err) + require.Nil(t, err) - assert.NoError(t, f.Close()) + require.NoError(t, f.Close()) } }() // Create Dir @@ -601,10 +601,10 @@ func (s *concurrentListingTest) Test_ListWithMoveDir(t *testing.T) { for i := 0; i < iterationsForHeavyOperations; i++ { // Adjust iteration count if needed // Move Dir in the target dir err = operations.Move(path.Join(testDirPath, "move_dir"), path.Join(targetDir, "move_dir")) - assert.NoError(t, err) + require.NoError(t, err) // Move Dir out of the target dir err = operations.Move(path.Join(targetDir, "move_dir"), path.Join(testDirPath, "move_dir")) - assert.NoError(t, err) + require.NoError(t, err) } }() @@ -640,7 +640,7 @@ func (s *concurrentListingTest) Test_StatWithNewFileWrite(t *testing.T) { for i := 0; i < iterationsForMediumOperations; i++ { _, err := os.Stat(targetDir) - assert.NoError(t, err) + require.NoError(t, err) } }() @@ -652,7 +652,7 @@ func (s *concurrentListingTest) Test_StatWithNewFileWrite(t *testing.T) { filePath := path.Join(targetDir, fmt.Sprintf("tmp_file_%d.txt", i)) err := os.WriteFile(filePath, []byte("Hello, world!"), setup.FilePermission_0600) - assert.NoError(t, err) + require.NoError(t, err) } }() From 4b98d972b94e5d333b7bcf1743a526ea77b0586d Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 5 May 2025 17:53:20 +0530 Subject: [PATCH 0402/1298] feat: inactive timeout support for range reader (#3261) * feat: inactive timeout support for range reader * review comments * few minor review comments * review comments. --- internal/gcsx/inactive_timeout_reader.go | 245 +++++++++++++ internal/gcsx/inactive_timeout_reader_test.go | 336 ++++++++++++++++++ 2 files changed, 581 insertions(+) create mode 100644 internal/gcsx/inactive_timeout_reader.go create mode 100644 internal/gcsx/inactive_timeout_reader_test.go diff --git a/internal/gcsx/inactive_timeout_reader.go b/internal/gcsx/inactive_timeout_reader.go new file mode 100644 index 0000000000..066964b997 --- /dev/null +++ b/internal/gcsx/inactive_timeout_reader.go @@ -0,0 +1,245 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "errors" + "fmt" + "time" + + storagev2 "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "golang.org/x/net/context" +) + +// inactiveTimeoutReader is a wrapper over gcs.StorageReader that automatically +// closes the wrapped GCS reader connection after a specified period of +// inactivity (timeout). When a read operation is attempted on an inactive +// (closed) reader, it automatically attempts to reconnect using the last known +// read handle and the appropriate offset based on bytes previously read. +// +// This is useful for managing resources, especially when dealing with many +// potentially inactive or idle readers. +// +// Important notes: +// - Inactivity Timer: A background goroutine monitors read activity. If no +// Read calls occur within the timeout duration, the underlying gcsReader is +// closed. +// - Due to the activity check happens periodically (every timeout duration), the +// actual reader connection closure can happen anywhere b/w timeout and 2 * timeout +// after the very last read operation, depending on when the last read occurred +// relative to the background routine wake-up cycle. +// - Thread Safety: The reader is safe for concurrent use by multiple goroutines, +// protected by an internal mutex. +type inactiveTimeoutReader struct { + object *gcs.MinObject + bucket gcs.Bucket + + // The underlying GCS storage reader; nil if closed due to inactivity. + gcsReader gcs.StorageReader + + // Total number of bytes successfully read so far. + seen uint64 + + // Requested range [start, end) from this reader. + reqRange gcs.ByteRange + + // The read handle used for efficient reconnection for zonal bucket. + readHandle []byte + + // Derived from the parent context, used for creating new readers and monitoring cancellation. + ctx context.Context + cancel context.CancelFunc + + // Mutex protecting internal state (mainly gcsReader & isActive). + mu locker.Locker + + // Flag set by Read and reset by the monitor goroutine to track activity within the timeout window. + isActive bool +} + +var ( + ErrZeroInactivityTimeout = errors.New("ErrZeroInactivityTimeout") +) + +// NewInactiveTimeoutReader creates a new gcs.StorageReader that wraps an +// underlying GCS reader. It attempts to create the initial reader using the +// provided parameters. If successful, it starts a background goroutine to monitor +// for inactivity based on the specified timeout. +// +// If the timeout duration is zero, it returns (nil, ErrZeroInactivityTimeout) as a zero timeout +// defeats the purpose of this wrapper. +func NewInactiveTimeoutReader(ctx context.Context, bucket gcs.Bucket, object *gcs.MinObject, readHandle []byte, byteRange gcs.ByteRange, timeout time.Duration) (gcs.StorageReader, error) { + return NewInactiveTimeoutReaderWithClock(ctx, bucket, object, readHandle, byteRange, timeout, clock.RealClock{}) +} + +func NewInactiveTimeoutReaderWithClock(ctx context.Context, bucket gcs.Bucket, object *gcs.MinObject, readHandle []byte, byteRange gcs.ByteRange, timeout time.Duration, clock clock.Clock) (gcs.StorageReader, error) { + if timeout == 0 { + return nil, ErrZeroInactivityTimeout + } + + itr := &inactiveTimeoutReader{ + object: object, + bucket: bucket, + reqRange: byteRange, + readHandle: readHandle, + mu: locker.New("inactiveTimeoutReader: "+object.Name, func() {}), + isActive: false, + } + itr.ctx, itr.cancel = context.WithCancel(ctx) + + var err error + if itr.gcsReader, err = itr.createGCSReader(); err != nil { + return nil, err + } + + // Start the background periodic routine. + go itr.monitor(clock, timeout) + + return itr, nil +} + +// createGCSReader is a helper method to create the underlined reader from itr.start + itr.seen offset. +func (itr *inactiveTimeoutReader) createGCSReader() (gcs.StorageReader, error) { + reader, err := itr.bucket.NewReaderWithReadHandle( + itr.ctx, + &gcs.ReadObjectRequest{ + Name: itr.object.Name, + Generation: itr.object.Generation, + Range: &gcs.ByteRange{ + Start: itr.reqRange.Start + itr.seen, + Limit: itr.reqRange.Limit, + }, + ReadCompressed: itr.object.HasContentEncodingGzip(), + ReadHandle: itr.readHandle, + }) + if err != nil { + return nil, fmt.Errorf("NewReaderWithReadHandle: %w", err) + } + return reader, nil +} + +// Read implements io.Reader interface. +// +// If the underlying reader has been closed due to inactivity, Read automatically +// attempts to reconnect using the stored read handle and the correct offset +// (start + bytes previously seen). If reconnection fails, the error is returned. +// +// Each successful Read call marks the reader as active, resetting the inactivity timer +// in the background monitor. This method is thread-safe. +// +// Calling Read() after explicitly calling Close() is not supported and will +// lead to undefined behavior. +func (itr *inactiveTimeoutReader) Read(p []byte) (n int, err error) { + itr.mu.Lock() + defer itr.mu.Unlock() + + itr.isActive = true + + if itr.gcsReader == nil { + if itr.gcsReader, err = itr.createGCSReader(); err != nil { + return 0, err + } + } + + n, err = itr.gcsReader.Read(p) + itr.seen += uint64(n) + return n, err +} + +// Close explicitly closes the underlying gcs.StorageReader if it's currently open. +// It also signals the background monitor goroutine to stop. +// Returns an error if closing the underlying reader fails. +func (itr *inactiveTimeoutReader) Close() (err error) { + itr.mu.Lock() + defer itr.mu.Unlock() + + itr.cancel = nil + itr.ctx = nil + + if itr.gcsReader == nil { + return nil + } + + err = itr.gcsReader.Close() + itr.gcsReader = nil + if err != nil { + return fmt.Errorf("close reader: %w", err) + } + return err +} + +// ReadHandle returns the read handle associated with the underlying GCS reader. +// If the reader has been closed due to inactivity, it returns the handle +// stored from the last active reader. +func (itr *inactiveTimeoutReader) ReadHandle() (rh storagev2.ReadHandle) { + itr.mu.Lock() + defer itr.mu.Unlock() + + if itr.gcsReader == nil { + return itr.readHandle + } + + return itr.gcsReader.ReadHandle() +} + +// monitor runs in a background goroutine, and checks for inactivity. +func (itr *inactiveTimeoutReader) monitor(clock clock.Clock, timeout time.Duration) { + timer := clock.After(timeout) + for { + select { + case <-timer: + itr.handleTimeout() + timer = clock.After(timeout) + case <-itr.ctx.Done(): + return + } + } +} + +// handleTimeout is called when the inactivity timer fires. It acquires the +// reader's lock, checks the activity state, and takes appropriate action. +// If the reader was marked as active since the last check, it resets the +// activity flag. If the reader was inactive, it closes the underlying GCS reader. +// It always returns a new timer channel for the next fire. +func (itr *inactiveTimeoutReader) handleTimeout() { + itr.mu.Lock() + defer itr.mu.Unlock() + + if itr.isActive { + itr.isActive = false + } else { + itr.closeGCSReader() + } +} + +// closeGCSReader closes the wrapped gcsReader, itr.mu.Lock() should be taken +// before calling this. +func (itr *inactiveTimeoutReader) closeGCSReader() { + if itr.gcsReader == nil { + return + } + + // Not printing the timeout explicitly, as can be refer from the code/config. + logger.Infof("Closing reader for object %q due to inactivity.\n", itr.object.Name) + itr.readHandle = itr.gcsReader.ReadHandle() + if err := itr.gcsReader.Close(); err != nil { + logger.Warnf("Error closing inactive reader for object %q: %v", itr.object.Name, err) + } + itr.gcsReader = nil +} diff --git a/internal/gcsx/inactive_timeout_reader_test.go b/internal/gcsx/inactive_timeout_reader_test.go new file mode 100644 index 0000000000..4d708213ae --- /dev/null +++ b/internal/gcsx/inactive_timeout_reader_test.go @@ -0,0 +1,336 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "bytes" + "context" + "errors" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type InactiveTimeoutReaderTestSuite struct { + suite.Suite + ctx context.Context + mockBucket *storage.TestifyMockBucket + object *gcs.MinObject + reader gcs.StorageReader // The reader under test + + // Fields to hold results from setup for individual tests + initialData []byte + readHandle []byte + initialFakeReader *fake.FakeReader + timeout time.Duration + simulatedClock *clock.SimulatedClock +} + +func TestInactiveTimeoutReaderTestSuite(t *testing.T) { + suite.Run(t, new(InactiveTimeoutReaderTestSuite)) +} + +func (s *InactiveTimeoutReaderTestSuite) SetupTest() { + s.ctx = context.Background() + s.mockBucket = new(storage.TestifyMockBucket) + + // Default values, can be overridden by tests before calling setupReader + s.initialData = []byte("default data") + s.timeout = 100 * time.Millisecond + s.object = &gcs.MinObject{Name: "test-object", Generation: 123, Size: uint64(len(s.initialData))} + s.reader = nil // Reset reader before each test + s.initialFakeReader = nil + s.simulatedClock = clock.NewSimulatedClock(time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC)) +} + +func (s *InactiveTimeoutReaderTestSuite) TearDownTest() { + if s.reader == nil { + return + } + + // Close the wrapper reader, not the potentially nil internal one + s.reader.Close() + s.reader = nil + s.mockBucket.AssertExpectations(s.T()) +} + +// setupReader is a helper within the suite to create the reader under test. +// Tests should call this after setting specific suite properties like initialData or timeout. +func (s *InactiveTimeoutReaderTestSuite) setupReader(startOffset int64) { + s.object.Size = uint64(len(s.initialData)) // Ensure object size matches data + readCloser := getReadCloser(s.initialData) + s.initialFakeReader = &fake.FakeReader{ReadCloser: readCloser, Handle: s.readHandle} + + readObjectRequest := &gcs.ReadObjectRequest{ + Name: s.object.Name, + Generation: s.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(startOffset), + Limit: s.object.Size, + }, + ReadCompressed: s.object.HasContentEncodingGzip(), + ReadHandle: s.readHandle, + } + s.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(s.initialFakeReader, nil).Times(1) + + var err error + // Use NewInactiveTimeoutReader directly as NewStorageReaderWithInactiveTimeout is deprecated. + s.reader, err = NewInactiveTimeoutReaderWithClock(s.ctx, s.mockBucket, s.object, s.readHandle, gcs.ByteRange{Start: uint64(startOffset), Limit: s.object.Size}, s.timeout, s.simulatedClock) + time.Sleep(5 * time.Millisecond) // Allow time to schedule and create a timer. + s.Require().Nil(err) + s.Require().NotNil(s.reader) +} + +func (s *InactiveTimeoutReaderTestSuite) Test_NewInactiveTimeoutReader_InitialReadError() { + s.initialData = make([]byte, 100) // Size doesn't matter here + s.object = &gcs.MinObject{Name: "fail-object", Generation: 456, Size: 100} + s.timeout = 100 * time.Millisecond + initialErr := errors.New("initial connection failed") + // Expect the initial NewReader call to fail + s.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(req *gcs.ReadObjectRequest) bool { + return req.Name == s.object.Name && + req.Generation == s.object.Generation && + req.Range.Start == 0 && + req.Range.Limit == 100 + })).Return(nil, initialErr).Once() + + _, err := NewInactiveTimeoutReader(s.ctx, s.mockBucket, s.object, []byte{}, gcs.ByteRange{Start: 0, Limit: 100}, s.timeout) + + s.Error(err) + s.ErrorIs(err, initialErr) // Should be the exact error from the bucket +} + +func (s *InactiveTimeoutReaderTestSuite) Test_NewInactiveTimeoutReader_ZeroTimeoutError() { + s.initialData = []byte("zero timeout") + s.timeout = 0 // Zero timeout + + _, err := NewInactiveTimeoutReader(s.ctx, s.mockBucket, s.object, []byte{}, gcs.ByteRange{Start: 0, Limit: 100}, s.timeout) + + s.Error(err) + s.ErrorIs(err, ErrZeroInactivityTimeout) +} + +func (s *InactiveTimeoutReaderTestSuite) Test_Read_InitialReadNoError() { + s.initialData = []byte("hello world") + s.timeout = 100 * time.Millisecond + s.setupReader(0) + buf := make([]byte, 5) + + n, err := s.reader.Read(buf) + + s.NoError(err) + s.Equal(5, n) + s.Equal("hello", string(buf[:n])) +} + +func (s *InactiveTimeoutReaderTestSuite) Test_NoReadCloserWithinTimeout() { + s.initialData = []byte("hello world!") + s.timeout = 100 * time.Millisecond + s.setupReader(0) + buf := make([]byte, 6) + n1, err1 := s.reader.Read(buf) + s.NoError(err1) + s.Equal(6, n1) + s.simulatedClock.AdvanceTime(s.timeout / 2) + // Allow some time to routine incase timer fired in half timeout. + time.Sleep(5 * time.Millisecond) + + n2, err2 := s.reader.Read(buf) + + inactiveReader := s.reader.(*inactiveTimeoutReader) + s.True(inactiveReader.isActive) + s.NoError(err2) + s.Equal(6, n2) + s.Equal("world!", string(buf[:n2])) +} + +func (s *InactiveTimeoutReaderTestSuite) Test_ReadFull_Succeeds() { + buf := make([]byte, 16) + s.initialData = []byte("hello world!") + s.timeout = 100 * time.Millisecond + s.setupReader(0) + + n, err := s.reader.Read(buf) + + s.NoError(err) + s.Equal(12, n) +} + +func (s *InactiveTimeoutReaderTestSuite) Test_Read_ReconnectFails() { + buf := make([]byte, 5) + s.initialData = []byte("reconnect failure") + s.timeout = 50 * time.Millisecond + s.setupReader(0) + n, err := s.reader.Read(buf) + s.Require().NoError(err) + s.Require().Equal(5, n) + // First timeout fire will make the reader inactive. + s.simulatedClock.AdvanceTime(s.timeout + time.Millisecond) + // Wait for the monitor routine to make the read inactive. + require.Eventually(s.T(), func() bool { + rr := s.reader.(*inactiveTimeoutReader) + return !rr.isActive + }, time.Second, 10*time.Millisecond, "Monitor did mark the reader inactive in time") + // 2nd fire will close the inactive reader. + s.simulatedClock.AdvanceTime(s.timeout + time.Millisecond) + // Wait for the monitor routine to close the wrapped reader. + require.Eventually(s.T(), func() bool { + rr := s.reader.(*inactiveTimeoutReader) + return (rr.gcsReader == nil) + }, time.Second, 10*time.Millisecond, "Monitor did not close the reader in time") + reconnectErr := errors.New("failed to create new reader") + expectedReadHandle := s.initialFakeReader.Handle + s.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(req *gcs.ReadObjectRequest) bool { + return req.Name == s.object.Name && + req.Range.Start == 5 && // Expect reconnect from offset 5 + bytes.Equal(req.ReadHandle, expectedReadHandle) + })).Return(nil, reconnectErr).Times(1) // Expect only one call for the first failed attempt + + nFail, errFail := s.reader.Read(buf) + + // First failed reconnect attempt + s.ErrorIs(errFail, reconnectErr) + s.Equal(0, nFail) +} + +func (s *InactiveTimeoutReaderTestSuite) Test_Read_TimeoutAndSuccessfulReconnect() { + s.initialData = []byte("abcdefghijklmnopqrstuvwxyz") + s.timeout = 50 * time.Second + s.setupReader(0) + buf := make([]byte, 10) + n, err := s.reader.Read(buf) + s.Require().NoError(err) + s.Require().Equal(10, n) + s.Equal("abcdefghij", string(buf[:n])) + // First timeout fire will make the reader inactive. + s.simulatedClock.AdvanceTime(s.timeout + time.Millisecond) + // Wait for the monitor routine to make the read inactive. + require.Eventually(s.T(), func() bool { + rr := s.reader.(*inactiveTimeoutReader) + return !rr.isActive + }, time.Second, 10*time.Millisecond, "Monitor did mark the reader inactive in time") + // 2nd fire will close the inactive reader. + s.simulatedClock.AdvanceTime(s.timeout + time.Millisecond) + // Wait for the monitor routine to close the wrapped reader. + require.Eventually(s.T(), func() bool { + rr := s.reader.(*inactiveTimeoutReader) + return (rr.gcsReader == nil) + }, time.Second, 10*time.Millisecond, "Monitor did not close the reader in time") + expectedReadHandleAfterClose := s.initialFakeReader.Handle // The handle that should be stored after close + reconnectReadObjectRequest := &gcs.ReadObjectRequest{ + Name: s.object.Name, + Generation: s.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(10), // Expect reconnect from offset 10 + Limit: s.object.Size, + }, + ReadCompressed: s.object.HasContentEncodingGzip(), + ReadHandle: expectedReadHandleAfterClose, // Expect the stored handle to be used for reconnect + } + // Use the same initialFakeReader for simplicity, as it tracks the read offset internally. + s.mockBucket.On("NewReaderWithReadHandle", mock.Anything, reconnectReadObjectRequest).Return(s.initialFakeReader, nil).Times(1) + bufReconnect := make([]byte, 5) + + // Read after timeout (should trigger reconnect) + nReconnect, errReconnect := s.reader.Read(bufReconnect) + + s.Nil(errReconnect) + s.Equal(5, nReconnect) + s.Equal("klmno", string(bufReconnect[:nReconnect])) +} + +func (s *InactiveTimeoutReaderTestSuite) Test_Close_ExplicitClose() { + s.initialData = []byte("close me") + s.timeout = 100 * time.Millisecond + s.setupReader(0) + + err := s.reader.Close() + + s.NoError(err) + s.Nil(s.reader.(*inactiveTimeoutReader).cancel) + s.Nil(s.reader.(*inactiveTimeoutReader).ctx) + s.reader = nil // Prevent TearDownTest from closing again +} + +func (s *InactiveTimeoutReaderTestSuite) Test_handleTimeout_InactiveClose() { + s.initialData = []byte("simple close test") + s.timeout = 50 * time.Millisecond + s.readHandle = []byte("handle-before-close") + s.setupReader(0) // Sets up s.reader and s.initialFakeReader + expectedHandleAfterClose := []byte("handle-after-close") + s.initialFakeReader.Handle = expectedHandleAfterClose + itr := s.reader.(*inactiveTimeoutReader) + itr.isActive = false // Simulate inactivity + + itr.handleTimeout() + + s.Nil(itr.gcsReader) + s.False(itr.isActive, "isActive should remain false") + s.Equal(expectedHandleAfterClose, itr.readHandle, "readHandle should be updated from closed reader") +} + +func (s *InactiveTimeoutReaderTestSuite) Test_handleTimeout_ActiveBecomeInactive() { + s.initialData = []byte("simple close test") + s.timeout = 50 * time.Millisecond + s.readHandle = []byte("handle-before-close") + s.setupReader(0) // Sets up s.reader and s.initialFakeReader + expectedHandleAfterClose := []byte("handle-after-close") + s.initialFakeReader.Handle = expectedHandleAfterClose + itr := s.reader.(*inactiveTimeoutReader) + itr.isActive = true + + itr.handleTimeout() + + s.NotNil(itr.gcsReader) + s.False(itr.isActive, "isActive become false") +} + +func (s *InactiveTimeoutReaderTestSuite) Test_closeGCSReader_NilReader() { + s.initialData = []byte("simple close test") + s.timeout = 50 * time.Millisecond + s.readHandle = []byte("handle-before-close") + s.setupReader(0) // Sets up s.reader and s.initialFakeReader + itr := s.reader.(*inactiveTimeoutReader) + itr.gcsReader = nil + + itr.closeGCSReader() + + s.Nil(itr.gcsReader) + s.Equal(s.readHandle, itr.readHandle) +} + +func (s *InactiveTimeoutReaderTestSuite) Test_closeGCSReader_NonNilReader() { + s.initialData = []byte("simple close test") + s.timeout = 50 * time.Millisecond + s.readHandle = []byte("handle-before-close") + s.setupReader(0) // Sets up s.reader and s.initialFakeReader + expectedHandleAfterClose := []byte("handle-after-close") + s.initialFakeReader.Handle = expectedHandleAfterClose + itr := s.reader.(*inactiveTimeoutReader) + + itr.closeGCSReader() + + s.Nil(itr.gcsReader) + s.Equal(expectedHandleAfterClose, itr.readHandle, "readHandle should be updated from closed reader") +} + +// TODO: Add concurrent test to make sure thread safety. From b2693b85d03fc9cf963db962d5b88362d06867dc Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 6 May 2025 08:52:13 +0000 Subject: [PATCH 0403/1298] Forward env variable `GCE_METADATA_HOST` to gcsfuse daemon process in case of background run (#3253) * forward GCE_METADATA_HOST to gcsfuse daemon process * readability * minimize code by clubbing with older code * add log to print out the value of GCE_METADATA_HOST * Revert "add log to print out the value of GCE_METADATA_HOST" This reverts commit 8eb298232847eadf230bf19ed39fef09ce35bd34. * fix code comment --- cmd/legacy_main.go | 119 +++++++++++++++++++++------------------- cmd/legacy_main_test.go | 32 +++++++++++ 2 files changed, 96 insertions(+), 55 deletions(-) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 9ca92d5f4e..d8d6f45f3d 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -249,6 +249,69 @@ func isDynamicMount(bucketName string) bool { return bucketName == "" || bucketName == "_" } +// forwardedEnvVars collects and returns all the environment +// variables which should be sent to the gcsfuse daemon +// process in case of background run. +func forwardedEnvVars() []string { + // Pass along PATH so that the daemon can find fusermount on Linux. + env := []string{ + fmt.Sprintf("PATH=%s", os.Getenv("PATH")), + } + + // Pass through the https_proxy/http_proxy environment variable, + // in case the host requires a proxy server to reach the GCS endpoint. + // https_proxy has precedence over http_proxy, in case both are set + if p, ok := os.LookupEnv("https_proxy"); ok { + env = append(env, fmt.Sprintf("https_proxy=%s", p)) + fmt.Fprintf( + os.Stdout, + "Added environment https_proxy: %s\n", + p) + } else if p, ok := os.LookupEnv("http_proxy"); ok { + env = append(env, fmt.Sprintf("http_proxy=%s", p)) + fmt.Fprintf( + os.Stdout, + "Added environment http_proxy: %s\n", + p) + } + + // Forward GOOGLE_APPLICATION_CREDENTIALS, since we document in + // mounting.md that it can be used for specifying a key file. + // Forward the no_proxy environment variable. Whenever + // using the http(s)_proxy environment variables. This should + // also be included to know for which hosts the use of proxies + // should be ignored. + // Forward GCE_METADATA_HOST, GCE_METADATA_ROOT, GCE_METADATA_IP as these are used for mocked metadata services. + for _, envvar := range []string{"GOOGLE_APPLICATION_CREDENTIALS", "no_proxy", "GCE_METADATA_HOST", "GCE_METADATA_ROOT", "GCE_METADATA_IP"} { + if envval, ok := os.LookupEnv(envvar); ok { + env = append(env, fmt.Sprintf("%s=%s", envvar, envval)) + fmt.Fprintf( + os.Stdout, + "Added environment %s: %s\n", + envvar, envval) + } + } + + // Pass the parent process working directory to child process via + // environment variable. This variable will be used to resolve relative paths. + if parentProcessExecutionDir, err := os.Getwd(); err == nil { + env = append(env, fmt.Sprintf("%s=%s", util.GCSFUSE_PARENT_PROCESS_DIR, + parentProcessExecutionDir)) + } + + // Here, parent process doesn't pass the $HOME to child process implicitly, + // hence we need to pass it explicitly. + if homeDir, err := os.UserHomeDir(); err == nil { + env = append(env, fmt.Sprintf("HOME=%s", homeDir)) + } + + // This environment variable will be helpful to distinguish b/w the main + // process and daemon process. If this environment variable set that means + // programme is running as daemon process. + env = append(env, fmt.Sprintf("%s=true", logger.GCSFuseInBackgroundMode)) + return env +} + func Mount(newConfig *cfg.Config, bucketName, mountPoint string) (err error) { // Ideally this call to SetLogFormat (which internally creates a new defaultLogger) // should be set as an else to the 'if flags.Foreground' check below, but currently @@ -300,61 +363,7 @@ func Mount(newConfig *cfg.Config, bucketName, mountPoint string) (err error) { args := append([]string{"--foreground"}, os.Args[1:]...) args[len(args)-1] = mountPoint - // Pass along PATH so that the daemon can find fusermount on Linux. - env := []string{ - fmt.Sprintf("PATH=%s", os.Getenv("PATH")), - } - - // Pass along GOOGLE_APPLICATION_CREDENTIALS, since we document in - // mounting.md that it can be used for specifying a key file. - if p, ok := os.LookupEnv("GOOGLE_APPLICATION_CREDENTIALS"); ok { - env = append(env, fmt.Sprintf("GOOGLE_APPLICATION_CREDENTIALS=%s", p)) - } - // Pass through the https_proxy/http_proxy environment variable, - // in case the host requires a proxy server to reach the GCS endpoint. - // https_proxy has precedence over http_proxy, in case both are set - if p, ok := os.LookupEnv("https_proxy"); ok { - env = append(env, fmt.Sprintf("https_proxy=%s", p)) - fmt.Fprintf( - os.Stdout, - "Added environment https_proxy: %s\n", - p) - } else if p, ok := os.LookupEnv("http_proxy"); ok { - env = append(env, fmt.Sprintf("http_proxy=%s", p)) - fmt.Fprintf( - os.Stdout, - "Added environment http_proxy: %s\n", - p) - } - // Pass through the no_proxy environment variable. Whenever - // using the http(s)_proxy environment variables. This should - // also be included to know for which hosts the use of proxies - // should be ignored. - if p, ok := os.LookupEnv("no_proxy"); ok { - env = append(env, fmt.Sprintf("no_proxy=%s", p)) - fmt.Fprintf( - os.Stdout, - "Added environment no_proxy: %s\n", - p) - } - - // Pass the parent process working directory to child process via - // environment variable. This variable will be used to resolve relative paths. - if parentProcessExecutionDir, err := os.Getwd(); err == nil { - env = append(env, fmt.Sprintf("%s=%s", util.GCSFUSE_PARENT_PROCESS_DIR, - parentProcessExecutionDir)) - } - - // Here, parent process doesn't pass the $HOME to child process implicitly, - // hence we need to pass it explicitly. - if homeDir, _ := os.UserHomeDir(); err == nil { - env = append(env, fmt.Sprintf("HOME=%s", homeDir)) - } - - // This environment variable will be helpful to distinguish b/w the main - // process and daemon process. If this environment variable set that means - // programme is running as daemon process. - env = append(env, fmt.Sprintf("%s=true", logger.GCSFuseInBackgroundMode)) + env := forwardedEnvVars() // logfile.stderr will capture the standard error (stderr) output of the gcsfuse background process. var stderrFile *os.File diff --git a/cmd/legacy_main_test.go b/cmd/legacy_main_test.go index eec6de22b4..4bbd4e0270 100644 --- a/cmd/legacy_main_test.go +++ b/cmd/legacy_main_test.go @@ -261,3 +261,35 @@ func (t *MainTest) TestIsDynamicMount() { assert.Equal(t.T(), input.isDynamic, isDynamicMount(input.bucketName)) } } + +func (t *MainTest) TestForwardedEnvVars() { + for _, input := range []struct { + inputEnvVars map[string]string + expectedForwardedEnvVars []string + }{{ + inputEnvVars: map[string]string{"GCE_METADATA_HOST": "www.metadata-host.com", "GCE_METADATA_ROOT": "metadata-root", "GCE_METADATA_IP": "99.100.101.102"}, + expectedForwardedEnvVars: []string{"GCE_METADATA_HOST=www.metadata-host.com", "GCE_METADATA_ROOT=metadata-root", "GCE_METADATA_IP=99.100.101.102"}, + }, { + inputEnvVars: map[string]string{"https_proxy": "https-proxy-123", "http_proxy": "http-proxy-123", "no_proxy": "no-proxy-123"}, + expectedForwardedEnvVars: []string{"https_proxy=https-proxy-123", "no_proxy=no-proxy-123"}, + }, { + inputEnvVars: map[string]string{"http_proxy": "http-proxy-123", "no_proxy": "no-proxy-123"}, + expectedForwardedEnvVars: []string{"http_proxy=http-proxy-123", "no_proxy=no-proxy-123"}, + }, { + inputEnvVars: map[string]string{"GOOGLE_APPLICATION_CREDENTIALS": "goog-app-cred"}, + expectedForwardedEnvVars: []string{"GOOGLE_APPLICATION_CREDENTIALS=goog-app-cred"}, + }, { + expectedForwardedEnvVars: []string{"GCSFUSE_IN_BACKGROUND_MODE=true"}, + }, + } { + for envvar, envval := range input.inputEnvVars { + os.Setenv(envvar, envval) + } + forwardedEnvVars := forwardedEnvVars() + assert.Subset(t.T(), forwardedEnvVars, input.expectedForwardedEnvVars) + assert.Contains(t.T(), forwardedEnvVars, fmt.Sprintf("PATH=%s", os.Getenv("PATH"))) + for envvar := range input.inputEnvVars { + os.Unsetenv(envvar) + } + } +} From 1b7f821e915472d09718b2825a59a3e638864414 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 6 May 2025 11:15:20 +0000 Subject: [PATCH 0404/1298] [testing-on-gke] Dump bucket_name, machine_type in FIO output file (#3234) * dump machine_type, bucket_name from pod to files * dump bucket_name and machine_type in output file --- .../loading-test/templates/fio-tester.yaml | 2 ++ .../testing_on_gke/examples/fio/parse_logs.py | 35 +++++++++++++++---- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml index 256f15019e..8de731e540 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/loading-test/templates/fio-tester.yaml @@ -147,6 +147,8 @@ spec: echo "{{ .Values.gcsfuse.mountOptions }}" > ${output_dir}/gcsfuse_mount_options {{ end }} echo "{{ .Values.podName }}" > ${output_dir}/pod_name + echo "{{ .Values.nodeType }}" > ${output_dir}/machine_type + echo "{{ .Values.bucketName }}" > ${output_dir}/bucket_name for i in $(seq $epoch); do echo "[Epoch ${i}] start time:" `date +%s` diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py index c7862bf518..250cec1dfe 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py @@ -56,6 +56,8 @@ "e2e_latency_ns_p90": 0, "e2e_latency_ns_p99": 0, "e2e_latency_ns_p99.9": 0, + "machine_type": "", + "bucket_name": "", } @@ -126,13 +128,30 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: gcsfuse_mount_options = f.read().strip() print(f"gcsfuse_mount_options={gcsfuse_mount_options}") + # if directory contains bucket_name file, then parse it. + bucket_name = "" + bucket_name_file = root + "/bucket_name" + if os.path.isfile(bucket_name_file): + with open(bucket_name_file) as f: + bucket_name = f.read().strip() + print(f"bucket_name={bucket_name}") + + # if directory contains machine_type file, then parse it. + machine_type = "" + machine_type_file = root + "/machine_type" + if os.path.isfile(machine_type_file): + with open(machine_type_file) as f: + machine_type = f.read().strip() + print(f"machine_type={machine_type}") + # if directory has files, it must also contain pod_name file, # and we should extract pod-name from it in the record. pod_name = "" pod_name_file = root + "/pod_name" - with open(pod_name_file) as f: - pod_name = f.read().strip() - print(f"pod_name={pod_name}") + if os.path.isfile(pod_name_file): + with open(pod_name_file) as f: + pod_name = f.read().strip() + print(f"pod_name={pod_name}") for file in files: # Ignore non-json files to avoid unnecessary failure. @@ -252,6 +271,8 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: ) r["gcsfuse_mount_options"] = gcsfuse_mount_options + r["bucket_name"] = bucket_name + r["machine_type"] = machine_type r["blockSize"] = bs r["filesPerThread"] = nrfiles r["numThreads"] = numjobs @@ -287,8 +308,9 @@ def writeRecordsToCsvOutputFile(output: dict, output_file_path: str): " Memory (MB),GCSFuse Highest Memory (MB),GCSFuse Lowest CPU" " (core),GCSFuse Highest CPU" " (core),Pod,Start,End,GcsfuseMoutOptions,BlockSize,FilesPerThread,NumThreads,InstanceID," - "e2e_latency_ns_max,e2e_latency_ns_p50,e2e_latency_ns_p90,e2e_latency_ns_p99,e2e_latency_ns_p99.9" # - "\n" + "e2e_latency_ns_max,e2e_latency_ns_p50,e2e_latency_ns_p90,e2e_latency_ns_p99,e2e_latency_ns_p99.9," + "bucket_name,machine_type" # + "\n", ) for key in output: @@ -329,8 +351,9 @@ def writeRecordsToCsvOutputFile(output: dict, output_file_path: str): f"{record_set['mean_file_size']},{record_set['read_type']},{scenario},{r['epoch']},{r['duration']},{r['throughput_mb_per_second']},{r['IOPS']},{r['throughput_over_local_ssd']},{r['lowest_memory']},{r['highest_memory']},{r['lowest_cpu']},{r['highest_cpu']},{r['pod_name']},{r['start']},{r['end']},\"{r['gcsfuse_mount_options']}\",{r['blockSize']},{r['filesPerThread']},{r['numThreads']},{args.instance_id}," ) output_file_fwr.write( - f"{r['e2e_latency_ns_max']},{r['e2e_latency_ns_p50']},{r['e2e_latency_ns_p90']},{r['e2e_latency_ns_p99']},{r['e2e_latency_ns_p99.9']}\n" + f"{r['e2e_latency_ns_max']},{r['e2e_latency_ns_p50']},{r['e2e_latency_ns_p90']},{r['e2e_latency_ns_p99']},{r['e2e_latency_ns_p99.9']}," ) + output_file_fwr.write(f"{r['bucket_name']},{r['machine_type']}\n") output_file_fwr.close() From 5ffc968e924a7439adf70e917d3da5404c4c06f8 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 7 May 2025 12:55:08 +0530 Subject: [PATCH 0405/1298] Adding race detector test for inactive timeout reader (#3269) * Adding race detector test for inactive timeout reader * minor improvements * fixing unit test --- .github/workflows/ci.yml | 4 +- internal/gcsx/inactive_timeout_reader.go | 8 ++-- internal/gcsx/inactive_timeout_reader_test.go | 48 +++++++++++++++++-- 3 files changed, 51 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c35789ebc9..3952a29af5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,8 +45,8 @@ jobs: build_gcsfuse . /tmp ${GITHUB_SHA} - name: Test all run: CGO_ENABLED=0 go test -p 1 -count 1 -covermode=atomic -coverprofile=coverage.out -coverpkg=./... -v -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` `go list ./...` - - name: Cache RaceDetector Test - run: go test -p 1 -count 1 -v -race -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` ./internal/cache/... + - name: RaceDetector Test + run: go test -p 1 -count 1 -v -race -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` ./internal/cache/... ./internal/gcsx/... - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4.3.1 timeout-minutes: 5 diff --git a/internal/gcsx/inactive_timeout_reader.go b/internal/gcsx/inactive_timeout_reader.go index 066964b997..2adc61d00d 100644 --- a/internal/gcsx/inactive_timeout_reader.go +++ b/internal/gcsx/inactive_timeout_reader.go @@ -169,8 +169,8 @@ func (itr *inactiveTimeoutReader) Close() (err error) { itr.mu.Lock() defer itr.mu.Unlock() - itr.cancel = nil - itr.ctx = nil + // Signal background periodic routine to stop. + itr.cancel() if itr.gcsReader == nil { return nil @@ -179,9 +179,9 @@ func (itr *inactiveTimeoutReader) Close() (err error) { err = itr.gcsReader.Close() itr.gcsReader = nil if err != nil { - return fmt.Errorf("close reader: %w", err) + return fmt.Errorf("Close reader: %w", err) } - return err + return nil } // ReadHandle returns the read handle associated with the underlying GCS reader. diff --git a/internal/gcsx/inactive_timeout_reader_test.go b/internal/gcsx/inactive_timeout_reader_test.go index 4d708213ae..10e9c62f33 100644 --- a/internal/gcsx/inactive_timeout_reader_test.go +++ b/internal/gcsx/inactive_timeout_reader_test.go @@ -18,6 +18,8 @@ import ( "bytes" "context" "errors" + "strings" + "sync" "testing" "time" @@ -188,6 +190,8 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_ReconnectFails() { // Wait for the monitor routine to make the read inactive. require.Eventually(s.T(), func() bool { rr := s.reader.(*inactiveTimeoutReader) + rr.mu.Lock() + defer rr.mu.Unlock() return !rr.isActive }, time.Second, 10*time.Millisecond, "Monitor did mark the reader inactive in time") // 2nd fire will close the inactive reader. @@ -195,6 +199,8 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_ReconnectFails() { // Wait for the monitor routine to close the wrapped reader. require.Eventually(s.T(), func() bool { rr := s.reader.(*inactiveTimeoutReader) + rr.mu.Lock() + defer rr.mu.Unlock() return (rr.gcsReader == nil) }, time.Second, 10*time.Millisecond, "Monitor did not close the reader in time") reconnectErr := errors.New("failed to create new reader") @@ -226,6 +232,8 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_TimeoutAndSuccessfulReconnect // Wait for the monitor routine to make the read inactive. require.Eventually(s.T(), func() bool { rr := s.reader.(*inactiveTimeoutReader) + rr.mu.Lock() + defer rr.mu.Unlock() return !rr.isActive }, time.Second, 10*time.Millisecond, "Monitor did mark the reader inactive in time") // 2nd fire will close the inactive reader. @@ -233,6 +241,8 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_TimeoutAndSuccessfulReconnect // Wait for the monitor routine to close the wrapped reader. require.Eventually(s.T(), func() bool { rr := s.reader.(*inactiveTimeoutReader) + rr.mu.Lock() + defer rr.mu.Unlock() return (rr.gcsReader == nil) }, time.Second, 10*time.Millisecond, "Monitor did not close the reader in time") expectedReadHandleAfterClose := s.initialFakeReader.Handle // The handle that should be stored after close @@ -266,8 +276,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Close_ExplicitClose() { err := s.reader.Close() s.NoError(err) - s.Nil(s.reader.(*inactiveTimeoutReader).cancel) - s.Nil(s.reader.(*inactiveTimeoutReader).ctx) + s.Nil(s.reader.(*inactiveTimeoutReader).gcsReader) s.reader = nil // Prevent TearDownTest from closing again } @@ -333,4 +342,37 @@ func (s *InactiveTimeoutReaderTestSuite) Test_closeGCSReader_NonNilReader() { s.Equal(expectedHandleAfterClose, itr.readHandle, "readHandle should be updated from closed reader") } -// TODO: Add concurrent test to make sure thread safety. +func (s *InactiveTimeoutReaderTestSuite) TestRaceCondition() { + var wg sync.WaitGroup + wg.Add(2) + s.initialData = []byte(strings.Repeat("abc", 1000)) + s.timeout = time.Second + s.readHandle = []byte("handle-before-close") + s.setupReader(0) // Sets up s.reader and s.initialFakeReader + + // Read() + go func() { + defer wg.Done() + // Read the complete object with buffer size 100. + for offset := 0; offset < int(s.object.Size); offset += 100 { + rc := &fake.FakeReader{ReadCloser: getReadCloser(s.initialData[offset:])} + s.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(rc, nil).Maybe() + buf := make([]byte, 100) + + n, err := s.reader.Read(buf) + + s.NoError(err) + s.Equal(100, n) + } + }() + + // Concurrent handleTimeout. + go func() { + defer wg.Done() + for i := 0; i < 1000; i++ { + s.reader.(*inactiveTimeoutReader).handleTimeout() + } + }() + + wg.Wait() +} From a479946f6519986a415dc223350b11819c9d2943 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 7 May 2025 13:31:50 +0530 Subject: [PATCH 0406/1298] Flag to support inactive timeout reader (#3271) * Flag to support inactive timeout reader * flag rename to read-inactive-stream-timeout * review comments --- cfg/config.go | 16 +++++++ cfg/params.yaml | 15 +++++-- cfg/rationalize.go | 8 ++++ cfg/rationalize_test.go | 45 +++++++++++++++++++ cmd/root_test.go | 40 +++++++++++++++++ cmd/testdata/read_config/empty.yaml | 0 cmd/testdata/read_config/override.yaml | 2 + .../read_config/override_with_grpc.yaml | 4 ++ cmd/testdata/valid_config.yaml | 2 + 9 files changed, 129 insertions(+), 3 deletions(-) create mode 100644 cmd/testdata/read_config/empty.yaml create mode 100644 cmd/testdata/read_config/override.yaml create mode 100644 cmd/testdata/read_config/override_with_grpc.yaml diff --git a/cfg/config.go b/cfg/config.go index c83cd5834e..be5a8adf55 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -66,6 +66,8 @@ type Config struct { OnlyDir string `yaml:"only-dir"` + Read ReadConfig `yaml:"read"` + Write WriteConfig `yaml:"write"` } @@ -229,6 +231,10 @@ type MonitoringConfig struct { ExperimentalTracingSamplingRatio float64 `yaml:"experimental-tracing-sampling-ratio"` } +type ReadConfig struct { + InactiveStreamTimeout time.Duration `yaml:"inactive-stream-timeout"` +} + type ReadStallGcsRetriesConfig struct { Enable bool `yaml:"enable"` @@ -501,6 +507,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.IntP("prometheus-port", "", 0, "Expose Prometheus metrics endpoint on this port and a path of /metrics.") + flagSet.DurationP("read-inactive-stream-timeout", "", 0*time.Nanosecond, "Duration of inactivity after which an open GCS read stream is automatically closed. This helps conserve resources when a file handle remains open without active Read calls. A value of '0s' disables this timeout. Note: Currently only applies when using the 'grpc' client-protocol.") + + if err := flagSet.MarkHidden("read-inactive-stream-timeout"); err != nil { + return err + } + flagSet.DurationP("read-stall-initial-req-timeout", "", 20000000000*time.Nanosecond, "Initial value of the read-request dynamic timeout.") if err := flagSet.MarkHidden("read-stall-initial-req-timeout"); err != nil { @@ -860,6 +872,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("read.inactive-stream-timeout", flagSet.Lookup("read-inactive-stream-timeout")); err != nil { + return err + } + if err := v.BindPFlag("gcs-retries.read-stall.initial-req-timeout", flagSet.Lookup("read-stall-initial-req-timeout")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index c5e82e29d0..b0fb1a0708 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -300,7 +300,6 @@ https://storage.googleapis.com/storage/v1. default: "" - - config-path: "gcs-connection.experimental-enable-json-read" flag-name: "experimental-enable-json-read" type: "bool" @@ -655,6 +654,17 @@ usage: "Mount only a specific directory within the bucket. See docs/mounting for more information" default: "" +- config-path: "read.inactive-stream-timeout" + flag-name: "read-inactive-stream-timeout" + type: "duration" + usage: >- + Duration of inactivity after which an open GCS read stream is automatically closed. + This helps conserve resources when a file handle remains open without active Read calls. + A value of '0s' disables this timeout. + Note: Currently only applies when using the 'grpc' client-protocol. + default: "0s" + hide-flag: true + - config-path: "write.block-size-mb" flag-name: "write-block-size-mb" type: "int" @@ -667,8 +677,7 @@ - config-path: "write.create-empty-file" flag-name: "create-empty-file" type: "bool" - usage: "For a new file, it creates an empty file in Cloud Storage bucket as a - hold." + usage: "For a new file, it creates an empty file in Cloud Storage bucket as a hold." default: false - config-path: "write.enable-streaming-writes" diff --git a/cfg/rationalize.go b/cfg/rationalize.go index a49dd797d5..f95b5cfabd 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -121,6 +121,13 @@ func resolveParallelDownloadsValue(v isSet, fc *FileCacheConfig, c *Config) { } } +func resolveReadConfig(c *Config, r *ReadConfig) { + // Only enable for GRPC client protocol. + if c.GcsConnection.ClientProtocol != GRPC { + r.InactiveStreamTimeout = 0 + } +} + // Rationalize updates the config fields based on the values of other fields. func Rationalize(v isSet, c *Config, optimizedFlags []string) error { var err error @@ -141,6 +148,7 @@ func Rationalize(v isSet, c *Config, optimizedFlags []string) error { resolveStatCacheMaxSizeMB(v, &c.MetadataCache, optimizedFlags) resolveCloudMetricsUploadIntervalSecs(&c.Metrics) resolveParallelDownloadsValue(v, &c.FileCache, c) + resolveReadConfig(c, &c.Read) return nil } diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index e142ee1a82..5b62d96186 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -20,6 +20,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type mockIsSet struct{} @@ -579,3 +580,47 @@ func TestRationalize_ParallelDownloadsConfig(t *testing.T) { }) } } + +func TestRationalizeReadInactiveTimeoutConfig(t *testing.T) { + t.Parallel() + testCases := []struct { + name string + config *Config + expectedTimeout time.Duration + }{ + { + name: "http_client_protocol", + config: &Config{ + Read: ReadConfig{ + InactiveStreamTimeout: 60 * time.Second, + }, + GcsConnection: GcsConnectionConfig{ + ClientProtocol: HTTP1, + }, + }, + expectedTimeout: 0, + }, + { + name: "grpc_client_protocol", + config: &Config{ + Read: ReadConfig{ + InactiveStreamTimeout: 60 * time.Second, + }, + GcsConnection: GcsConnectionConfig{ + ClientProtocol: GRPC, + }, + }, + expectedTimeout: 60 * time.Second, + }, + } + + for _, tc := range testCases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + require.NoError(t, Rationalize(&mockIsSet{}, tc.config, []string{})) + assert.EqualValues(t, tc.expectedTimeout, tc.config.Read.InactiveStreamTimeout) + }) + } +} diff --git a/cmd/root_test.go b/cmd/root_test.go index fdd67ef6be..b22b7b4774 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1282,3 +1282,43 @@ func TestArgParsing_GCSRetries(t *testing.T) { }) } } + +func TestArgsParsing_ReadInactiveTimeoutConfig(t *testing.T) { + tests := []struct { + name string + cfgFile string + expectedTimeout time.Duration + }{ + { + name: "default", + cfgFile: "empty.yaml", + expectedTimeout: 0, + }, + { + name: "override_default", + cfgFile: "override.yaml", + expectedTimeout: 0, + }, + { + name: "override_with_grpc", + cfgFile: "override_with_grpc.yaml", + expectedTimeout: 30 * time.Second, + }, + } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var gotConfig *cfg.Config + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { + gotConfig = cfg + return nil + }) + require.Nil(t, err) + cmd.SetArgs(convertToPosixArgs([]string{"gcsfuse", fmt.Sprintf("--config-file=testdata/read_config/%s", tc.cfgFile), "abc", "pqr"}, cmd)) + + err = cmd.Execute() + + require.NoError(t, err) + assert.Equal(t, tc.expectedTimeout, gotConfig.Read.InactiveStreamTimeout) + }) + } +} diff --git a/cmd/testdata/read_config/empty.yaml b/cmd/testdata/read_config/empty.yaml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cmd/testdata/read_config/override.yaml b/cmd/testdata/read_config/override.yaml new file mode 100644 index 0000000000..43b0e019b1 --- /dev/null +++ b/cmd/testdata/read_config/override.yaml @@ -0,0 +1,2 @@ +read: + inactive-stream-timeout: 30s diff --git a/cmd/testdata/read_config/override_with_grpc.yaml b/cmd/testdata/read_config/override_with_grpc.yaml new file mode 100644 index 0000000000..d3f4518720 --- /dev/null +++ b/cmd/testdata/read_config/override_with_grpc.yaml @@ -0,0 +1,4 @@ +read: + inactive-stream-timeout: 30s +gcs-connection: + client-protocol: grpc diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 08ab5940fb..0d00ffa574 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -1,4 +1,6 @@ app-name: hello +read: + inactive-stream-timeout: 10s write: create-empty-file: true enable-streaming-writes: true From 0369877ae7ebd003d2c7c3b428d3c59052288d36 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 7 May 2025 08:11:06 +0000 Subject: [PATCH 0407/1298] Adding gcsx path in the flake race-detector test --- .github/workflows/flake-detector.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/flake-detector.yml b/.github/workflows/flake-detector.yml index ebcb1cf69a..165b9aff65 100644 --- a/.github/workflows/flake-detector.yml +++ b/.github/workflows/flake-detector.yml @@ -3,7 +3,7 @@ on: # Enable triggering the workflow manually workflow_dispatch: push: - branches: [ "master" ] + branches: ["master"] jobs: flake-detector: @@ -34,5 +34,5 @@ jobs: - name: Test all run: CGO_ENABLED=0 go test -p 1 -timeout 75m -count 5 -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` `go list ./...` - - name: Cache RaceDetector Test - run: CGO_ENABLED=0 go test -p 1 -timeout 75m -count 5 ./internal/cache/... + - name: RaceDetector Test + run: CGO_ENABLED=0 go test -p 1 -timeout 75m -count 5 ./internal/cache/... ./internal/gcsx/... From ce864b1f00b3cd6e13e6492d33335a669159fe8b Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 7 May 2025 21:29:32 +0530 Subject: [PATCH 0408/1298] Make NewReaderWithReadHandle to return fullGCSReaderCloser (#3260) * Make NewReaderWithReadHandle to return fullGCSReaderCloser * fixing test failure * fixing linux tests --- internal/monitor/bucket.go | 1 - internal/storage/bucket_handle.go | 6 +++++- internal/{monitor => storage}/full_read_closer.go | 2 +- internal/{monitor => storage}/full_read_closer_test.go | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) rename internal/{monitor => storage}/full_read_closer.go (99%) rename internal/{monitor => storage}/full_read_closer_test.go (99%) diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index 0d60a2c581..2e861e5637 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -64,7 +64,6 @@ func setupReader(ctx context.Context, mb *monitoringBucket, req *gcs.ReadObjectR rc, err := mb.wrapped.NewReaderWithReadHandle(ctx, req) if err == nil { - rc = newGCSFullReadCloser(rc) rc = newMonitoringReadCloser(ctx, req.Name, rc, mb.metricHandle) } diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 4b7e6ac27a..c8c8225046 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -95,7 +95,11 @@ func (bh *bucketHandle) NewReaderWithReadHandle( } // NewRangeReader creates a "storage.Reader" object which is also io.ReadCloser since it contains both Read() and Close() methods present in io.ReadCloser interface. - reader, err = obj.NewRangeReader(ctx, start, length) + var storageReader *storage.Reader + storageReader, err = obj.NewRangeReader(ctx, start, length) + if err == nil { + reader = newGCSFullReadCloser(storageReader) + } return } diff --git a/internal/monitor/full_read_closer.go b/internal/storage/full_read_closer.go similarity index 99% rename from internal/monitor/full_read_closer.go rename to internal/storage/full_read_closer.go index abfa9f2314..f0afefe8ae 100644 --- a/internal/monitor/full_read_closer.go +++ b/internal/storage/full_read_closer.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package monitor +package storage import ( "io" diff --git a/internal/monitor/full_read_closer_test.go b/internal/storage/full_read_closer_test.go similarity index 99% rename from internal/monitor/full_read_closer_test.go rename to internal/storage/full_read_closer_test.go index f1f64f2915..6449809e29 100644 --- a/internal/monitor/full_read_closer_test.go +++ b/internal/storage/full_read_closer_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package monitor +package storage import ( "bytes" From c7cfbdece8a42d505bf7330b6dfa33796391aa33 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 8 May 2025 08:23:32 +0000 Subject: [PATCH 0409/1298] [testing-on-gke] Remove depedency on mash cli tool (#3270) * Replace mash with monitoring-api everywhere Purposes. * Consistent behavior across all machines * Monitoring API has faster runtime than mash. * Monitoring API is supported on GCE VM too. * fix some var names * address review comment & cleanup --- .../examples/dlio/parse_logs.py | 45 +------- .../testing_on_gke/examples/fio/parse_logs.py | 42 +------ .../testing_on_gke/examples/run-gke-tests.sh | 15 +-- .../examples/utils/parse_logs_common.py | 23 ++++ .../testing_on_gke/examples/utils/utils.py | 107 ------------------ .../examples/utils/utils_test.py | 53 +++------ 6 files changed, 52 insertions(+), 233 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py index 7bcf0e6dcd..c3565bd0bb 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py @@ -26,8 +26,8 @@ # local library imports sys.path.append("../") import dlio_workload -from utils.utils import get_memory, get_cpu, unix_to_timestamp, standard_timestamp, is_mash_installed, get_memory_from_monitoring_api, get_cpu_from_monitoring_api, timestamp_to_epoch -from utils.parse_logs_common import ensure_directory_exists, download_gcs_objects, parse_arguments, SUPPORTED_SCENARIOS +from utils.parse_logs_common import ensure_directory_exists, download_gcs_objects, parse_arguments, SUPPORTED_SCENARIOS, fetch_cpu_memory_data +from utils.utils import unix_to_timestamp, standard_timestamp, timestamp_to_epoch _LOCAL_LOGS_LOCATION = "../../bin/dlio-logs/logs" @@ -174,42 +174,7 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: ) r["end"] = standard_timestamp(per_epoch_stats_data[str(i + 1)]["end"]) - def fetch_cpu_memory_data(): - if r["scenario"] != "local-ssd": - if mash_installed: - r["lowest_memory"], r["highest_memory"] = get_memory( - r["pod_name"], - r["start"], - r["end"], - project_number=args.project_number, - ) - r["lowest_cpu"], r["highest_cpu"] = get_cpu( - r["pod_name"], - r["start"], - r["end"], - project_number=args.project_number, - ) - else: - r["lowest_memory"], r["highest_memory"] = ( - get_memory_from_monitoring_api( - pod_name=r["pod_name"], - start_epoch=r["start_epoch"], - end_epoch=r["end_epoch"], - project_id=args.project_id, - cluster_name=args.cluster_name, - namespace_name=args.namespace_name, - ) - ) - r["lowest_cpu"], r["highest_cpu"] = get_cpu_from_monitoring_api( - pod_name=r["pod_name"], - start_epoch=r["start_epoch"], - end_epoch=r["end_epoch"], - project_id=args.project_id, - cluster_name=args.cluster_name, - namespace_name=args.namespace_name, - ) - - fetch_cpu_memory_data() + fetch_cpu_memory_data(args=args, record=r) r["gcsfuse_mount_options"] = gcsfuse_mount_options @@ -300,10 +265,6 @@ def writeRecordsToCsvOutputFile(output: dict, output_file_path: str): ) downloadDlioOutputs(dlioWorkloads, args.instance_id) - mash_installed = is_mash_installed() - if not mash_installed: - print("Mash is not installed, will skip parsing CPU and memory usage.") - output = createOutputScenariosFromDownloadedFiles(args) output_file_path = args.output_file diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py index 250cec1dfe..936aa5d854 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py @@ -26,8 +26,8 @@ # local library imports sys.path.append("../") import fio_workload -from utils.utils import get_memory, get_cpu, unix_to_timestamp, is_mash_installed, get_memory_from_monitoring_api, get_cpu_from_monitoring_api -from utils.parse_logs_common import ensure_directory_exists, download_gcs_objects, parse_arguments, SUPPORTED_SCENARIOS +from utils.parse_logs_common import ensure_directory_exists, download_gcs_objects, parse_arguments, SUPPORTED_SCENARIOS, fetch_cpu_memory_data +from utils.utils import unix_to_timestamp _LOCAL_LOGS_LOCATION = "../../bin/fio-logs" @@ -236,39 +236,7 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: ) r["end"] = unix_to_timestamp(per_epoch_output_data["timestamp_ms"]) - if r["scenario"] != "local-ssd": - if mash_installed: - r["lowest_memory"], r["highest_memory"] = get_memory( - r["pod_name"], - r["start"], - r["end"], - project_number=args.project_number, - ) - r["lowest_cpu"], r["highest_cpu"] = get_cpu( - r["pod_name"], - r["start"], - r["end"], - project_number=args.project_number, - ) - else: - r["lowest_memory"], r["highest_memory"] = ( - get_memory_from_monitoring_api( - pod_name=r["pod_name"], - start_epoch=r["start_epoch"], - end_epoch=r["end_epoch"], - project_id=args.project_id, - cluster_name=args.cluster_name, - namespace_name=args.namespace_name, - ) - ) - r["lowest_cpu"], r["highest_cpu"] = get_cpu_from_monitoring_api( - pod_name=r["pod_name"], - start_epoch=r["start_epoch"], - end_epoch=r["end_epoch"], - project_id=args.project_id, - cluster_name=args.cluster_name, - namespace_name=args.namespace_name, - ) + fetch_cpu_memory_data(args=args, record=r) r["gcsfuse_mount_options"] = gcsfuse_mount_options r["bucket_name"] = bucket_name @@ -368,10 +336,6 @@ def writeRecordsToCsvOutputFile(output: dict, output_file_path: str): ) downloadFioOutputs(fioWorkloads, args.instance_id) - mash_installed = is_mash_installed() - if not mash_installed: - print("Mash is not installed, will skip parsing CPU and memory usage.") - output = createOutputScenariosFromDownloadedFiles(args) output_file_path = args.output_file diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index 4b6d29701b..bcb0381560 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -337,16 +337,11 @@ function installDependencies() { apt-cache policy docker-ce sudo apt install docker-ce -y >/dev/null fi - # Install mash, as it is needed for fetching cpu/memory values for test runs - # in cloudtop. Even if mash install fails, don't panic, go ahead and install - # google-cloud-monitoring as an alternative. - which mash || sudo apt-get install -y monarch-tools >/dev/null || true - # Ensure that gcloud monitoring tools are installed. This is alternative to - # mash on gce vm. - # pip install --upgrade google-cloud-storage - # pip install --ignore-installed --upgrade google-api-python-client - # pip install --ignore-installed --upgrade google-cloud - pip install --upgrade google-cloud-monitoring >/dev/null + # Ensure that gcloud monitoring tools are installed. + pip install --upgrade google-cloud-storage 1>/dev/null + pip install --ignore-installed --upgrade google-api-python-client 1>/dev/null + pip install --ignore-installed --upgrade google-cloud 1>/dev/null + pip install --upgrade google-cloud-monitoring 1>/dev/null # Ensure that jq is installed. which jq || sudo apt-get install -y jq >/dev/null # Ensure sudoless docker is installed. diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py b/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py index 5e336a6cb0..83a5618c96 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py @@ -19,6 +19,7 @@ import os import subprocess from typing import Tuple +from utils.utils import get_cpu_from_monitoring_api, get_memory_from_monitoring_api SUPPORTED_SCENARIOS = [ "local-ssd", @@ -120,3 +121,25 @@ def parse_arguments() -> object: action="store_true", ) return parser.parse_args() + + +def fetch_cpu_memory_data(args, record): + if record["scenario"] != "local-ssd": + record["lowest_memory"], record["highest_memory"] = ( + get_memory_from_monitoring_api( + pod_name=record["pod_name"], + start_epoch=record["start_epoch"], + end_epoch=record["end_epoch"], + project_id=args.project_id, + cluster_name=args.cluster_name, + namespace_name=args.namespace_name, + ) + ) + record["lowest_cpu"], record["highest_cpu"] = get_cpu_from_monitoring_api( + pod_name=record["pod_name"], + start_epoch=record["start_epoch"], + end_epoch=record["end_epoch"], + project_id=args.project_id, + cluster_name=args.cluster_name, + namespace_name=args.namespace_name, + ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py b/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py index 7ed2b27f82..5cf4d000fe 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py @@ -24,113 +24,6 @@ _GCSFUSE_CONTAINER_NAME = "gke-gcsfuse-sidecar" -def is_mash_installed() -> bool: - try: - subprocess.run( - ["mash", "--version"], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - check=True, - ) - return True - except subprocess.CalledProcessError: - return False - except FileNotFoundError: - return False - - -def get_memory( - pod_name: str, start: str, end: str, project_number: int -) -> Tuple[int, int]: - # for some reason, the mash filter does not always work, so we fetch all the metrics for all the pods and filter later. - result = subprocess.run( - [ - "mash", - "--namespace=cloud_prod", - "--output=csv", - ( - "Query(Fetch(Raw('cloud.kubernetes.K8sContainer'," - " 'kubernetes.io/container/memory/used_bytes'), {'project':" - f" '{project_number}', 'metric:memory_type': 'non-evictable'}})|" - " Window(Align('10m'))| GroupBy(['pod_name', 'container_name']," - f" Max()), TimeInterval('{start}', '{end}'), '5s')" - ), - ], - capture_output=True, - text=True, - ) - - data_points_int = [] - data_points_by_pod_container = result.stdout.strip().split("\n") - for data_points in data_points_by_pod_container[1:]: - data_points_split = data_points.split(",") - if len(data_points_split) < 6: - continue - pn = data_points_split[4] - container_name = data_points_split[5] - if pn == pod_name and container_name == _GCSFUSE_CONTAINER_NAME: - try: - data_points_int = [int(d) for d in data_points_split[7:]] - except: - print( - f"failed to parse memory for pod {pod_name}, {start}, {end}, data" - f" {data_points_int}" - ) - break - if not data_points_int: - return 0, 0 - - return int(min(data_points_int) / 1024**2), int( - max(data_points_int) / 1024**2 - ) - - -def get_cpu( - pod_name: str, start: str, end: str, project_number: int -) -> Tuple[float, float]: - # for some reason, the mash filter does not always work, so we fetch all the metrics for all the pods and filter later. - result = subprocess.run( - [ - "mash", - "--namespace=cloud_prod", - "--output=csv", - ( - "Query(Fetch(Raw('cloud.kubernetes.K8sContainer'," - " 'kubernetes.io/container/cpu/core_usage_time'), {'project':" - f" '{project_number}'}})| Window(Rate('10m'))|" - " GroupBy(['pod_name', 'container_name'], Max())," - f" TimeInterval('{start}', '{end}'), '5s')" - ), - ], - capture_output=True, - text=True, - ) - - data_points_float = [] - data_points_by_pod_container = result.stdout.split("\n") - for data_points in data_points_by_pod_container[1:]: - data_points_split = data_points.split(",") - if len(data_points_split) < 6: - continue - pn = data_points_split[4] - container_name = data_points_split[5] - if pn == pod_name and container_name == _GCSFUSE_CONTAINER_NAME: - try: - data_points_float = [float(d) for d in data_points_split[6:]] - except: - print( - f"failed to parse CPU for pod {pod_name}, {start}, {end}, data" - f" {data_points_float}" - ) - - break - - if not data_points_float: - return 0.0, 0.0 - - return round(min(data_points_float), 5), round(max(data_points_float), 5) - - def unix_to_timestamp(unix_timestamp: int) -> str: # Convert Unix timestamp to a datetime object (aware of UTC) datetime_utc = datetime.datetime.fromtimestamp( diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/utils_test.py b/perfmetrics/scripts/testing_on_gke/examples/utils/utils_test.py index dae89cd41a..3c5bf1eeb7 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/utils_test.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/utils_test.py @@ -17,7 +17,7 @@ import unittest import utils -from utils import get_cpu, get_cpu_from_monitoring_api, get_memory, get_memory_from_monitoring_api, timestamp_to_epoch +from utils import get_cpu_from_monitoring_api, get_memory_from_monitoring_api, timestamp_to_epoch class UtilsTest(unittest.TestCase): @@ -34,8 +34,8 @@ def setUpClass(self): self.start = '2024-09-25 06:32:22 UTC' self.end = '2024-09-25 07:06:22 UTC' - def test_get_memory_methods(self): - low1, high1 = get_memory_from_monitoring_api( + def test_get_memory_from_monitoring_api(self): + low, high = get_memory_from_monitoring_api( project_id=self.project_id, cluster_name=self.cluster_name, pod_name=self.pod_name, @@ -43,22 +43,12 @@ def test_get_memory_methods(self): start_epoch=self.start_epoch, end_epoch=self.end_epoch, ) - self.assertLessEqual(low1, high1) - self.assertGreater(high1, 0) - low2, high2 = get_memory( - project_number=self.project_number, - pod_name=self.pod_name, - start=self.start, - end=self.end, - ) - self.assertLessEqual(low2, high2) - self.assertGreater(high2, 0) - - self.assertTrue(high1 >= 0.99 * high2 and high1 <= 1.01 * high2) + self.assertLessEqual(low, high) + self.assertGreater(high, 0) - def test_get_cpu_methods(self): - low1, high1 = get_cpu_from_monitoring_api( + def test_get_cpu_from_monitoring_api(self): + low, high = get_cpu_from_monitoring_api( project_id=self.project_id, cluster_name=self.cluster_name, pod_name=self.pod_name, @@ -66,19 +56,9 @@ def test_get_cpu_methods(self): start_epoch=self.start_epoch, end_epoch=self.end_epoch, ) - self.assertLessEqual(low1, high1) - self.assertGreater(high1, 0) - - low2, high2 = get_cpu( - project_number=self.project_number, - pod_name=self.pod_name, - start=self.start, - end=self.end, - ) - self.assertLessEqual(low2, high2) - self.assertGreater(high2, 0) - self.assertTrue(high1 >= 0.99 * high2 and high1 <= 1.01 * high2) + self.assertLessEqual(low, high) + self.assertGreater(high, 0) def test_timestamp_to_epoch(self): self.assertEqual(timestamp_to_epoch('2024-08-21T19:20:25'), 1724268025) @@ -88,7 +68,7 @@ def test_timestamp_to_epoch_with_nznano(self): timestamp_to_epoch('2024-08-21T19:20:25.547456'), 1724268025 ) - def test_resource_limit(self): + def test_resource_limits(self): inputs = [ { 'nodeType': 'n2-standard-32', @@ -118,17 +98,20 @@ def test_resource_limit(self): {'nodeType': 'n2-standard-1', 'expected_error': True}, {'nodeType': 'unknown-machine-type', 'expected_error': True}, ] + for input in inputs: - self.assertEqual(dict, type(input)) - try: + + if input['expected_error']: + + with self.assertRaises(utils.UnknownMachineTypeError): + utils.resource_limits(input['nodeType']) + else: resource_limits = utils.resource_limits(input['nodeType']) + self.assertEqual( input['expected_limits_cpu'], resource_limits[0]['cpu'], ) - self.assertFalse(input['expected_error']) - except utils.UnknownMachineTypeError: - self.assertTrue(input['expected_error']) if __name__ == '__main__': From 7e11cf85d09676db03817b09786b316bdb4ab46a Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 8 May 2025 12:44:12 +0000 Subject: [PATCH 0410/1298] [testing-on-gke] Add utility to create a BQ table for fio metrics (#3274) * link to old requirement.* files * install python dependencies for bigquery * Add utility to create a BQ table for fio metrics * undo unintentionally modified files * cosmetic changes --- .../testing_on_gke/examples/fio/bq_utils.py | 234 ++++++++++++++++++ .../examples/fio/requirements.in | 1 + .../examples/fio/requirements.txt | 1 + .../testing_on_gke/examples/run-gke-tests.sh | 6 + 4 files changed, 242 insertions(+) create mode 100644 perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py create mode 120000 perfmetrics/scripts/testing_on_gke/examples/fio/requirements.in create mode 120000 perfmetrics/scripts/testing_on_gke/examples/fio/requirements.txt diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py new file mode 100644 index 0000000000..7c04b521b8 --- /dev/null +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py @@ -0,0 +1,234 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Python module for setting up the dataset and tables in BigQuery. + +This python module creates the dataset and the table that will store fio +workload and metrics data in BigQuery. It can also be used to upload data +to the tables. +""" + +import argparse +import os +import socket +import sys +import time +import uuid + +# Add relative path ../../../ for class ExperimentsGCSFuseBQ . +sys.path.append(os.path.join(os.path.dirname(__file__), '../../../')) + +from google.cloud import bigquery +from google.cloud.bigquery import table +from google.cloud.bigquery.job import QueryJob +from bigquery.experiments_gcsfuse_bq import ExperimentsGCSFuseBQ + + +""" Timestamp is a new data-type to represent Timestamp +values in string form.""" + + +class Timestamp: + + def __init__(self, val: str): + self.val = val + + def __str__(self): + return self.val + + +"""FIO_TABLE_ROW_SCHEMA specifies the names of the fields and the order in which they are columns in the BQ table.""" +FIO_TABLE_ROW_SCHEMA = [ + 'fio_workload_id', + 'experiment_id', + 'epoch', + 'operation', + 'file_size', + 'file_size_in_bytes', + 'block_size', + 'block_size_in_bytes', + 'num_threads', + 'files_per_thread', + 'bucket_name', + 'machine_type', + 'gcsfuse_mount_options', + 'start_time', + 'end_time', + 'start_epoch', + 'end_epoch', + 'duration_in_seconds', + 'lowest_cpu_usage', + 'highest_cpu_usage', + 'lowest_memory_usage', + 'highest_memory_usage', + 'pod_name', + 'scenario', + 'e2e_latency_ns_max', + 'e2e_latency_ns_p50', + 'e2e_latency_ns_p90', + 'e2e_latency_ns_p99', + 'e2e_latency_ns_p99_9', + 'iops', + 'throughput_in_mbps', +] + + +class FioTableRow: + """Class containing all the fields of the fio bigquery table as elements. + + This class represents the types and zero-values of all the fields/columns, and + will also be handy to send data for inserting rows into this table. + """ + + def __init__(self): + self.fio_workload_id = str('') + self.experiment_id = str('') + self.epoch = int(0) + self.operation = str('') + self.file_size = str('') + self.file_size_in_bytes = int(0) + self.block_size = str('') + self.block_size_in_bytes = int(0) + self.num_threads = int(0) + self.files_per_thread = int(0) + self.bucket_name = str('') + self.machine_type = str('') + self.gcsfuse_mount_options = str() + self.start_time = Timestamp('') + self.end_time = Timestamp('') + self.start_epoch = int(0) + self.end_epoch = int(0) + self.duration_in_seconds = int(0) + self.lowest_cpu_usage = float(0.0) + self.highest_cpu_usage = float(0.0) + self.lowest_memory_usage = float(0.0) + self.highest_memory_usage = float(0.0) + self.pod_name = str('') + self.scenario = str('') + self.e2e_latency_ns_max = float(0.0) + self.e2e_latency_ns_p50 = float(0.0) + self.e2e_latency_ns_p90 = float(0.0) + self.e2e_latency_ns_p99 = float(0.0) + self.e2e_latency_ns_p99_9 = float(0.0) + self.iops = float(0.0) + self.throughput_in_mbps = float(0.0) + + +def map_type_to_bq_type_str(t) -> str: + if t == str: + return 'STRING' + elif t == int: + return 'INT64' + elif t == float: + return 'FLOAT64' + elif t == Timestamp: + return 'TIMESTAMP' + else: + raise Exception(f'Unknown type: {t}') + + +class FioBigqueryExporter(ExperimentsGCSFuseBQ): + """Class to create and interact with create/update Bigquery dataset and table for storing fio workload configurations and their output metrics. + + Attributes: + project_id (str): The GCP project in which dataset and tables will be + created + dataset_id (str): The name of the dataset in the project that will store the + tables + table_id (str): The name of the bigquery table configurations and output + metrics will be stored. + bq_client (google.cloud.bigquery.client.Client): The client for interacting + with Bigquery. Default value is bigquery.Client(project=project_id). + """ + + def __init__(self, project_id: str, dataset_id: str, table_id: str): + super().__init__(project_id, dataset_id) + self.table_id = table_id + + self._setup_dataset_and_tables() + + def _setup_dataset_and_tables(self): + f""" + Creates the dataset to store the tables and the experiment configuration table + to store the configuration details and creates the + {self.table_id} table to store the metrics. + """ + # Create dataset if not exists + dataset = bigquery.Dataset(f'{self.project_id}.{self.dataset_id}') + try: + self.client.create_dataset(dataset, exists_ok=True) + print( + f'Created dataset {dataset}, now sleeping for sometime to let it' + ' reflect ...' + ) + # Wait for the dataset to be created and ready to be referenced + time.sleep(120) + except Exception as e: + print(f'Failed to create dataset {dataset}: {e}') + raise + + # Query for creating fio_metrics table + query_create_table_fio_metrics = """ + CREATE TABLE IF NOT EXISTS {}.{}.{}(""".format( + self.project_id, + self.dataset_id, + self.table_id, + ) + fio_table_header = FioTableRow() + for field in FIO_TABLE_ROW_SCHEMA: + bqFieldType = map_type_to_bq_type_str( + type(getattr(fio_table_header, field)) + ) + query_create_table_fio_metrics += f'{field} {bqFieldType}, ' + query_create_table_fio_metrics += """) OPTIONS (description = 'Table for storing FIO metrics extracted from gke AI/ML tool.'); + """ + try: + self._execute_query(query_create_table_fio_metrics) + except Exception as e: + print(f'Failed to create fio table {self.table_id}: {e}') + raise + + +# The functions below this are purely for standalone +# manual testing of this utility. +def parse_arguments() -> object: + parser = argparse.ArgumentParser( + prog='', + description=(), + ) + parser.add_argument( + '--project-id', + metavar='Optional GCP Project ID/name for Bigquery table', + help='Ensure that this project has BigQuery enabled.', + required=False, + ) + parser.add_argument( + '--dataset-id', + help='Optional BigQuery dataset ID', + required=False, + ) + parser.add_argument( + '--table-id', + help='Optional BigQuery table name', + required=False, + ) + return parser.parse_args() + + +if __name__ == '__main__': + args = parse_arguments() + + fioBqExporter = FioBigqueryExporter( + 'gcs-fuse-test-ml', 'gke_test_tool_outputs', 'fio_outputs' + ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/requirements.in b/perfmetrics/scripts/testing_on_gke/examples/fio/requirements.in new file mode 120000 index 0000000000..e900ee1267 --- /dev/null +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/requirements.in @@ -0,0 +1 @@ +../../../requirements.in \ No newline at end of file diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/requirements.txt b/perfmetrics/scripts/testing_on_gke/examples/fio/requirements.txt new file mode 120000 index 0000000000..5f81160832 --- /dev/null +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/requirements.txt @@ -0,0 +1 @@ +../../../requirements.txt \ No newline at end of file diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index bcb0381560..1093351f8c 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -350,6 +350,12 @@ function installDependencies() { echoerror "sudo addgroup docker && sudo usermod -aG docker $USER && newgrp docker" return 1 fi + # Install python client for bigquery. + # TODO: Make this conditional on bigquery export ! + pip install --require-hashes -r $(dirname ${0})/fio/requirements.txt >/dev/null + pip3 install --upgrade google-cloud-bigquery >/dev/null + pip3 install --upgrade google-cloud-storage >/dev/null + pip install google-api-python-client >/dev/null } # Make sure you have access to the necessary GCP resources. The easiest way to enable it is to use @google.com as active auth. From 8e1d0888254633aa10fc09b17e49816793ce31a4 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Thu, 8 May 2025 20:40:03 +0530 Subject: [PATCH 0411/1298] Fix flaky benchmark tests (#3279) * fix the iterations to 100 * increasing threshold to 4x --- .../integration_tests/benchmarking/benchmark_delete_test.go | 2 +- .../integration_tests/benchmarking/benchmark_rename_test.go | 2 +- tools/integration_tests/benchmarking/benchmark_stat_test.go | 4 ++-- tools/integration_tests/benchmarking/setup_test.go | 6 +++++- tools/integration_tests/run_e2e_tests.sh | 4 +++- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/tools/integration_tests/benchmarking/benchmark_delete_test.go b/tools/integration_tests/benchmarking/benchmark_delete_test.go index 5e61051aa5..61f10375cc 100644 --- a/tools/integration_tests/benchmarking/benchmark_delete_test.go +++ b/tools/integration_tests/benchmarking/benchmark_delete_test.go @@ -27,7 +27,7 @@ import ( ) const ( - expectedDeleteLatency time.Duration = 675 * time.Millisecond + expectedDeleteLatency time.Duration = 1800 * time.Millisecond ) type benchmarkDeleteTest struct { diff --git a/tools/integration_tests/benchmarking/benchmark_rename_test.go b/tools/integration_tests/benchmarking/benchmark_rename_test.go index 3b9b69a8a6..29af67c222 100644 --- a/tools/integration_tests/benchmarking/benchmark_rename_test.go +++ b/tools/integration_tests/benchmarking/benchmark_rename_test.go @@ -31,7 +31,7 @@ type benchmarkRenameTest struct { } const ( - expectedRenameLatency time.Duration = 700 * time.Millisecond + expectedRenameLatency time.Duration = 1900 * time.Millisecond ) func (s *benchmarkRenameTest) SetupB(b *testing.B) { diff --git a/tools/integration_tests/benchmarking/benchmark_stat_test.go b/tools/integration_tests/benchmarking/benchmark_stat_test.go index bda401f73c..a5062cb224 100644 --- a/tools/integration_tests/benchmarking/benchmark_stat_test.go +++ b/tools/integration_tests/benchmarking/benchmark_stat_test.go @@ -30,7 +30,7 @@ import ( //////////////////////////////////////////////////////////////////////// const ( - expectedStatLatency time.Duration = 390 * time.Millisecond + expectedStatLatency time.Duration = 1100 * time.Millisecond ) type benchmarkStatTest struct { @@ -48,7 +48,7 @@ func (s *benchmarkStatTest) TeardownB(b *testing.B) { // createFilesToStat creates the below object in the bucket. // benchmarking/a.txt func createFilesToStat(b *testing.B) { - operations.CreateFileOfSize(5, path.Join(testDirPath, "a.txt"), b) + operations.CreateFileOfSize(1, path.Join(testDirPath, "a.txt"), b) } //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/benchmarking/setup_test.go b/tools/integration_tests/benchmarking/setup_test.go index 39f4b3991a..e97a496939 100644 --- a/tools/integration_tests/benchmarking/setup_test.go +++ b/tools/integration_tests/benchmarking/setup_test.go @@ -11,6 +11,10 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +// +// Note that the expected latency thresholds for the various operations has +// been set to 4 times the observed latency. Any failure of the benchmark tests +// is a direct indicator of anomaly. package benchmarking @@ -62,7 +66,7 @@ func mountGCSFuseAndSetupTestDir(flags []string, testDirName string) { // benchmarking/a{i}.txt where i is a counter based on the benchtime value. func createFiles(b *testing.B) { for i := 0; i < b.N; i++ { - operations.CreateFileOfSize(5, path.Join(testDirPath, fmt.Sprintf("a%d.txt", i)), b) + operations.CreateFileOfSize(1, path.Join(testDirPath, fmt.Sprintf("a%d.txt", i)), b) } } diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 07f16c64d4..9f466e29fc 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -287,8 +287,10 @@ function run_parallel_tests() { # Unlike regular tests,benchmark tests are not executed by default when using go test . # The -bench flag yells go test to run the benchmark tests and report their results by # enabling the benchmarking framework. + # The -benchtime flag specifies exact number of iterations a benchmark should run , in this + # case, setting this to 100 to avoid flakiness. if [ $test_dir_p == "benchmarking" ]; then - benchmark_flags="-bench=." + benchmark_flags="-bench=. -benchtime=100x" fi test_path_parallel="./tools/integration_tests/$test_dir_p" # To make it clear whether tests are running on a flat or HNS bucket, We kept the log file naming From 6b4d42f3fee12c85b2a2212cb4278dad59646862 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 9 May 2025 09:00:50 +0000 Subject: [PATCH 0412/1298] [testing-on-gke] Implement insert_rows function in BqExporter (#3281) It follows the LLD at go/fio-perf-dashboard-design . It implements insert_rows function in class FioBigqueryExporter . It also moves the test code in bq_utils.py to bq_utils_test.py . Link to the issue in case of a bug fix: b/412924212 * implement append_rows * fix comments and function headers * add some useful comments * address self-review comments in v2 * move test code from bq_utils ty bq_utils_test * address review comments in part2 * fix function comments --- .../testing_on_gke/examples/fio/bq_utils.py | 71 +++++++++-------- .../examples/fio/bq_utils_test.py | 77 +++++++++++++++++++ 2 files changed, 116 insertions(+), 32 deletions(-) create mode 100644 perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py index 7c04b521b8..2a88fa59fe 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py @@ -199,36 +199,43 @@ def _setup_dataset_and_tables(self): print(f'Failed to create fio table {self.table_id}: {e}') raise + def insert_rows(self, fioTableRows: []): + """Pass a list of FioTableRow objects to insert into the fio-table. -# The functions below this are purely for standalone -# manual testing of this utility. -def parse_arguments() -> object: - parser = argparse.ArgumentParser( - prog='', - description=(), - ) - parser.add_argument( - '--project-id', - metavar='Optional GCP Project ID/name for Bigquery table', - help='Ensure that this project has BigQuery enabled.', - required=False, - ) - parser.add_argument( - '--dataset-id', - help='Optional BigQuery dataset ID', - required=False, - ) - parser.add_argument( - '--table-id', - help='Optional BigQuery table name', - required=False, - ) - return parser.parse_args() - - -if __name__ == '__main__': - args = parse_arguments() - - fioBqExporter = FioBigqueryExporter( - 'gcs-fuse-test-ml', 'gke_test_tool_outputs', 'fio_outputs' - ) + This inserts all the given rows of data in a single transaction. + + Arguments: + + fioTableRows: a list of FioTableRow objects. + + Raises: + Exception: If some row insertion failed. + """ + + # Edge-case. + if fioTableRows is None or len(fioTableRows) == 0: + return + + # Create a list of tuples from the given list of FioTableRow objects. + # Each tuple should have the values for each row in the + # same order as in FIO_TABLE_ROW_SCHEMA. + rows_to_insert = [] + for fioTableRow in fioTableRows: + # Create a temporary list first for appending because tuples are immutable. + row_to_be_inserted = [] + for field in FIO_TABLE_ROW_SCHEMA: + row_to_be_inserted.append(str(getattr(fioTableRow, field))) + rows_to_insert.append(tuple(row_to_be_inserted)) + + # Now that the list of tuples is available, insert it + # into the table. + table = self._get_table_from_table_id(self.table_id) + try: + result = self.client.insert_rows(table, rows_to_insert) + if result: + raise Exception(f'{result}') + except Exception as e: + raise Exception( + 'Error inserting data to BigQuery table' + f' {self.project_id}:{self.dataset_id}.{self.table_id}: {e}' + ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py new file mode 100644 index 0000000000..8c7bcc46f3 --- /dev/null +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py @@ -0,0 +1,77 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This file defines tests for functionalities in bq_utils.py""" + +import unittest +from bq_utils import FioBigqueryExporter, FioTableRow, Timestamp +import utils + + +class BqUtilsTest(unittest.TestCase): + + @classmethod + def setUpClass(self): + self.bq_project_id = 'gcs-fuse-test-ml' + self.bq_dataset_id = 'gargnitin_test_gke_test_tool_outputs' + self.bq_table_id = 'fio_outputs_test' + + def test_create_bq_table(self): + # Create a sample table for manual testing. + fioBqExporter = FioBigqueryExporter( + self.bq_project_id, self.bq_dataset_id, self.bq_table_id + ) + + # sample append call. + rows = [] + + row = FioTableRow() + row.fio_workload_id = 'fio-workload-id-1' + row.experiment_id = 'expt-id-1' + row.epoch = 1 + row.file_size = '1M' + row.file_size_in_bytes = 2**20 + row.block_size = '256K' + row.block_size_in_bytes = 2**18 + row.bucket_name = 'sample-zb-bucket' + row.duration_in_seconds = 10 + row.e2e_latency_ns_max = 100 + row.e2e_latency_ns_p50 = 50 + row.e2e_latency_ns_p90 = 90 + row.e2e_latency_ns_p99 = 99 + row.e2e_latency_ns_p99_9 = 99.9 + row.end_epoch = 1746678693 + row.start_epoch = 1746678683 + row.files_per_thread = 20000 + row.gcsfuse_mount_options = 'implicit-dirs' + row.highest_cpu_usage = 10.0 + row.lowest_cpu_usage = 1.0 + row.highest_memory_usage = 10000 + row.lowest_memory_usage = 100 + row.iops = 1000 + row.machine_type = 'n2-standard-32' + row.num_threads = 50 + row.operation = 'read' + row.pod_name = 'sample-pod-name' + row.scenario = 'gcsfuse-generic' + row.start_time = Timestamp('2025-05-08 04:31:23 UTC') + row.end_time = Timestamp('2025-05-08 04:31:33 UTC') + row.throughput_in_mbps = 8000 + + rows.append(row) + fioBqExporter.insert_rows(rows) + + +if __name__ == '__main__': + unittest.main() From 2666ca6e71799711d06646238be0742c30919a0e Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 9 May 2025 10:14:03 +0000 Subject: [PATCH 0413/1298] [testing-on-gke] Implement export to bigquery table in gke ai/ml tool (#3278) This adds the remaining proposed elements from the LLD at [go/fio-perf-dashboard-design](http://goto.google.com/fio-perf-dashboard-design) . - implements export to bigquery table in python script (by creating a new FioBqExporter and invoking append_rows on it from the fio output metrics) - adds arguments for bigquery project-id, dataset-id and table-id in fio output parser - Passes the BQ arguments from the top-level CLI . - add utility for converting file/block size like `64K` etc. to `65536` etc for calculating metrics like file_size_in_bytes etc. - add utility for creating a fio_workload_id from the output metrics. [b/412924212](http://b/412924212) * implement exporting to BQ in fio parser * fix adding to bq * fixes in exporting to BQ * remove unncessary debug log * fixes in bq output exporting * add back unintentionally removed sleep * fix fio parser helper message * address self-review comments in v3 * improvement to part3 * address review comments in part3 * add values for {file|block}_size_in_bytes * refactor code a bit * calculate and insert fio_workload_id * pass BQ export arguments from run cli * dont pass experiment_id to insert_rows * address review comments * fix function names * fix function names --- .../examples/dlio/dlio_workload.py | 2 +- .../examples/dlio/parse_logs.py | 14 +-- .../testing_on_gke/examples/dlio/run_tests.py | 2 +- .../testing_on_gke/examples/fio/bq_utils.py | 4 - .../examples/fio/fio_workload.py | 2 +- .../testing_on_gke/examples/fio/parse_logs.py | 117 +++++++++++++++--- .../testing_on_gke/examples/fio/run_tests.py | 2 +- .../testing_on_gke/examples/run-gke-tests.sh | 9 +- .../examples/utils/parse_logs_common.py | 26 +++- .../testing_on_gke/examples/utils/utils.py | 40 ++++++ .../examples/utils/utils_test.py | 35 +++++- 11 files changed, 218 insertions(+), 35 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py index 8c7127bca2..89e56bd465 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py @@ -142,7 +142,7 @@ def __init__( self.numEpochs = numEpochs -def ParseTestConfigForDlioWorkloads(testConfigFileName: str): +def parse_test_config_for_dlio_workloads(testConfigFileName: str): """Parses the given workload test configuration file for DLIO workloads.""" print(f'Parsing {testConfigFileName} for DLIO workloads ...') with open(testConfigFileName) as f: diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py index c3565bd0bb..e8b51730ad 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py @@ -52,7 +52,7 @@ } -def downloadDlioOutputs(dlioWorkloads: set, instanceId: str) -> int: +def download_dlio_outputs(dlioWorkloads: set, instanceId: str) -> int: """Downloads instanceId-specific dlio outputs for each dlioWorkload locally. Outputs in the bucket are in the following object naming format @@ -79,7 +79,7 @@ def downloadDlioOutputs(dlioWorkloads: set, instanceId: str) -> int: return 0 -def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: +def create_output_scenarios_from_downloaded_files(args: dict) -> dict: """Creates output records from the downloaded local files. The following creates a dict called 'output' @@ -192,7 +192,7 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: return output -def writeRecordsToCsvOutputFile(output: dict, output_file_path: str): +def write_records_to_csv_output_file(output: dict, output_file_path: str): with open(output_file_path, "a") as output_file: # Write a new header row. output_file.write( @@ -260,18 +260,18 @@ def writeRecordsToCsvOutputFile(output: dict, output_file_path: str): args = parse_arguments() ensure_directory_exists(_LOCAL_LOGS_LOCATION) - dlioWorkloads = dlio_workload.ParseTestConfigForDlioWorkloads( + dlioWorkloads = dlio_workload.parse_test_config_for_dlio_workloads( args.workload_config ) - downloadDlioOutputs(dlioWorkloads, args.instance_id) + download_dlio_outputs(dlioWorkloads, args.instance_id) - output = createOutputScenariosFromDownloadedFiles(args) + output = create_output_scenarios_from_downloaded_files(args) output_file_path = args.output_file # Create the parent directory of output_file_path if doesn't # exist already. ensure_directory_exists(os.path.dirname(output_file_path)) - writeRecordsToCsvOutputFile(output, output_file_path) + write_records_to_csv_output_file(output, output_file_path) print( "\n\nSuccessfully published outputs of DLIO test runs to" f" {output_file_path} !!!\n\n" diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py index 41cbfe9604..0cabd6499f 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py @@ -89,7 +89,7 @@ def createHelmInstallCommands( def main(args) -> None: - dlioWorkloads = dlio_workload.ParseTestConfigForDlioWorkloads( + dlioWorkloads = dlio_workload.parse_test_config_for_dlio_workloads( args.workload_config ) helmInstallCommands = createHelmInstallCommands( diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py index 2a88fa59fe..33df6469fc 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py @@ -168,10 +168,6 @@ def _setup_dataset_and_tables(self): dataset = bigquery.Dataset(f'{self.project_id}.{self.dataset_id}') try: self.client.create_dataset(dataset, exists_ok=True) - print( - f'Created dataset {dataset}, now sleeping for sometime to let it' - ' reflect ...' - ) # Wait for the dataset to be created and ready to be referenced time.sleep(120) except Exception as e: diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py index b0031e89a2..0586c84697 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py @@ -166,7 +166,7 @@ def PPrint(self): ) -def ParseTestConfigForFioWorkloads(fioTestConfigFile: str): +def parse_test_config_for_fio_workloads(fioTestConfigFile: str): """Parses the given workload test configuration file for FIO workloads.""" print(f'Parsing {fioTestConfigFile} for FIO workloads ...') with open(fioTestConfigFile) as f: diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py index 936aa5d854..b24e83f2a1 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py @@ -27,7 +27,8 @@ sys.path.append("../") import fio_workload from utils.parse_logs_common import ensure_directory_exists, download_gcs_objects, parse_arguments, SUPPORTED_SCENARIOS, fetch_cpu_memory_data -from utils.utils import unix_to_timestamp +from fio.bq_utils import FioBigqueryExporter, FioTableRow, Timestamp +from utils.utils import unix_to_timestamp, convert_size_to_bytes _LOCAL_LOGS_LOCATION = "../../bin/fio-logs" @@ -61,7 +62,7 @@ } -def downloadFioOutputs(fioWorkloads: set, instanceId: str) -> int: +def download_fio_outputs(fioWorkloads: set, instanceId: str) -> int: """Downloads instanceId-specific fio outputs for each fioWorkload locally. Outputs in the bucket are in the following object naming format @@ -91,7 +92,7 @@ def downloadFioOutputs(fioWorkloads: set, instanceId: str) -> int: return 0 -def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: +def create_output_scenarios_from_downloaded_files(args: dict) -> dict: """Creates output records from the downloaded local files. The following creates a dict called 'output' @@ -266,7 +267,7 @@ def createOutputScenariosFromDownloadedFiles(args: dict) -> dict: return output -def writeRecordsToCsvOutputFile(output: dict, output_file_path: str): +def write_records_to_csv_output_file(output: dict, output_file_path: str): with open(output_file_path, "a") as output_file_fwr: # Write a new header. output_file_fwr.write( @@ -324,26 +325,112 @@ def writeRecordsToCsvOutputFile(output: dict, output_file_path: str): output_file_fwr.write(f"{r['bucket_name']},{r['machine_type']}\n") output_file_fwr.close() + print( + "\nSuccessfully published outputs of FIO test runs to" + f" {output_file_path} !!!\n" + ) + + +def fio_workload_id(row: FioTableRow) -> str: + return f"{row.experiment_id}_{row.operation}_{row.file_size}_{row.block_size}_{row.num_threads}_{row.files_per_thread}_{row.start_epoch}" + + +def write_records_to_bq_table( + output: dict, + experiment_id: str, + bq_project_id: str, + bq_dataset_id: str, + bq_table_id: str, +): + fioBqExporter = FioBigqueryExporter(bq_project_id, bq_dataset_id, bq_table_id) + + # list of FioRowTable objects to be populated to be inserted into BigQuery + # table using the above exporter. + rows = [] + + for key in output: + record_set = output[key] + + for scenario in record_set["records"]: + if scenario not in SUPPORTED_SCENARIOS: + print(f"Unknown scenario: {scenario}. Ignoring it...") + continue + + for epoch in range(len(record_set["records"][scenario])): + r = record_set["records"][scenario][epoch] + row = FioTableRow() + row.experiment_id = experiment_id + row.epoch = r["epoch"] + row.operation = record_set["read_type"] + row.file_size = record_set["mean_file_size"] + row.file_size_in_bytes = convert_size_to_bytes(row.file_size) + row.block_size = r["blockSize"] + row.block_size_in_bytes = convert_size_to_bytes(row.block_size) + row.num_threads = r["numThreads"] + row.files_per_thread = r["filesPerThread"] + row.bucket_name = r["bucket_name"] + row.machine_type = r["machine_type"] + row.gcsfuse_mount_options = r["gcsfuse_mount_options"] + row.start_time = Timestamp(r["start"]) + row.end_time = Timestamp(r["end"]) + row.start_epoch = r["start_epoch"] + row.end_epoch = r["end_epoch"] + row.duration_in_seconds = r["duration"] + row.lowest_cpu_usage = r["lowest_cpu"] + row.highest_cpu_usage = r["highest_cpu"] + row.lowest_memory_usage = r["lowest_memory"] + row.highest_memory_usage = r["highest_memory"] + row.pod_name = r["pod_name"] + row.scenario = scenario + row.e2e_latency_ns_max = r["e2e_latency_ns_max"] + row.e2e_latency_ns_p50 = r["e2e_latency_ns_p50"] + row.e2e_latency_ns_p90 = r["e2e_latency_ns_p90"] + row.e2e_latency_ns_p99 = r["e2e_latency_ns_p99"] + row.e2e_latency_ns_p99_9 = r["e2e_latency_ns_p99.9"] + row.iops = r["IOPS"] + row.throughput_in_mbps = r["throughput_mb_per_second"] + row.fio_workload_id = fio_workload_id(row) + + rows.append(row) + + fioBqExporter.insert_rows(fioTableRows=rows) + print( + "\nSuccessfully exported outputs of FIO test runs to" + f" BigQuery table {bq_project_id}:{bq_dataset_id}.{bq_table_id} !!!\n" + ) if __name__ == "__main__": - args = parse_arguments() + args = parse_arguments(fio_or_dlio="FIO", add_bq_support=True) ensure_directory_exists(_LOCAL_LOGS_LOCATION) if not args.predownloaded_output_files: - fioWorkloads = fio_workload.ParseTestConfigForFioWorkloads( + fioWorkloads = fio_workload.parse_test_config_for_fio_workloads( args.workload_config ) - downloadFioOutputs(fioWorkloads, args.instance_id) + download_fio_outputs(fioWorkloads, args.instance_id) - output = createOutputScenariosFromDownloadedFiles(args) + output = create_output_scenarios_from_downloaded_files(args) + # Export output dict to CSV. output_file_path = args.output_file - # Create the parent directory of output_file_path if doesn't - # exist already. + # Create the parent directory of output_file_path if doesn't exist already. ensure_directory_exists(os.path.dirname(output_file_path)) - writeRecordsToCsvOutputFile(output, output_file_path) - print( - "\n\nSuccessfully published outputs of FIO test runs to" - f" {output_file_path} !!!\n\n" - ) + write_records_to_csv_output_file(output, output_file_path) + + # Export output dict to bigquery table. + if ( + args.bq_project_id + and args.bq_project_id.strip() + and args.bq_dataset_id + and args.bq_dataset_id.strip() + and args.bq_table_id + and args.bq_table_id.strip() + ): + write_records_to_bq_table( + output=output, + bq_project_id=args.bq_project_id, + bq_dataset_id=args.bq_dataset_id, + bq_table_id=args.bq_table_id, + experiment_id=args.instance_id, + ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py b/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py index f38ab9e192..9dfe1a631a 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py @@ -88,7 +88,7 @@ def createHelmInstallCommands( def main(args) -> None: - fioWorkloads = fio_workload.ParseTestConfigForFioWorkloads( + fioWorkloads = fio_workload.parse_test_config_for_fio_workloads( args.workload_config ) helmInstallCommands = createHelmInstallCommands( diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index 1093351f8c..f1552829d0 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -67,6 +67,12 @@ readonly DEFAULT_POD_TIMEOUT_IN_SECONDS=604800 readonly DEFAULT_FORCE_UPDATE_GCSFUSE_CODE=false readonly DEFAULT_ZONAL=false +# Config for exporting fio outputs to a Bigquery table. +readonly DEFAULT_BQ_PROJECT_ID='gcs-fuse-test-ml' +readonly DEFAULT_BQ_DATASET_ID='gke_test_tool_outputs' +readonly DEFAULT_BQ_TABLE_ID='fio_outputs' + + function printHelp() { echo "Usage guide: " echo "[ENV_OPTIONS] "${0}" [ARGS]" @@ -352,7 +358,6 @@ function installDependencies() { fi # Install python client for bigquery. # TODO: Make this conditional on bigquery export ! - pip install --require-hashes -r $(dirname ${0})/fio/requirements.txt >/dev/null pip3 install --upgrade google-cloud-bigquery >/dev/null pip3 install --upgrade google-cloud-storage >/dev/null pip install google-api-python-client >/dev/null @@ -793,7 +798,7 @@ function areThereAnyDLIOWorkloads() { function fetchAndParseFioOutputs() { printf "\nFetching and parsing fio outputs ...\n\n" cd "${gke_testing_dir}"/examples/fio - parse_logs_args="--project-number=${project_number} --workload-config ${workload_config} --instance-id ${instance_id} --output-file ${output_dir}/fio/output.csv --project-id=${project_id} --cluster-name=${cluster_name} --namespace-name=${appnamespace}" + parse_logs_args="--project-number=${project_number} --workload-config ${workload_config} --instance-id ${instance_id} --output-file ${output_dir}/fio/output.csv --project-id=${project_id} --cluster-name=${cluster_name} --namespace-name=${appnamespace} --bq-project-id=${DEFAULT_BQ_PROJECT_ID} --bq-dataset-id=${DEFAULT_BQ_DATASET_ID} --bq-table-id=${DEFAULT_BQ_TABLE_ID}" if ${zonal}; then python3 parse_logs.py ${parse_logs_args} --predownloaded-output-files else diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py b/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py index 83a5618c96..3c31488da3 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py @@ -54,9 +54,13 @@ def download_gcs_objects(src: str, dst: str) -> Tuple[int, str]: return result.returncode, "" -def parse_arguments() -> object: +# Common argument parser for both fio and dlio +# output parsers. +def parse_arguments( + fio_or_dlio: str = "DLIO", add_bq_support: bool = False +) -> object: parser = argparse.ArgumentParser( - prog="DLIO Unet3d test output parser", + prog=f"{fio_or_dlio} output parser", description=( "This program takes in a json workload configuration file and parses" " it for valid FIO workloads and the locations of their test outputs" @@ -120,6 +124,24 @@ def parse_arguments() -> object: default=False, action="store_true", ) + if add_bq_support: + parser.add_argument( + "--bq-project-id", + metavar="GCP Project ID/name", + help="Bigquery project ID", + required=False, + ) + parser.add_argument( + "--bq-dataset-id", + help="Bigquery dataset id", + required=False, + ) + parser.add_argument( + "--bq-table-id", + help="Bigquery table name", + required=False, + ) + return parser.parse_args() diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py b/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py index 5cf4d000fe..002e2e6cda 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/utils.py @@ -273,3 +273,43 @@ def get_cpu_from_monitoring_api( " it !" ) return -1, -1 + + +def convert_size_to_bytes(size_in_string: str) -> int: + """Converts string-form numbers like 1k, 1m, 1g, 1K, 1M, 1G etc. + + to normal numbers i.e. 10^3, 10^6, 10^9, 2^10, 2^20, 2^30 respectively. + + The conversion is based on how FIO represents sizes e.g. small codes i.e. + k,m,g for powers of 10, and capital codes i.e. K,M,G for powers of 1024. + + Arguments: + + size_in_string: strings like '1k', '1m', '1g', '1K', '1M', '1G' etc. + + Returns: + 1000, 1000000, 1000000000 for input '1k','1m','1g' etc. respectively. + 1024, 1048576 for input '1K', '1M' etc. respectively. + """ + if size_in_string is None: + return 0 + + # Remove all leading and trailing spaces. + size_in_string = size_in_string.strip() + if len(size_in_string) == 0: + return 0 + multiplier_char = size_in_string[-1] + multiplier_map = { + "k": 10**3, + "m": 10**6, + "g": 10**9, + "K": 2**10, + "M": 2**20, + "G": 2**30, + } + if multiplier_char in multiplier_map: + multiplier = multiplier_map[multiplier_char] + base_value = 1 if len(size_in_string) == 1 else int(size_in_string[:-1]) + return base_value * multiplier + else: + return int(size_in_string) diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/utils_test.py b/perfmetrics/scripts/testing_on_gke/examples/utils/utils_test.py index 3c5bf1eeb7..7757fb5e64 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/utils_test.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/utils_test.py @@ -17,7 +17,7 @@ import unittest import utils -from utils import get_cpu_from_monitoring_api, get_memory_from_monitoring_api, timestamp_to_epoch +from utils import convert_size_to_bytes, get_cpu_from_monitoring_api, get_memory_from_monitoring_api, timestamp_to_epoch class UtilsTest(unittest.TestCase): @@ -113,6 +113,39 @@ def test_resource_limits(self): resource_limits[0]['cpu'], ) + def test_convert_size_to_bytes(self): + inputs = { + '': 0, + '1': 1, + '-1': -1, + '0': 0, + 'k': 1000, + 'm': 1000000, + 'g': 1000000000, + 'K': 1024, + 'M': 1048576, + 'G': 1073741824, + '1k': 1000, + '1m': 1000000, + '1g': 1000000000, + '1K': 1024, + '1M': 1048576, + '1G': 1073741824, + '2k': 2000, + '2m': 2000000, + '2g': 2000000000, + '2K': 2048, + '2M': 2097152, + '2G': 2147483648, + } + for input, output in inputs.items(): + self.assertEqual( + convert_size_to_bytes(input), + output, + f'Failed to convert for input = "{input}", expected-output =' + f' {output}', + ) + if __name__ == '__main__': unittest.main() From 5bc260fde552a0e2c1ab125eab634b9b28c74523 Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Fri, 9 May 2025 18:30:56 +0000 Subject: [PATCH 0414/1298] Add Parallel Downloads Default ON CPU troubleshooting guidance. --- docs/troubleshooting.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 96928b261e..b97d548768 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -179,4 +179,9 @@ During a Git clone, Git doesn’t just fetch object data, it builds out the enti While using the mount configuration `o=sync,o=dirsync`, all modifications to the config file incur a network call due to enforced synchronous writes, resulting in performance bottleneck. \ -**Note** : There is no impact of disabling this mount configuration on the user workflow, since we avoid flushing data to GCS on sync( happens multiple times during the course of a Git clone ) , but only on close(), thus ensuring data persistence. \ No newline at end of file +**Note** : There is no impact of disabling this mount configuration on the user workflow, since we avoid flushing data to GCS on sync( happens multiple times during the course of a Git clone ) , but only on close(), thus ensuring data persistence. + +### Increased CPU Utilization with File Cache after upgrade to version 2.12.0 +Starting with [version 2.12.0](https://github.com/GoogleCloudPlatform/gcsfuse/releases/tag/v2.12.0), you might observe a slight increase in CPU utilization when the file cache is enabled. This occurs because GCSFuse uses parallel threads to download data to the read cache. While this dramatically improves read performance, it may consume slightly more CPU than in previous versions. + +If this increased CPU usage negatively impacts your workload's performance, you can disable this behavior by setting the `file-cache:enable-parallel-downloads` configuration option to `false`. From d9cc5ead2e0e71d690c50af7b1fc6d1281e6524b Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 12 May 2025 14:58:08 +0530 Subject: [PATCH 0415/1298] Add GCSFuse release version in flag parse error (#3267) * adding gcsfuse release version * review comments --- main.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.go b/main.go index 9ba069bcbb..f8c72c54ac 100644 --- a/main.go +++ b/main.go @@ -23,6 +23,7 @@ import ( "log" "github.com/googlecloudplatform/gcsfuse/v2/cmd" + "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/googlecloudplatform/gcsfuse/v2/internal/perf" ) @@ -41,6 +42,7 @@ func logPanic() { // //go:generate go run -C tools/config-gen . --paramsFile=../../cfg/params.yaml --outDir=../../cfg --templateDir=templates func main() { + logger.Infof("Running gcsfuse/%s", common.GetVersion()) // Common configuration for all commands defer logPanic() // Make logging output better. From 7ad34c3c3f08f0c99aa017985e556edc04432b7a Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 12 May 2025 10:10:30 +0000 Subject: [PATCH 0416/1298] fix bug in zb output download (#3286) --- perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index f1552829d0..b833c6266d 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -755,8 +755,8 @@ function downloadFioOutputsFromBucket() { dst_dir="${gcsfuse_src_dir}/perfmetrics/scripts/testing_on_gke/bin/fio-logs/${instance_id}/${fileSize}" if test -d "${src_dir}" ; then mkdir -pv "${dst_dir}" - echo "Copying files from \"${src_dir}\" to \"${dst_dir}/\" ... " - cp -rfvu "${src_dir}"/* "${dst_dir}"/ + echo "Copying files of type ${fileSize}* from \"${src_dir}\" to \"${dst_dir}/\" ... " + cp -rfvu "${src_dir}"/${fileSize}* "${dst_dir}"/ fi echo " Unmounting \"${bucket}\" from \"${mountpath}\" ... " From 075f1fe70bd76ef7523c8f05a0771d898928ae0b Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 12 May 2025 17:51:41 +0530 Subject: [PATCH 0417/1298] Integrate inactiveTimeoutReader in random reader (#3284) --- internal/fs/fs.go | 4 +- internal/fs/handle/file.go | 9 ++- internal/gcsx/random_reader.go | 38 +++++++--- internal/gcsx/random_reader_stretchr_test.go | 76 +++++++++++++++++++- internal/gcsx/random_reader_test.go | 4 +- 5 files changed, 113 insertions(+), 18 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 8630be08f0..dfc0ecc47f 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1839,7 +1839,7 @@ func (fs *fileSystem) CreateFile( fs.nextHandleID++ // Creating new file is always a write operation, hence passing readOnly as false. - fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, false) + fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, false, &fs.newConfig.Read) op.Handle = handleID fs.mu.Unlock() @@ -2561,7 +2561,7 @@ func (fs *fileSystem) OpenFile( handleID := fs.nextHandleID fs.nextHandleID++ - fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, op.OpenFlags.IsReadOnly()) + fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, op.OpenFlags.IsReadOnly(), &fs.newConfig.Read) op.Handle = handleID // When we observe object generations that we didn't create, we assign them diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index bc265cd810..fc6584b6eb 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -19,6 +19,7 @@ import ( "fmt" "io" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" @@ -52,16 +53,20 @@ type FileHandle struct { // as we are not doing anything special for append. When required we will // define an enum instead of boolean to hold the type of open. readOnly bool + + // Read related mounting configuration. + readConfig *cfg.ReadConfig } // LOCKS_REQUIRED(fh.inode.mu) -func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle, readOnly bool) (fh *FileHandle) { +func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle, readOnly bool, rc *cfg.ReadConfig) (fh *FileHandle) { fh = &FileHandle{ inode: inode, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, metricHandle: metricHandle, readOnly: readOnly, + readConfig: rc, } fh.inode.RegisterFileHandle(fh.readOnly) @@ -205,7 +210,7 @@ func (fh *FileHandle) tryEnsureReader(ctx context.Context, sequentialReadSizeMb } // Attempt to create an appropriate reader. - rr := gcsx.NewRandomReader(fh.inode.Source(), fh.inode.Bucket(), sequentialReadSizeMb, fh.fileCacheHandler, fh.cacheFileForRangeRead, fh.metricHandle, &fh.inode.MRDWrapper) + rr := gcsx.NewRandomReader(fh.inode.Source(), fh.inode.Bucket(), sequentialReadSizeMb, fh.fileCacheHandler, fh.cacheFileForRangeRead, fh.metricHandle, &fh.inode.MRDWrapper, fh.readConfig) fh.reader = rr return diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 1f616115c4..5983333eca 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -22,6 +22,7 @@ import ( "time" "github.com/google/uuid" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" @@ -103,7 +104,7 @@ const ( // NewRandomReader create a random reader for the supplied object record that // reads using the given bucket. -func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb int32, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle, mrdWrapper *MultiRangeDownloaderWrapper) RandomReader { +func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb int32, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle, mrdWrapper *MultiRangeDownloaderWrapper, config *cfg.ReadConfig) RandomReader { return &randomReader{ object: o, bucket: bucket, @@ -117,6 +118,7 @@ func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb i cacheFileForRangeRead: cacheFileForRangeRead, mrdWrapper: mrdWrapper, metricHandle: metricHandle, + config: config, } } @@ -172,6 +174,8 @@ type randomReader struct { isMRDInUse bool metricHandle common.MetricHandle + + config *cfg.ReadConfig } func (rr *randomReader) CheckInvariants() { @@ -453,18 +457,31 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { // Begin the read. ctx, cancel := context.WithCancel(context.Background()) - rc, err := rr.bucket.NewReaderWithReadHandle( - ctx, - &gcs.ReadObjectRequest{ - Name: rr.object.Name, - Generation: rr.object.Generation, - Range: &gcs.ByteRange{ + if rr.config != nil && rr.config.InactiveStreamTimeout > 0 { + rr.reader, err = NewInactiveTimeoutReader( + ctx, + rr.bucket, + rr.object, + rr.readHandle, + gcs.ByteRange{ Start: uint64(start), Limit: uint64(end), }, - ReadCompressed: rr.object.HasContentEncodingGzip(), - ReadHandle: rr.readHandle, - }) + rr.config.InactiveStreamTimeout) + } else { + rr.reader, err = rr.bucket.NewReaderWithReadHandle( + ctx, + &gcs.ReadObjectRequest{ + Name: rr.object.Name, + Generation: rr.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(start), + Limit: uint64(end), + }, + ReadCompressed: rr.object.HasContentEncodingGzip(), + ReadHandle: rr.readHandle, + }) + } // If a file handle is open locally, but the corresponding object doesn't exist // in GCS, it indicates a file clobbering scenario. This likely occurred because: @@ -483,7 +500,6 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { return } - rr.reader = rc rr.cancel = cancel rr.start = start rr.limit = end diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 87247f7e62..5c27038f4a 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -79,7 +79,7 @@ func (t *RandomReaderStretchrTest) SetupTest() { t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) // Set up the reader. - rr := NewRandomReader(t.object, t.mockBucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics(), nil) + rr := NewRandomReader(t.object, t.mockBucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics(), nil, nil) t.rr.wrapped = rr.(*randomReader) } @@ -863,3 +863,77 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ValidateTimeout }) } } + +// Validates: +// 1. No change in ReadAt behavior based inactiveStreamTimeout config. +// 2. Valid timeout config creates inactiveTimeoutReader instance of storage.Reader. +func (t *RandomReaderStretchrTest) Test_ReadAt_WithAndWithoutReadConfig() { + testCases := []struct { + name string + config *cfg.ReadConfig + expectInactiveTimeoutReader bool + }{ + { + name: "WithoutReadConfig", + config: nil, + expectInactiveTimeoutReader: false, + }, + { + name: "WithReadConfigAndZeroTimeout", + config: &cfg.ReadConfig{InactiveStreamTimeout: 0}, + expectInactiveTimeoutReader: false, + }, + { + name: "WithReadConfigAndPositiveTimeout", + config: &cfg.ReadConfig{InactiveStreamTimeout: 10 * time.Millisecond}, + expectInactiveTimeoutReader: true, + }, + } + + objectSize := uint64(20) + readOffset := int64(0) + readLength := 10 // Reading only 10 bytes from the complete object reader. + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.SetupTest() // Resets mockBucket, rr, etc. for each sub-test + defer t.TearDownTest() + + t.rr.wrapped.config = tc.config + t.rr.wrapped.reader = nil // Ensure startRead path is taken in ReadAt + t.object.Size = objectSize + // Prepare fake content for the GCS object. + // startRead will attempt to read the entire object [0, objectSize) + // because objectSize is small compared to typical sequentialReadSizeMb. + fakeReaderContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rc := &fake.FakeReader{ReadCloser: getReadCloser(fakeReaderContent)} + expectedReadObjectRequest := &gcs.ReadObjectRequest{ + Name: t.rr.wrapped.object.Name, + Generation: t.rr.wrapped.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(readOffset), // Read from the beginning + Limit: uint64(t.object.Size), // getReadInfo will determine this limit + }, + ReadCompressed: t.rr.wrapped.object.HasContentEncodingGzip(), + ReadHandle: nil, // No existing read handle + } + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, expectedReadObjectRequest).Return(rc, nil).Once() + // BucketType is called by ReadAt -> getReadInfo -> readerType to determine reader strategy. + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: false}).Once() + buf := make([]byte, readLength) + + objectData, err := t.rr.ReadAt(buf, readOffset) + + t.mockBucket.AssertExpectations(t.T()) + assert.NoError(t.T(), err) + assert.Equal(t.T(), readLength, objectData.Size) + assert.Equal(t.T(), fakeReaderContent[:readLength], buf[:objectData.Size]) // Ensure buffer is populated correctly + assert.NotNil(t.T(), t.rr.wrapped.reader, "Reader should be active as partial data read from the requested range.") + assert.NotNil(t.T(), t.rr.wrapped.cancel) + assert.Equal(t.T(), int64(readLength), t.rr.wrapped.start) + assert.Equal(t.T(), int64(t.object.Size), t.rr.wrapped.limit) + _, isInactiveTimeoutReader := t.rr.wrapped.reader.(*inactiveTimeoutReader) + assert.Equal(t.T(), tc.expectInactiveTimeoutReader, isInactiveTimeoutReader) + }) + } +} diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index ac7bb76734..c7d32fde95 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -181,7 +181,7 @@ func (t *RandomReaderTest) SetUp(ti *TestInfo) { t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) // Set up the reader. - rr := NewRandomReader(t.object, t.bucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics(), nil) + rr := NewRandomReader(t.object, t.bucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics(), nil, nil) t.rr.wrapped = rr.(*randomReader) } @@ -531,7 +531,7 @@ func (t *RandomReaderTest) UpgradesSequentialReads_NoExistingReader() { t.object.Size = 1 << 40 const readSize = 1 * MiB // Set up the custom randomReader. - rr := NewRandomReader(t.object, t.bucket, readSize/MiB, nil, false, common.NewNoopMetrics(), nil) + rr := NewRandomReader(t.object, t.bucket, readSize/MiB, nil, false, common.NewNoopMetrics(), nil, nil) t.rr.wrapped = rr.(*randomReader) // Simulate a previous exhausted reader that ended at the offset from which From af3d37930ac75e2cae99d5272ed4ff7310183e62 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 13 May 2025 10:31:01 +0530 Subject: [PATCH 0418/1298] [Random Reader Refactoring] GCS reader implementation - 1 (#3258) * adding mrd * add unit test * add tests * adding unit tests * small fixes * adding gcs reader * adding comments * small fix * change the destroy order * review comments * review comments * review comment --- internal/gcsx/client_readers/gcs_reader.go | 197 +++++++++++++ .../gcsx/client_readers/gcs_reader_test.go | 271 ++++++++++++++++++ internal/gcsx/client_readers/range_reader.go | 15 + .../gcsx/client_readers/range_reader_test.go | 19 +- internal/gcsx/reader.go | 1 + 5 files changed, 497 insertions(+), 6 deletions(-) create mode 100644 internal/gcsx/client_readers/gcs_reader.go create mode 100644 internal/gcsx/client_readers/gcs_reader_test.go diff --git a/internal/gcsx/client_readers/gcs_reader.go b/internal/gcsx/client_readers/gcs_reader.go new file mode 100644 index 0000000000..4b738d9dca --- /dev/null +++ b/internal/gcsx/client_readers/gcs_reader.go @@ -0,0 +1,197 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "context" + "errors" + "fmt" + "io" + + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" +) + +// ReaderType represents different types of go-sdk gcs readers. +type ReaderType int + +// ReaderType enum values. +const ( + MB = 1 << 20 + + // Min read size in bytes for random reads. + // We will not send a request to GCS for less than this many bytes (unless the + // end of the object comes first). + minReadSize = MB + + // Minimum number of seeks before evaluating if the read pattern is random. + minSeeksForRandom = 2 +) + +// ReaderType enum values. +const ( + // RangeReaderType corresponds to NewReader method in bucket_handle.go + RangeReaderType ReaderType = iota + + // MultiRangeReaderType corresponds to NewMultiRangeDownloader method in bucket_handle.go + MultiRangeReaderType +) + +type GCSReader struct { + gcsx.Reader + object *gcs.MinObject + bucket gcs.Bucket + + rangeReader *RangeReader + mrr *MultiRangeReader + + // ReadType of the reader. Will be sequential by default. + readType string + + sequentialReadSizeMb int32 + + // seeks represents the number of random reads performed by the reader. + seeks uint64 + + // totalReadBytes is the total number of bytes read by the reader. + totalReadBytes uint64 +} + +func NewGCSReader(obj *gcs.MinObject, bucket gcs.Bucket, metricHandle common.MetricHandle, mrdWrapper *gcsx.MultiRangeDownloaderWrapper, sequentialReadSizeMb int32) *GCSReader { + return &GCSReader{ + object: obj, + bucket: bucket, + sequentialReadSizeMb: sequentialReadSizeMb, + rangeReader: NewRangeReader(obj, bucket, metricHandle), + mrr: NewMultiRangeReader(obj, metricHandle, mrdWrapper), + readType: util.Sequential, + } +} + +func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.ReaderResponse, error) { + var readerResponse gcsx.ReaderResponse + + if offset >= int64(gr.object.Size) { + return readerResponse, io.EOF + } + + if gr.rangeReader.invalidateReaderIfMisalignedOrTooSmall(offset, p) { + gr.seeks++ + } + + readReq := &gcsx.GCSReaderRequest{ + Buffer: p, + Offset: offset, + EndOffset: -1, + } + + readerResponse, err := gr.rangeReader.readFromExistingReader(ctx, readReq) + if err == nil { + return readerResponse, nil + } + if !errors.Is(err, gcsx.FallbackToAnotherReader) { + return readerResponse, err + } + + // If we don't have a reader, determine whether to read from RangeReader or MultiRangeReader. + end, err := gr.getReadInfo(offset, int64(len(p))) + if err != nil { + err = fmt.Errorf("ReadAt: getReaderInfo: %w", err) + return readerResponse, err + } + + readReq.EndOffset = end + readerType := gr.readerType(offset, end, gr.bucket.BucketType()) + if readerType == RangeReaderType { + readerResponse, err = gr.rangeReader.ReadAt(ctx, readReq) + gr.totalReadBytes += uint64(readerResponse.Size) + return readerResponse, err + } + + readerResponse, err = gr.mrr.ReadAt(ctx, readReq) + gr.totalReadBytes += uint64(readerResponse.Size) + + return readerResponse, err +} + +// readerType specifies the go-sdk interface to use for reads. +func (gr *GCSReader) readerType(start int64, end int64, bucketType gcs.BucketType) ReaderType { + bytesToBeRead := end - start + if gr.readType == util.Random && bytesToBeRead < maxReadSize && bucketType.Zonal { + return MultiRangeReaderType + } + return RangeReaderType +} + +// getReadInfo determines the readType and provides the range to query GCS. +// Range here is [start, end]. end is computed using the readType, start offset +// and size of the data the caller needs. +func (gr *GCSReader) getReadInfo(start int64, size int64) (int64, error) { + // Make sure start and size are legal. + if start < 0 || uint64(start) > gr.object.Size || size < 0 { + return 0, fmt.Errorf("range [%d, %d) is illegal for %d-byte object", start, start+size, gr.object.Size) + } + + // Determine the end position based on the read pattern. + end := gr.determineEnd(start) + + // Limit the end position to sequentialReadSizeMb. + end = gr.limitEnd(start, end) + + return end, nil +} + +// determineEnd calculates the end position for a read operation based on the current read pattern. +func (gr *GCSReader) determineEnd(start int64) int64 { + end := int64(gr.object.Size) + if gr.seeks >= minSeeksForRandom { + gr.readType = util.Random + averageReadBytes := gr.totalReadBytes / gr.seeks + if averageReadBytes < maxReadSize { + randomReadSize := int64(((averageReadBytes / MB) + 1) * MB) + if randomReadSize < minReadSize { + randomReadSize = minReadSize + } + if randomReadSize > maxReadSize { + randomReadSize = maxReadSize + } + end = start + randomReadSize + } + } + if end > int64(gr.object.Size) { + end = int64(gr.object.Size) + } + return end +} + +// Limit the read end to ensure it doesn't exceed the maximum sequential read size. +func (gr *GCSReader) limitEnd(start, currentEnd int64) int64 { + maxSize := int64(gr.sequentialReadSizeMb) * MB + if currentEnd-start > maxSize { + return start + maxSize + } + return currentEnd +} + +func (gr *GCSReader) Destroy() { + gr.rangeReader.destroy() + gr.mrr.destroy() +} + +func (gr *GCSReader) CheckInvariants() { + gr.rangeReader.checkInvariants() +} diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go new file mode 100644 index 0000000000..561e5b8165 --- /dev/null +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -0,0 +1,271 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "context" + "io" + "strings" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + testUtil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func (t *gcsReaderTest) readAt(offset int64, size int64) (gcsx.ReaderResponse, error) { + t.gcsReader.CheckInvariants() + defer t.gcsReader.CheckInvariants() + return t.gcsReader.ReadAt(t.ctx, make([]byte, size), offset) +} + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type gcsReaderTest struct { + suite.Suite + ctx context.Context + object *gcs.MinObject + mockBucket *storage.TestifyMockBucket + gcsReader *GCSReader +} + +func TestGCSReaderTestSuite(t *testing.T) { + suite.Run(t, new(gcsReaderTest)) +} + +func (t *gcsReaderTest) SetupTest() { + t.object = &gcs.MinObject{ + Name: testObject, + Size: 17, + Generation: 1234, + } + t.mockBucket = new(storage.TestifyMockBucket) + t.gcsReader = NewGCSReader(t.object, t.mockBucket, common.NewNoopMetrics(), nil, 200) + t.ctx = context.Background() +} + +func (t *gcsReaderTest) TearDownTest() { + t.gcsReader.Destroy() +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *gcsReaderTest) Test_NewGCSReader() { + object := &gcs.MinObject{ + Name: testObject, + Size: 30, + Generation: 4321, + } + + gcsReader := NewGCSReader(object, t.mockBucket, common.NewNoopMetrics(), nil, 200) + + assert.Equal(t.T(), object, gcsReader.object) + assert.Equal(t.T(), t.mockBucket, gcsReader.bucket) + assert.Equal(t.T(), testUtil.Sequential, gcsReader.readType) +} + +func (t *gcsReaderTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedDataSize() { + t.object.Size = 10 + // Simulate an existing reader. + t.gcsReader.rangeReader.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte("xxx")), Handle: []byte("fake")} + t.gcsReader.rangeReader.cancel = func() {} + t.gcsReader.rangeReader.start = 2 + t.gcsReader.rangeReader.limit = 5 + content := "verify" + rc := &fake.FakeReader{ReadCloser: getReadCloser([]byte(content))} + expectedHandleInRequest := []byte(t.gcsReader.rangeReader.reader.ReadHandle()) + readObjectRequest := &gcs.ReadObjectRequest{ + Name: t.gcsReader.rangeReader.object.Name, + Generation: t.gcsReader.rangeReader.object.Generation, + Range: &gcs.ByteRange{ + Start: 2, + Limit: t.object.Size, + }, + ReadCompressed: t.gcsReader.rangeReader.object.HasContentEncodingGzip(), + ReadHandle: expectedHandleInRequest, + } + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + requestSize := 6 + + readerResponse, err := t.readAt(2, int64(requestSize)) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), rc, t.gcsReader.rangeReader.reader) + assert.Equal(t.T(), requestSize, readerResponse.Size) + assert.Equal(t.T(), content, string(readerResponse.DataBuf[:readerResponse.Size])) + assert.Equal(t.T(), uint64(requestSize), t.gcsReader.totalReadBytes) + assert.Equal(t.T(), expectedHandleInRequest, t.gcsReader.rangeReader.readHandle) +} + +func (t *gcsReaderTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedObjectSize() { + t.object.Size = 5 + // Simulate an existing reader + t.gcsReader.rangeReader.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte("xxx")), Handle: []byte("fake")} + t.gcsReader.rangeReader.cancel = func() {} + t.gcsReader.rangeReader.start = 0 + t.gcsReader.rangeReader.limit = 3 + content := "abcde" + rc := &fake.FakeReader{ReadCloser: getReadCloser([]byte(content))} + expectedHandleInRequest := t.gcsReader.rangeReader.reader.ReadHandle() + readObjectRequest := &gcs.ReadObjectRequest{ + Name: t.gcsReader.rangeReader.object.Name, + Generation: t.gcsReader.rangeReader.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: t.object.Size, + }, + ReadCompressed: t.gcsReader.rangeReader.object.HasContentEncodingGzip(), + ReadHandle: expectedHandleInRequest, + } + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + requestSize := 6 + + readerResponse, err := t.readAt(0, int64(requestSize)) + + assert.NoError(t.T(), err) + assert.Nil(t.T(), t.gcsReader.rangeReader.reader) + assert.Equal(t.T(), int(t.object.Size), readerResponse.Size) + assert.Equal(t.T(), content, string(readerResponse.DataBuf[:readerResponse.Size])) + assert.Equal(t.T(), []byte(nil), t.gcsReader.rangeReader.readHandle) +} + +func (t *gcsReaderTest) Test_ExistingReader_WrongOffset() { + testCases := []struct { + name string + readHandle []byte + }{ + { + name: "ReaderHasReadHandle", + readHandle: []byte("fake-handle"), + }, + { + name: "ReaderHasNoReadHandle", + readHandle: nil, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.object.Size = 5 + // Simulate an existing reader. + t.gcsReader.rangeReader.readHandle = tc.readHandle + t.gcsReader.rangeReader.reader = &fake.FakeReader{ + ReadCloser: io.NopCloser(strings.NewReader("xxx")), + Handle: tc.readHandle, + } + t.gcsReader.rangeReader.cancel = func() {} + t.gcsReader.rangeReader.start = 2 + t.gcsReader.rangeReader.limit = 5 + content := "abcde" + rc := &fake.FakeReader{ReadCloser: getReadCloser([]byte(content))} + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(rc, nil).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + requestSize := 6 + + readerResponse, err := t.readAt(0, int64(requestSize)) + + t.mockBucket.AssertExpectations(t.T()) + assert.NoError(t.T(), err) + assert.Nil(t.T(), t.gcsReader.rangeReader.reader) + assert.Equal(t.T(), int(t.object.Size), readerResponse.Size) + assert.Equal(t.T(), content, string(readerResponse.DataBuf[:readerResponse.Size])) + assert.Equal(t.T(), []byte(nil), t.gcsReader.rangeReader.readHandle) + }) + } +} + +func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { + testCases := []struct { + name string + dataSize int + bucketType gcs.BucketType + readRanges [][]int + expectedReadTypes []string + }{ + { + name: "SequentialReadFlat", + dataSize: 100, + bucketType: gcs.BucketType{Zonal: false}, + readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, + expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Sequential, testUtil.Sequential}, + }, + { + name: "SequentialReadZonal", + dataSize: 100, + bucketType: gcs.BucketType{Zonal: true}, + readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, + expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Sequential, testUtil.Sequential}, + }, + { + name: "RandomReadFlat", + dataSize: 100, + bucketType: gcs.BucketType{Zonal: false}, + readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, + expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Random, testUtil.Random, testUtil.Random}, + }, + { + name: "RandomReadZonal", + dataSize: 100, + bucketType: gcs.BucketType{Zonal: true}, + readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, + expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Random, testUtil.Random, testUtil.Random}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.SetupTest() + require.Equal(t.T(), len(tc.readRanges), len(tc.expectedReadTypes), "Test Parameter Error: readRanges and expectedReadTypes should have same length") + t.gcsReader.mrr.isMRDInUse = false + t.gcsReader.seeks = 0 + t.gcsReader.rangeReader.readType = testUtil.Sequential + t.object.Size = uint64(tc.dataSize) + testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + require.NoError(t.T(), err, "Error in creating MRDWrapper") + t.gcsReader.mrr.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)) + t.mockBucket.On("BucketType", mock.Anything).Return(tc.bucketType).Times(len(tc.readRanges)) + + for i, readRange := range tc.readRanges { + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(&fake.FakeReader{ReadCloser: getReadCloser(testContent)}, nil).Once() + + _, err = t.readAt(int64(readRange[0]), int64(readRange[1]-readRange[0])) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), tc.expectedReadTypes[i], t.gcsReader.readType) + } + }) + } +} diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index 699e90e036..a6497ffb5b 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -325,3 +325,18 @@ func (rr *RangeReader) invalidateReaderIfMisalignedOrTooSmall(offset int64, p [] } return false } + +// readFromExistingReader attempts to read data from an existing reader if one is available. +// If a reader exists and the read is successful, the data is returned. +// Otherwise, it returns an error indicating that a fallback to another reader is needed. +// Make sure to call invalidateReaderIfMisalignedOrTooSmall before using this method. +func (rr *RangeReader) readFromExistingReader(ctx context.Context, req *gcsx.GCSReaderRequest) (gcsx.ReaderResponse, error) { + if rr.reader != nil { + return rr.ReadAt(ctx, req) + } + + return gcsx.ReaderResponse{ + DataBuf: req.Buffer, + Size: 0, + }, gcsx.FallbackToAnotherReader +} diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go index 3df7de7a77..8076fab981 100644 --- a/internal/gcsx/client_readers/range_reader_test.go +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -140,12 +140,19 @@ func (cc *countingCloser) Close() (err error) { //////////////////////////////////////////////////////////////////////// func (t *rangeReaderTest) Test_NewRangeReader() { - // The setup instantiates rangeReader with NewRangeReader. - assert.Equal(t.T(), t.object, t.rangeReader.object) - assert.Equal(t.T(), t.mockBucket, t.rangeReader.bucket) - assert.Equal(t.T(), common.NewNoopMetrics(), t.rangeReader.metricHandle) - assert.Equal(t.T(), int64(-1), t.rangeReader.start) - assert.Equal(t.T(), int64(-1), t.rangeReader.limit) + object := &gcs.MinObject{ + Name: testObject, + Size: 30, + Generation: 4321, + } + + reader := NewRangeReader(object, t.mockBucket, common.NewNoopMetrics()) + + assert.Equal(t.T(), object, reader.object) + assert.Equal(t.T(), t.mockBucket, reader.bucket) + assert.Equal(t.T(), common.NewNoopMetrics(), reader.metricHandle) + assert.Equal(t.T(), int64(-1), reader.start) + assert.Equal(t.T(), int64(-1), reader.limit) } func (t *rangeReaderTest) Test_CheckInvariants() { diff --git a/internal/gcsx/reader.go b/internal/gcsx/reader.go index 354b2e66ae..cb67226f6b 100644 --- a/internal/gcsx/reader.go +++ b/internal/gcsx/reader.go @@ -56,6 +56,7 @@ type Reader interface { // It returns an ReaderResponse containing the data read and the number of bytes read. // To indicate that the operation should be handled by an alternative reader, return // the error FallbackToAnotherReader. + // If an error occurs, the size in ReaderResponse will be zero. ReadAt(ctx context.Context, p []byte, offset int64) (ReaderResponse, error) // Destroy is called to release any resources held by the reader. From 664ca826c4b030463841389ab67842687be8c864 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 13 May 2025 10:34:21 +0530 Subject: [PATCH 0419/1298] Refactor metrics to move unit conversion logic to otel_metrics (#3287) Metrics units should be a concern of metrics related files as opposed to the code that generates those metrics. --- common/noop_metrics.go | 29 ++++++++++++++++------------- common/otel_metrics.go | 13 +++++++------ common/telemetry.go | 7 ++++--- internal/fs/wrappers/monitoring.go | 2 +- internal/gcsx/file_cache_reader.go | 2 +- internal/monitor/bucket.go | 4 +--- 6 files changed, 30 insertions(+), 27 deletions(-) diff --git a/common/noop_metrics.go b/common/noop_metrics.go index b31fab8186..4b1aa6df4a 100644 --- a/common/noop_metrics.go +++ b/common/noop_metrics.go @@ -14,7 +14,10 @@ package common -import "context" +import ( + "context" + "time" +) func NewNoopMetrics() MetricHandle { var n noopMetrics @@ -23,17 +26,17 @@ func NewNoopMetrics() MetricHandle { type noopMetrics struct{} -func (*noopMetrics) GCSReadBytesCount(_ context.Context, _ int64) {} -func (*noopMetrics) GCSReaderCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) GCSRequestCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) GCSRequestLatency(_ context.Context, value float64, _ []MetricAttr) {} -func (*noopMetrics) GCSReadCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) GCSDownloadBytesCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) GCSReadBytesCount(_ context.Context, _ int64) {} +func (*noopMetrics) GCSReaderCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) GCSRequestCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) GCSRequestLatency(_ context.Context, _ time.Duration, _ []MetricAttr) {} +func (*noopMetrics) GCSReadCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) GCSDownloadBytesCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) OpsCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) OpsLatency(_ context.Context, value float64, _ []MetricAttr) {} -func (*noopMetrics) OpsErrorCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) OpsCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) OpsLatency(_ context.Context, _ time.Duration, _ []MetricAttr) {} +func (*noopMetrics) OpsErrorCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) FileCacheReadCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) FileCacheReadBytesCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) FileCacheReadLatency(_ context.Context, value float64, _ []MetricAttr) {} +func (*noopMetrics) FileCacheReadCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) FileCacheReadBytesCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) FileCacheReadLatency(_ context.Context, _ time.Duration, _ []MetricAttr) {} diff --git a/common/otel_metrics.go b/common/otel_metrics.go index 57ed13d6ef..f3823141fc 100644 --- a/common/otel_metrics.go +++ b/common/otel_metrics.go @@ -18,6 +18,7 @@ import ( "context" "errors" "sync/atomic" + "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" @@ -96,8 +97,8 @@ func (o *otelMetrics) GCSRequestCount(ctx context.Context, inc int64, attrs []Me o.gcsRequestCount.Add(ctx, inc, attrsToAddOption(attrs)...) } -func (o *otelMetrics) GCSRequestLatency(ctx context.Context, value float64, attrs []MetricAttr) { - o.gcsRequestLatency.Record(ctx, value, attrsToRecordOption(attrs)...) +func (o *otelMetrics) GCSRequestLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) { + o.gcsRequestLatency.Record(ctx, float64(latency.Milliseconds()), attrsToRecordOption(attrs)...) } func (o *otelMetrics) GCSReadCount(ctx context.Context, inc int64, attrs []MetricAttr) { @@ -112,8 +113,8 @@ func (o *otelMetrics) OpsCount(ctx context.Context, inc int64, attrs []MetricAtt o.fsOpsCount.Add(ctx, inc, attrsToAddOption(attrs)...) } -func (o *otelMetrics) OpsLatency(ctx context.Context, value float64, attrs []MetricAttr) { - o.fsOpsLatency.Record(ctx, value, attrsToRecordOption(attrs)...) +func (o *otelMetrics) OpsLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) { + o.fsOpsLatency.Record(ctx, float64(latency.Microseconds()), attrsToRecordOption(attrs)...) } func (o *otelMetrics) OpsErrorCount(ctx context.Context, inc int64, attrs []MetricAttr) { @@ -128,8 +129,8 @@ func (o *otelMetrics) FileCacheReadBytesCount(ctx context.Context, inc int64, at o.fileCacheReadBytesCount.Add(ctx, inc, attrsToAddOption(attrs)...) } -func (o *otelMetrics) FileCacheReadLatency(ctx context.Context, value float64, attrs []MetricAttr) { - o.fileCacheReadLatency.Record(ctx, value, attrsToRecordOption(attrs)...) +func (o *otelMetrics) FileCacheReadLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) { + o.fileCacheReadLatency.Record(ctx, float64(latency.Microseconds()), attrsToRecordOption(attrs)...) } func NewOTelMetrics() (MetricHandle, error) { diff --git a/common/telemetry.go b/common/telemetry.go index e7e678f8b6..4acf538a4b 100644 --- a/common/telemetry.go +++ b/common/telemetry.go @@ -18,6 +18,7 @@ import ( "context" "errors" "fmt" + "time" "go.opentelemetry.io/otel/metric" ) @@ -55,21 +56,21 @@ type GCSMetricHandle interface { GCSReadBytesCount(ctx context.Context, inc int64) GCSReaderCount(ctx context.Context, inc int64, attrs []MetricAttr) GCSRequestCount(ctx context.Context, inc int64, attrs []MetricAttr) - GCSRequestLatency(ctx context.Context, value float64, attrs []MetricAttr) + GCSRequestLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) GCSReadCount(ctx context.Context, inc int64, attrs []MetricAttr) GCSDownloadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) } type OpsMetricHandle interface { OpsCount(ctx context.Context, inc int64, attrs []MetricAttr) - OpsLatency(ctx context.Context, value float64, attrs []MetricAttr) + OpsLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) OpsErrorCount(ctx context.Context, inc int64, attrs []MetricAttr) } type FileCacheMetricHandle interface { FileCacheReadCount(ctx context.Context, inc int64, attrs []MetricAttr) FileCacheReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) - FileCacheReadLatency(ctx context.Context, value float64, attrs []MetricAttr) + FileCacheReadLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) } type MetricHandle interface { GCSMetricHandle diff --git a/internal/fs/wrappers/monitoring.go b/internal/fs/wrappers/monitoring.go index 79b79f21e0..113869a811 100644 --- a/internal/fs/wrappers/monitoring.go +++ b/internal/fs/wrappers/monitoring.go @@ -236,7 +236,7 @@ func recordOp(ctx context.Context, metricHandle common.MetricHandle, method stri {Key: common.FSErrCategory, Value: errCategory}}, ) } - metricHandle.OpsLatency(ctx, float64(time.Since(start).Microseconds()), []common.MetricAttr{{Key: common.FSOp, Value: method}}) + metricHandle.OpsLatency(ctx, time.Since(start), []common.MetricAttr{{Key: common.FSOp, Value: method}}) } // WithMonitoring takes a FileSystem, returns a FileSystem with monitoring diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index a61c044fef..f33ad2cb14 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -207,7 +207,7 @@ func captureFileCacheMetrics(ctx context.Context, metricHandle common.MetricHand }) metricHandle.FileCacheReadBytesCount(ctx, int64(readDataSize), []common.MetricAttr{{Key: common.ReadType, Value: readType}}) - metricHandle.FileCacheReadLatency(ctx, float64(readLatency.Microseconds()), []common.MetricAttr{{Key: common.CacheHit, Value: strconv.FormatBool(cacheHit)}}) + metricHandle.FileCacheReadLatency(ctx, readLatency, []common.MetricAttr{{Key: common.CacheHit, Value: strconv.FormatBool(cacheHit)}}) } func (fc *FileCacheReader) Destroy() { diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index 2e861e5637..a3d1f8d365 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -28,9 +28,7 @@ import ( func recordRequest(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time) { metricHandle.GCSRequestCount(ctx, 1, []common.MetricAttr{{Key: common.GCSMethod, Value: method}}) - latencyUs := time.Since(start).Microseconds() - latencyMs := float64(latencyUs) / 1000.0 - metricHandle.GCSRequestLatency(ctx, latencyMs, []common.MetricAttr{{Key: common.GCSMethod, Value: method}}) + metricHandle.GCSRequestLatency(ctx, time.Since(start), []common.MetricAttr{{Key: common.GCSMethod, Value: method}}) } func CaptureMultiRangeDownloaderMetrics(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time) { From 1f7e551582e98f7f90a80714bad46c68e4551ce3 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 13 May 2025 11:24:03 +0530 Subject: [PATCH 0420/1298] Refactor stale handle package and start running streaming writes tests for ZB. (#3104) * refactor stale handle package * remove stale_handle_streaming_writes package. * fix changes * Add skip conditions to stale handle package. * remove files * fix tests * skip tests and add reason * refactor tests * change file name * remove comment * fix review comment * park tests * refactor stale handle tests * fix creating local file * fix tests * fix space * refactor and add test * fix test * skip correct test * add comments for skip conditions * revert mod and sum change * revert mod and sum change * Add comment * fix test * fix review comments * fix review comments * fix review comments * fix test * ensure stale handle tests run on mountedDir --- tools/cd_scripts/e2e_test.sh | 1 - tools/integration_tests/run_e2e_tests.sh | 1 - .../stale_handle/setup_test.go | 33 ++++-- .../stale_file_handle_common_test.go | 67 +++++++++-- .../stale_file_handle_local_file_test.go | 25 +++- .../stale_file_handle_synced_file_test.go | 107 +++++++++-------- .../setup_test.go | 89 -------------- ...ile_handle_streaming_writes_common_test.go | 91 -------------- ...le_streaming_writes_empty_gcs_file_test.go | 111 ------------------ ...handle_streaming_writes_local_file_test.go | 49 -------- .../streaming_writes/rename_file_test.go | 20 ---- .../util/operations/file_operations.go | 54 +++++++++ .../util/operations/validation_helper.go | 5 + 13 files changed, 218 insertions(+), 435 deletions(-) delete mode 100644 tools/integration_tests/stale_handle_streaming_writes/setup_test.go delete mode 100644 tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go delete mode 100644 tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go delete mode 100644 tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_local_file_test.go diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 0a4bb07acf..e82822bfae 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -191,7 +191,6 @@ TEST_DIR_PARALLEL=( "concurrent_operations" "mount_timeout" "stale_handle" - "stale_handle_streaming_writes" "negative_stat_cache" "streaming_writes" ) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 9f466e29fc..4644b399a5 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -104,7 +104,6 @@ TEST_DIR_PARALLEL=( "benchmarking" "mount_timeout" "stale_handle" - "stale_handle_streaming_writes" "negative_stat_cache" "streaming_writes" ) diff --git a/tools/integration_tests/stale_handle/setup_test.go b/tools/integration_tests/stale_handle/setup_test.go index 9b6199d6a8..5bc945f991 100644 --- a/tools/integration_tests/stale_handle/setup_test.go +++ b/tools/integration_tests/stale_handle/setup_test.go @@ -26,9 +26,16 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) +const ( + testDirName = "StaleHandleTest" +) + var ( storageClient *storage.Client ctx context.Context + rootDir string + mountFunc func([]string) error + flagsSet [][]string ) //////////////////////////////////////////////////////////////////////// @@ -49,20 +56,28 @@ func TestMain(m *testing.M) { } }() - // If Mounted Directory flag is set, run tests for mounted directory. - setup.RunTestsForMountedDirectoryFlag(m) - // Else run tests for testBucket. + // To run mountedDirectory tests, we need both testBucket and mountedDirectory + // flags to be set, as stale handle tests validates content from the bucket. + // Note: These tests by default can only be run for non streaming mounts. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + rootDir = setup.MountedDirectory() + setup.RunTestsForMountedDirectoryFlag(m) + return + } + // Set up test directory. setup.SetUpTestDirForTestBucketFlag() + rootDir = setup.MntDir() - // Define flag set to run the tests. - flagsSet := [][]string{ - {"--metadata-cache-ttl-secs=0", "--precondition-errors=true"}, + flagsSet = [][]string{ + {"--metadata-cache-ttl-secs=0"}, + {"--metadata-cache-ttl-secs=0", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1"}, } - // Run all tests for GRPC. + // Run all tests with GRPC. setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc", "") - successCode := static_mounting.RunTests(flagsSet, m) - + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() os.Exit(successCode) } diff --git a/tools/integration_tests/stale_handle/stale_file_handle_common_test.go b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go index de464b300b..6e3da8c975 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_common_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go @@ -19,8 +19,10 @@ import ( "path" "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -30,39 +32,84 @@ import ( // ////////////////////////////////////////////////////////////////////// type staleFileHandleCommon struct { - f1 *os.File + flags []string + f1 *os.File + fileName string + data string + testDirPath string + isStreamingWritesEnabled bool + isLocal bool suite.Suite } +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// +func (s *staleFileHandleCommon) SetupSuite() { + setup.MountGCSFuseWithGivenMountFunc(s.flags, mountFunc) + s.testDirPath = setup.SetupTestDirectory(testDirName) + s.data = setup.GenerateRandomString(5 * util.MiB) +} + +func (s *staleFileHandleCommon) TearDownSuite() { + setup.UnmountGCSFuse(rootDir) + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) +} + //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// func (s *staleFileHandleCommon) TestClobberedFileSyncAndCloseThrowsStaleFileHandleError() { + // TODO(b/410698332): Remove skip condition once takeover support is available. + if s.isStreamingWritesEnabled && setup.IsZonalBucketRun() { + s.T().Skip("Skip test due to unable to overwrite the unfinalized zonal object.") + } // Dirty the file by giving it some contents. - operations.WriteWithoutClose(s.f1, Content, s.T()) - // Replace the underlying object with a new generation. - err := WriteToObject(ctx, storageClient, path.Join(s.T().Name(), FileName1), FileContents, storage.Conditions{}) + operations.WriteWithoutClose(s.f1, s.data, s.T()) + // Clobber file by replacing the underlying object with a new generation. + err := WriteToObject(ctx, storageClient, path.Join(testDirName, s.fileName), FileContents, storage.Conditions{}) assert.NoError(s.T(), err) - err = s.f1.Sync() + operations.ValidateSyncGivenThatFileIsClobbered(s.T(), s.f1, s.isStreamingWritesEnabled) - operations.ValidateESTALEError(s.T(), err) err = s.f1.Close() operations.ValidateESTALEError(s.T(), err) + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, s.fileName, FileContents, s.T()) } func (s *staleFileHandleCommon) TestFileDeletedLocallySyncAndCloseDoNotThrowError() { // Dirty the file by giving it some contents. - _, err := s.f1.WriteString(Content) - assert.NoError(s.T(), err) + operations.WriteWithoutClose(s.f1, s.data, s.T()) + // Delete the file. operations.RemoveFile(s.f1.Name()) + // Verify unlink operation succeeds. operations.ValidateNoFileOrDirError(s.T(), s.f1.Name()) // Attempt to write to file should not give any error. - operations.WriteWithoutClose(s.f1, Content2, s.T()) - + operations.WriteWithoutClose(s.f1, s.data, s.T()) operations.SyncFile(s.f1, s.T()) operations.CloseFileShouldNotThrowError(s.T(), s.f1) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, s.fileName, s.T()) +} + +func (s *staleFileHandleCommon) TestRenamedFileSyncAndCloseThrowsStaleFileHandleError() { + // Dirty the file by giving it some contents. + _, err := s.f1.WriteString(s.data) + assert.NoError(s.T(), err) + newFile := "new" + s.fileName + + err = operations.RenameFile(s.f1.Name(), path.Join(s.testDirPath, newFile)) + + // TODO(b/402335988): Update this test once rename flow is fixed. + if s.isLocal && !s.isStreamingWritesEnabled { + // Rename operation not supported in this scenario. + operations.ValidateEOPNOTSUPPError(s.T(), err) + return + } + assert.NoError(s.T(), err) + operations.ValidateWriteGivenThatFileIsRenamed(s.T(), s.f1, s.isStreamingWritesEnabled, s.data) + operations.ValidateSyncGivenThatFileIsClobbered(s.T(), s.f1, s.isStreamingWritesEnabled) + operations.ValidateCloseGivenThatFileIsRenamed(s.T(), s.f1, s.isStreamingWritesEnabled) } diff --git a/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go index 65da6a242f..a70d081659 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go @@ -15,9 +15,11 @@ package stale_handle import ( + "path" + "slices" "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/suite" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" @@ -31,10 +33,15 @@ type staleFileHandleLocalFile struct { staleFileHandleCommon } +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// + func (s *staleFileHandleLocalFile) SetupTest() { - testDirPath := setup.SetupTestDirectory(s.T().Name()) // Create a local file. - _, s.f1 = CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, s.T()) + s.fileName = path.Base(s.T().Name()) + setup.GenerateRandomString(5) + s.f1 = operations.OpenFileWithODirect(s.T(), path.Join(s.testDirPath, s.fileName)) + s.isLocal = true } //////////////////////////////////////////////////////////////////////// @@ -42,5 +49,15 @@ func (s *staleFileHandleLocalFile) SetupTest() { //////////////////////////////////////////////////////////////////////// func TestStaleFileHandleLocalFileTest(t *testing.T) { - suite.Run(t, new(staleFileHandleLocalFile)) + // Run tests for mounted directory if the flag is set and return. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + suite.Run(t, new(staleFileHandleLocalFile)) + return + } + for _, flags := range flagsSet { + s := new(staleFileHandleLocalFile) + s.flags = flags + s.isStreamingWritesEnabled = slices.Contains(s.flags, "--enable-streaming-writes=true") + suite.Run(t, s) + } } diff --git a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go index f7102b3dd1..40e4a5e227 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go @@ -15,9 +15,8 @@ package stale_handle import ( - "os" "path" - "syscall" + "slices" "testing" "cloud.google.com/go/storage" @@ -32,90 +31,98 @@ import ( // Boilerplate // ////////////////////////////////////////////////////////////////////// -const Content = "foobar" -const Content2 = "foobar2" - -type staleFileHandleSyncedFile struct { +type staleFileHandleEmptyGcsFile struct { staleFileHandleCommon } -func (s *staleFileHandleSyncedFile) SetupTest() { - testDirPath := setup.SetupTestDirectory(s.T().Name()) - // Create an object on bucket - err := CreateObjectOnGCS(ctx, storageClient, path.Join(s.T().Name(), FileName1), GCSFileContent) - assert.NoError(s.T(), err) - s.f1, err = os.OpenFile(path.Join(testDirPath, FileName1), os.O_RDWR|syscall.O_DIRECT, operations.FilePermission_0600) +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// + +func (s *staleFileHandleEmptyGcsFile) SetupTest() { + // Create an empty object on GCS. + s.fileName = path.Base(s.T().Name()) + setup.GenerateRandomString(5) + err := CreateObjectOnGCS(ctx, storageClient, path.Join(testDirName, s.fileName), "") assert.NoError(s.T(), err) + s.f1 = operations.OpenFileWithODirect(s.T(), path.Join(s.testDirPath, s.fileName)) } //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// -func (s *staleFileHandleSyncedFile) TestClobberedFileReadThrowsStaleFileHandleError() { - // Replace the underlying object with a new generation. - err := WriteToObject(ctx, storageClient, path.Join(s.T().Name(), FileName1), FileContents, storage.Conditions{}) +func (s *staleFileHandleEmptyGcsFile) TestClobberedFileReadThrowsStaleFileHandleError() { + // TODO(b/410698332): Remove skip condition once takeover support is available. + if s.isStreamingWritesEnabled && setup.IsZonalBucketRun() { + s.T().Skip("Skip test due to takeover support not available.") + } + // Dirty the file by giving it some contents. + _, err := s.f1.WriteAt([]byte(s.data), 0) assert.NoError(s.T(), err) + operations.SyncFile(s.f1, s.T()) - buffer := make([]byte, GCSFileSize) - _, err = s.f1.Read(buffer) - - operations.ValidateESTALEError(s.T(), err) -} - -func (s *staleFileHandleSyncedFile) TestClobberedFileFirstWriteThrowsStaleFileHandleError() { // Replace the underlying object with a new generation. - err := WriteToObject(ctx, storageClient, path.Join(s.T().Name(), FileName1), FileContents, storage.Conditions{}) - assert.NoError(s.T(), err) + err = WriteToObject(ctx, storageClient, path.Join(testDirName, s.fileName), FileContents, storage.Conditions{}) - _, err = s.f1.WriteString(Content) - - operations.ValidateESTALEError(s.T(), err) - // Attempt to sync to file should not result in error as we first check if the - // content has been dirtied before clobbered check in Sync flow. - operations.SyncFile(s.f1, s.T()) + assert.NoError(s.T(), err) + operations.ValidateReadGivenThatFileIsClobbered(s.T(), s.f1, s.isStreamingWritesEnabled, s.data) } -func (s *staleFileHandleSyncedFile) TestRenamedFileSyncAndCloseThrowsStaleFileHandleError() { - // Dirty the file by giving it some contents. - _, err := s.f1.WriteString(Content) - assert.NoError(s.T(), err) - err = operations.RenameFile(s.f1.Name(), path.Join(setup.MntDir(), s.T().Name(), FileName2)) - assert.NoError(s.T(), err) - // Attempt to write to file should not give any error. - _, err = s.f1.WriteString(Content2) +func (s *staleFileHandleEmptyGcsFile) TestClobberedFileFirstWriteThrowsStaleFileHandleError() { + // TODO(b/410698332): Remove skip condition once takeover support is available. + if s.isStreamingWritesEnabled && setup.IsZonalBucketRun() { + s.T().Skip("Skip test due to takeover support not available.") + } + // Clobber file by replacing the underlying object with a new generation. + err := WriteToObject(ctx, storageClient, path.Join(testDirName, s.fileName), FileContents, storage.Conditions{}) assert.NoError(s.T(), err) - err = s.f1.Sync() + // Attempt first write to the file should give stale NFS file handle error. + _, err = s.f1.Write([]byte(s.data)) - operations.ValidateESTALEError(s.T(), err) + assert.NoError(s.T(), err) + operations.ValidateSyncGivenThatFileIsClobbered(s.T(), s.f1, s.isStreamingWritesEnabled) err = s.f1.Close() operations.ValidateESTALEError(s.T(), err) + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, s.fileName, FileContents, s.T()) } -func (s *staleFileHandleSyncedFile) TestFileDeletedRemotelySyncAndCloseThrowsStaleFileHandleError() { +func (s *staleFileHandleEmptyGcsFile) TestFileDeletedRemotelySyncAndCloseThrowsStaleFileHandleError() { + // TODO(mohitkyadav): Enable test once fix in b/415713332 is released + if s.isStreamingWritesEnabled && setup.IsZonalBucketRun() { + s.T().Skip("Skip test due to bug (b/415713332) in client.") + } // Dirty the file by giving it some contents. - _, err := s.f1.WriteString(Content) - assert.NoError(s.T(), err) + operations.WriteWithoutClose(s.f1, s.data, s.T()) // Delete the file remotely. - err = DeleteObjectOnGCS(ctx, storageClient, path.Join(s.T().Name(), FileName1)) + err := DeleteObjectOnGCS(ctx, storageClient, path.Join(testDirName, s.fileName)) assert.NoError(s.T(), err) // Verify unlink operation succeeds. - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, s.T().Name(), FileName1, s.T()) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, s.fileName, s.T()) // Attempt to write to file should not give any error. - operations.WriteWithoutClose(s.f1, Content2, s.T()) + operations.WriteWithoutClose(s.f1, s.data, s.T()) - err = s.f1.Sync() + operations.ValidateSyncGivenThatFileIsClobbered(s.T(), s.f1, s.isStreamingWritesEnabled) - operations.ValidateESTALEError(s.T(), err) err = s.f1.Close() operations.ValidateESTALEError(s.T(), err) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, s.fileName, s.T()) } //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// -func TestStaleFileHandleSyncedFileTest(t *testing.T) { - suite.Run(t, new(staleFileHandleSyncedFile)) +func TestStaleFileHandleEmptyGcsFileTest(t *testing.T) { + // Run tests for mounted directory if the flag is set and return. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + suite.Run(t, new(staleFileHandleEmptyGcsFile)) + return + } + for _, flags := range flagsSet { + s := new(staleFileHandleEmptyGcsFile) + s.flags = flags + s.isStreamingWritesEnabled = slices.Contains(s.flags, "--enable-streaming-writes=true") + suite.Run(t, s) + } } diff --git a/tools/integration_tests/stale_handle_streaming_writes/setup_test.go b/tools/integration_tests/stale_handle_streaming_writes/setup_test.go deleted file mode 100644 index 4936835a57..0000000000 --- a/tools/integration_tests/stale_handle_streaming_writes/setup_test.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stale_handle_streaming_writes - -import ( - "context" - "log" - "os" - "testing" - - "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" -) - -const ( - testDirName = "StaleHandleStreamingWritesTest" -) - -var ( - flags []string - mountFunc func([]string) error - // root directory is the directory to be unmounted. - rootDir string - storageClient *storage.Client - ctx context.Context -) - -//////////////////////////////////////////////////////////////////////// -// TestMain -//////////////////////////////////////////////////////////////////////// - -func TestMain(m *testing.M) { - setup.ParseSetUpFlags() - - // Create storage client before running tests. - ctx = context.Background() - closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) - defer func() { - err := closeStorageClient() - if err != nil { - log.Fatalf("closeStorageClient failed: %v", err) - } - }() - - // To run mountedDirectory tests, we need both testBucket and mountedDirectory - // flags to be set, as operations tests validates content from the bucket. - if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - rootDir = setup.MountedDirectory() - setup.RunTestsForMountedDirectoryFlag(m) - } - - // Set up test directory. - setup.SetUpTestDirForTestBucketFlag() - rootDir = setup.MntDir() - // Define flag set to run the tests. - flagsSet := [][]string{ - {"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1"}, - } - // Run all tests for GRPC. - setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc", "") - - log.Println("Running static mounting tests...") - mountFunc = static_mounting.MountGcsfuseWithStaticMounting - - var successCode int - for i := range flagsSet { - log.Printf("Running tests with flags: %v", flagsSet[i]) - flags = flagsSet[i] - successCode = m.Run() - if successCode != 0 { - break - } - } - os.Exit(successCode) -} diff --git a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go deleted file mode 100644 index 3c334f3b21..0000000000 --- a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_common_test.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stale_handle_streaming_writes - -import ( - "os" - "path" - - "cloud.google.com/go/storage" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" -) - -// ////////////////////////////////////////////////////////////////////// -// Boilerplate -// ////////////////////////////////////////////////////////////////////// - -type staleFileHandleStreamingWritesCommon struct { - f1 *os.File - data string - testDirPath string - fileName string - filePath string - suite.Suite -} - -//////////////////////////////////////////////////////////////////////// -// Helpers -//////////////////////////////////////////////////////////////////////// - -func (t *staleFileHandleStreamingWritesCommon) SetupSuite() { - setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) - t.testDirPath = setup.SetupTestDirectory(testDirName) - t.data = setup.GenerateRandomString(operations.MiB * 5) -} - -func (t *staleFileHandleStreamingWritesCommon) TearDownSuite() { - setup.UnmountGCSFuse(rootDir) -} - -//////////////////////////////////////////////////////////////////////// -// Tests -//////////////////////////////////////////////////////////////////////// - -func (t *staleFileHandleStreamingWritesCommon) TestFileDeletedLocallySyncAndCloseDoNotThrowError() { - // Dirty the file by giving it some contents. - bytesWrote, err := t.f1.WriteAt([]byte(t.data), 0) - assert.NoError(t.T(), err) - // Delete the file. - operations.RemoveFile(t.f1.Name()) - // Verify unlink operation succeeds. - operations.ValidateNoFileOrDirError(t.T(), t.f1.Name()) - - // Write should not give an error. - _, err = t.f1.WriteAt([]byte(t.data), int64(bytesWrote)) - - assert.NoError(t.T(), err) - operations.SyncFile(t.f1, t.T()) - operations.CloseFileShouldNotThrowError(t.T(), t.f1) -} - -func (t *staleFileHandleStreamingWritesCommon) TestClosingFileHandleForClobberedFileReturnsStaleFileHandleError() { - // Dirty the file by giving it some contents. - _, err := t.f1.WriteAt([]byte(t.data), 0) - assert.NoError(t.T(), err) - // Clobber file by replacing the underlying object with a new generation. - err = WriteToObject(ctx, storageClient, path.Join(testDirName, t.fileName), FileContents, storage.Conditions{}) - assert.NoError(t.T(), err) - operations.SyncFile(t.f1, t.T()) - - // Closing the file/writer returns stale NFS file handle error. - err = t.f1.Close() - - operations.ValidateESTALEError(t.T(), err) - ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, FileContents, t.T()) -} diff --git a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go deleted file mode 100644 index 1de7c08b8a..0000000000 --- a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_empty_gcs_file_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stale_handle_streaming_writes - -import ( - "path" - "testing" - - "cloud.google.com/go/storage" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/suite" -) - -// ////////////////////////////////////////////////////////////////////// -// Boilerplate -// ////////////////////////////////////////////////////////////////////// - -type staleFileHandleStreamingWritesEmptyGcsFile struct { - staleFileHandleStreamingWritesCommon -} - -//////////////////////////////////////////////////////////////////////// -// Helpers -//////////////////////////////////////////////////////////////////////// - -func (t *staleFileHandleStreamingWritesEmptyGcsFile) SetupTest() { - t.fileName = setup.GenerateRandomString(5) - // Create an empty object on GCS - CreateObjectInGCSTestDir(ctx, storageClient, testDirName, t.fileName, "", t.T()) - ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, "", t.T()) - t.filePath = path.Join(t.testDirPath, t.fileName) - t.f1 = operations.OpenFile(t.filePath, t.T()) -} - -//////////////////////////////////////////////////////////////////////// -// Tests -//////////////////////////////////////////////////////////////////////// - -func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestFirstWriteToClobberedFileThrowsStaleFileHandleError() { - // Clobber file by replacing the underlying object with a new generation. - err := WriteToObject(ctx, storageClient, path.Join(testDirName, t.fileName), FileContents, storage.Conditions{}) - assert.NoError(t.T(), err) - - // Attempt first write to the file. - _, err = t.f1.WriteAt([]byte(t.data), 0) - - operations.ValidateESTALEError(t.T(), err) - operations.SyncFile(t.f1, t.T()) - operations.CloseFileShouldNotThrowError(t.T(), t.f1) -} - -func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestWriteOnRenamedFileThrowsStaleFileHandleError() { - // Dirty the file by giving it some contents. - n, err := t.f1.WriteAt([]byte(t.data), 0) - assert.NoError(t.T(), err) - newFile := "new" + t.fileName - err = operations.RenameFile(t.filePath, path.Join(t.testDirPath, newFile)) - assert.NoError(t.T(), err) - - // Attempt to write to file should give stale NFS file handle erorr. - _, err = t.f1.WriteAt([]byte(t.data), int64(n)) - - operations.ValidateESTALEError(t.T(), err) - // Sync and Close succeeds. - operations.SyncFile(t.f1, t.T()) - operations.CloseFileShouldNotThrowError(t.T(), t.f1) - ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, t.data, t.T()) -} - -func (t *staleFileHandleStreamingWritesEmptyGcsFile) TestFileDeletedRemotelyWriteAndSyncDoNoThrowStaleFileHandleError() { - // Dirty the file by giving it some contents. - n, err := t.f1.WriteAt([]byte(t.data), 0) - assert.NoError(t.T(), err) - // Delete the file remotely. - err = DeleteObjectOnGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) - assert.NoError(t.T(), err) - // Verify unlink operation succeeds. - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) - _, err = t.f1.WriteAt([]byte(t.data), int64(n)) - assert.NoError(t.T(), err) - operations.SyncFile(t.f1, t.T()) - - // Closing the file/writer returns stale NFS file handle error. - err = t.f1.Close() - - operations.ValidateESTALEError(t.T(), err) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) -} - -//////////////////////////////////////////////////////////////////////// -// Test Function (Runs once before all tests) -//////////////////////////////////////////////////////////////////////// - -func TestStaleFileHandleStreamingWritesEmptyGcsFileTest(t *testing.T) { - suite.Run(t, new(staleFileHandleStreamingWritesEmptyGcsFile)) -} diff --git a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_local_file_test.go b/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_local_file_test.go deleted file mode 100644 index 1b3a836d64..0000000000 --- a/tools/integration_tests/stale_handle_streaming_writes/stale_file_handle_streaming_writes_local_file_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package stale_handle_streaming_writes - -import ( - "testing" - - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/stretchr/testify/suite" -) - -// ////////////////////////////////////////////////////////////////////// -// Boilerplate -// ////////////////////////////////////////////////////////////////////// - -type staleFileHandleStreamingWritesLocalFile struct { - staleFileHandleStreamingWritesCommon -} - -//////////////////////////////////////////////////////////////////////// -// Helpers -//////////////////////////////////////////////////////////////////////// - -func (t *staleFileHandleStreamingWritesLocalFile) SetupTest() { - t.fileName = setup.GenerateRandomString(5) - // Create a local file. - t.filePath, t.f1 = CreateLocalFileInTestDir(ctx, storageClient, t.testDirPath, t.fileName, t.T()) -} - -//////////////////////////////////////////////////////////////////////// -// Test Function (Runs once before all tests) -//////////////////////////////////////////////////////////////////////// - -func TestStaleFileHandleStreamingWritesLocalFileTest(t *testing.T) { - suite.Run(t, new(staleFileHandleStreamingWritesLocalFile)) -} diff --git a/tools/integration_tests/streaming_writes/rename_file_test.go b/tools/integration_tests/streaming_writes/rename_file_test.go index aac699166f..c8935cad87 100644 --- a/tools/integration_tests/streaming_writes/rename_file_test.go +++ b/tools/integration_tests/streaming_writes/rename_file_test.go @@ -62,23 +62,3 @@ func (t *defaultMountCommonTest) TestSyncAfterRenameSucceeds() { // Check if old object is deleted. ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) } - -func (t *defaultMountCommonTest) TestAfterRenameWriteFailsWithStaleNFSFileHandleError() { - _, err := t.f1.WriteAt([]byte(t.data), 0) - require.NoError(t.T(), err) - operations.VerifyStatFile(t.filePath, int64(len(t.data)), FilePerms, t.T()) - err = t.f1.Sync() - require.NoError(t.T(), err) - newFile := "new" + t.fileName - err = operations.RenameFile(t.filePath, path.Join(testDirPath, newFile)) - require.NoError(t.T(), err) - - _, err = t.f1.WriteAt([]byte(t.data), int64(len(t.data))) - - operations.ValidateESTALEError(t.T(), err) - // Verify the new object contents. - ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, newFile, string(t.data), t.T()) - require.NoError(t.T(), t.f1.Close()) - // Check if old object is deleted. - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) -} diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 647d19df49..b4524ad5c8 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -802,3 +802,57 @@ func CheckLogFileForMessage(t *testing.T, expectedLog, logFile string) bool { } return false } + +// This method validates sync operation on file which has already been clobbered. +// 1. With streaming writes sync operation only uploads pending buffers and it doesn't return any error. +// 2. Without streaming writes file is synced with GCS and returns ESTALE error. +func ValidateSyncGivenThatFileIsClobbered(t *testing.T, file *os.File, streamingWrites bool) { + t.Helper() + err := file.Sync() + if streamingWrites { + assert.NoError(t, err) + } else { + ValidateESTALEError(t, err) + } +} + +// This method validates read operation on file which has already been clobbered. +// 1. With streaming writes read operation is not supported. +// 2. Without streaming writes read operation returns ESTALE error encountered during reader creation. +func ValidateReadGivenThatFileIsClobbered(t *testing.T, file *os.File, streamingWrites bool, content string) { + t.Helper() + buffer := make([]byte, len(content)) + _, err := file.Read(buffer) + if streamingWrites { + ValidateEOPNOTSUPPError(t, err) + } else { + ValidateESTALEError(t, err) + } +} + +// This method validates write operation on file which has been renamed. +// 1. With streaming writes write operation returns ESTALE error as buffer upload fails. +// 2. Without streaming writes write operation succeeds. +func ValidateWriteGivenThatFileIsRenamed(t *testing.T, file *os.File, streamingWrites bool, content string) { + t.Helper() + n, err := file.WriteString(content) + if streamingWrites { + ValidateESTALEError(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, len(content), n) + } +} + +// This method validates close operation on file which has been renamed. +// 1. With streaming writes close operation succeeds as nothing is written on file after rename. +// 2. Without streaming writes close operation fails as object not found on GCS. +func ValidateCloseGivenThatFileIsRenamed(t *testing.T, file *os.File, streamingWrites bool) { + t.Helper() + err := file.Close() + if streamingWrites { + require.NoError(t, err) + } else { + ValidateESTALEError(t, err) + } +} diff --git a/tools/integration_tests/util/operations/validation_helper.go b/tools/integration_tests/util/operations/validation_helper.go index f7b8b1481c..98e17984b6 100644 --- a/tools/integration_tests/util/operations/validation_helper.go +++ b/tools/integration_tests/util/operations/validation_helper.go @@ -52,6 +52,11 @@ func ValidateESTALEError(t *testing.T, err error) { assert.Regexp(t, syscall.ESTALE.Error(), err.Error()) } +func ValidateEOPNOTSUPPError(t *testing.T, err error) { + require.Error(t, err) + assert.Regexp(t, syscall.EOPNOTSUPP.Error(), err.Error()) +} + func CheckErrorForReadOnlyFileSystem(t *testing.T, err error) { if err == nil { t.Error("permission denied error expected but got nil error.") From 83d2a12f6e21c73f468ea7e57bd2efa410761d41 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 13 May 2025 16:57:18 +0530 Subject: [PATCH 0421/1298] Unify the code-flow with and without metrics being enabled. (#3288) Currently, enabling metrics will conditionally enable a decorator that activates the code flows to record metrics. However, this leads to test-gaps since we currently don't enable metrics in our tests and that decorator doesn't come into picture while running tests. So, we unify the flow with/without metrics enabled so that the monitoring decorator will always be set even in cases when no metrics are to be computed. --- internal/gcsx/bucket_manager.go | 4 +--- internal/storage/bucket_handle.go | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/gcsx/bucket_manager.go b/internal/gcsx/bucket_manager.go index 9e36d98d03..d9959803ff 100644 --- a/internal/gcsx/bucket_manager.go +++ b/internal/gcsx/bucket_manager.go @@ -175,9 +175,7 @@ func (bm *bucketManager) SetUpBucket( } // Enable monitoring. - if bm.config.EnableMonitoring { - b = monitor.NewMonitoringBucket(b, metricHandle) - } + b = monitor.NewMonitoringBucket(b, metricHandle) // Enable gcs logs. b = storage.NewDebugBucket(b) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index c8c8225046..9750272868 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -95,8 +95,7 @@ func (bh *bucketHandle) NewReaderWithReadHandle( } // NewRangeReader creates a "storage.Reader" object which is also io.ReadCloser since it contains both Read() and Close() methods present in io.ReadCloser interface. - var storageReader *storage.Reader - storageReader, err = obj.NewRangeReader(ctx, start, length) + storageReader, err := obj.NewRangeReader(ctx, start, length) if err == nil { reader = newGCSFullReadCloser(storageReader) } From a67a89bd399101eee0b5a847b3b6764d003d0241 Mon Sep 17 00:00:00 2001 From: codechanges Date: Wed, 14 May 2025 07:45:26 +0530 Subject: [PATCH 0422/1298] Encapsulate test environment variables (#3289) * Encapsulate test environment variables * added a comment * added a comment * added a comment --- .../disabled_negative_stat_cache_test.go | 6 +-- .../finite_int_negative_stat_cache_test.go | 6 +-- .../infinite_negative_stat_cache_test.go | 14 +++---- .../negative_stat_cache/setup_test.go | 41 +++++++++++-------- 4 files changed, 36 insertions(+), 31 deletions(-) diff --git a/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go index dc9acf5faa..7a4620314b 100644 --- a/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go +++ b/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go @@ -36,7 +36,7 @@ func (s *disabledNegativeStatCacheTest) Setup(t *testing.T) { } func (s *disabledNegativeStatCacheTest) Teardown(t *testing.T) { - setup.UnmountGCSFuse(rootDir) + setup.UnmountGCSFuse(testEnv.rootDir) } //////////////////////////////////////////////////////////////////////// @@ -44,7 +44,7 @@ func (s *disabledNegativeStatCacheTest) Teardown(t *testing.T) { //////////////////////////////////////////////////////////////////////// func (s *disabledNegativeStatCacheTest) TestNegativeStatCacheDisabled(t *testing.T) { - targetDir := path.Join(testDirPath, "explicit_dir") + targetDir := path.Join(testEnv.testDirPath, "explicit_dir") // Create test directory operations.CreateDirectory(targetDir, t) targetFile := path.Join(targetDir, "file1.txt") @@ -57,7 +57,7 @@ func (s *disabledNegativeStatCacheTest) TestNegativeStatCacheDisabled(t *testing assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") // Adding the object with same name - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, "explicit_dir/file1.txt", "some-content", t) + client.CreateObjectInGCSTestDir(testEnv.ctx, testEnv.storageClient, testDirName, "explicit_dir/file1.txt", "some-content", t) // File should be returned, as call will be served from GCS and gcsfuse should not return from cache f, err := os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) diff --git a/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go index 5e130e5191..d99871df26 100644 --- a/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go +++ b/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go @@ -37,7 +37,7 @@ func (s *finiteNegativeStatCacheTest) Setup(t *testing.T) { } func (s *finiteNegativeStatCacheTest) Teardown(t *testing.T) { - setup.UnmountGCSFuse(rootDir) + setup.UnmountGCSFuse(testEnv.rootDir) } //////////////////////////////////////////////////////////////////////// @@ -45,7 +45,7 @@ func (s *finiteNegativeStatCacheTest) Teardown(t *testing.T) { //////////////////////////////////////////////////////////////////////// func (s *finiteNegativeStatCacheTest) TestFiniteNegativeStatCache(t *testing.T) { - targetDir := path.Join(testDirPath, "explicit_dir") + targetDir := path.Join(testEnv.testDirPath, "explicit_dir") // Create test directory operations.CreateDirectory(targetDir, t) targetFile := path.Join(targetDir, "file1.txt") @@ -58,7 +58,7 @@ func (s *finiteNegativeStatCacheTest) TestFiniteNegativeStatCache(t *testing.T) assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") // Adding the object with same name - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file1.txt"), "some-content", t) + client.CreateObjectInGCSTestDir(testEnv.ctx, testEnv.storageClient, testDirName, path.Join("explicit_dir", "file1.txt"), "some-content", t) // Error should be returned again, as call will not be served from GCS due to finite gcsfuse stat cache _, err = os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) diff --git a/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go index 1b3019a1dc..2fb25cd053 100644 --- a/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go +++ b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go @@ -38,7 +38,7 @@ func (s *infiniteNegativeStatCacheTest) Setup(t *testing.T) { } func (s *infiniteNegativeStatCacheTest) Teardown(t *testing.T) { - setup.UnmountGCSFuse(rootDir) + setup.UnmountGCSFuse(testEnv.rootDir) } //////////////////////////////////////////////////////////////////////// @@ -46,7 +46,7 @@ func (s *infiniteNegativeStatCacheTest) Teardown(t *testing.T) { //////////////////////////////////////////////////////////////////////// func (s *infiniteNegativeStatCacheTest) TestInfiniteNegativeStatCache(t *testing.T) { - targetDir := path.Join(testDirPath, "explicit_dir") + targetDir := path.Join(testEnv.testDirPath, "explicit_dir") // Create test directory operations.CreateDirectory(targetDir, t) targetFile := path.Join(targetDir, "file1.txt") @@ -59,7 +59,7 @@ func (s *infiniteNegativeStatCacheTest) TestInfiniteNegativeStatCache(t *testing assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") // Adding the object with same name - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, "explicit_dir/file1.txt", "some-content", t) + client.CreateObjectInGCSTestDir(testEnv.ctx, testEnv.storageClient, testDirName, "explicit_dir/file1.txt", "some-content", t) // Error should be returned again, as call will not be served from GCS due to infinite gcsfuse stat cache _, err = os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) @@ -77,7 +77,7 @@ func (s *infiniteNegativeStatCacheTest) TestInfiniteNegativeStatCache(t *testing // leading to an EEXIST error when attempting to create the same folder again. func (s *infiniteNegativeStatCacheTest) TestAlreadyExistFolder(t *testing.T) { dirName := "testAlreadyExistFolder" - dirPath := path.Join(testDirPath, dirName) + dirPath := path.Join(testEnv.testDirPath, dirName) dirPathOnBucket := path.Join(testDirName, dirName) // Stat should return an error because the directory doesn't exist yet, // populating the negative metadata cache. @@ -85,10 +85,10 @@ func (s *infiniteNegativeStatCacheTest) TestAlreadyExistFolder(t *testing.T) { require.Error(t, err) require.True(t, os.IsNotExist(err)) // Create the directory in the bucket using a different client outside of gcsfuse. - if setup.IsHierarchicalBucket(ctx, storageClient) { - _, err = client.CreateFolderInBucket(ctx, storageControlClient, dirPathOnBucket) + if setup.IsHierarchicalBucket(testEnv.ctx, testEnv.storageClient) { + _, err = client.CreateFolderInBucket(testEnv.ctx, testEnv.storageControlClient, dirPathOnBucket) } else { - err = client.CreateObjectOnGCS(ctx, storageClient, dirPathOnBucket+"/", "") + err = client.CreateObjectOnGCS(testEnv.ctx, testEnv.storageClient, dirPathOnBucket+"/", "") } require.NoError(t, err) diff --git a/tools/integration_tests/negative_stat_cache/setup_test.go b/tools/integration_tests/negative_stat_cache/setup_test.go index 57ba9f2aeb..15a46afdf6 100644 --- a/tools/integration_tests/negative_stat_cache/setup_test.go +++ b/tools/integration_tests/negative_stat_cache/setup_test.go @@ -35,7 +35,11 @@ const ( onlyDirMounted = "OnlyDirMountNegativeStatCache" ) -var ( +// IMPORTANT: To prevent global variable pollution, enhance code clarity, +// and avoid inadvertent errors. We strongly suggest that, all new package-level +// variables (which would otherwise be declared with `var` at the package root) should +// be added as fields to this 'env' struct instead. +type env struct { testDirPath string mountFunc func([]string) error // mount directory is where our tests run. @@ -45,20 +49,21 @@ var ( storageClient *storage.Client storageControlClient *control.StorageControlClient ctx context.Context -) +} + +var testEnv env //////////////////////////////////////////////////////////////////////// // Helpers //////////////////////////////////////////////////////////////////////// func mountGCSFuseAndSetupTestDir(flags []string, testDirName string) { - // When tests are running in GKE environment, use the mounted directory provided as test flag. if setup.MountedDirectory() != "" { - mountDir = setup.MountedDirectory() + testEnv.mountDir = setup.MountedDirectory() } - setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) - setup.SetMntDir(mountDir) - testDirPath = setup.SetupTestDirectory(testDirName) + setup.MountGCSFuseWithGivenMountFunc(flags, testEnv.mountFunc) + setup.SetMntDir(testEnv.mountDir) + testEnv.testDirPath = setup.SetupTestDirectory(testDirName) } //////////////////////////////////////////////////////////////////////// @@ -70,15 +75,15 @@ func TestMain(m *testing.M) { setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() // Create common storage client to be used in test. - ctx = context.Background() - closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + testEnv.ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&testEnv.ctx, &testEnv.storageClient) defer func() { err := closeStorageClient() if err != nil { log.Fatalf("closeStorageClient failed: %v", err) } }() - closeStorageControlClient := client.CreateControlClientWithCancel(&ctx, &storageControlClient) + closeStorageControlClient := client.CreateControlClientWithCancel(&testEnv.ctx, &testEnv.storageControlClient) defer func() { err := closeStorageControlClient() if err != nil { @@ -93,30 +98,30 @@ func TestMain(m *testing.M) { setup.SetUpTestDirForTestBucketFlag() // Save mount and root directory variables. - mountDir, rootDir = setup.MntDir(), setup.MntDir() + testEnv.mountDir, testEnv.rootDir = setup.MntDir(), setup.MntDir() log.Println("Running static mounting tests...") - mountFunc = static_mounting.MountGcsfuseWithStaticMounting + testEnv.mountFunc = static_mounting.MountGcsfuseWithStaticMounting successCode := m.Run() if successCode == 0 { log.Println("Running dynamic mounting tests...") // Save mount directory variable to have path of bucket to run tests. - mountDir = path.Join(setup.MntDir(), setup.TestBucket()) - mountFunc = dynamic_mounting.MountGcsfuseWithDynamicMounting + testEnv.mountDir = path.Join(setup.MntDir(), setup.TestBucket()) + testEnv.mountFunc = dynamic_mounting.MountGcsfuseWithDynamicMounting successCode = m.Run() } if successCode == 0 { log.Println("Running only dir mounting tests...") setup.SetOnlyDirMounted(onlyDirMounted + "/") - mountDir = rootDir - mountFunc = only_dir_mounting.MountGcsfuseWithOnlyDir + testEnv.mountDir = testEnv.rootDir + testEnv.mountFunc = only_dir_mounting.MountGcsfuseWithOnlyDir successCode = m.Run() - setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), setup.OnlyDirMounted(), testDirName)) + setup.CleanupDirectoryOnGCS(testEnv.ctx, testEnv.storageClient, path.Join(setup.TestBucket(), setup.OnlyDirMounted(), testDirName)) } // Clean up test directory created. - setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) + setup.CleanupDirectoryOnGCS(testEnv.ctx, testEnv.storageClient, path.Join(setup.TestBucket(), testDirName)) os.Exit(successCode) } From ca7c8fff3c32a0fd8c77b7944991b27c4703c93d Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 14 May 2025 06:24:56 +0000 Subject: [PATCH 0423/1298] [testing-on-gke] Make user-passed instance_id unique (#3295) * Create a smart instance_id If user passes an instance_id, then prefix it with user-name/date-time/uuid to make it unique. * fix an error log * fix a comment * fix a help log * improvements --- .../testing_on_gke/examples/run-gke-tests.sh | 46 ++++++++++++++++--- .../examples/utils/run_tests_common.py | 2 +- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index b833c6266d..ec45f9ef25 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -59,7 +59,6 @@ readonly csi_driver_branch=main readonly gcsfuse_github_path=https://github.com/googlecloudplatform/gcsfuse readonly DEFAULT_GCSFUSE_BRANCH=garnitin/add-gke-load-testing/v1 # Test runtime configuration -readonly DEFAULT_INSTANCE_ID=${USER}-$(date +%Y%m%d-%H%M%S) # 5 minutes readonly DEFAULT_POD_WAIT_TIME_IN_SECONDS=300 # 1 week @@ -72,6 +71,19 @@ readonly DEFAULT_BQ_PROJECT_ID='gcs-fuse-test-ml' readonly DEFAULT_BQ_DATASET_ID='gke_test_tool_outputs' readonly DEFAULT_BQ_TABLE_ID='fio_outputs' +# Create and return a unique instance_id taking +# into account user's passed instance_id. +function create_unique_instance_id() { + new_uuid=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 4 | head -n 1) + local generated_unique_instance_id=${USER}-$(date +%Y%m%d-%H%M%S)-${new_uuid} + if [ $# -gt 0 ] && [ -n "${1}" ]; then + local user_provided_instance_id="${1}" + instance_id=${user_provided_instance_id// /-}"-"${generated_unique_instance_id} + else + instance_id=${generated_unique_instance_id} + fi + echo "${instance_id}" +} function printHelp() { echo "Usage guide: " @@ -99,7 +111,7 @@ function printHelp() { # Test runtime configuration echo "pod_wait_time_in_seconds=" echo "pod_timeout_in_seconds=" - echo "instance_id=" echo "output_dir=" echo "force_update_gcsfuse_code=" @@ -141,7 +153,7 @@ fi test -n "${zone}" || export zone=${DEFAULT_ZONE} # GKE cluster related if test -z "${cluster_name}"; then - exitWithError "${cluster_name} was not set." + exitWithError "cluster_name was not set." fi test -n "${node_pool}" || export node_pool=${DEFAULT_NODE_POOL} test -n "${machine_type}" || export machine_type=${DEFAULT_MACHINE_TYPE} @@ -219,7 +231,24 @@ fi # Test runtime configuration test -n "${pod_wait_time_in_seconds}" || export pod_wait_time_in_seconds="${DEFAULT_POD_WAIT_TIME_IN_SECONDS}" test -n "${pod_timeout_in_seconds}" || export pod_timeout_in_seconds="${DEFAULT_POD_TIMEOUT_IN_SECONDS}" -test -n "${instance_id}" || export instance_id="${DEFAULT_INSTANCE_ID}" + +if test -z ${only_parse} ; then + export only_parse=false +elif [ "$only_parse" != "true" ] && [ "$only_parse" != "false" ]; then + exitWithError "Unexpected value of only_parse: ${only_parse}. Expected: true or false ." +fi + +# If user passes only_parse=true, then expect an instance_id +# also with it, and use it as it is. +if ${only_parse}; then + if [ -z "${instance_id}" ]; then + exitWithError "instance_id not passed with only_parse=true" + fi +else + # create a new instance_id + export user_passed_instance_id="${instance_id}" + export instance_id=$(create_unique_instance_id "${user_passed_instance_id}") +fi if [[ ${pod_timeout_in_seconds} -le ${pod_wait_time_in_seconds} ]]; then exitWithError "pod_timeout_in_seconds (${pod_timeout_in_seconds}) <= pod_wait_time_in_seconds (${pod_wait_time_in_seconds})" @@ -275,11 +304,14 @@ function printRunParameters() { # Test runtime configuration echo "pod_wait_time_in_seconds=\"${pod_wait_time_in_seconds}\"" echo "pod_timeout_in_seconds=\"${pod_timeout_in_seconds}\"" - echo "instance_id=\"${instance_id}\"" + echo "instance_id=User passed: \"${user_passed_instance_id}\", internally created: \"${instance_id}\"" echo "workload_config=\"${workload_config}\"" echo "output_dir=\"${output_dir}\"" echo "force_update_gcsfuse_code=\"${force_update_gcsfuse_code}\"" echo "zonal=\"${zonal}\"" + if ${only_parse}; then + echo "only_parse=${only_parse}" + fi echo "" echo "" echo "" @@ -818,8 +850,8 @@ function fetchAndParseDlioOutputs() { printRunParameters installDependencies -# if only_parse is not set or is set as false, then -if test -z ${only_parse} || ! ${only_parse} ; then +# if only_parse is false, then +if ! ${only_parse} ; then validateMachineConfig ${machine_type} ${num_nodes} ${num_ssd} if ${zonal} && $(areThereAnyDLIOWorkloads); then diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py b/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py index 196fe7d432..4459e0597d 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py @@ -36,7 +36,7 @@ def run_command(command: str) -> int: def escape_commas_in_string(unescapedStr: str) -> str: - """Returns equivalent string with ',' replaced with '\,' .""" + """Returns equivalent string with , replaced with \, .""" return unescapedStr.replace(',', '\,') From 0fc547b19f271c23870b1b2988e5b245263082ea Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 14 May 2025 06:31:14 +0000 Subject: [PATCH 0424/1298] refactor local file test package to use stretchr testify (#3298) --- internal/fs/local_file_test.go | 358 +++++++++++++++++---------------- 1 file changed, 183 insertions(+), 175 deletions(-) diff --git a/internal/fs/local_file_test.go b/internal/fs/local_file_test.go index fd734502b6..ed03df2507 100644 --- a/internal/fs/local_file_test.go +++ b/internal/fs/local_file_test.go @@ -26,6 +26,7 @@ import ( "path/filepath" "reflect" "strings" + "testing" "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" @@ -34,8 +35,9 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/jacobsa/fuse/fusetesting" - . "github.com/jacobsa/ogletest" - "github.com/jacobsa/timeutil" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) // ////////////////////////////////////////////////////////////////////// @@ -53,13 +55,14 @@ type LocalFileTest struct { // fsTest has f1 *osFile and f2 *osFile which we will reuse here. f3 *os.File fsTest + suite.Suite } -func init() { - RegisterTestSuite(&LocalFileTest{}) +func TestLocalFileTest(t *testing.T) { + suite.Run(t, &LocalFileTest{}) } -func (t *LocalFileTest) SetUpTestSuite() { +func (t *LocalFileTest) SetupSuite() { t.serverCfg.ImplicitDirectories = true t.serverCfg.NewConfig = &cfg.Config{ Write: cfg.WriteConfig{ @@ -68,10 +71,14 @@ func (t *LocalFileTest) SetUpTestSuite() { t.fsTest.SetUpTestSuite() } -func (t *LocalFileTest) TearDown() { +func (t *LocalFileTest) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} + +func (t *LocalFileTest) TearDownTest() { // Close t.f3 in case of test failure. if t.f3 != nil { - AssertEq(nil, t.f3.Close()) + assert.NoError(t.T(), t.f3.Close()) t.f3 = nil } @@ -83,41 +90,46 @@ func (t *LocalFileTest) TearDown() { // Helpers // ////////////////////////////////////////////////////////////////////// func (t *LocalFileTest) createLocalFile(fileName string) (filePath string, f *os.File) { + t.T().Helper() // Creating a file shouldn't create file on GCS. filePath = path.Join(mntDir, fileName) f, err := os.Create(filePath) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(fileName) return } func (t *LocalFileTest) verifyLocalFileEntry(entry os.DirEntry, fileName string, size int) { - AssertEq(false, entry.IsDir()) - AssertEq(fileName, entry.Name()) + t.T().Helper() + assert.False(t.T(), entry.IsDir()) + assert.Equal(t.T(), fileName, entry.Name()) fileInfo, err := entry.Info() - AssertEq(nil, err) - AssertEq(size, fileInfo.Size()) + require.NoError(t.T(), err) + assert.EqualValues(t.T(), size, fileInfo.Size()) } func (t *LocalFileTest) verifyDirectoryEntry(entry os.DirEntry, dirName string) { - AssertEq(true, entry.IsDir()) - AssertEq(dirName, entry.Name()) + t.T().Helper() + assert.True(t.T(), entry.IsDir()) + assert.Equal(t.T(), dirName, entry.Name()) } func (t *LocalFileTest) readDirectory(dirPath string) (entries []os.DirEntry) { + t.T().Helper() entries, err := os.ReadDir(dirPath) - AssertEq(nil, err) + require.NoError(t.T(), err) return } func (t *LocalFileTest) validateObjectNotFoundErr(fileName string) { + t.T().Helper() var notFoundErr *gcs.NotFoundError _, err := storageutil.ReadObject(ctx, bucket, fileName) - ExpectTrue(errors.As(err, ¬FoundErr)) + assert.True(t.T(), errors.As(err, ¬FoundErr)) } func (t *LocalFileTest) closeLocalFile(f **os.File) error { @@ -127,23 +139,26 @@ func (t *LocalFileTest) closeLocalFile(f **os.File) error { } func (t *LocalFileTest) closeFileAndValidateObjectContents(f **os.File, fileName string, contents string) { + t.T().Helper() err := t.closeLocalFile(f) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectContents(fileName, contents) } func (t *LocalFileTest) validateObjectContents(fileName string, contents string) { + t.T().Helper() contentBytes, err := storageutil.ReadObject(ctx, bucket, fileName) - AssertEq(nil, err) - ExpectEq(contents, string(contentBytes)) + require.NoError(t.T(), err) + assert.Equal(t.T(), contents, string(contentBytes)) } func (t *LocalFileTest) newFileShouldGetSyncedToGCSAtClose(fileName string) { + t.T().Helper() // Create a local file. _, t.f1 = t.createLocalFile(fileName) // Writing contents to local file shouldn't create file on GCS. _, err := t.f1.Write([]byte(FileContents)) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(fileName) // Close the file and validate if the file is created on GCS. @@ -151,37 +166,37 @@ func (t *LocalFileTest) newFileShouldGetSyncedToGCSAtClose(fileName string) { // Validate object attributes non-nil and non-empty. minObject, extendedAttr, err := bucket.StatObject(ctx, &gcs.StatObjectRequest{Name: fileName, ForceFetchFromGcs: true, ReturnExtendedObjectAttributes: true}) - AssertEq(nil, err) - AssertNe(nil, extendedAttr) - AssertNe(nil, minObject) - ExpectFalse(reflect.DeepEqual(*extendedAttr, gcs.ExtendedObjectAttributes{})) - ExpectFalse(reflect.DeepEqual(*minObject, gcs.MinObject{})) + require.NoError(t.T(), err) + require.NotNil(t.T(), extendedAttr) + require.NotNil(t.T(), minObject) + assert.False(t.T(), reflect.DeepEqual(*extendedAttr, gcs.ExtendedObjectAttributes{})) + assert.False(t.T(), reflect.DeepEqual(*minObject, gcs.MinObject{})) } func (t *LocalFileTest) validateNoFileOrDirError(filename string) { _, err := os.Stat(path.Join(mntDir, filename)) - AssertNe(nil, err) - AssertTrue(strings.Contains(err.Error(), "no such file or directory")) + require.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) } //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// -func (t *LocalFileTest) NewFileShouldNotGetSyncedToGCSTillClose() { +func (t *LocalFileTest) TestNewFileShouldNotGetSyncedToGCSTillClose() { t.newFileShouldGetSyncedToGCSAtClose(FileName) } -func (t *LocalFileTest) NewFileUnderExplicitDirectoryShouldNotGetSyncedToGCSTillClose() { +func (t *LocalFileTest) TestNewFileUnderExplicitDirectoryShouldNotGetSyncedToGCSTillClose() { err := os.Mkdir(path.Join(mntDir, "explicit"), dirPerms) - AssertEq(nil, err) + require.NoError(t.T(), err) t.newFileShouldGetSyncedToGCSAtClose("explicit/foo") } -func (t *LocalFileTest) NewFileUnderImplicitDirectoryShouldNotGetSyncedToGCSTillClose() { - AssertEq( - nil, +func (t *LocalFileTest) TestNewFileUnderImplicitDirectoryShouldNotGetSyncedToGCSTillClose() { + require.NoError( + t.T(), t.createObjects( map[string]string{ // File @@ -191,108 +206,108 @@ func (t *LocalFileTest) NewFileUnderImplicitDirectoryShouldNotGetSyncedToGCSTill t.newFileShouldGetSyncedToGCSAtClose("implicitFoo/foo") } -func (t *LocalFileTest) StatOnLocalFile() { +func (t *LocalFileTest) TestStatOnLocalFile() { // Create a local file. var filePath string filePath, t.f1 = t.createLocalFile(FileName) // Stat the local file. fi, err := os.Stat(filePath) - AssertEq(nil, err) - ExpectEq(path.Base(filePath), fi.Name()) - ExpectEq(0, fi.Size()) - ExpectEq(filePerms, fi.Mode()) + require.NoError(t.T(), err) + assert.Equal(t.T(), path.Base(filePath), fi.Name()) + assert.EqualValues(t.T(), 0, fi.Size()) + assert.Equal(t.T(), filePerms, fi.Mode()) // Writing contents to local file shouldn't create file on GCS. _, err = t.f1.Write([]byte(FileContents)) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(FileName) // Stat the local file again to check if new contents are written. fi, err = os.Stat(filePath) - AssertEq(nil, err) - ExpectEq(path.Base(filePath), fi.Name()) - ExpectEq(10, fi.Size()) - ExpectEq(filePerms, fi.Mode()) + require.NoError(t.T(), err) + assert.Equal(t.T(), path.Base(filePath), fi.Name()) + assert.EqualValues(t.T(), 10, fi.Size()) + assert.Equal(t.T(), filePerms, fi.Mode()) // Close the file and validate if the file is created on GCS. t.closeFileAndValidateObjectContents(&t.f1, FileName, FileContents) } -func (t *LocalFileTest) StatOnLocalFileWithConflictingFileNameSuffix() { +func (t *LocalFileTest) TestStatOnLocalFileWithConflictingFileNameSuffix() { // Create a local file. var filePath string filePath, t.f1 = t.createLocalFile(FileName) // Stat the local file. fi, err := os.Stat(filePath + inode.ConflictingFileNameSuffix) - AssertEq(nil, err) - ExpectEq(path.Base(filePath)+inode.ConflictingFileNameSuffix, fi.Name()) - ExpectEq(0, fi.Size()) - ExpectEq(filePerms, fi.Mode()) + require.NoError(t.T(), err) + assert.Equal(t.T(), path.Base(filePath)+inode.ConflictingFileNameSuffix, fi.Name()) + assert.EqualValues(t.T(), 0, fi.Size()) + assert.Equal(t.T(), filePerms, fi.Mode()) // Close the file and validate if the file is created on GCS. t.closeFileAndValidateObjectContents(&t.f1, FileName, "") } -func (t *LocalFileTest) StatOnUnlinkedLocalFile() { +func (t *LocalFileTest) TestStatOnUnlinkedLocalFile() { // Create a local file. var filePath string filePath, t.f1 = t.createLocalFile(FileName) // unlink the local file. err := os.Remove(filePath) - AssertEq(nil, err) + require.NoError(t.T(), err) // Stat the local file and validate error. t.validateNoFileOrDirError(FileName) // Close the file and validate that file is not created on GCS. err = t.closeLocalFile(&t.f1) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(FileName) } -func (t *LocalFileTest) TruncateLocalFile() { +func (t *LocalFileTest) TestTruncateLocalFile() { // Create a local file. var filePath string filePath, t.f1 = t.createLocalFile(FileName) // Writing contents to local file . _, err := t.f1.Write([]byte(FileContents)) - AssertEq(nil, err) + require.NoError(t.T(), err) // Stat the file to validate if new contents are written. fi, err := os.Stat(filePath) - AssertEq(nil, err) - ExpectEq(path.Base(filePath), fi.Name()) - ExpectEq(10, fi.Size()) - ExpectEq(filePerms, fi.Mode()) + require.NoError(t.T(), err) + assert.Equal(t.T(), path.Base(filePath), fi.Name()) + assert.EqualValues(t.T(), 10, fi.Size()) + assert.Equal(t.T(), filePerms, fi.Mode()) // Truncate the file to update the file size. err = os.Truncate(filePath, 5) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(FileName) // Stat the file to validate if file is truncated correctly. fi, err = os.Stat(filePath) - AssertEq(nil, err) - ExpectEq(path.Base(filePath), fi.Name()) - ExpectEq(5, fi.Size()) - ExpectEq(filePerms, fi.Mode()) + require.NoError(t.T(), err) + assert.Equal(t.T(), path.Base(filePath), fi.Name()) + assert.EqualValues(t.T(), 5, fi.Size()) + assert.Equal(t.T(), filePerms, fi.Mode()) // Close the file and validate if the file is created on GCS. t.closeFileAndValidateObjectContents(&t.f1, FileName, "tests") } -func (t *LocalFileTest) MultipleWritesToLocalFile() { +func (t *LocalFileTest) TestMultipleWritesToLocalFile() { // Create a local file. _, t.f1 = t.createLocalFile(FileName) // Write some contents to file sequentially. _, err := t.f1.Write([]byte("string1")) - AssertEq(nil, err) + require.NoError(t.T(), err) _, err = t.f1.Write([]byte("string2")) - AssertEq(nil, err) + require.NoError(t.T(), err) _, err = t.f1.Write([]byte("string3")) - AssertEq(nil, err) + require.NoError(t.T(), err) // File shouldn't get created on GCS. t.validateObjectNotFoundErr(FileName) @@ -300,24 +315,24 @@ func (t *LocalFileTest) MultipleWritesToLocalFile() { t.closeFileAndValidateObjectContents(&t.f1, FileName, "string1string2string3") } -func (t *LocalFileTest) RandomWritesToLocalFile() { +func (t *LocalFileTest) TestRandomWritesToLocalFile() { // Create a local file. _, t.f1 = t.createLocalFile(FileName) // Write some contents to file randomly. _, err := t.f1.WriteAt([]byte("string1"), 0) - AssertEq(nil, err) + require.NoError(t.T(), err) _, err = t.f1.WriteAt([]byte("string2"), 2) - AssertEq(nil, err) + require.NoError(t.T(), err) _, err = t.f1.WriteAt([]byte("string3"), 3) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(FileName) // Close the file and validate if the file is created on GCS. t.closeFileAndValidateObjectContents(&t.f1, FileName, "stsstring3") } -func (t *LocalFileTest) TestReadDirWithEmptyLocalFiles() { +func (t *LocalFileTest) TestTestReadDirWithEmptyLocalFiles() { // Create local files. _, t.f1 = t.createLocalFile(FileName) _, t.f2 = t.createLocalFile(FileName2) @@ -326,7 +341,7 @@ func (t *LocalFileTest) TestReadDirWithEmptyLocalFiles() { entries := t.readDirectory(mntDir) // Verify entries received successfully. - AssertEq(2, len(entries)) + require.Equal(t.T(), 2, len(entries)) t.verifyLocalFileEntry(entries[0], FileName, 0) t.verifyLocalFileEntry(entries[1], FileName2, 0) // Close the local files. @@ -338,13 +353,13 @@ func (t *LocalFileTest) TestReadDirWithNonEmptyLocalFile() { // Create local files. _, t.f1 = t.createLocalFile(FileName) _, err := t.f1.WriteString(FileContents) - AssertEq(nil, err) + require.NoError(t.T(), err) // Attempt to list mntDir. entries := t.readDirectory(mntDir) // Verify entries received successfully. - AssertEq(1, len(entries)) + require.Equal(t.T(), 1, len(entries)) t.verifyLocalFileEntry(entries[0], FileName, 10) // Close the local files. t.closeFileAndValidateObjectContents(&t.f1, FileName, FileContents) @@ -352,8 +367,7 @@ func (t *LocalFileTest) TestReadDirWithNonEmptyLocalFile() { func (t *LocalFileTest) TestReadDirForExplicitDirWithLocalFile() { // Create explicit dir with 2 local files. - AssertEq( - nil, + assert.Nil(t.T(), t.createObjects( map[string]string{ "explicitFoo/": "", @@ -365,7 +379,7 @@ func (t *LocalFileTest) TestReadDirForExplicitDirWithLocalFile() { entries := t.readDirectory(path.Join(mntDir, "explicitFoo/")) // Verify entries received successfully. - AssertEq(2, len(entries)) + assert.Equal(t.T(), 2, len(entries)) t.verifyLocalFileEntry(entries[0], FileName, 0) t.verifyLocalFileEntry(entries[1], FileName2, 0) // Close the local files. @@ -375,8 +389,7 @@ func (t *LocalFileTest) TestReadDirForExplicitDirWithLocalFile() { func (t *LocalFileTest) TestReadDirForImplicitDirWithLocalFile() { // Create implicit dir with 2 local files and 1 synced file. - AssertEq( - nil, + require.NoError(t.T(), t.createObjects( map[string]string{ // File @@ -389,7 +402,7 @@ func (t *LocalFileTest) TestReadDirForImplicitDirWithLocalFile() { entries := t.readDirectory(path.Join(mntDir, "implicitFoo/")) // Verify entries received successfully. - AssertEq(3, len(entries)) + require.Equal(t.T(), 3, len(entries)) t.verifyLocalFileEntry(entries[0], "bar", 0) t.verifyLocalFileEntry(entries[1], FileName, 0) t.verifyLocalFileEntry(entries[2], FileName2, 0) @@ -409,8 +422,7 @@ func (t *LocalFileTest) TestRecursiveListingWithLocalFiles() { // - implicitLocalFile --- file // Create implicit dir with 1 local file1 and 1 synced file. - AssertEq( - nil, + require.NoError(t.T(), t.createObjects( map[string]string{ // File @@ -418,8 +430,7 @@ func (t *LocalFileTest) TestRecursiveListingWithLocalFiles() { })) _, t.f1 = t.createLocalFile("implicitFoo/" + implicitLocalFileName) // Create explicit dir with 1 local file. - AssertEq( - nil, + require.NoError(t.T(), t.createObjects( map[string]string{ "explicitFoo/": "", @@ -439,12 +450,12 @@ func (t *LocalFileTest) TestRecursiveListingWithLocalFiles() { } objs, err := os.ReadDir(path) - AssertEq(nil, err) + require.NoError(t.T(), err) // Check if mntDir has correct objects. if path == mntDir { // numberOfObjects = 3 - AssertEq(3, len(objs)) + require.Equal(t.T(), 3, len(objs)) t.verifyDirectoryEntry(objs[0], "explicitFoo") t.verifyLocalFileEntry(objs[1], FileName, 0) t.verifyDirectoryEntry(objs[2], "implicitFoo") @@ -453,14 +464,14 @@ func (t *LocalFileTest) TestRecursiveListingWithLocalFiles() { // Check if mntDir/explicitFoo/ has correct objects. if path == mntDir+"/explicitFoo" { // numberOfObjects = 1 - AssertEq(1, len(objs)) + require.Equal(t.T(), 1, len(objs)) t.verifyLocalFileEntry(objs[0], explicitLocalFileName, 0) } // Check if mntDir/implicitFoo/ has correct objects. if path == mntDir+"/implicitFoo" { // numberOfObjects = 2 - AssertEq(2, len(objs)) + require.Equal(t.T(), 2, len(objs)) t.verifyLocalFileEntry(objs[0], "bar", 0) t.verifyLocalFileEntry(objs[1], implicitLocalFileName, 0) } @@ -468,7 +479,7 @@ func (t *LocalFileTest) TestRecursiveListingWithLocalFiles() { }) // Validate and close the files. - AssertEq(nil, err) + require.NoError(t.T(), err) t.closeFileAndValidateObjectContents(&t.f1, "implicitFoo/"+implicitLocalFileName, "") t.closeFileAndValidateObjectContents(&t.f2, "explicitFoo/"+explicitLocalFileName, "") t.closeFileAndValidateObjectContents(&t.f3, ""+FileName, "") @@ -478,25 +489,24 @@ func (t *LocalFileTest) TestRenameOfLocalFileFails() { // Create local file with some content. _, t.f1 = t.createLocalFile(FileName) _, err := t.f1.WriteString(FileContents) - AssertEq(nil, err) + require.NoError(t.T(), err) // Attempt to rename local file. err = os.Rename(path.Join(mntDir, FileName), path.Join(mntDir, "newName")) // Verify rename operation fails. - AssertNe(nil, err) - AssertTrue(strings.Contains(err.Error(), "operation not supported")) + require.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "operation not supported")) // write more content to local file. _, err = t.f1.WriteString(FileContents) - AssertEq(nil, err) + require.NoError(t.T(), err) // Close the local file. t.closeFileAndValidateObjectContents(&t.f1, FileName, FileContents+FileContents) } func (t *LocalFileTest) TestRenameOfDirectoryWithLocalFileFails() { // Create directory foo. - AssertEq( - nil, + require.NoError(t.T(), t.createObjects( map[string]string{ "foo/": "", @@ -505,17 +515,17 @@ func (t *LocalFileTest) TestRenameOfDirectoryWithLocalFileFails() { // Create local file with some content. _, t.f1 = t.createLocalFile("foo/" + FileName) _, err := t.f1.WriteString(FileContents) - AssertEq(nil, err) + require.NoError(t.T(), err) // Attempt to rename directory containing local file. err = os.Rename(path.Join(mntDir, "foo/"), path.Join(mntDir, "bar/")) // Verify rename operation fails. - AssertNe(nil, err) - AssertTrue(strings.Contains(err.Error(), "operation not supported")) + require.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "operation not supported")) // write more content to local file. _, err = t.f1.WriteString(FileContents) - AssertEq(nil, err) + require.NoError(t.T(), err) // Close the local file. t.closeFileAndValidateObjectContents(&t.f1, "foo/"+FileName, FileContents+FileContents) } @@ -527,7 +537,7 @@ func (t *LocalFileTest) TestRenameOfLocalFileSucceedsAfterSync() { err := os.Rename(path.Join(mntDir, FileName), path.Join(mntDir, "newName")) // Validate. - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectContents("newName", FileContents+FileContents) t.validateObjectNotFoundErr(FileName) } @@ -539,7 +549,7 @@ func (t *LocalFileTest) TestRenameOfDirectoryWithLocalFileSucceedsAfterSync() { err := os.Rename(path.Join(mntDir, "foo/"), path.Join(mntDir, "bar/")) // Validate. - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectContents("bar/"+FileName, FileContents+FileContents) t.validateObjectNotFoundErr("foo/" + FileName) t.validateObjectContents("bar/gcsFile", "") @@ -553,16 +563,16 @@ func (t *LocalFileTest) ReadLocalFile() { // Write some contents to file. contents := "string1string2string3" _, err := t.f1.Write([]byte(contents)) - AssertEq(nil, err) + require.NoError(t.T(), err) // File shouldn't get created on GCS. t.validateObjectNotFoundErr(FileName) // Read the local file contents. buf := make([]byte, len(contents)) n, err := t.f1.ReadAt(buf, 0) - AssertEq(nil, err) - AssertEq(len(contents), n) - AssertEq(contents, string(buf)) + require.NoError(t.T(), err) + assert.Equal(t.T(), len(contents), n) + assert.Equal(t.T(), contents, string(buf)) // Close the file and validate if the file is created on GCS. t.closeFileAndValidateObjectContents(&t.f1, FileName, contents) @@ -576,13 +586,13 @@ func (t *LocalFileTest) TestReadDirContainingUnlinkedLocalFiles() { filepath3, t.f3 = t.createLocalFile(FileName + "3") // Unlink local file 3 err := os.Remove(filepath3) - AssertEq(nil, err) + require.NoError(t.T(), err) // Attempt to list mntDir. entries := t.readDirectory(mntDir) // Verify unlinked entries are not listed. - AssertEq(2, len(entries)) + require.Equal(t.T(), 2, len(entries)) t.verifyLocalFileEntry(entries[0], FileName+"1", 0) t.verifyLocalFileEntry(entries[1], FileName+"2", 0) // Close the local files. @@ -590,87 +600,86 @@ func (t *LocalFileTest) TestReadDirContainingUnlinkedLocalFiles() { t.closeFileAndValidateObjectContents(&t.f2, FileName+"2", "") // Verify unlinked file is not written to GCS err = t.closeLocalFile(&t.f3) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(FileName + "3") } func (t *LocalFileTest) TestUnlinkOfLocalFile() { // Create empty local file. - var filepath string - filepath, t.f1 = t.createLocalFile(FileName) + var filePath string + filePath, t.f1 = t.createLocalFile(FileName) // Attempt to unlink local file. - err := os.Remove(filepath) + err := os.Remove(filePath) // Verify unlink operation succeeds. - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateNoFileOrDirError(FileName) err = t.closeLocalFile(&t.f1) - AssertEq(nil, err) + require.NoError(t.T(), err) // Validate file it is not present on GCS. t.validateObjectNotFoundErr(FileName) } func (t *LocalFileTest) TestWriteOnUnlinkedLocalFileSucceeds() { // Create local file and unlink. - var filepath string - filepath, t.f1 = t.createLocalFile(FileName) - err := os.Remove(filepath) + var filePath string + filePath, t.f1 = t.createLocalFile(FileName) + err := os.Remove(filePath) // Verify unlink operation succeeds. - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateNoFileOrDirError(FileName) // Write to unlinked local file. _, err = t.f1.WriteString(FileContents) - AssertEq(nil, err) + require.NoError(t.T(), err) err = t.closeLocalFile(&t.f1) // Validate flush file does not throw error. - AssertEq(nil, err) + require.NoError(t.T(), err) // Validate unlinked file is not written to GCS t.validateObjectNotFoundErr(FileName) } func (t *LocalFileTest) TestSyncOnUnlinkedLocalFile() { // Create local file. - var filepath string - filepath, t.f1 = t.createLocalFile(FileName) + var filePath string + filePath, t.f1 = t.createLocalFile(FileName) // Attempt to unlink local file. - err := os.Remove(filepath) + err := os.Remove(filePath) // Verify unlink operation succeeds. - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateNoFileOrDirError(FileName) // Validate sync operation does not write to GCS after unlink. err = t.f1.Sync() - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(FileName) // Close the local file and validate it is not present on GCS. err = t.closeLocalFile(&t.f1) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(FileName) } func (t *LocalFileTest) TestUnlinkOfSyncedLocalFile() { // Create local file and sync to GCS. - var filepath string - filepath, t.f1 = t.createLocalFile(FileName) + var filePath string + filePath, t.f1 = t.createLocalFile(FileName) t.closeFileAndValidateObjectContents(&t.f1, FileName, "") // Attempt to unlink synced file. - err := os.Remove(filepath) + err := os.Remove(filePath) // Verify unlink operation succeeds. - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateNoFileOrDirError(FileName) t.validateObjectNotFoundErr(FileName) } func (t *LocalFileTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { // Create explicit directory with one synced and one local file. - AssertEq( - nil, + require.NoError(t.T(), t.createObjects( map[string]string{ // File @@ -683,16 +692,16 @@ func (t *LocalFileTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { err := os.RemoveAll(path.Join(mntDir, "explicit")) // Verify rmDir operation succeeds. - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateNoFileOrDirError("explicit/" + explicitLocalFileName) t.validateNoFileOrDirError("explicit/foo") t.validateNoFileOrDirError("explicit") // Validate writing content to unlinked local file does not throw error _, err = t.f1.WriteString(FileContents) - AssertEq(nil, err) + require.NoError(t.T(), err) // Validate flush file throws IO error and does not create object on GCS err = t.closeLocalFile(&t.f1) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr("explicit/" + explicitLocalFileName) // Validate synced files are also deleted. t.validateObjectNotFoundErr("explicit/foo") @@ -702,7 +711,7 @@ func (t *LocalFileTest) TestRmDirOfDirectoryContainingGCSAndLocalFiles() { func (t *LocalFileTest) TestRmDirOfDirectoryContainingOnlyLocalFiles() { // Create a directory with two local files. err := os.Mkdir(path.Join(mntDir, "explicit"), dirPerms) - AssertEq(nil, err) + require.NoError(t.T(), err) _, t.f1 = t.createLocalFile("explicit/" + explicitLocalFileName) _, t.f2 = t.createLocalFile("explicit/" + FileName) @@ -710,16 +719,16 @@ func (t *LocalFileTest) TestRmDirOfDirectoryContainingOnlyLocalFiles() { err = os.RemoveAll(path.Join(mntDir, "explicit")) // Verify rmDir operation succeeds. - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateNoFileOrDirError("explicit/" + explicitLocalFileName) t.validateNoFileOrDirError("explicit/" + FileName) t.validateNoFileOrDirError("explicit") // Close the local files and validate they are not present on GCS. err = t.closeLocalFile(&t.f1) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr("explicit/" + explicitLocalFileName) err = t.closeLocalFile(&t.f2) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr("explicit/" + FileName) // Validate directory is also deleted. t.validateObjectNotFoundErr("explicit/") @@ -727,8 +736,7 @@ func (t *LocalFileTest) TestRmDirOfDirectoryContainingOnlyLocalFiles() { func (t *LocalFileTest) TestRmDirOfDirectoryContainingOnlyGCSFiles() { // Create explicit directory with one synced and one local file. - AssertEq( - nil, + require.NoError(t.T(), t.createObjects( map[string]string{ // File @@ -741,7 +749,7 @@ func (t *LocalFileTest) TestRmDirOfDirectoryContainingOnlyGCSFiles() { err := os.RemoveAll(path.Join(mntDir, "explicit")) // Verify rmDir operation succeeds. - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateNoFileOrDirError("explicit") t.validateNoFileOrDirError("explicit/foo") t.validateNoFileOrDirError("explicit/bar") @@ -757,21 +765,21 @@ func (t *LocalFileTest) TestCreateSymlinkForLocalFile() { filePath, t.f1 = t.createLocalFile(FileName) // Writing contents to local file shouldn't create file on GCS. _, err := t.f1.Write([]byte(FileContents)) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(FileName) // Create the symlink. symlinkName := path.Join(mntDir, "bar") err = os.Symlink(filePath, symlinkName) - AssertEq(nil, err) + require.NoError(t.T(), err) // Read the link. target, err := os.Readlink(symlinkName) - AssertEq(nil, err) - ExpectEq(filePath, target) + require.NoError(t.T(), err) + assert.Equal(t.T(), filePath, target) contents, err := os.ReadFile(symlinkName) - AssertEq(nil, err) - ExpectEq(FileContents, string(contents)) + require.NoError(t.T(), err) + assert.Equal(t.T(), FileContents, string(contents)) t.closeFileAndValidateObjectContents(&t.f1, FileName, FileContents) } @@ -781,56 +789,56 @@ func (t *LocalFileTest) TestReadSymlinkForDeletedLocalFile() { filePath, t.f1 = t.createLocalFile(FileName) // Writing contents to local file shouldn't create file on GCS. _, err := t.f1.Write([]byte(FileContents)) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(FileName) // Create the symlink. symlinkName := path.Join(mntDir, "bar") err = os.Symlink(filePath, symlinkName) - AssertEq(nil, err) + require.NoError(t.T(), err) // Read the link. target, err := os.Readlink(symlinkName) - AssertEq(nil, err) - ExpectEq(filePath, target) + require.NoError(t.T(), err) + assert.Equal(t.T(), filePath, target) // Remove filePath and then close the fileHandle to avoid syncing to GCS. err = os.Remove(filePath) - AssertEq(nil, err) + require.NoError(t.T(), err) err = t.closeLocalFile(&t.f1) - AssertEq(nil, err) + require.NoError(t.T(), err) t.validateObjectNotFoundErr(FileName) // Reading symlink should fail. _, err = os.Stat(symlinkName) - AssertTrue(strings.Contains(err.Error(), "no such file or directory")) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) } -func (t *LocalFileTest) AtimeMtimeAndCtime() { +func (t *LocalFileTest) TestAtimeMtimeAndCtime() { createTime := mtimeClock.Now() var filePath string // Create a local file. filePath, t.f1 = t.createLocalFile(FileName) var err error fi, err := os.Stat(filePath) - AssertEq(nil, err) + require.NoError(t.T(), err) // Check if mtime is returned correctly for unsynced file. _, _, mtime := fusetesting.GetTimes(fi) - ExpectThat(mtime, timeutil.TimeNear(createTime, Delta)) + assert.WithinDuration(t.T(), createTime, mtime, Delta) // Write some contents. _, err = t.f1.Write([]byte("test contents")) - AssertEq(nil, err) + require.NoError(t.T(), err) // Stat it. fi, err = os.Stat(filePath) - AssertEq(nil, err) + require.NoError(t.T(), err) // We require only that atime and ctime be "reasonable". atime, ctime, mtime := fusetesting.GetTimes(fi) - ExpectThat(mtime, timeutil.TimeNear(createTime, Delta)) - ExpectThat(atime, timeutil.TimeNear(createTime, Delta)) - ExpectThat(ctime, timeutil.TimeNear(createTime, Delta)) + assert.WithinDuration(t.T(), createTime, mtime, Delta) + assert.WithinDuration(t.T(), createTime, atime, Delta) + assert.WithinDuration(t.T(), createTime, ctime, Delta) } // Create local file inside - test.txt @@ -841,21 +849,21 @@ func (t *LocalFileTest) AtimeMtimeAndCtime() { func (t *LocalFileTest) TestStatLocalFileAfterRecreatingItWithSameName() { filePath := path.Join(mntDir, "test.txt") f1, err := os.Create(filePath) - defer AssertEq(nil, f1.Close()) - AssertEq(nil, err) + defer require.NoError(t.T(), f1.Close()) + require.NoError(t.T(), err) _, err = os.Stat(filePath) - AssertEq(nil, err) + require.NoError(t.T(), err) err = os.Remove(filePath) - AssertEq(nil, err) + require.NoError(t.T(), err) f2, err := os.Create(filePath) - AssertEq(nil, err) - defer AssertEq(nil, f2.Close()) + require.NoError(t.T(), err) + defer require.NoError(t.T(), f2.Close()) f, err := os.Stat(filePath) - AssertEq(nil, err) - ExpectEq("test.txt", f.Name()) - ExpectFalse(f.IsDir()) + require.NoError(t.T(), err) + assert.Equal(t.T(), "test.txt", f.Name()) + assert.False(t.T(), f.IsDir()) } func (t *LocalFileTest) TestStatFailsOnNewFileAfterDeletion() { @@ -871,11 +879,11 @@ func (t *LocalFileTest) TestStatFailsOnNewFileAfterDeletion() { t.serverCfg.MetricHandle = common.NewNoopMetrics() filePath := path.Join(mntDir, "test.txt") f1, err := os.Create(filePath) - AssertEq(nil, err) - defer AssertEq(nil, f1.Close()) - AssertEq(nil, os.Remove(filePath)) + require.NoError(t.T(), err) + defer assert.Equal(t.T(), nil, f1.Close()) + assert.Equal(t.T(), nil, os.Remove(filePath)) _, err = os.Stat(filePath) - AssertNe(nil, err) + require.Error(t.T(), err) } From de0b9590250eaf7e51ff92f76bccf14df8bb022d Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 14 May 2025 07:00:05 +0000 Subject: [PATCH 0425/1298] [testing-on-gke] Add support to output throughput in MB/s (#3293) * Rename MB/s to MiB/s wherever appropriate * minor code simplification in fio output metric parsing * add througput_mb_per_second * print record only on failure --- .../examples/dlio/parse_logs.py | 4 +- .../testing_on_gke/examples/fio/parse_logs.py | 101 ++++++++++-------- 2 files changed, 58 insertions(+), 47 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py index e8b51730ad..d76fca9e45 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py @@ -196,10 +196,10 @@ def write_records_to_csv_output_file(output: dict, output_file_path: str): with open(output_file_path, "a") as output_file: # Write a new header row. output_file.write( - "File Size,File #,Total Size (GB),Batch Size,Scenario,Epoch,Duration" + "File Size,File #,Total Size (GiB),Batch Size,Scenario,Epoch,Duration" " (s),GPU Utilization (%),Throughput (sample/s),Throughput" " (MB/s),Throughput over Local SSD (%),GCSFuse Lowest Memory" - " (MB),GCSFuse Highest Memory (MB),GCSFuse Lowest CPU (core),GCSFuse" + " (MiB),GCSFuse Highest Memory (MiB),GCSFuse Lowest CPU (core),GCSFuse" " Highest CPU (core),Pod,Start,End,GcsfuseMountOptions,InstanceID\n" ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py index b24e83f2a1..bfadbdfb26 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py @@ -38,7 +38,9 @@ "scenario": "", "duration": 0, "IOPS": 0, - "throughput_mb_per_second": 0, + "throughput_mib_per_second": 0.0, # in 1024^2 bytes/second + "throughput_bytes_per_second": 0.0, + "throughput_mb_per_second": 0.0, # output metric in 10^6 bytes/second "throughput_over_local_ssd": 0, "start_epoch": "", "end_epoch": "", @@ -203,7 +205,9 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: global_options = per_epoch_output_data["global options"] nrfiles = int(global_options["nrfiles"]) numjobs = int(global_options["numjobs"]) - bs = per_epoch_output_data["jobs"][0]["job options"]["bs"] + job0 = per_epoch_output_data["jobs"][0] + + bs = job0["job options"]["bs"] # If the record for this key has not been added, create a new entry # for it. @@ -217,44 +221,49 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: }, } + job0_read_metrics = job0["read"] + bs = job0["job options"]["bs"] + # Create a record for this key. r = record.copy() - bs = per_epoch_output_data["jobs"][0]["job options"]["bs"] - r["pod_name"] = pod_name - r["epoch"] = epoch - r["scenario"] = scenario - r["duration"] = int( - per_epoch_output_data["jobs"][0]["read"]["runtime"] / 1000 - ) - r["IOPS"] = int(per_epoch_output_data["jobs"][0]["read"]["iops"]) - r["throughput_mb_per_second"] = int( - per_epoch_output_data["jobs"][0]["read"]["bw_bytes"] / (1024**2) - ) - r["start_epoch"] = per_epoch_output_data["jobs"][0]["job_start"] // 1000 - r["end_epoch"] = per_epoch_output_data["timestamp_ms"] // 1000 - r["start"] = unix_to_timestamp( - per_epoch_output_data["jobs"][0]["job_start"] - ) - r["end"] = unix_to_timestamp(per_epoch_output_data["timestamp_ms"]) - - fetch_cpu_memory_data(args=args, record=r) - - r["gcsfuse_mount_options"] = gcsfuse_mount_options - r["bucket_name"] = bucket_name - r["machine_type"] = machine_type - r["blockSize"] = bs - r["filesPerThread"] = nrfiles - r["numThreads"] = numjobs - clat_ns = per_epoch_output_data["jobs"][0]["read"]["clat_ns"] - r["e2e_latency_ns_max"] = clat_ns["max"] - clat_ns_percentile = clat_ns["percentile"] - r["e2e_latency_ns_p50"] = clat_ns_percentile["50.000000"] - r["e2e_latency_ns_p90"] = clat_ns_percentile["90.000000"] - r["e2e_latency_ns_p99"] = clat_ns_percentile["99.000000"] - r["e2e_latency_ns_p99.9"] = clat_ns_percentile["99.900000"] - - # This print is for debugging in case something goes wrong. - pprint.pprint(r) + try: + r["pod_name"] = pod_name + r["epoch"] = epoch + r["scenario"] = scenario + r["duration"] = int(job0_read_metrics["runtime"] / 1000) + r["IOPS"] = int(job0_read_metrics["iops"]) + r["throughput_bytes_per_second"] = job0_read_metrics["bw_bytes"] + r["throughput_mib_per_second"] = round( + (r["throughput_bytes_per_second"] / (1024**2)), 2 + ) + r["throughput_mb_per_second"] = round( + (r["throughput_bytes_per_second"] / 1e6), 2 + ) + r["start_epoch"] = job0["job_start"] // 1000 + r["end_epoch"] = per_epoch_output_data["timestamp_ms"] // 1000 + r["start"] = unix_to_timestamp(job0["job_start"]) + r["end"] = unix_to_timestamp(per_epoch_output_data["timestamp_ms"]) + + fetch_cpu_memory_data(args=args, record=r) + + r["gcsfuse_mount_options"] = gcsfuse_mount_options + r["bucket_name"] = bucket_name + r["machine_type"] = machine_type + r["blockSize"] = bs + r["filesPerThread"] = nrfiles + r["numThreads"] = numjobs + clat_ns = job0_read_metrics["clat_ns"] + r["e2e_latency_ns_max"] = clat_ns["max"] + clat_ns_percentile = clat_ns["percentile"] + r["e2e_latency_ns_p50"] = clat_ns_percentile["50.000000"] + r["e2e_latency_ns_p90"] = clat_ns_percentile["90.000000"] + r["e2e_latency_ns_p99"] = clat_ns_percentile["99.000000"] + r["e2e_latency_ns_p99.9"] = clat_ns_percentile["99.900000"] + except Exception as e: + print(f"Failed to create following record with error: {e}") + # This print is for debugging in case something goes wrong. + pprint.pprint(r) + continue # If a slot for record for this particular epoch has not been created yet, # append enough empty records to make a slot. @@ -272,13 +281,14 @@ def write_records_to_csv_output_file(output: dict, output_file_path: str): # Write a new header. output_file_fwr.write( "File Size,Read Type,Scenario,Epoch,Duration" - " (s),Throughput (MB/s),IOPS,Throughput over Local SSD (%),GCSFuse" + " (s),Throughput (MiB/s),IOPS,Throughput over Local SSD (%),GCSFuse" " Lowest" - " Memory (MB),GCSFuse Highest Memory (MB),GCSFuse Lowest CPU" + " Memory (MiB),GCSFuse Highest Memory (MiB),GCSFuse Lowest CPU" " (core),GCSFuse Highest CPU" " (core),Pod,Start,End,GcsfuseMoutOptions,BlockSize,FilesPerThread,NumThreads,InstanceID," "e2e_latency_ns_max,e2e_latency_ns_p50,e2e_latency_ns_p90,e2e_latency_ns_p99,e2e_latency_ns_p99.9," - "bucket_name,machine_type" # + "bucket_name,machine_type," # + "Throughput (MB/s)" "\n", ) @@ -299,9 +309,9 @@ def write_records_to_csv_output_file(output: dict, output_file_path: str): == len(record_set["records"][scenario]) ): r["throughput_over_local_ssd"] = round( - r["throughput_mb_per_second"] + r["throughput_mib_per_second"] / record_set["records"]["local-ssd"][i][ - "throughput_mb_per_second" + "throughput_mib_per_second" ] * 100, 2, @@ -317,12 +327,13 @@ def write_records_to_csv_output_file(output: dict, output_file_path: str): continue output_file_fwr.write( - f"{record_set['mean_file_size']},{record_set['read_type']},{scenario},{r['epoch']},{r['duration']},{r['throughput_mb_per_second']},{r['IOPS']},{r['throughput_over_local_ssd']},{r['lowest_memory']},{r['highest_memory']},{r['lowest_cpu']},{r['highest_cpu']},{r['pod_name']},{r['start']},{r['end']},\"{r['gcsfuse_mount_options']}\",{r['blockSize']},{r['filesPerThread']},{r['numThreads']},{args.instance_id}," + f"{record_set['mean_file_size']},{record_set['read_type']},{scenario},{r['epoch']},{r['duration']},{r['throughput_mib_per_second']},{r['IOPS']},{r['throughput_over_local_ssd']},{r['lowest_memory']},{r['highest_memory']},{r['lowest_cpu']},{r['highest_cpu']},{r['pod_name']},{r['start']},{r['end']},\"{r['gcsfuse_mount_options']}\",{r['blockSize']},{r['filesPerThread']},{r['numThreads']},{args.instance_id}," ) output_file_fwr.write( f"{r['e2e_latency_ns_max']},{r['e2e_latency_ns_p50']},{r['e2e_latency_ns_p90']},{r['e2e_latency_ns_p99']},{r['e2e_latency_ns_p99.9']}," ) - output_file_fwr.write(f"{r['bucket_name']},{r['machine_type']}\n") + output_file_fwr.write(f"{r['bucket_name']},{r['machine_type']},") + output_file_fwr.write(f"{r['throughput_mb_per_second']}\n") output_file_fwr.close() print( From 8fdbfff5bce291a02fdd7c8ad98c955bf92842ed Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Wed, 14 May 2025 14:10:17 +0530 Subject: [PATCH 0426/1298] Extract file open mode from OpenFileOp (#3259) * support file open mode semantics * util files * comment descriptions * adding license header * upgrading jacobsa/fuse dep * corrected modes grouping and other fixes * rephrasing --- go.mod | 4 ++-- go.sum | 4 ++++ internal/fs/fs.go | 6 +++-- internal/fs/handle/file.go | 15 ++++++------ internal/util/file_util.go | 49 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 internal/util/file_util.go diff --git a/go.mod b/go.mod index af4c4b05e2..926fa47e0b 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.1 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec - github.com/jacobsa/fuse v0.0.0-20250228153609-219b4762b40e + github.com/jacobsa/fuse v0.0.0-20250506064942-d10bfe4fb08e github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 @@ -41,7 +41,7 @@ require ( golang.org/x/net v0.39.0 golang.org/x/oauth2 v0.29.0 golang.org/x/sync v0.13.0 - golang.org/x/sys v0.32.0 + golang.org/x/sys v0.33.0 golang.org/x/text v0.24.0 golang.org/x/time v0.11.0 google.golang.org/api v0.229.0 diff --git a/go.sum b/go.sum index 5096761592..a966839e3e 100644 --- a/go.sum +++ b/go.sum @@ -132,6 +132,8 @@ github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtE github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= github.com/jacobsa/fuse v0.0.0-20250228153609-219b4762b40e h1:1k0eO4yveJsWQDzjA7YbnqDfT631ld5wGfr9uPN15N0= github.com/jacobsa/fuse v0.0.0-20250228153609-219b4762b40e/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= +github.com/jacobsa/fuse v0.0.0-20250506064942-d10bfe4fb08e h1:l3GV2yC45OcDO/ErTSS/LnLQdytjUSmmBFcQCjWSUDY= +github.com/jacobsa/fuse v0.0.0-20250506064942-d10bfe4fb08e/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= @@ -276,6 +278,8 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= diff --git a/internal/fs/fs.go b/internal/fs/fs.go index dfc0ecc47f..0b10f46bc4 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1839,7 +1839,7 @@ func (fs *fileSystem) CreateFile( fs.nextHandleID++ // Creating new file is always a write operation, hence passing readOnly as false. - fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, false, &fs.newConfig.Read) + fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, util.Write, &fs.newConfig.Read) op.Handle = handleID fs.mu.Unlock() @@ -2561,7 +2561,9 @@ func (fs *fileSystem) OpenFile( handleID := fs.nextHandleID fs.nextHandleID++ - fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, op.OpenFlags.IsReadOnly(), &fs.newConfig.Read) + // Figure out the mode in which the file is being opened. + openMode := util.FileOpenMode(op) + fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, openMode, &fs.newConfig.Read) op.Handle = handleID // When we observe object generations that we didn't create, we assign them diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index fc6584b6eb..c4ee321ed0 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -24,6 +24,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/jacobsa/syncutil" "golang.org/x/net/context" ) @@ -49,27 +50,25 @@ type FileHandle struct { // will be downloaded for random reads as well too. cacheFileForRangeRead bool metricHandle common.MetricHandle - // For now, we will consider the files which are open in append mode also as write, - // as we are not doing anything special for append. When required we will - // define an enum instead of boolean to hold the type of open. - readOnly bool + // openMode is used to store the mode in which the file is opened. + openMode util.OpenMode // Read related mounting configuration. readConfig *cfg.ReadConfig } // LOCKS_REQUIRED(fh.inode.mu) -func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle, readOnly bool, rc *cfg.ReadConfig) (fh *FileHandle) { +func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle, openMode util.OpenMode, rc *cfg.ReadConfig) (fh *FileHandle) { fh = &FileHandle{ inode: inode, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, metricHandle: metricHandle, - readOnly: readOnly, + openMode: openMode, readConfig: rc, } - fh.inode.RegisterFileHandle(fh.readOnly) + fh.inode.RegisterFileHandle(fh.openMode == util.Read) fh.mu = syncutil.NewInvariantMutex(fh.checkInvariants) return @@ -83,7 +82,7 @@ func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, func (fh *FileHandle) Destroy() { // Deregister the fileHandle with the inode. fh.inode.Lock() - fh.inode.DeRegisterFileHandle(fh.readOnly) + fh.inode.DeRegisterFileHandle(fh.openMode == util.Read) fh.inode.Unlock() if fh.reader != nil { fh.reader.Destroy() diff --git a/internal/util/file_util.go b/internal/util/file_util.go new file mode 100644 index 0000000000..ce34348afd --- /dev/null +++ b/internal/util/file_util.go @@ -0,0 +1,49 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import ( + "github.com/jacobsa/fuse/fuseops" +) + +// OpenMode represents the file access mode. +type OpenMode int + +// Available file open modes. +const ( + Read OpenMode = iota + Write + Append +) + +// FileOpenMode analyzes the open flags to determine the file's open mode. +// It returns Read, Write, or Append. +// +// GCSFuse needs to distinguish between the append mode, readonly mode and modes where +// writes are supported. +// read vs write is required to initialize the writeHandle count currently in fileHandle. +// The main difference of r vs (r+, w, w+) is the support for reads which is +// implicitly handled by kernel. Hence combining r+, w, w+ as writes. +// Same goes for a vs a+, hence grouped as append. +func FileOpenMode(op *fuseops.OpenFileOp) OpenMode { + switch { + case op.OpenFlags.IsAppend(): + return Append + case op.OpenFlags.IsReadOnly(): + return Read + default: + return Write + } +} From 783e195a4402e4b6d755f62a85f9f721c1e1cb0f Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Wed, 14 May 2025 15:58:20 +0530 Subject: [PATCH 0427/1298] Adding warning log for unexpected EOFs while reading (#3294) * adding log for unexpected EOFs * nit --- internal/fs/handle/file.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index c4ee321ed0..d92ce9c28e 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -24,6 +24,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/jacobsa/syncutil" "golang.org/x/net/context" @@ -139,7 +140,10 @@ func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequen objectData, err = fh.reader.ReadAt(ctx, dst, offset) switch { case errors.Is(err, io.EOF): - err = io.EOF + if err != io.EOF { + logger.Warnf("Unexpected EOF error encountered while reading, err: %v type: %T ", err, err) + err = io.EOF + } return case err != nil: From 7525cb76a78c7917770d7b759734bc3a2a940ea5 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 14 May 2025 10:54:46 +0000 Subject: [PATCH 0428/1298] run flush anf finalize writer tests in prefix bucket (#3301) --- internal/gcsx/prefix_bucket_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/gcsx/prefix_bucket_test.go b/internal/gcsx/prefix_bucket_test.go index 879c93a826..60105a64fd 100644 --- a/internal/gcsx/prefix_bucket_test.go +++ b/internal/gcsx/prefix_bucket_test.go @@ -308,7 +308,7 @@ func (t *PrefixBucketTest) Test_CreateObject() { assert.Equal(t.T(), contents, string(actual)) } -func (t *PrefixBucketTest) CreateObjectChunkWriterAndFinalizeUpload() { +func (t *PrefixBucketTest) TestCreateObjectChunkWriterAndFinalizeUpload() { var err error suffix := "taco" content := []byte("foobar") @@ -337,7 +337,7 @@ func (t *PrefixBucketTest) CreateObjectChunkWriterAndFinalizeUpload() { assert.Equal(t.T(), string(content), string(actual)) } -func (t *PrefixBucketTest) CreateObjectChunkWriterAndFlushPendingWrites() { +func (t *PrefixBucketTest) TestCreateObjectChunkWriterAndFlushPendingWrites() { var err error suffix := "taco" content := []byte("foobar") @@ -354,10 +354,10 @@ func (t *PrefixBucketTest) CreateObjectChunkWriterAndFlushPendingWrites() { assert.NoError(t.T(), err) _, err = w.Write(content) assert.NoError(t.T(), err) - offset, err := t.bucket.FlushPendingWrites(t.ctx, w) + o, err := t.bucket.FlushPendingWrites(t.ctx, w) assert.NoError(t.T(), err) - assert.Equal(t.T(), int64(len(content)), offset) + assert.EqualValues(t.T(), int64(len(content)), o.Size) // Read it through the back door. actual, err := storageutil.ReadObject(t.ctx, t.wrapped, t.prefix+suffix) assert.Equal(t.T(), nil, err) From 4c4800860a1b63d3659c60c25ff9f3deb8e090e5 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 15 May 2025 11:29:37 +0530 Subject: [PATCH 0429/1298] Create helper functions for micro benchmarks (#3303) * adding helper function * remove create table * remove pyche files * add comments * add more unit tests --- .../scripts/micro_benchmarks/helper.py | 126 ++++++++++++++++++ .../scripts/micro_benchmarks/helper_test.py | 95 +++++++++++++ .../scripts/micro_benchmarks/requirements.txt | 5 + 3 files changed, 226 insertions(+) create mode 100644 perfmetrics/scripts/micro_benchmarks/helper.py create mode 100644 perfmetrics/scripts/micro_benchmarks/helper_test.py create mode 100644 perfmetrics/scripts/micro_benchmarks/requirements.txt diff --git a/perfmetrics/scripts/micro_benchmarks/helper.py b/perfmetrics/scripts/micro_benchmarks/helper.py new file mode 100644 index 0000000000..3036f684df --- /dev/null +++ b/perfmetrics/scripts/micro_benchmarks/helper.py @@ -0,0 +1,126 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from datetime import datetime, timezone +from google.cloud import bigquery +import os +import subprocess +import pandas as pd + +PROJECT_ID = "gcs-fuse-test-ml" +DATASET_ID = "benchmark_results" +TABLE_ID = "gcsfuse_benchmarks" + +def mount_bucket(mount_dir: str, bucket_name: str, flags: str) -> bool: + """ + Mounts a Google Cloud Storage (GCS) bucket using gcsfuse. + + This function attempts to create the mount directory (if it doesn't exist), + then runs the `gcsfuse` command to mount the specified GCS bucket using the provided flags. + + Args: + mount_dir (str): The local directory where the GCS bucket should be mounted. + bucket_name (str): The name of the GCS bucket to mount. + flags (str): Additional flags or options to pass to the `gcsfuse` command + (e.g., "--implicit-dirs"). + + Returns: + bool: + - True if the mount operation succeeded. + - False if the `gcsfuse` command failed (e.g., due to permissions, bucket issues, etc.). + + Prints: + Status messages indicating success or failure of the mount operation. + """ + os.makedirs(mount_dir, exist_ok=True) + cmd = f"gcsfuse {flags} {bucket_name} {mount_dir}" + print(f"Mounting: {cmd}") + try: + subprocess.run(cmd, shell=True, check=True) + print(f"Successfully mounted {bucket_name} at {mount_dir}") + return True + except subprocess.CalledProcessError as e: + print(f"Failed to mount {bucket_name} at {mount_dir}: {e}") + return False + + +def unmount_gcs_directory(mount_point: str) -> bool: + """ + Unmounts a GCS bucket that was mounted with gcsfuse. + + Args: + mount_point (str): The local mount point directory. + + Prints: + Success or failure message based on the unmount operation. + + Returns: + bool: + - True if the mount operation succeeded. + - False if the `fusermount` command failed. + """ + try: + subprocess.run(["fusermount", "-u", mount_point], check=True) + print(f"Successfully unmounted {mount_point}") + return True + except subprocess.CalledProcessError as e: + print(f"Failed to unmount {mount_point}: {e}. Ensure the directory is correctly mounted.") + return False + + +def log_to_bigquery(duration_sec: float, total_bytes: int, gcsfuse_config: str, workload_type: str) -> None: + """Logs performance metrics to a BigQuery table. + + This function calculates bandwidth, creates a pandas DataFrame with the + provided data, converts the data to the appropriate types, and then inserts + the data into the specified BigQuery table. If the table does not exist, + this query can be used to create it: + + CREATE TABLE `your-project-id.benchmark_results.gcsfuse_benchmarks` ( + timestamp TIMESTAMP, + duration_seconds FLOAT64, + bandwidth_mbps FLOAT64, + gcsfuse_config STRING, + workload_type STRING + ); + + Args: + duration_sec (float): Duration of the operation in seconds. + total_bytes (int): Total data processed in bytes. + gcsfuse_config (str): Configuration flags used with gcsfuse. + workload_type (str): Type of workload (e.g., "read", "write"). + + Prints: + Performance metrics and confirmation of successful logging. + """ + bandwidth_mbps = total_bytes / duration_sec / 1000 / 1000 + print(f"Duration: {duration_sec:.2f}s | Data: {total_bytes / (1000 ** 3):.2f} GB | Bandwidth: {bandwidth_mbps:.2f} MB/s") + + client = bigquery.Client(project=PROJECT_ID) + table_ref = client.dataset(DATASET_ID).table(TABLE_ID) + + df = pd.DataFrame([{ + "timestamp": datetime.now(timezone.utc), + "duration_seconds": duration_sec, + "bandwidth_mbps": bandwidth_mbps, + "gcsfuse_config": gcsfuse_config, + "workload_type": workload_type, + }]) + + df['timestamp'] = pd.to_datetime(df['timestamp']) + df['duration_seconds'] = df['duration_seconds'].astype(float) + df['bandwidth_mbps'] = df['bandwidth_mbps'].astype(float) + + client.load_table_from_dataframe(df, table_ref).result() + print("Successfully logged data to BigQuery.") diff --git a/perfmetrics/scripts/micro_benchmarks/helper_test.py b/perfmetrics/scripts/micro_benchmarks/helper_test.py new file mode 100644 index 0000000000..2ebda8beb8 --- /dev/null +++ b/perfmetrics/scripts/micro_benchmarks/helper_test.py @@ -0,0 +1,95 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +from unittest.mock import patch, MagicMock +import helper +import subprocess + +class TestHelperFunctions(unittest.TestCase): + @patch("helper.subprocess.run") + @patch("helper.os.makedirs") + def test_mount_bucket_success(self, mock_makedirs, mock_run): + result = helper.mount_bucket("/mnt/success", "bucket", "--flags") + + self.assertTrue(result) + + @patch("helper.subprocess.run", side_effect=subprocess.CalledProcessError(1, "gcsfuse")) + @patch("helper.os.makedirs") + def test_mount_bucket_failure(self, mock_makedirs, mock_run): + result = helper.mount_bucket("/mnt/fail", "bucket", "--flags") + + self.assertFalse(result) + + @patch('subprocess.run') + def test_unmount_success(self, mock_run): + mount_point = "/mnt/gcs_test" + mock_run.return_value = subprocess.CompletedProcess(args=["fusermount", "-u", mount_point], returncode=0) + + result = helper.unmount_gcs_directory(mount_point) + + mock_run.assert_called_once_with(["fusermount", "-u", mount_point], check=True) + self.assertTrue(result) + + @patch('subprocess.run', side_effect=subprocess.CalledProcessError(1, ["fusermount", "-u", "/mnt/gcs_test"])) + def test_unmount_failure(self, mock_run): + mount_point = "/mnt/gcs_test" + + result = helper.unmount_gcs_directory(mount_point) + + mock_run.assert_called_once_with(["fusermount", "-u", mount_point], check=True) + self.assertFalse(result) + + @patch("helper.bigquery.Client") # Only patch bigquery.Client + def test_log_to_bigquery(self, mock_bq_client): + duration = 10 + total_bytes = 100 * 1000 * 1000 # 100 MB + flags = "--implicit-dirs" + workload_type = "write" + # Setup BigQuery mock + mock_bq_instance = MagicMock() + mock_bq_client.return_value = mock_bq_instance + mock_dataset = mock_bq_instance.dataset.return_value + mock_bq_instance.load_table_from_dataframe.return_value.result.return_value = None + + helper.log_to_bigquery(duration, total_bytes, flags, workload_type) + + mock_bq_instance.dataset.assert_called_with("benchmark_results") + mock_dataset.table.assert_called_with("gcsfuse_benchmarks") + mock_bq_instance.load_table_from_dataframe.assert_called_once() + mock_bq_instance.load_table_from_dataframe().result.assert_called_once() + + @patch("helper.bigquery.Client") + def test_log_to_bigquery_failure(self, mock_client_cls): + # Create a mock client and a mock load job + mock_client = MagicMock() + mock_load_job = MagicMock() + mock_client.load_table_from_dataframe.return_value = mock_load_job + # Simulate failure on .result() + mock_load_job.result.side_effect = Exception("BigQuery load failed") + # Assign our mock client + mock_client_cls.return_value = mock_client + + with self.assertRaises(Exception) as context: + helper.log_to_bigquery( + duration_sec=10.0, + total_bytes=100_000_000, + gcsfuse_config="--implicit-dirs", + workload_type="read" + ) + + self.assertIn("BigQuery load failed", str(context.exception)) + +if __name__ == "__main__": + unittest.main() diff --git a/perfmetrics/scripts/micro_benchmarks/requirements.txt b/perfmetrics/scripts/micro_benchmarks/requirements.txt new file mode 100644 index 0000000000..3b02d76a02 --- /dev/null +++ b/perfmetrics/scripts/micro_benchmarks/requirements.txt @@ -0,0 +1,5 @@ +pandas +google-cloud-bigquery +google-cloud-storage +pyarrow +pandas_gbq From 2d3f6e33224024fad12fbae834721d4fe23d7b38 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 15 May 2025 09:51:26 +0000 Subject: [PATCH 0430/1298] [testing-on-gke] Switch from instance_id to experiment_id everywhere for consistency. (#3302) * s/instance id/experiment id/g * handle if user passes instance_id use experiment_id=instance_id if user did not pass experiment_id too * simplify deprecation message --- .../examples/dlio/dlio_workload.py | 6 +- .../examples/dlio/parse_logs.py | 34 +++++----- .../testing_on_gke/examples/dlio/run_tests.py | 8 +-- .../examples/fio/fio_workload.py | 6 +- .../testing_on_gke/examples/fio/parse_logs.py | 38 ++++++----- .../testing_on_gke/examples/fio/run_tests.py | 8 +-- .../testing_on_gke/examples/run-automated.sh | 10 +-- .../testing_on_gke/examples/run-gke-tests.sh | 68 ++++++++++++------- .../examples/utils/parse_logs_common.py | 2 +- .../examples/utils/run_tests_common.py | 4 +- 10 files changed, 103 insertions(+), 81 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py index 89e56bd465..9c368e6b02 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/dlio_workload.py @@ -182,7 +182,7 @@ def parse_test_config_for_dlio_workloads(testConfigFileName: str): def DlioChartNamePodName( - dlioWorkload: DlioWorkload, instanceID: str, batchSize: int + dlioWorkload: DlioWorkload, experimentID: str, batchSize: int ) -> (str, str, str): shortenScenario = { 'local-ssd': 'ssd', @@ -194,11 +194,11 @@ def DlioChartNamePodName( else 'other' ) - hashOfWorkload = str(hash((instanceID, batchSize, dlioWorkload))).replace( + hashOfWorkload = str(hash((experimentID, batchSize, dlioWorkload))).replace( '-', '' ) return ( f'dlio-unet3d-{shortForScenario}-{dlioWorkload.recordLength}-{hashOfWorkload}', f'dlio-tester-{shortForScenario}-{dlioWorkload.recordLength}-{hashOfWorkload}', - f'{instanceID}/{dlioWorkload.numFilesTrain}-{dlioWorkload.recordLength}-{batchSize}-{hashOfWorkload}/{dlioWorkload.scenario}', + f'{experimentID}/{dlioWorkload.numFilesTrain}-{dlioWorkload.recordLength}-{batchSize}-{hashOfWorkload}/{dlioWorkload.scenario}', ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py index d76fca9e45..697c272d4f 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py @@ -52,23 +52,23 @@ } -def download_dlio_outputs(dlioWorkloads: set, instanceId: str) -> int: - """Downloads instanceId-specific dlio outputs for each dlioWorkload locally. +def download_dlio_outputs(dlioWorkloads: set, experimentID: str) -> int: + """Downloads experimentID-specific dlio outputs for each dlioWorkload locally. Outputs in the bucket are in the following object naming format (details in ./unet3d-loading-test/templates/dlio-tester.yaml). - gs:///logs//---//per_epoch_stats.json - gs:///logs//---//summary.json - gs:///logs//---/gcsfuse-generic/gcsfuse_mount_options + gs:///logs//---//per_epoch_stats.json + gs:///logs//---//summary.json + gs:///logs//---/gcsfuse-generic/gcsfuse_mount_options These are downloaded locally as: - <_LOCAL_LOGS_LOCATION>//---//per_epoch_stats.json - <_LOCAL_LOGS_LOCATION>//---//summary.json - <_LOCAL_LOGS_LOCATION>//---/gcsfuse-generic/gcsfuse_mount_options + <_LOCAL_LOGS_LOCATION>//---//per_epoch_stats.json + <_LOCAL_LOGS_LOCATION>//---//summary.json + <_LOCAL_LOGS_LOCATION>//---/gcsfuse-generic/gcsfuse_mount_options """ for dlioWorkload in dlioWorkloads: - srcObjects = f"gs://{dlioWorkload.bucket}/logs/{instanceId}" + srcObjects = f"gs://{dlioWorkload.bucket}/logs/{experimentID}" print(f"Downloading DLIO logs from the {srcObjects} ...") returncode, errorStr = download_gcs_objects( srcObjects, _LOCAL_LOGS_LOCATION @@ -85,9 +85,9 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: The following creates a dict called 'output' from the downloaded dlio output files, which are in the following format. - <_LOCAL_LOGS_LOCATION>//---//per_epoch_stats.json - <_LOCAL_LOGS_LOCATION>//---//summary.json - <_LOCAL_LOGS_LOCATION>//---/gcsfuse-generic/gcsfuse_mount_options + <_LOCAL_LOGS_LOCATION>//---//per_epoch_stats.json + <_LOCAL_LOGS_LOCATION>//---//summary.json + <_LOCAL_LOGS_LOCATION>//---/gcsfuse-generic/gcsfuse_mount_options Output dict structure: "{num_files_train}-{mean_file_size}-{batch_size}": @@ -100,7 +100,9 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: """ output = {} - for root, _, files in os.walk(_LOCAL_LOGS_LOCATION + "/" + args.instance_id): + for root, _, files in os.walk( + _LOCAL_LOGS_LOCATION + "/" + args.experiment_id + ): print(f"Parsing directory {root} ...") if files: # If directory contains gcsfuse_mount_options file, then parse gcsfuse @@ -200,7 +202,7 @@ def write_records_to_csv_output_file(output: dict, output_file_path: str): " (s),GPU Utilization (%),Throughput (sample/s),Throughput" " (MB/s),Throughput over Local SSD (%),GCSFuse Lowest Memory" " (MiB),GCSFuse Highest Memory (MiB),GCSFuse Lowest CPU (core),GCSFuse" - " Highest CPU (core),Pod,Start,End,GcsfuseMountOptions,InstanceID\n" + " Highest CPU (core),Pod,Start,End,GcsfuseMountOptions,ExperimentID\n" ) for key in output: @@ -250,7 +252,7 @@ def write_records_to_csv_output_file(output: dict, output_file_path: str): f"{record_set['mean_file_size']},{record_set['num_files_train']},{total_size},{record_set['batch_size']},{scenario}," ) output_file.write( - f"{r['epoch']},{r['duration']},{r['train_au_percentage']},{r['train_throughput_samples_per_second']},{r['train_throughput_mb_per_second']},{r['throughput_over_local_ssd']},{r['lowest_memory']},{r['highest_memory']},{r['lowest_cpu']},{r['highest_cpu']},{r['pod_name']},{r['start']},{r['end']},\"{r['gcsfuse_mount_options']}\",{args.instance_id}\n" + f"{r['epoch']},{r['duration']},{r['train_au_percentage']},{r['train_throughput_samples_per_second']},{r['train_throughput_mb_per_second']},{r['throughput_over_local_ssd']},{r['lowest_memory']},{r['highest_memory']},{r['lowest_cpu']},{r['highest_cpu']},{r['pod_name']},{r['start']},{r['end']},\"{r['gcsfuse_mount_options']}\",{args.experiment_id}\n" ) output_file.close() @@ -263,7 +265,7 @@ def write_records_to_csv_output_file(output: dict, output_file_path: str): dlioWorkloads = dlio_workload.parse_test_config_for_dlio_workloads( args.workload_config ) - download_dlio_outputs(dlioWorkloads, args.instance_id) + download_dlio_outputs(dlioWorkloads, args.experiment_id) output = create_output_scenarios_from_downloaded_files(args) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py index 0cabd6499f..c06e9666ff 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/run_tests.py @@ -38,7 +38,7 @@ def createHelmInstallCommands( - dlioWorkloads: set, instanceId: str, machineType: str, customCSIDriver: str + dlioWorkloads: set, experimentID: str, machineType: str, customCSIDriver: str ) -> list: """Creates helm install commands for the given dlioWorkload objects.""" helm_commands = [] @@ -57,7 +57,7 @@ def createHelmInstallCommands( for batchSize in dlioWorkload.batchSizes: chartName, podName, outputDirPrefix = ( dlio_workload.DlioChartNamePodName( - dlioWorkload, instanceId, batchSize + dlioWorkload, experimentID, batchSize ) ) commands = [ @@ -67,7 +67,7 @@ def createHelmInstallCommands( f'--set dlio.numFilesTrain={dlioWorkload.numFilesTrain}', f'--set dlio.recordLength={dlioWorkload.recordLength}', f'--set dlio.batchSize={batchSize}', - f'--set instanceId={instanceId}', + f'--set experimentID={experimentID}', ( '--set' f' gcsfuse.mountOptions={escape_commas_in_string(dlioWorkload.gcsfuseMountOptions)}' @@ -93,7 +93,7 @@ def main(args) -> None: args.workload_config ) helmInstallCommands = createHelmInstallCommands( - dlioWorkloads, args.instance_id, args.machine_type, args.custom_csi_driver + dlioWorkloads, args.experiment_id, args.machine_type, args.custom_csi_driver ) buckets = [dlioWorkload.bucket for dlioWorkload in dlioWorkloads] role = 'roles/storage.objectUser' diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py index 0586c84697..828872e200 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py @@ -212,7 +212,7 @@ def parse_test_config_for_fio_workloads(fioTestConfigFile: str): def FioChartNamePodName( - fioWorkload: FioWorkload, instanceID: str, readType: str + fioWorkload: FioWorkload, experimentID: str, readType: str ) -> (str, str, str): shortenScenario = { 'local-ssd': 'ssd', @@ -230,11 +230,11 @@ def FioChartNamePodName( else 'ur' ) - hashOfWorkload = str(hash((fioWorkload, instanceID, readType))).replace( + hashOfWorkload = str(hash((fioWorkload, experimentID, readType))).replace( '-', '' ) return ( f'fio-load-{shortForScenario}-{shortForReadType}-{fioWorkload.fileSize.lower()}-{hashOfWorkload}', f'fio-tester-{shortForScenario}-{shortForReadType}-{fioWorkload.fileSize.lower()}-{hashOfWorkload}', - f'{instanceID}/{fioWorkload.fileSize}-{fioWorkload.blockSize}-{fioWorkload.numThreads}-{fioWorkload.filesPerThread}-{hashOfWorkload}/{fioWorkload.scenario}/{readType}', + f'{experimentID}/{fioWorkload.fileSize}-{fioWorkload.blockSize}-{fioWorkload.numThreads}-{fioWorkload.filesPerThread}-{hashOfWorkload}/{fioWorkload.scenario}/{readType}', ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py index bfadbdfb26..c4bf5656cc 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py @@ -64,28 +64,28 @@ } -def download_fio_outputs(fioWorkloads: set, instanceId: str) -> int: - """Downloads instanceId-specific fio outputs for each fioWorkload locally. +def download_fio_outputs(fioWorkloads: set, experimentID: str) -> int: + """Downloads experimentID-specific fio outputs for each fioWorkload locally. Outputs in the bucket are in the following object naming format (details in ./loading-test/templates/fio-tester.yaml). - gs:///fio-output//----///epoch[N].json - gs:///fio-output//----///pod_name - gs:///fio-output//----/gcsfuse-generic//gcsfuse_mount_options + gs:///fio-output//----///epoch[N].json + gs:///fio-output//----///pod_name + gs:///fio-output//----/gcsfuse-generic//gcsfuse_mount_options These are downloaded locally as: - <_LOCAL_LOGS_LOCATION>///----///epoch[N].json - <_LOCAL_LOGS_LOCATION>///----///pod_name - <_LOCAL_LOGS_LOCATION>///----/gcsfuse-generic//gcsfuse_mount_options + <_LOCAL_LOGS_LOCATION>///----///epoch[N].json + <_LOCAL_LOGS_LOCATION>///----///pod_name + <_LOCAL_LOGS_LOCATION>///----/gcsfuse-generic//gcsfuse_mount_options """ for fioWorkload in fioWorkloads: dstDir = ( - _LOCAL_LOGS_LOCATION + "/" + instanceId + "/" + fioWorkload.fileSize + _LOCAL_LOGS_LOCATION + "/" + experimentID + "/" + fioWorkload.fileSize ) ensure_directory_exists(dstDir) - srcObjects = f"gs://{fioWorkload.bucket}/fio-output/{instanceId}/*" + srcObjects = f"gs://{fioWorkload.bucket}/fio-output/{experimentID}/*" print(f"Downloading FIO outputs from {srcObjects} ...") returncode, errorStr = download_gcs_objects(srcObjects, dstDir) if returncode < 0: @@ -100,10 +100,10 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: The following creates a dict called 'output' from the downloaded fio output files, which are in the following format. - <_LOCAL_LOGS_LOCATION>///----///epoch[N].json + <_LOCAL_LOGS_LOCATION>///----///epoch[N].json where N=1-#epochs - <_LOCAL_LOGS_LOCATION>///----///pod_name - <_LOCAL_LOGS_LOCATION>///----/gcsfuse-generic//gcsfuse_mount_options + <_LOCAL_LOGS_LOCATION>///----///pod_name + <_LOCAL_LOGS_LOCATION>///----/gcsfuse-generic//gcsfuse_mount_options Output dict structure: "{read_type}-{mean_file_size}-{bs}-{numjobs}-{nrfiles}": @@ -115,7 +115,9 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: """ output = {} - for root, _, files in os.walk(_LOCAL_LOGS_LOCATION + "/" + args.instance_id): + for root, _, files in os.walk( + _LOCAL_LOGS_LOCATION + "/" + args.experiment_id + ): print(f"Parsing directory {root} ...") if not files: @@ -285,7 +287,7 @@ def write_records_to_csv_output_file(output: dict, output_file_path: str): " Lowest" " Memory (MiB),GCSFuse Highest Memory (MiB),GCSFuse Lowest CPU" " (core),GCSFuse Highest CPU" - " (core),Pod,Start,End,GcsfuseMoutOptions,BlockSize,FilesPerThread,NumThreads,InstanceID," + " (core),Pod,Start,End,GcsfuseMoutOptions,BlockSize,FilesPerThread,NumThreads,ExperimentID," "e2e_latency_ns_max,e2e_latency_ns_p50,e2e_latency_ns_p90,e2e_latency_ns_p99,e2e_latency_ns_p99.9," "bucket_name,machine_type," # "Throughput (MB/s)" @@ -327,7 +329,7 @@ def write_records_to_csv_output_file(output: dict, output_file_path: str): continue output_file_fwr.write( - f"{record_set['mean_file_size']},{record_set['read_type']},{scenario},{r['epoch']},{r['duration']},{r['throughput_mib_per_second']},{r['IOPS']},{r['throughput_over_local_ssd']},{r['lowest_memory']},{r['highest_memory']},{r['lowest_cpu']},{r['highest_cpu']},{r['pod_name']},{r['start']},{r['end']},\"{r['gcsfuse_mount_options']}\",{r['blockSize']},{r['filesPerThread']},{r['numThreads']},{args.instance_id}," + f"{record_set['mean_file_size']},{record_set['read_type']},{scenario},{r['epoch']},{r['duration']},{r['throughput_mib_per_second']},{r['IOPS']},{r['throughput_over_local_ssd']},{r['lowest_memory']},{r['highest_memory']},{r['lowest_cpu']},{r['highest_cpu']},{r['pod_name']},{r['start']},{r['end']},\"{r['gcsfuse_mount_options']}\",{r['blockSize']},{r['filesPerThread']},{r['numThreads']},{args.experiment_id}," ) output_file_fwr.write( f"{r['e2e_latency_ns_max']},{r['e2e_latency_ns_p50']},{r['e2e_latency_ns_p90']},{r['e2e_latency_ns_p99']},{r['e2e_latency_ns_p99.9']}," @@ -419,7 +421,7 @@ def write_records_to_bq_table( fioWorkloads = fio_workload.parse_test_config_for_fio_workloads( args.workload_config ) - download_fio_outputs(fioWorkloads, args.instance_id) + download_fio_outputs(fioWorkloads, args.experiment_id) output = create_output_scenarios_from_downloaded_files(args) @@ -443,5 +445,5 @@ def write_records_to_bq_table( bq_project_id=args.bq_project_id, bq_dataset_id=args.bq_dataset_id, bq_table_id=args.bq_table_id, - experiment_id=args.instance_id, + experiment_id=args.experiment_id, ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py b/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py index 9dfe1a631a..3aa9fd8bcd 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py @@ -37,7 +37,7 @@ def createHelmInstallCommands( - fioWorkloads: set, instanceId: str, machineType: str, customCSIDriver: str + fioWorkloads: set, experimentID: str, machineType: str, customCSIDriver: str ) -> list: """Creates helm install commands for the given fioWorkload objects.""" helm_commands = [] @@ -55,7 +55,7 @@ def createHelmInstallCommands( if fioWorkload.numEpochs > 0: for readType in fioWorkload.readTypes: chartName, podName, outputDirPrefix = fio_workload.FioChartNamePodName( - fioWorkload, instanceId, readType + fioWorkload, experimentID, readType ) commands = [ f'helm install {chartName} loading-test', @@ -66,7 +66,7 @@ def createHelmInstallCommands( f'--set fio.blockSize={fioWorkload.blockSize}', f'--set fio.filesPerThread={fioWorkload.filesPerThread}', f'--set fio.numThreads={fioWorkload.numThreads}', - f'--set instanceId={instanceId}', + f'--set experimentID={experimentID}', ( '--set' f' gcsfuse.mountOptions={escape_commas_in_string(fioWorkload.gcsfuseMountOptions)}' @@ -92,7 +92,7 @@ def main(args) -> None: args.workload_config ) helmInstallCommands = createHelmInstallCommands( - fioWorkloads, args.instance_id, args.machine_type, args.custom_csi_driver + fioWorkloads, args.experiment_id, args.machine_type, args.custom_csi_driver ) buckets = (fioWorkload.bucket for fioWorkload in fioWorkloads) role = 'roles/storage.objectUser' diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-automated.sh b/perfmetrics/scripts/testing_on_gke/examples/run-automated.sh index a1458bb958..cd50d9d83c 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-automated.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-automated.sh @@ -63,10 +63,10 @@ if test -z "${gcsfuse_branch}"; then fi export pod_wait_time_in_seconds=300 export pod_timeout_in_seconds=64800 -# Pass instance_id from outside to continue previous run, if it got terminated +# Pass experiment_id from outside to continue previous run, if it got terminated # somehow (timeout of ssh etc.) -if test -z ${instance_id}; then - export instance_id=$(echo ${USER} | sed 's/_google//' | sed 's/_com//')-$(date +%Y%m%d-%H%M%S) +if test -z ${experiment_id}; then + export experiment_id=$(echo ${USER} | sed 's/_google//' | sed 's/_com//')-$(date +%Y%m%d-%H%M%S) fi if test -z "${output_gsheet_id}"; then echo "output_gsheet_id has not been set." @@ -94,7 +94,7 @@ echo "Run started at ${start_time}" touch log (./run-gke-tests.sh --debug |& tee -a log) || true # Use the following if you want to run it in a tmux session instead. -# tmux new-session -d -s ${instance_id} 'bash -c "(./run-gke-tests.sh --debug |& tee -a log); sleep 604800 "' +# tmux new-session -d -s ${experiment_id} 'bash -c "(./run-gke-tests.sh --debug |& tee -a log); sleep 604800 "' end_time=$(date +%Y-%m-%dT%H:%M:%SZ) echo "Run ended at ${end_time}" @@ -114,7 +114,7 @@ if test -z "${output_bucket}"; then echo "output_bucket has not been set." exit 1 fi -output_path_uri=gs://${output_bucket}/outputs/${instance_id} +output_path_uri=gs://${output_bucket}/outputs/${experiment_id} for file in fio/output.csv dlio/output.csv log run-gke-tests.sh workloads.json gcsfuse_commithash gcs_fuse_csi_driver_commithash; do if test -f ${file} ; then gcloud storage cp --content-type=text/text ${file} ${output_path_uri}/${file} diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index ec45f9ef25..547616a964 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -37,6 +37,7 @@ fi function exitWithSuccess() { exit 0; } function exitWithFailure() { exit 1; } function echoerror() { >&2 echo "Error: "$@ ; } +function echowarning() { >&2 echo "Warning: "${@} ; } function exitWithError() { echoerror "$@" ; exitWithFailure ; } function returnWithError() { echoerror "$@" ; return 1 ; } @@ -71,18 +72,35 @@ readonly DEFAULT_BQ_PROJECT_ID='gcs-fuse-test-ml' readonly DEFAULT_BQ_DATASET_ID='gke_test_tool_outputs' readonly DEFAULT_BQ_TABLE_ID='fio_outputs' -# Create and return a unique instance_id taking -# into account user's passed instance_id. -function create_unique_instance_id() { +# Handling of deprecated flag instance_id if it has been passed. +if test -n "${instance_id}" ; then + deprecation_message="instance_id flag is now deprecated, but has been passed (with value \"${instance_id}\"). In future, please use experiment_id instead." + + # If instance_id is set, but experiment_id is not + # set, then let this be only a warning message and pass the value of + # instance_id to experiment_id. + if test -z "${experiment_id}" ; then + echowarning ${deprecation_message}" For now, setting experiment_id=\"${instance_id}\" ." + export experiment_id="${instance_id}" + unset instance_id + else + # Otherwise, halt the run as this is an ambiguous situation. + exitWithError "${deprecation_message}" + fi +fi + +# Create and return a unique experiment_id taking +# into account user's passed experiment_id. +function create_unique_experiment_id() { new_uuid=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 4 | head -n 1) - local generated_unique_instance_id=${USER}-$(date +%Y%m%d-%H%M%S)-${new_uuid} + local generated_unique_experiment_id=${USER}-$(date +%Y%m%d-%H%M%S)-${new_uuid} if [ $# -gt 0 ] && [ -n "${1}" ]; then - local user_provided_instance_id="${1}" - instance_id=${user_provided_instance_id// /-}"-"${generated_unique_instance_id} + local user_provided_experiment_id="${1}" + experiment_id=${user_provided_experiment_id// /-}"-"${generated_unique_experiment_id} else - instance_id=${generated_unique_instance_id} + experiment_id=${generated_unique_experiment_id} fi - echo "${instance_id}" + echo "${experiment_id}" } function printHelp() { @@ -111,7 +129,7 @@ function printHelp() { # Test runtime configuration echo "pod_wait_time_in_seconds=" echo "pod_timeout_in_seconds=" - echo "instance_id=" echo "output_dir=" echo "force_update_gcsfuse_code=" @@ -238,16 +256,16 @@ elif [ "$only_parse" != "true" ] && [ "$only_parse" != "false" ]; then exitWithError "Unexpected value of only_parse: ${only_parse}. Expected: true or false ." fi -# If user passes only_parse=true, then expect an instance_id +# If user passes only_parse=true, then expect an experiment_id # also with it, and use it as it is. if ${only_parse}; then - if [ -z "${instance_id}" ]; then - exitWithError "instance_id not passed with only_parse=true" + if [ -z "${experiment_id}" ]; then + exitWithError "experiment_id not passed with only_parse=true" fi else - # create a new instance_id - export user_passed_instance_id="${instance_id}" - export instance_id=$(create_unique_instance_id "${user_passed_instance_id}") + # create a new experiment_id + export user_passed_experiment_id="${experiment_id}" + export experiment_id=$(create_unique_experiment_id "${user_passed_experiment_id}") fi if [[ ${pod_timeout_in_seconds} -le ${pod_wait_time_in_seconds} ]]; then @@ -304,7 +322,7 @@ function printRunParameters() { # Test runtime configuration echo "pod_wait_time_in_seconds=\"${pod_wait_time_in_seconds}\"" echo "pod_timeout_in_seconds=\"${pod_timeout_in_seconds}\"" - echo "instance_id=User passed: \"${user_passed_instance_id}\", internally created: \"${instance_id}\"" + echo "experiment_id=User passed: \"${user_passed_experiment_id}\", internally created: \"${experiment_id}\"" echo "workload_config=\"${workload_config}\"" echo "output_dir=\"${output_dir}\"" echo "force_update_gcsfuse_code=\"${force_update_gcsfuse_code}\"" @@ -682,14 +700,14 @@ function deleteAllPods() { function deployAllFioHelmCharts() { printf "\nDeploying all fio helm charts ...\n\n" cd "${gke_testing_dir}"/examples/fio - python3 ./run_tests.py --workload-config "${workload_config}" --instance-id ${instance_id} --machine-type="${machine_type}" --project-id=${project_id} --project-number=${project_number} --namespace=${appnamespace} --ksa=${ksa} --custom-csi-driver=${applied_custom_csi_driver} + python3 ./run_tests.py --workload-config "${workload_config}" --experiment-id ${experiment_id} --machine-type="${machine_type}" --project-id=${project_id} --project-number=${project_number} --namespace=${appnamespace} --ksa=${ksa} --custom-csi-driver=${applied_custom_csi_driver} cd - } function deployAllDlioHelmCharts() { printf "\nDeploying all dlio helm charts ...\n\n" cd "${gke_testing_dir}"/examples/dlio - python3 ./run_tests.py --workload-config "${workload_config}" --instance-id ${instance_id} --machine-type="${machine_type}" --project-id=${project_id} --project-number=${project_number} --namespace=${appnamespace} --ksa=${ksa} --custom-csi-driver=${applied_custom_csi_driver} + python3 ./run_tests.py --workload-config "${workload_config}" --experiment-id ${experiment_id} --machine-type="${machine_type}" --project-id=${project_id} --project-number=${project_number} --namespace=${appnamespace} --ksa=${ksa} --custom-csi-driver=${applied_custom_csi_driver} cd - } @@ -729,7 +747,7 @@ function waitTillAllPodsComplete() { else message="\n${num_noncompleted_pods} pod(s) is/are still pending/running (time till timeout=${time_till_timeout} seconds). Will check again in "${pod_wait_time_in_seconds}" seconds. Sleeping for now.\n\n" message+="\nYou can take a break too if you want. Just kill this run and connect back to it later, for fetching and parsing outputs, using the following command: \n\n" - message+=" only_parse=true instance_id=${instance_id} project_id=${project_id} project_number=${project_number} zone=${zone} machine_type=${machine_type}" + message+=" only_parse=true experiment_id=${experiment_id} project_id=${project_id} project_number=${project_number} zone=${zone} machine_type=${machine_type}" message+=" use_custom_csi_driver=${use_custom_csi_driver}" if test -n "${custom_csi_driver}"; then message+=" custom_csi_driver=${custom_csi_driver}" @@ -758,7 +776,7 @@ function waitTillAllPodsComplete() { done } -# Download all the fio workload outputs for the current instance-id from the +# Download all the fio workload outputs for the current experiment-id from the # given bucket and file-size. function downloadFioOutputsFromBucket() { local bucket=$1 @@ -781,10 +799,10 @@ function downloadFioOutputsFromBucket() { # Return to original directory. cd - >/dev/null - # If the given bucket has the fio outputs for the given instance-id, then + # If the given bucket has the fio outputs for the given experiment-id, then # copy/download them locally to the appropriate folder. - src_dir="${mountpath}/fio-output/${instance_id}" - dst_dir="${gcsfuse_src_dir}/perfmetrics/scripts/testing_on_gke/bin/fio-logs/${instance_id}/${fileSize}" + src_dir="${mountpath}/fio-output/${experiment_id}" + dst_dir="${gcsfuse_src_dir}/perfmetrics/scripts/testing_on_gke/bin/fio-logs/${experiment_id}/${fileSize}" if test -d "${src_dir}" ; then mkdir -pv "${dst_dir}" echo "Copying files of type ${fileSize}* from \"${src_dir}\" to \"${dst_dir}/\" ... " @@ -830,7 +848,7 @@ function areThereAnyDLIOWorkloads() { function fetchAndParseFioOutputs() { printf "\nFetching and parsing fio outputs ...\n\n" cd "${gke_testing_dir}"/examples/fio - parse_logs_args="--project-number=${project_number} --workload-config ${workload_config} --instance-id ${instance_id} --output-file ${output_dir}/fio/output.csv --project-id=${project_id} --cluster-name=${cluster_name} --namespace-name=${appnamespace} --bq-project-id=${DEFAULT_BQ_PROJECT_ID} --bq-dataset-id=${DEFAULT_BQ_DATASET_ID} --bq-table-id=${DEFAULT_BQ_TABLE_ID}" + parse_logs_args="--project-number=${project_number} --workload-config ${workload_config} --experiment-id ${experiment_id} --output-file ${output_dir}/fio/output.csv --project-id=${project_id} --cluster-name=${cluster_name} --namespace-name=${appnamespace} --bq-project-id=${DEFAULT_BQ_PROJECT_ID} --bq-dataset-id=${DEFAULT_BQ_DATASET_ID} --bq-table-id=${DEFAULT_BQ_TABLE_ID}" if ${zonal}; then python3 parse_logs.py ${parse_logs_args} --predownloaded-output-files else @@ -842,7 +860,7 @@ function fetchAndParseFioOutputs() { function fetchAndParseDlioOutputs() { printf "\nFetching and parsing dlio outputs ...\n\n" cd "${gke_testing_dir}"/examples/dlio - python3 parse_logs.py --project-number=${project_number} --workload-config "${workload_config}" --instance-id ${instance_id} --output-file "${output_dir}"/dlio/output.csv --project-id=${project_id} --cluster-name=${cluster_name} --namespace-name=${appnamespace} + python3 parse_logs.py --project-number=${project_number} --workload-config "${workload_config}" --experiment-id ${experiment_id} --output-file "${output_dir}"/dlio/output.csv --project-id=${project_id} --cluster-name=${cluster_name} --namespace-name=${appnamespace} cd - } diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py b/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py index 3c31488da3..276852612e 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/parse_logs_common.py @@ -96,7 +96,7 @@ def parse_arguments( required=True, ) parser.add_argument( - "--instance-id", + "--experiment-id", help="unique string ID for current test-run", required=True, ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py b/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py index 4459e0597d..3d306bd513 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py +++ b/perfmetrics/scripts/testing_on_gke/examples/utils/run_tests_common.py @@ -56,7 +56,7 @@ def parse_args(): required=True, ) parser.add_argument( - '--instance-id', + '--experiment-id', metavar='A unique string ID to represent the test-run', help=( 'Set to a unique string ID for current test-run. Do not put spaces' @@ -119,7 +119,7 @@ def parse_args(): args = parser.parse_args() for argument in [ - 'instance_id', + 'experiment_id', 'machine_type', 'project_id', 'namespace', From 02d283471e9ae87430d9e2af45ef4fe469ffe712 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 15 May 2025 12:51:51 +0000 Subject: [PATCH 0431/1298] [testing-on-gke] Ignore output directories which are missing any of the metadata values (#3305) * dont print metadata value if no failure * minor fix * remove outdated comment * fix a comment * fix a local constant name * minor code refactor * dont bother with BQ insertion if no entries --- .../testing_on_gke/examples/fio/parse_logs.py | 91 ++++++++++--------- 1 file changed, 48 insertions(+), 43 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py index c4bf5656cc..73bf6edcba 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py @@ -20,6 +20,7 @@ import json import os import pprint +import re import subprocess import sys @@ -31,6 +32,7 @@ from utils.utils import unix_to_timestamp, convert_size_to_bytes _LOCAL_LOGS_LOCATION = "../../bin/fio-logs" +_EPOCH_FILENAME_REGEX = "^epoch[0-9]+.json$" record = { "pod_name": "", @@ -120,47 +122,46 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: ): print(f"Parsing directory {root} ...") + metadata_values = dict() if not files: - # ignore intermediate directories. + # Ignore intermediate directories. + # Don't combine it with the else or it will lead to a lot of unnecessary + # logs. continue + else: + # Skip this directory if doesn't have any epoch[N].json file in it. + if not any(re.search(_EPOCH_FILENAME_REGEX, file) for file in files): + print( + f"Directory {root} does not have any" + " epoch[N].json files in it, so skipping it." + ) + continue + # Skip this directory if it is missing any of metadata files + # i.e. gcsfuse_mount_options, pod_name, bucket_name, machine_type etc. + skip_this_directory = False + for metadata in [ + "gcsfuse_mount_options", + "bucket_name", + "machine_type", + "pod_name", + ]: + metadata_file = root + f"/{metadata}" + if os.path.isfile(metadata_file): + with open(metadata_file) as f: + metadata_values[metadata] = f.read().strip() + else: + print( + f"Error: Directory {root} does not have file {metadata} in it" + " as expected, so skipping it." + ) + skip_this_directory = True + break - # if directory contains gcsfuse_mount_options file, then parse gcsfuse - # mount options from it in record. - gcsfuse_mount_options = "" - gcsfuse_mount_options_file = root + "/gcsfuse_mount_options" - if os.path.isfile(gcsfuse_mount_options_file): - with open(gcsfuse_mount_options_file) as f: - gcsfuse_mount_options = f.read().strip() - print(f"gcsfuse_mount_options={gcsfuse_mount_options}") - - # if directory contains bucket_name file, then parse it. - bucket_name = "" - bucket_name_file = root + "/bucket_name" - if os.path.isfile(bucket_name_file): - with open(bucket_name_file) as f: - bucket_name = f.read().strip() - print(f"bucket_name={bucket_name}") - - # if directory contains machine_type file, then parse it. - machine_type = "" - machine_type_file = root + "/machine_type" - if os.path.isfile(machine_type_file): - with open(machine_type_file) as f: - machine_type = f.read().strip() - print(f"machine_type={machine_type}") - - # if directory has files, it must also contain pod_name file, - # and we should extract pod-name from it in the record. - pod_name = "" - pod_name_file = root + "/pod_name" - if os.path.isfile(pod_name_file): - with open(pod_name_file) as f: - pod_name = f.read().strip() - print(f"pod_name={pod_name}") + if skip_this_directory: + continue for file in files: - # Ignore non-json files to avoid unnecessary failure. - if not file.endswith(".json"): + if not re.search(_EPOCH_FILENAME_REGEX, file): continue per_epoch_output = root + f"/{file}" @@ -229,7 +230,8 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: # Create a record for this key. r = record.copy() try: - r["pod_name"] = pod_name + for metadata, metadata_value in metadata_values.items(): + r[metadata] = metadata_value r["epoch"] = epoch r["scenario"] = scenario r["duration"] = int(job0_read_metrics["runtime"] / 1000) @@ -248,9 +250,6 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: fetch_cpu_memory_data(args=args, record=r) - r["gcsfuse_mount_options"] = gcsfuse_mount_options - r["bucket_name"] = bucket_name - r["machine_type"] = machine_type r["blockSize"] = bs r["filesPerThread"] = nrfiles r["numThreads"] = numjobs @@ -262,7 +261,10 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: r["e2e_latency_ns_p99"] = clat_ns_percentile["99.000000"] r["e2e_latency_ns_p99.9"] = clat_ns_percentile["99.900000"] except Exception as e: - print(f"Failed to create following record with error: {e}") + print( + f"Failed to create following record with error: {e}, metadata:" + f" {repr(metadata_values)}" + ) # This print is for debugging in case something goes wrong. pprint.pprint(r) continue @@ -355,8 +357,6 @@ def write_records_to_bq_table( bq_dataset_id: str, bq_table_id: str, ): - fioBqExporter = FioBigqueryExporter(bq_project_id, bq_dataset_id, bq_table_id) - # list of FioRowTable objects to be populated to be inserted into BigQuery # table using the above exporter. rows = [] @@ -406,6 +406,11 @@ def write_records_to_bq_table( rows.append(row) + if len(rows) == 0: + print("No output rows to insert into the BQ table.") + return + + fioBqExporter = FioBigqueryExporter(bq_project_id, bq_dataset_id, bq_table_id) fioBqExporter.insert_rows(fioTableRows=rows) print( "\nSuccessfully exported outputs of FIO test runs to" From 3bc8e8a37358ce6b8c009fe7326b32e68ec54a4e Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 16 May 2025 10:16:13 +0530 Subject: [PATCH 0432/1298] Script to find micro benchmarks for reading file with single thread (#3307) * adding function to read file of given size and count * update tests * update file name * add one more unit tests * small fixes --- .../micro_benchmarks/read_single_thread.py | 146 ++++++++++++++++ .../read_single_thread_test.py | 163 ++++++++++++++++++ .../scripts/micro_benchmarks/requirements.txt | 1 + 3 files changed, 310 insertions(+) create mode 100644 perfmetrics/scripts/micro_benchmarks/read_single_thread.py create mode 100644 perfmetrics/scripts/micro_benchmarks/read_single_thread_test.py diff --git a/perfmetrics/scripts/micro_benchmarks/read_single_thread.py b/perfmetrics/scripts/micro_benchmarks/read_single_thread.py new file mode 100644 index 0000000000..5a60ce984f --- /dev/null +++ b/perfmetrics/scripts/micro_benchmarks/read_single_thread.py @@ -0,0 +1,146 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import subprocess +import time +import argparse +from google.cloud import storage +import helper + +MOUNT_DIR = "gcs" +FILE_PREFIX = "testfile_read" + +def check_and_create_files(bucket_name: str, total_files: int, file_size_gb: int): + """ + Ensures that the specified number of files exist in the given GCS bucket. + If a file is missing or its not given size, it is (re)created and uploaded. + + Args: + bucket_name (str): Name of the GCS bucket. + total_files (int): Number of files to check or create. + file_size_gb (int): Expected size of each file in gigabytes (GB, base-10). + """ + client = storage.Client() + bucket = client.get_bucket(bucket_name) + expected_size = file_size_gb * (10 ** 9) # Use base-10 GB + + print(f"Ensuring all {total_files} files exist in gs://{bucket_name}...") + + for i in range(total_files): + fname = f"{FILE_PREFIX}_{file_size_gb}_{i}.bin" + blob = bucket.blob(fname) + + blob_exists = blob.exists() + # Use a default value for blob.size if it's None (e.g., if blob doesn't exist) + if blob_exists: + blob.reload() + current_blob_size = blob.size if blob_exists and blob.size is not None else 0 + + if not blob_exists or current_blob_size != expected_size: + if blob_exists: + print(f"{fname} exists but has size {current_blob_size} bytes (expected ~{expected_size}). Re-uploading...") + + print(f" Creating {file_size_gb}GB dummy file {fname}...") + local_path = f"/tmp/{fname}" + + try: + # Use subprocess.run to execute fallocate + gb_in_bytes = file_size_gb * 10**9 + subprocess.run(f"fallocate -l {gb_in_bytes} {local_path}", shell=True, check=True) + + except subprocess.CalledProcessError as e: + print(f"Error creating dummy file {local_path}: {e}") + continue # Skip to the next file if creation fails + + try: + blob.upload_from_filename(local_path) + print(f"Uploaded {fname} to gs://{bucket_name}/{fname}") + except Exception as e: # Catch broader exceptions for upload issues + print(f"Error uploading {fname} to GCS: {e}") + finally: + # Ensure local file is removed even if upload fails + if os.path.exists(local_path): + os.remove(local_path) + else: + print(f"{fname} already exists with acceptable size.") + +def read_all_files(total_files: int, file_size_gb: int) -> int: + """ + Reads a specified number of files, calculates, and returns the total number + of bytes read across all files. + + The files are expected to be named with a common prefix and index suffix: + {FILE_PREFIX}_{file_size_gb}_{i}.bin, located inside the directory MOUNT_DIR. + + Args: + total_files (int): The number of files to read. + file_size_gb (int): file size in gb + Returns: + int: The total number of bytes read from all files. + + Raises: + RuntimeError: If any error occurs while reading any file, including + file not found, permission issues, or other IO errors. + """ + total_bytes = 0 + for i in range(total_files): + path = os.path.join(MOUNT_DIR, f"{FILE_PREFIX}_{file_size_gb}_{i}.bin") + try: + with open(path, "rb") as f: + total_bytes += len(f.read()) + except Exception as e: # catch all exceptions + print(f"Error reading file {path}: {e}") + raise RuntimeError(f"Failed to read file: {path}") from e + + return total_bytes + +def main(): + parser = argparse.ArgumentParser(description="Measure GCS read bandwidth via gcsfuse.") + parser.add_argument("--bucket", required=True, help="GCS bucket name") + parser.add_argument("--gcsfuse-config", default="--implicit-dirs", help="GCSFuse mount flags") + parser.add_argument("--total-files", type=int, default=10, help="Number of files to read") + parser.add_argument("--file-size-gb", type=int, default=15, help="Size of each file in GB") + + workflow_type = "READ_{args.total_files}_{args.file_size_gb}GB_SINGLE_THREAD" + args = parser.parse_args() + + # Mount the bucket + helper.mount_bucket(MOUNT_DIR, args.bucket, args.gcsfuse_config) + + # Ensure test files exist + check_and_create_files(args.bucket, args.total_files, args.file_size_gb) + + print(f"Starting read of {args.total_files} files...") + start = time.time() + try: + total_bytes = read_all_files(args.total_files, args.file_size_gb) + print(f"Total bytes read from {args.total_files} files: {total_bytes}") + except RuntimeError as e: + print(f"Failed during file read: {e}") + duration = time.time() - start + + # Unmount after test + helper.unmount_gcs_directory(MOUNT_DIR) + + # Log to BigQuery + helper.log_to_bigquery( + duration_sec=duration, + total_bytes=total_bytes, + gcsfuse_config=args.gcsfuse_config, + workload_type=workflow_type, + ) + +if __name__ == "__main__": + main() diff --git a/perfmetrics/scripts/micro_benchmarks/read_single_thread_test.py b/perfmetrics/scripts/micro_benchmarks/read_single_thread_test.py new file mode 100644 index 0000000000..cd18593ad9 --- /dev/null +++ b/perfmetrics/scripts/micro_benchmarks/read_single_thread_test.py @@ -0,0 +1,163 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unittest +from unittest import mock +import read_single_thread + +class TestReadFiles(unittest.TestCase): + + @mock.patch("builtins.open", new_callable=mock.mock_open, read_data=b"abc") + @mock.patch("os.path.join", side_effect=lambda a, b: f"{a}/{b}") + def test_reads_all_files_success(self, mock_join, mock_file): + total_files = 3 + file_size = len(b"abc") + expected_bytes = 3 * file_size + + result = read_single_thread.read_all_files(total_files, file_size) + + self.assertEqual(result, expected_bytes) + actual_calls = mock_file.call_args_list + expected_calls = [ + mock.call(f"{read_single_thread.MOUNT_DIR}/{read_single_thread.FILE_PREFIX}_{file_size}_{i}.bin", "rb") + for i in range(total_files) + ] + self.assertEqual(actual_calls, expected_calls) + + @mock.patch("builtins.open", side_effect=FileNotFoundError("File not found")) + @mock.patch("os.path.join", side_effect=lambda a, b: f"{a}/{b}") + def test_file_not_found_raises_runtime_error(self, mock_join, mock_file): + total_files = 1 + + with self.assertRaises(RuntimeError) as cm: + read_single_thread.read_all_files(total_files, 0) + + self.assertIn("Failed to read file", str(cm.exception)) + + @mock.patch("builtins.open", side_effect=PermissionError("Permission denied")) + @mock.patch("os.path.join", side_effect=lambda a, b: f"{a}/{b}") + def test_permission_error_raises_runtime_error(self, mock_join, mock_file): + total_files = 1 + + with self.assertRaises(RuntimeError) as cm: + read_single_thread.read_all_files(total_files, 0) + + self.assertIn("Failed to read file", str(cm.exception)) + + @mock.patch("builtins.open", new_callable=mock.mock_open, read_data=b"data") + @mock.patch("os.path.join", side_effect=lambda a, b: f"{a}/{b}") + def test_partial_failure(self, mock_join, mock_file): + # Simulate first file reads correctly, second file throws IOError + def side_effect(path, mode="rb"): + if path.endswith("file_0.bin"): + return mock.mock_open(read_data=b"data").return_value + else: + raise IOError("Read error") + mock_file.side_effect = side_effect + + with self.assertRaises(RuntimeError) as cm: + read_single_thread.read_all_files(2, 0) + + self.assertIn("Failed to read file", str(cm.exception)) + + @mock.patch('google.cloud.storage.Client') + @mock.patch('subprocess.run') + @mock.patch('os.remove') + @mock.patch('builtins.print') + def test_upload_failure(self, mock_print, mock_os_remove, mock_subprocess_run, MockClient): + """ + Tests the scenario where blob.upload_from_filename fails. + Verifies that os.remove is still called (cleanup). + """ + mock_blob = mock.MagicMock() + mock_blob.exists.return_value = False + mock_blob.upload_from_filename.side_effect = Exception("GCS upload error") # Simulate upload failure + mock_bucket = mock.MagicMock() + mock_bucket.blob.return_value = mock_blob + MockClient.return_value.get_bucket.return_value = mock_bucket + # Mock os.path.exists for the finally block in the function + with mock.patch('os.path.exists', return_value=True): + bucket_name = "test-bucket" + total_files = 1 + file_size_gb = 1 + + read_single_thread.check_and_create_files(bucket_name, total_files, file_size_gb) + + mock_subprocess_run.assert_called_once() + mock_blob.upload_from_filename.assert_called_once() + mock_os_remove.assert_called_once() # Should be called for cleanup + mock_print.assert_any_call(mock.ANY) # Check if print was called + mock_print.assert_any_call(f"Error uploading {read_single_thread.FILE_PREFIX}_{file_size_gb}_0.bin to GCS: GCS upload error") + + @mock.patch("read_single_thread.os.remove") + @mock.patch("read_single_thread.os.path.exists", return_value=True) + @mock.patch("read_single_thread.subprocess.run") + @mock.patch("read_single_thread.storage.Client") + def test_file_missing_triggers_creation_and_upload(self, mock_storage_client, mock_subprocess_run, mock_path_exists, mock_os_remove): + mock_bucket = mock.MagicMock() + mock_blob = mock.MagicMock() + mock_blob.exists.return_value = False + mock_blob.size = None + mock_bucket.blob.return_value = mock_blob + mock_storage_client.return_value.get_bucket.return_value = mock_bucket + + read_single_thread.check_and_create_files("test-bucket", total_files=1, file_size_gb=1) + + mock_subprocess_run.assert_called_once() # fallocate + mock_blob.upload_from_filename.assert_called_once() + mock_os_remove.assert_called_once() + + @mock.patch("read_single_thread.os.remove") + @mock.patch("read_single_thread.os.path.exists", return_value=True) + @mock.patch("read_single_thread.subprocess.run") + @mock.patch("read_single_thread.storage.Client") + def test_file_too_small_triggers_upload(self, mock_storage_client, mock_subprocess_run, mock_path_exists, mock_os_remove): + mock_bucket = mock.MagicMock() + mock_blob = mock.MagicMock() + mock_blob.exists.return_value = True + mock_blob.size = 1 * (10**9) - 200 * (2**20) # 200 MiB under expected + mock_bucket.blob.return_value = mock_blob + mock_storage_client.return_value.get_bucket.return_value = mock_bucket + + read_single_thread.check_and_create_files("test-bucket", total_files=1, file_size_gb=1) + + mock_subprocess_run.assert_called_once() + mock_blob.upload_from_filename.assert_called_once() + mock_os_remove.assert_called_once() + + @mock.patch("read_single_thread.os.remove") + @mock.patch("read_single_thread.os.path.exists", return_value=True) + @mock.patch("read_single_thread.subprocess.run") + @mock.patch("read_single_thread.storage.Client") + def test_file_equal_file_size_not_trigger_upload(self, mock_storage_client, mock_subprocess_run, mock_path_exists, mock_os_remove): + mock_bucket = mock.MagicMock() + mock_blob = mock.MagicMock() + # Simulate blob already existing with correct size (1 GB) + mock_blob.exists.return_value = True + mock_blob.size = 1 * (10**9) + # Setup bucket and blob mocks + mock_bucket.blob.return_value = mock_blob + mock_storage_client.return_value.get_bucket.return_value = mock_bucket + + # Call function under test + read_single_thread.check_and_create_files("test-bucket", total_files=1, file_size_gb=1) + + # Assert that upload was not triggered + mock_blob.upload_from_filename.assert_not_called() + # subprocess.run should not be called either + mock_subprocess_run.assert_not_called() + + +if __name__ == '__main__': + unittest.main(argv=['first-arg-is-ignored'], exit=False) # For running in environments like notebooks diff --git a/perfmetrics/scripts/micro_benchmarks/requirements.txt b/perfmetrics/scripts/micro_benchmarks/requirements.txt index 3b02d76a02..d09a1d5a71 100644 --- a/perfmetrics/scripts/micro_benchmarks/requirements.txt +++ b/perfmetrics/scripts/micro_benchmarks/requirements.txt @@ -3,3 +3,4 @@ google-cloud-bigquery google-cloud-storage pyarrow pandas_gbq +google-crc32c From c3354b82fda07a066915ff6a57da165a10e5ac75 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 16 May 2025 12:01:06 +0530 Subject: [PATCH 0433/1298] Script to find micro benchmarks for writing file with single thread (#3311) * adding write micro bench script * remove unnecessary param * review comment * formating * exit if any error occured --- .../micro_benchmarks/read_single_thread.py | 3 + .../micro_benchmarks/write_single_thread.py | 135 ++++++++++++++++++ .../write_single_thread_test.py | 83 +++++++++++ 3 files changed, 221 insertions(+) create mode 100644 perfmetrics/scripts/micro_benchmarks/write_single_thread.py create mode 100644 perfmetrics/scripts/micro_benchmarks/write_single_thread_test.py diff --git a/perfmetrics/scripts/micro_benchmarks/read_single_thread.py b/perfmetrics/scripts/micro_benchmarks/read_single_thread.py index 5a60ce984f..04795ce9fc 100644 --- a/perfmetrics/scripts/micro_benchmarks/read_single_thread.py +++ b/perfmetrics/scripts/micro_benchmarks/read_single_thread.py @@ -13,6 +13,7 @@ # limitations under the License. import os +import sys import subprocess import time import argparse @@ -129,6 +130,8 @@ def main(): print(f"Total bytes read from {args.total_files} files: {total_bytes}") except RuntimeError as e: print(f"Failed during file read: {e}") + helper.unmount_gcs_directory(MOUNT_DIR) + sys.exit(1) # Exit with error status duration = time.time() - start # Unmount after test diff --git a/perfmetrics/scripts/micro_benchmarks/write_single_thread.py b/perfmetrics/scripts/micro_benchmarks/write_single_thread.py new file mode 100644 index 0000000000..582e241029 --- /dev/null +++ b/perfmetrics/scripts/micro_benchmarks/write_single_thread.py @@ -0,0 +1,135 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import argparse +import time +import helper +import sys + +MOUNT_DIR = "gcs" +FILE_PREFIX = "testfile" + +def delete_existing_file(file_path): + """ + Deletes the file at the specified path if it exists. + + Args: + file_path (str): The full path to the file to delete. + + Returns: + bool: True if file deleted or didn't exist; False on error. + """ + try: + if os.path.exists(file_path): + os.remove(file_path) + print(f"{file_path} existed and was cleared.") + return True + except Exception as e: + print(f"Error deleting file {file_path}: {e}") + return False + +def write_random_file(file_path, file_size_in_bytes): + """ + Creates a binary file filled with random data of the specified size. + + Args: + file_path (str): The full path where the file should be created. + file_size_in_bytes (int): The size of the file in bytes. + + Returns: + bool: True on success; False on failure. + """ + try: + with open(file_path, 'wb') as f: + f.write(os.urandom(file_size_in_bytes)) + print(f"Created {file_path} of size {file_size_in_bytes / (1000 ** 3):.4f} GB") + return True + except Exception as e: + print(f"Error writing file {file_path}: {e}") + return False + +def create_files(file_paths, file_size_in_gb): + """ + Writes random data to specified file paths, each of the given size in GB. + + Args: + file_paths (list[str]): List of file paths to create. + file_size_in_gb (float): Size of each file in GB (base 10). + + Returns: + int | None: Total bytes written on success, None on failure. + """ + total_bytes_written = 0 + file_size_in_bytes = int(file_size_in_gb * (1000 ** 3)) + + for file_path in file_paths: + try: + success = write_random_file(file_path, 1000 * 1000 * 1000) + if not success: + print("Write failed. Exiting.") + sys.exit(1) + total_bytes_written += file_size_in_bytes + except Exception as e: + print(f"Error creating file {file_path}: {e}") + return None + + print(f"Total bytes written: {total_bytes_written / (1000**3):.4f} GB") + return total_bytes_written + +def main(): + parser = argparse.ArgumentParser(description="Measure GCS write bandwidth via gcsfuse.") + parser.add_argument("--bucket", required=True) + parser.add_argument("--gcsfuse-config", default="--implicit-dirs") + parser.add_argument("--total-files", type=int, default=1) + parser.add_argument("--file-size-gb", type=int, default=15, help="Size of each file in GB") + args = parser.parse_args() + + workflow_type = f"WRITE_{args.total_files}_{args.file_size_gb}GB_SINGLE_THREAD" + helper.mount_bucket(MOUNT_DIR, args.bucket, args.gcsfuse_config) + + # Prepare file paths + file_paths = [ + os.path.join(MOUNT_DIR, f"{FILE_PREFIX}_{args.file_size_gb}_{i}.bin") + for i in range(args.total_files) + ] + + # Delete files if they already exist + for path in file_paths: + success = delete_existing_file(path) + if not success: + print("Delete failed. Exiting.") + sys.exit(1) + + print(f"Starting write of {args.total_files} files...") + start = time.time() + try: + total_bytes = create_files(file_paths, args.file_size_gb) + except RuntimeError as e: + print(f"Failed during file write: {e}") + helper.unmount_gcs_directory(MOUNT_DIR) + sys.exit(1) # Exit with error status + duration = time.time() - start + + helper.unmount_gcs_directory(MOUNT_DIR) + + helper.log_to_bigquery( + duration_sec=duration, + total_bytes=total_bytes, + gcsfuse_config=args.gcsfuse_config, + workload_type=workflow_type, + ) + +if __name__ == "__main__": + main() diff --git a/perfmetrics/scripts/micro_benchmarks/write_single_thread_test.py b/perfmetrics/scripts/micro_benchmarks/write_single_thread_test.py new file mode 100644 index 0000000000..bf4c81f558 --- /dev/null +++ b/perfmetrics/scripts/micro_benchmarks/write_single_thread_test.py @@ -0,0 +1,83 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import unittest +from unittest import mock +from write_single_thread import create_files, delete_existing_file, write_random_file + +class TestWriteFiles(unittest.TestCase): + + @mock.patch("os.path.exists", return_value=True) + @mock.patch("os.remove") + def test_delete_existing_file_success(self, mock_remove, mock_exists): + result = delete_existing_file("/fake/path") + + self.assertTrue(result) + mock_remove.assert_called_once_with("/fake/path") + + @mock.patch("os.path.exists", return_value=True) + @mock.patch("os.remove", side_effect=OSError("Permission denied")) + def test_delete_existing_file_failure(self, mock_remove, mock_exists): + result = delete_existing_file("/fake/path") + + self.assertFalse(result) + mock_remove.assert_called_once_with("/fake/path") + + @mock.patch("os.path.exists", return_value=False) + @mock.patch("os.remove") + def test_delete_existing_file_file_not_exist(self, mock_remove, mock_exists): + # When file doesn't exist, should return True and not call remove + result = delete_existing_file("/fake/path") + + self.assertTrue(result) + mock_remove.assert_not_called() + + @mock.patch("builtins.open", new_callable=mock.mock_open) + @mock.patch("os.urandom", return_value=b'x' * 10) + def test_write_random_file_success(self, mock_urandom, mock_open): + result = write_random_file("/fake/file", 10) + + self.assertTrue(result) + mock_open.assert_called_once_with("/fake/file", "wb") + mock_urandom.assert_called_once_with(10) + + @mock.patch("builtins.open", side_effect=IOError("Disk full")) + def test_write_random_file_failure(self, mock_open): + result = write_random_file("/fake/file", 10) + + self.assertFalse(result) + mock_open.assert_called_once_with("/fake/file", "wb") + + @mock.patch("os.urandom", return_value=b"x" * 10) + @mock.patch("builtins.open", new_callable=mock.mock_open) + def test_create_files_success(self, mock_open_file, mock_urandom): + paths = ["/tmp/file1.bin", "/tmp/file2.bin"] + expected_total = 20 # 2 files * 10 bytes + + total = create_files(paths, file_size_in_gb=1e-8) # ~10 bytes each + + self.assertEqual(total, expected_total) + self.assertEqual(mock_open_file.call_count, 2) + + @mock.patch("builtins.open", side_effect=Exception("write error")) + def test_create_files_failure(self, mock_open_file): + paths = ["/tmp/file1.bin"] + + with self.assertRaises(SystemExit) as cm: + create_files(paths, file_size_in_gb=1e-8) + + self.assertEqual(cm.exception.code, 1) + + +if __name__ == '__main__': + unittest.main(argv=['first-arg-is-ignored'], exit=False) From 47c879d16a4b8f70f7ea53ebd5f60525e4174ba2 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 16 May 2025 12:10:23 +0530 Subject: [PATCH 0434/1298] Create build script for micro benchmark (#3314) * add build script * add continous cfg file --- .../gcp_ubuntu/micro_benchmark/build.sh | 46 +++++++++++++++++++ .../gcp_ubuntu/micro_benchmark/continous.cfg | 22 +++++++++ 2 files changed, 68 insertions(+) create mode 100644 perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/build.sh create mode 100644 perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/continous.cfg diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/build.sh new file mode 100644 index 0000000000..628b39fc34 --- /dev/null +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/build.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -e +sudo apt-get update + +echo "Installing git" +sudo apt-get install git + +cd "${KOKORO_ARTIFACTS_DIR}/github/gcsfuse" +echo "Building and installing gcsfuse" +# Get the latest commitId of yesterday in the log file. Build gcsfuse and run +commitId=$(git log --before='yesterday 23:59:59' --max-count=1 --pretty=%H) +./perfmetrics/scripts/build_and_install_gcsfuse.sh $commitId + +cd "./perfmetrics/scripts/single_threaded_benchmark" + +echo "Installing dependencies..." +python3 -m venv venv +source venv/bin/activate +pip install -r requirements.txt + +echo "Running Python scripts for hns bucket..." +FILE_SIZE_READ_GB=15 +LOG_FILE=${KOKORO_ARTIFACTS_DIR}/gcsfuse-logs-single-threaded-read-${FILE_SIZE_READ_GB}gb-test.txt +GCSFUSE_READ_FLAGS="--log-file $LOG_FILE" +python3 read_single_thread.py --bucket single-threaded-tests --gcsfuse-config "$GCSFUSE_READ_FLAGS" --total-files 10 --file-size-gb ${FILE_SIZE_READ_GB} + +FILE_SIZE_WRITE_GB=15 +LOG_FILE=${KOKORO_ARTIFACTS_DIR}/gcsfuse-logs-single-threaded-write-${FILE_SIZE_WRITE_GB}gb-test.txt +GCSFUSE_WRITE_FLAGS="--log-file $LOG_FILE" +python3 write_single_thread.py --bucket single-threaded-tests --gcsfuse-config "$GCSFUSE_WRITE_FLAGS" --total-files 1 --file-size-gb ${FILE_SIZE_WRITE_GB} + +deactivate diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/continous.cfg b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/continous.cfg new file mode 100644 index 0000000000..60b5eea02b --- /dev/null +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/continous.cfg @@ -0,0 +1,22 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +action { + define_artifacts { + regex: "gcsfuse-logs-single-threaded*" + strip_prefix: "github/gcsfuse/perfmetrics/scripts" + } +} + +build_file: "gcsfuse/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/build.sh" From 5be0caa8dfe2fb4ecd613ab781cb9bd5af29a7ed Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 16 May 2025 12:13:00 +0530 Subject: [PATCH 0435/1298] [testing-on-gke] Add retry for failed insert_rows in FioBqExporter (#3282) * add retry on failed insert_rows in BQ table * Add more unit tests for bq_utils * handle some else-cases in error-handling * address self-review comments * address some self-review comments * undo retrying inserting stopped rows * try inserting rows one-by-one on failure * modify negative test * s/result/error/g --- .../testing_on_gke/examples/fio/bq_utils.py | 42 +++++- .../examples/fio/bq_utils_test.py | 121 ++++++++++++++---- 2 files changed, 134 insertions(+), 29 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py index 33df6469fc..30ac35c89e 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py @@ -195,6 +195,44 @@ def _setup_dataset_and_tables(self): print(f'Failed to create fio table {self.table_id}: {e}') raise + def _num_rows(self) -> int: + """Returns total number of rows in the current BQ table.""" + query = ( + f'select {FIO_TABLE_ROW_SCHEMA[0]} from' + f' {self.project_id}.{self.dataset_id}.{self.table_id}' + ) + results = self.client.query_and_wait(query) + return results.total_rows if results else 0 + + def _insert_rows_with_retry(self, table, rows_to_insert: []): + """Inserts given rows to the given table in a single transaction. + + If the transaction fails, it tries inserting all the rows in rows_to_insert + one by one. + + This function is a wrapper over BQ client insert_rows function. + + Arguments: + + table: A BQ table handle. + rows_to_insert: A list of tuples to insert rows into the above BQ table. + """ + # Call insert_rows on BQ client. If all goes well, error will be None. + error = self.client.insert_rows(table, rows_to_insert) + if error: + # As a fallback, try inserting all rows one-by-one. + print( + 'Some rows failed to insert using insert_rows.\n Error:' + f' {error}.\n Will now try to insert each row one by one.' + ) + for row_to_insert in rows_to_insert: + error = self.client.insert_rows(table, [row_to_insert]) + if error: + print( + 'Warning: Failed to insert the following row even on retry.' + f'\n row: {repr(row_to_insert)}\n Error: {error}' + ) + def insert_rows(self, fioTableRows: []): """Pass a list of FioTableRow objects to insert into the fio-table. @@ -227,9 +265,7 @@ def insert_rows(self, fioTableRows: []): # into the table. table = self._get_table_from_table_id(self.table_id) try: - result = self.client.insert_rows(table, rows_to_insert) - if result: - raise Exception(f'{result}') + self._insert_rows_with_retry(table, rows_to_insert) except Exception as e: raise Exception( 'Error inserting data to BigQuery table' diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py index 8c7bcc46f3..1d5fb52cb1 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py @@ -14,9 +14,16 @@ """This file defines tests for functionalities in bq_utils.py""" +import calendar +import copy +from random import randrange, uniform +import sys +import time import unittest + +sys.path.append('../') +from utils.utils import unix_to_timestamp from bq_utils import FioBigqueryExporter, FioTableRow, Timestamp -import utils class BqUtilsTest(unittest.TestCase): @@ -26,51 +33,113 @@ def setUpClass(self): self.bq_project_id = 'gcs-fuse-test-ml' self.bq_dataset_id = 'gargnitin_test_gke_test_tool_outputs' self.bq_table_id = 'fio_outputs_test' - - def test_create_bq_table(self): # Create a sample table for manual testing. - fioBqExporter = FioBigqueryExporter( + self.fioBqExporter = FioBigqueryExporter( self.bq_project_id, self.bq_dataset_id, self.bq_table_id ) - # sample append call. - rows = [] + @classmethod + def cur_epoch(self) -> int: + return int(calendar.timegm(time.gmtime())) + + @classmethod + def cur_timestamp(self) -> Timestamp: + return unix_to_timestamp(self.cur_epoch()) + @classmethod + def create_sample_fio_table_row(self): row = FioTableRow() - row.fio_workload_id = 'fio-workload-id-1' - row.experiment_id = 'expt-id-1' + row.fio_workload_id = 'fio-workload-{randrange(10000000000)}' + row.experiment_id = f'expt-{self.cur_epoch()}' row.epoch = 1 row.file_size = '1M' row.file_size_in_bytes = 2**20 row.block_size = '256K' row.block_size_in_bytes = 2**18 row.bucket_name = 'sample-zb-bucket' - row.duration_in_seconds = 10 - row.e2e_latency_ns_max = 100 - row.e2e_latency_ns_p50 = 50 - row.e2e_latency_ns_p90 = 90 - row.e2e_latency_ns_p99 = 99 - row.e2e_latency_ns_p99_9 = 99.9 - row.end_epoch = 1746678693 - row.start_epoch = 1746678683 + row.e2e_latency_ns_p50 = randrange(1, 1000) + row.e2e_latency_ns_p90 = randrange(row.e2e_latency_ns_p50, 10000) + row.e2e_latency_ns_p99 = randrange(row.e2e_latency_ns_p90, 100000) + row.e2e_latency_ns_p99_9 = randrange(row.e2e_latency_ns_p99, 1000000) + row.e2e_latency_ns_max = randrange(row.e2e_latency_ns_p99_9, 10000000) + row.start_epoch = self.cur_epoch() + row.duration_in_seconds = randrange(1, 60) + row.end_epoch = row.start_epoch + row.duration_in_seconds row.files_per_thread = 20000 row.gcsfuse_mount_options = 'implicit-dirs' - row.highest_cpu_usage = 10.0 - row.lowest_cpu_usage = 1.0 - row.highest_memory_usage = 10000 - row.lowest_memory_usage = 100 - row.iops = 1000 + row.lowest_cpu_usage = uniform(1.0, 100.0) + row.highest_cpu_usage = uniform(row.lowest_cpu_usage, 100.0) + row.lowest_memory_usage = uniform(10.0, 10000.0) + row.highest_memory_usage = uniform(row.lowest_memory_usage, 10000.0) + row.iops = uniform(10.0, 10000.0) row.machine_type = 'n2-standard-32' row.num_threads = 50 row.operation = 'read' - row.pod_name = 'sample-pod-name' + row.pod_name = f'sample-pod-name-{randrange(100000000000)}' row.scenario = 'gcsfuse-generic' - row.start_time = Timestamp('2025-05-08 04:31:23 UTC') - row.end_time = Timestamp('2025-05-08 04:31:33 UTC') - row.throughput_in_mbps = 8000 + row.start_time = self.cur_timestamp() + row.end_time = row.start_time + row.throughput_in_mbps = uniform(1.0, 10000.0) + return row + + def test_insert_multiple_rows(self): + rows = [] + orig_num_rows = self.fioBqExporter._num_rows() + + rowCommon = self.create_sample_fio_table_row() + + row = copy.deepcopy(rowCommon) + row.fio_workload_id = f'fio_workload1_{row.experiment_id}' + row.epoch = 1 + row.start_time = Timestamp('2025-05-09 08:31 UTC') + row.end_time = row.start_time + rows.append(row) + row = copy.deepcopy(row) + row.fio_workload_id = f'fio_workload2_{row.experiment_id}' + row.epoch = 1 + row.start_time = Timestamp('2025-05-09 08:32 UTC') + row.end_time = row.start_time rows.append(row) - fioBqExporter.insert_rows(rows) + + self.fioBqExporter.insert_rows(rows) + self.assertEqual(self.fioBqExporter._num_rows(), orig_num_rows + 2) + + def test_insert_rows_with_one_bad_row(self): + rows = [] + orig_num_rows = self.fioBqExporter._num_rows() + + rowCommon = self.create_sample_fio_table_row() + + row = copy.deepcopy(rowCommon) + num_rows = 20 + for i in range(num_rows): + row = copy.deepcopy(row) + row.fio_workload_id = f'fio_workload{i}_{row.experiment_id}' + row.epoch = 1 + if i == 0: + # First row is bad row because of empty start_time and end_time. + row.start_time = Timestamp('') + row.end_time = Timestamp('') + else: + row.start_time = self.cur_timestamp() + row.end_time = row.start_time + rows.append(row) + + # Despite bad row(s), the insert_rows itself will not fail + # because of the fallback in insert_rows. + self.fioBqExporter.insert_rows(rows) + self.assertEqual( + self.fioBqExporter._num_rows(), orig_num_rows + num_rows - 1 + ) + + def test_num_rows(self): + row = self.create_sample_fio_table_row() + orig_num_rows = self.fioBqExporter._num_rows() + + self.fioBqExporter.insert_rows([row]) + + self.assertEqual(self.fioBqExporter._num_rows(), orig_num_rows + 1) if __name__ == '__main__': From b0a2f7b9d0e330ab719ef16e65addc775bc5b136 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 16 May 2025 14:09:46 +0530 Subject: [PATCH 0436/1298] Add start time in table for micro benchmark (#3316) * add start time in ist * small fixes --- .../{micro_benchmark => micro_benchmarks}/build.sh | 2 +- .../continous.cfg | 2 +- perfmetrics/scripts/micro_benchmarks/helper.py | 10 +++++----- perfmetrics/scripts/micro_benchmarks/helper_test.py | 4 +++- .../scripts/micro_benchmarks/read_single_thread.py | 3 ++- .../scripts/micro_benchmarks/write_single_thread.py | 3 ++- 6 files changed, 14 insertions(+), 10 deletions(-) rename perfmetrics/scripts/continuous_test/gcp_ubuntu/{micro_benchmark => micro_benchmarks}/build.sh (97%) rename perfmetrics/scripts/continuous_test/gcp_ubuntu/{micro_benchmark => micro_benchmarks}/continous.cfg (96%) diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh similarity index 97% rename from perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/build.sh rename to perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh index 628b39fc34..2c710ed4c1 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh @@ -25,7 +25,7 @@ echo "Building and installing gcsfuse" commitId=$(git log --before='yesterday 23:59:59' --max-count=1 --pretty=%H) ./perfmetrics/scripts/build_and_install_gcsfuse.sh $commitId -cd "./perfmetrics/scripts/single_threaded_benchmark" +cd "./perfmetrics/scripts/micro_benchmarks" echo "Installing dependencies..." python3 -m venv venv diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/continous.cfg b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/continous.cfg similarity index 96% rename from perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/continous.cfg rename to perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/continous.cfg index 60b5eea02b..2ea8471574 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/continous.cfg +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/continous.cfg @@ -19,4 +19,4 @@ action { } } -build_file: "gcsfuse/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmark/build.sh" +build_file: "gcsfuse/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh" diff --git a/perfmetrics/scripts/micro_benchmarks/helper.py b/perfmetrics/scripts/micro_benchmarks/helper.py index 3036f684df..7bb2563ba0 100644 --- a/perfmetrics/scripts/micro_benchmarks/helper.py +++ b/perfmetrics/scripts/micro_benchmarks/helper.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from datetime import datetime, timezone +from datetime import datetime from google.cloud import bigquery import os import subprocess @@ -79,7 +79,7 @@ def unmount_gcs_directory(mount_point: str) -> bool: return False -def log_to_bigquery(duration_sec: float, total_bytes: int, gcsfuse_config: str, workload_type: str) -> None: +def log_to_bigquery(start_time_sec: float, duration_sec: float, total_bytes: int, gcsfuse_config: str, workload_type: str) -> None: """Logs performance metrics to a BigQuery table. This function calculates bandwidth, creates a pandas DataFrame with the @@ -88,7 +88,7 @@ def log_to_bigquery(duration_sec: float, total_bytes: int, gcsfuse_config: str, this query can be used to create it: CREATE TABLE `your-project-id.benchmark_results.gcsfuse_benchmarks` ( - timestamp TIMESTAMP, + start_time TIMESTAMP, duration_seconds FLOAT64, bandwidth_mbps FLOAT64, gcsfuse_config STRING, @@ -111,14 +111,14 @@ def log_to_bigquery(duration_sec: float, total_bytes: int, gcsfuse_config: str, table_ref = client.dataset(DATASET_ID).table(TABLE_ID) df = pd.DataFrame([{ - "timestamp": datetime.now(timezone.utc), + "start_time": datetime.fromtimestamp(start_time_sec), "duration_seconds": duration_sec, "bandwidth_mbps": bandwidth_mbps, "gcsfuse_config": gcsfuse_config, "workload_type": workload_type, }]) - df['timestamp'] = pd.to_datetime(df['timestamp']) + df['start_time'] = pd.to_datetime(df['start_time']) df['duration_seconds'] = df['duration_seconds'].astype(float) df['bandwidth_mbps'] = df['bandwidth_mbps'].astype(float) diff --git a/perfmetrics/scripts/micro_benchmarks/helper_test.py b/perfmetrics/scripts/micro_benchmarks/helper_test.py index 2ebda8beb8..f95033fa9b 100644 --- a/perfmetrics/scripts/micro_benchmarks/helper_test.py +++ b/perfmetrics/scripts/micro_benchmarks/helper_test.py @@ -54,6 +54,7 @@ def test_unmount_failure(self, mock_run): @patch("helper.bigquery.Client") # Only patch bigquery.Client def test_log_to_bigquery(self, mock_bq_client): duration = 10 + start_time = 0 total_bytes = 100 * 1000 * 1000 # 100 MB flags = "--implicit-dirs" workload_type = "write" @@ -63,7 +64,7 @@ def test_log_to_bigquery(self, mock_bq_client): mock_dataset = mock_bq_instance.dataset.return_value mock_bq_instance.load_table_from_dataframe.return_value.result.return_value = None - helper.log_to_bigquery(duration, total_bytes, flags, workload_type) + helper.log_to_bigquery(start_time, duration, total_bytes, flags, workload_type) mock_bq_instance.dataset.assert_called_with("benchmark_results") mock_dataset.table.assert_called_with("gcsfuse_benchmarks") @@ -83,6 +84,7 @@ def test_log_to_bigquery_failure(self, mock_client_cls): with self.assertRaises(Exception) as context: helper.log_to_bigquery( + start_time_sec=0, duration_sec=10.0, total_bytes=100_000_000, gcsfuse_config="--implicit-dirs", diff --git a/perfmetrics/scripts/micro_benchmarks/read_single_thread.py b/perfmetrics/scripts/micro_benchmarks/read_single_thread.py index 04795ce9fc..887527f4f1 100644 --- a/perfmetrics/scripts/micro_benchmarks/read_single_thread.py +++ b/perfmetrics/scripts/micro_benchmarks/read_single_thread.py @@ -114,8 +114,8 @@ def main(): parser.add_argument("--total-files", type=int, default=10, help="Number of files to read") parser.add_argument("--file-size-gb", type=int, default=15, help="Size of each file in GB") - workflow_type = "READ_{args.total_files}_{args.file_size_gb}GB_SINGLE_THREAD" args = parser.parse_args() + workflow_type = f"READ_{args.total_files}_{args.file_size_gb}GB_SINGLE_THREAD" # Mount the bucket helper.mount_bucket(MOUNT_DIR, args.bucket, args.gcsfuse_config) @@ -139,6 +139,7 @@ def main(): # Log to BigQuery helper.log_to_bigquery( + start_time_sec=start, duration_sec=duration, total_bytes=total_bytes, gcsfuse_config=args.gcsfuse_config, diff --git a/perfmetrics/scripts/micro_benchmarks/write_single_thread.py b/perfmetrics/scripts/micro_benchmarks/write_single_thread.py index 582e241029..7319a1c3fe 100644 --- a/perfmetrics/scripts/micro_benchmarks/write_single_thread.py +++ b/perfmetrics/scripts/micro_benchmarks/write_single_thread.py @@ -76,7 +76,7 @@ def create_files(file_paths, file_size_in_gb): for file_path in file_paths: try: - success = write_random_file(file_path, 1000 * 1000 * 1000) + success = write_random_file(file_path, file_size_in_bytes) if not success: print("Write failed. Exiting.") sys.exit(1) @@ -125,6 +125,7 @@ def main(): helper.unmount_gcs_directory(MOUNT_DIR) helper.log_to_bigquery( + start_time_sec=start, duration_sec=duration, total_bytes=total_bytes, gcsfuse_config=args.gcsfuse_config, From f179664bd7ea16f2f0f7884f098d637c9f767414 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Mon, 19 May 2025 09:40:25 +0530 Subject: [PATCH 0437/1298] integrating takeover API in all bucket layers (#3268) * integrating takeover API in all bucket layers * updating createAppendableObjectWriter method definition * updating createObjectChunkWriterRequest * pass correct request in prefix bucket * removing impl without UTs * update method signature * bucket_handle.go changes --- internal/gcsx/content_type_bucket.go | 5 ++++ internal/gcsx/prefix_bucket.go | 5 ++++ internal/monitor/bucket.go | 7 +++++ internal/ratelimit/throttled_bucket.go | 13 +++++++++ internal/storage/bucket_handle.go | 28 ++++++++++++++++++++ internal/storage/caching/fast_stat_bucket.go | 5 ++++ internal/storage/debug_bucket.go | 9 +++++++ internal/storage/fake/bucket.go | 5 ++++ internal/storage/gcs/bucket.go | 6 +++++ internal/storage/gcs/request.go | 14 ++++++++++ internal/storage/mock/testify_mock_bucket.go | 8 ++++++ internal/storage/mock_bucket.go | 5 ++++ internal/storage/testify_mock_bucket.go | 8 ++++++ 13 files changed, 118 insertions(+) diff --git a/internal/gcsx/content_type_bucket.go b/internal/gcsx/content_type_bucket.go index 093066c218..ebd8395841 100644 --- a/internal/gcsx/content_type_bucket.go +++ b/internal/gcsx/content_type_bucket.go @@ -67,3 +67,8 @@ func (b contentTypeBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs // Pass on the request. return b.Bucket.CreateObjectChunkWriter(ctx, req, chunkSize, callBack) } + +func (b contentTypeBucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (gcs.Writer, error) { + //TODO: Implementation + return nil, nil +} diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index 65a2c245cb..60dd5282ff 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -110,6 +110,11 @@ func (b *prefixBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cre return wc, err } +func (b *prefixBucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (gcs.Writer, error) { + //TODO: Implementation + return nil, nil +} + func (b *prefixBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { o, err = b.wrapped.FinalizeUpload(ctx, w) // Modify the returned object. diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index a3d1f8d365..b6fb3b9b6f 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -93,6 +93,13 @@ func (mb *monitoringBucket) CreateObjectChunkWriter(ctx context.Context, req *gc return wc, err } +func (mb *monitoringBucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (gcs.Writer, error) { + startTime := time.Now() + wc, err := mb.wrapped.CreateAppendableObjectWriter(ctx, req) + recordRequest(ctx, mb.metricHandle, "CreateAppendableObjectWriter", startTime) + return wc, err +} + func (mb *monitoringBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { startTime := time.Now() o, err := mb.wrapped.FinalizeUpload(ctx, w) diff --git a/internal/ratelimit/throttled_bucket.go b/internal/ratelimit/throttled_bucket.go index e548961210..fd79793128 100644 --- a/internal/ratelimit/throttled_bucket.go +++ b/internal/ratelimit/throttled_bucket.go @@ -107,6 +107,19 @@ func (b *throttledBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs. return } +func (b *throttledBucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (wc gcs.Writer, err error) { + // Wait for permission to call through. + err = b.opThrottle.Wait(ctx, 1) + if err != nil { + return + } + + // Call through. + wc, err = b.wrapped.CreateAppendableObjectWriter(ctx, req) + + return +} + func (b *throttledBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { // FinalizeUpload is not throttled to prevent permanent data loss in case the // limiter's burst size is exceeded. diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 9750272868..d6213de7f0 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -242,6 +242,34 @@ func (bh *bucketHandle) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cr return wc, nil } +func (bh *bucketHandle) CreateAppendableObjectWriter(ctx context.Context, + req *gcs.CreateObjectChunkWriterRequest) (gcs.Writer, error) { + obj := bh.getObjectHandleWithPreconditionsSet(&req.CreateObjectRequest) + callBack := func(bytesUploadedSoFar int64) { + logger.Tracef("gcs: Req %#16x: -- UploadBlock(%q): %20v bytes uploaded so far", ctx.Value(gcs.ReqIdField), req.Name, bytesUploadedSoFar) + } + + opts := storage.AppendableWriterOpts{ + ChunkSize: req.ChunkSize, + ProgressFunc: callBack, + FinalizeOnClose: false, + } + + tw, off, err := obj.NewWriterFromAppendableObject(ctx, &opts) // Takeover writer tw created from offset off. + + if err != nil { + err = fmt.Errorf("Error while creating appendable object writer : %w", err) + return nil, err + } + + if off != req.Offset { + err = fmt.Errorf("takeover offset for the created appendable object writer does not match the requested offset") + return nil, err + } + w := &ObjectWriter{tw} + return w, err +} + func (bh *bucketHandle) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { defer func() { err = gcs.GetGCSError(err) diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index a16cb0942d..9ad3737d14 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -240,6 +240,11 @@ func (b *fastStatBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.C return b.wrapped.CreateObjectChunkWriter(ctx, req, chunkSize, callBack) } +func (b *fastStatBucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (gcs.Writer, error) { + //TODO: implementation + return nil, nil +} + func (b *fastStatBucket) FinalizeUpload(ctx context.Context, writer gcs.Writer) (*gcs.MinObject, error) { name := writer.ObjectName() // Throw away any existing record for this object. diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index e98042c02a..04ef1a20fe 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -181,6 +181,15 @@ func (b *debugBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.Crea return } +func (b *debugBucket) CreateAppendableObjectWriter(ctx context.Context, + req *gcs.CreateObjectChunkWriterRequest) (wc gcs.Writer, err error) { + id, desc, start := b.startRequest("CreateAppendableObjectWriter(%q, %d)", req.Name, req.Offset) + defer b.finishRequest(id, desc, start, &err) + + wc, err = b.wrapped.CreateAppendableObjectWriter(context.WithValue(ctx, gcs.ReqIdField, id), req) + return +} + func (b *debugBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { id, desc, start := b.startRequest("FinalizeUpload(%q)", w.ObjectName()) defer b.finishRequest(id, desc, start, &err) diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index ed987b2cf2..486bfa23c4 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -691,6 +691,11 @@ func (b *bucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.CreateObj return NewFakeObjectWriter(b, req) } +func (b *bucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (gcs.Writer, error) { + //TODO implement it + return nil, nil +} + func (b *bucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { b.mu.Lock() defer b.mu.Unlock() diff --git a/internal/storage/gcs/bucket.go b/internal/storage/gcs/bucket.go index a0b1893646..28afd7cee7 100644 --- a/internal/storage/gcs/bucket.go +++ b/internal/storage/gcs/bucket.go @@ -102,6 +102,12 @@ type Bucket interface { // writer is closed (object is finalised). CreateObjectChunkWriter(ctx context.Context, req *CreateObjectRequest, chunkSize int, callBack func(bytesUploadedSoFar int64)) (Writer, error) + // CreateAppendableObjectWriter creates a *storage.Writer to an object which has been + // partially flushed to GCS, but not finalized. All bytes written will be appended + // continuing from the offset passed via the CreateObjectChunkWriterRequest. + CreateAppendableObjectWriter(ctx context.Context, + req *CreateObjectChunkWriterRequest) (Writer, error) + // FinalizeUpload closes the storage.Writer which completes the write // operation and creates an object on GCS. FinalizeUpload(ctx context.Context, writer Writer) (*MinObject, error) diff --git a/internal/storage/gcs/request.go b/internal/storage/gcs/request.go index 42696e1ac5..37b7b7a3aa 100644 --- a/internal/storage/gcs/request.go +++ b/internal/storage/gcs/request.go @@ -422,3 +422,17 @@ type MoveObjectRequest struct { // current meta-generation for the source object is equal to the given value. SrcMetaGenerationPrecondition *int64 } + +// CreateObjectChunkWriterRequest represents a request to create a storage.Writer +// which can be used for appendable object writes via the CreateAppendableObjectWriter +// method. +type CreateObjectChunkWriterRequest struct { + CreateObjectRequest + + // Size of each chunk to be uploaded to GCS + ChunkSize int + + // Offset from where write has to start. Used only in case of appends flows. + // Default value is zero which means it's a new object write. + Offset int64 +} diff --git a/internal/storage/mock/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go index ea131076d5..a1eaec2d09 100644 --- a/internal/storage/mock/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -60,6 +60,14 @@ func (m *TestifyMockBucket) CreateObjectChunkWriter(ctx context.Context, req *gc return args.Get(0).(gcs.Writer), nil } +func (m *TestifyMockBucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (wc gcs.Writer, err error) { + args := m.Called(ctx, req) + if args.Get(1) != nil { + return nil, args.Error(1) + } + return args.Get(0).(gcs.Writer), nil +} + func (m *TestifyMockBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { args := m.Called(ctx, w) return args.Get(0).(*gcs.MinObject), args.Error(1) diff --git a/internal/storage/mock_bucket.go b/internal/storage/mock_bucket.go index 21310cccd7..c524e8bcdc 100644 --- a/internal/storage/mock_bucket.go +++ b/internal/storage/mock_bucket.go @@ -160,6 +160,11 @@ func (m *mockBucket) CreateObjectChunkWriter(p0 context.Context, p1 *gcs.CreateO return } +func (m *mockBucket) CreateAppendableObjectWriter(p0 context.Context, p1 *gcs.CreateObjectChunkWriterRequest) (o0 gcs.Writer, o1 error) { + //TODO + return +} + func (m *mockBucket) FinalizeUpload(p0 context.Context, p1 gcs.Writer) (o0 *gcs.MinObject, o1 error) { // Get a file name and line number for the caller. _, file, line, _ := runtime.Caller(1) diff --git a/internal/storage/testify_mock_bucket.go b/internal/storage/testify_mock_bucket.go index 792a7f3ac8..61c67cc2ca 100644 --- a/internal/storage/testify_mock_bucket.go +++ b/internal/storage/testify_mock_bucket.go @@ -60,6 +60,14 @@ func (m *TestifyMockBucket) CreateObjectChunkWriter(ctx context.Context, req *gc return args.Get(0).(gcs.Writer), nil } +func (m *TestifyMockBucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (wc gcs.Writer, err error) { + args := m.Called(ctx, req) + if args.Get(1) != nil { + return nil, args.Error(1) + } + return args.Get(0).(gcs.Writer), nil +} + func (m *TestifyMockBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { args := m.Called(ctx, w.ObjectName()) return args.Get(0).(*gcs.MinObject), args.Error(1) From cad95da35596acf093eea09ccd47342f75106f8d Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 19 May 2025 10:03:30 +0530 Subject: [PATCH 0438/1298] [testing-on-gke] Don't re-insert entries for already inserted experiment_id to the BQ table (#3304) This detects if the data for the given experiment_id is already present in the BQ table, and skips inserting rows for it if so. This is dependent on https://github.com/GoogleCloudPlatform/gcsfuse/pull/3282 for unit test code. b/412923482 * add private utility _has_experiment_id in FioBqExporter * dont re-insert rows for the same experiment_id * add test for has_experiment_id function * add experiment_id check for all rows * add unit tests for experiment_id mismatch check * fix comments * add keyword Warning in log on duplicate insertion * split test test_has_experiment_id into positive and negative * dont pass experiment_id in insert_rows --- .../testing_on_gke/examples/fio/bq_utils.py | 44 ++++++++++++++++++- .../examples/fio/bq_utils_test.py | 32 ++++++++++++++ 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py index 30ac35c89e..f091712049 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils.py @@ -148,7 +148,7 @@ class FioBigqueryExporter(ExperimentsGCSFuseBQ): tables table_id (str): The name of the bigquery table configurations and output metrics will be stored. - bq_client (google.cloud.bigquery.client.Client): The client for interacting + client (google.cloud.bigquery.client.Client): The client for interacting with Bigquery. Default value is bigquery.Client(project=project_id). """ @@ -204,6 +204,16 @@ def _num_rows(self) -> int: results = self.client.query_and_wait(query) return results.total_rows if results else 0 + def _has_experiment_id(self, experiment_id: str) -> bool: + """Returns true if the current BQ table has any rows for the given experiment_id.""" + query = ( + 'select count(*) as num_rows from' + f' {self.project_id}.{self.dataset_id}.{self.table_id} where' + f" experiment_id='{experiment_id}' group by experiment_id" + ) + results = self.client.query_and_wait(query) + return results and results.total_rows > 0 + def _insert_rows_with_retry(self, table, rows_to_insert: []): """Inserts given rows to the given table in a single transaction. @@ -237,6 +247,8 @@ def insert_rows(self, fioTableRows: []): """Pass a list of FioTableRow objects to insert into the fio-table. This inserts all the given rows of data in a single transaction. + It is expected and verified that all the rows being inserted have the same + experiment_id (determined from the first row). Arguments: @@ -246,10 +258,38 @@ def insert_rows(self, fioTableRows: []): Exception: If some row insertion failed. """ - # Edge-case. + # Edge-cases. if fioTableRows is None or len(fioTableRows) == 0: return + # Confirm that all the rows being inserted have the same experiment_id. + # Future improvement: If there are rows with K different experiment_id + # values, then divide the rows into K batches each with homogeneous + # experiment_id and then insert only those batches whose experiment_id's + # are not there in the table already. + experiment_id = fioTableRows[0].experiment_id + if not experiment_id: + raise Exception('experiment_id is null for first row') + # Confirm that all the rows for insertion have the correct experiment_id. + for row in fioTableRows: + if row.experiment_id != experiment_id: + raise Exception( + 'There is a mismatch in the experiment_id for a row. Expected:' + f' {experiment_id}, Got: {row.experiment_id}' + ) + + # If this experiment_id already has rows in the BQ table, then don't insert + # the rows passed here to avoid duplicate entries in the + # table. + if self._has_experiment_id(experiment_id): + print( + 'Warning: Bigquery table' + f' {self.project_id}.{self.dataset_id}.{self.table_id} already has' + f' the experiment_id {experiment_id}, so skipping inserting rows for' + ' it..' + ) + return + # Create a list of tuples from the given list of FioTableRow objects. # Each tuple should have the values for each row in the # same order as in FIO_TABLE_ROW_SCHEMA. diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py index 1d5fb52cb1..671f702ef6 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/bq_utils_test.py @@ -141,6 +141,38 @@ def test_num_rows(self): self.assertEqual(self.fioBqExporter._num_rows(), orig_num_rows + 1) + def test_has_experiment_id(self): + row = self.create_sample_fio_table_row() + + self.fioBqExporter.insert_rows([row]) + + # _has_experiment_id should return true for an experiment_id + # which has already been added to the table. + self.assertTrue(self.fioBqExporter._has_experiment_id(row.experiment_id)) + + def test_has_experiment_id_negative(self): + # _has_experiment_id should return false for an experiment_id + # which has not been added to the table. + self.assertFalse( + self.fioBqExporter._has_experiment_id('invalid-experiment-id') + ) + + def test_insert_rows_with_unset_experiment_id(self): + row = self.create_sample_fio_table_row() + row.experiment_id = None + + with self.assertRaises(Exception): + self.fioBqExporter.insert_rows([row]) + + def test_insert_rows_with_mismatched_experiment_ids(self): + row1 = self.create_sample_fio_table_row() + row1.experiment_id = 'expt-id-1' + row2 = copy.deepcopy(row1) + row2.experiment_id = 'expt-id-2' + + with self.assertRaises(Exception): + self.fioBqExporter.insert_rows([row1, row2]) + if __name__ == '__main__': unittest.main() From 1b3292f9de4fdf47cc887e994ee231a8b7610271 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Mon, 19 May 2025 11:37:27 +0530 Subject: [PATCH 0439/1298] add chunk transfer timeout for streaming writes (HTTP only) (#3318) --- internal/bufferedwrites/upload_handler.go | 3 +-- internal/bufferedwrites/upload_handler_test.go | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index b4aae3ce2d..d502ebfcac 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -102,8 +102,7 @@ func (uh *UploadHandler) Upload(block block.Block) error { // createObjectWriter creates a GCS object writer. func (uh *UploadHandler) createObjectWriter() (err error) { - // TODO: b/381479965: Dynamically set chunkTransferTimeoutSecs based on chunk size. 0 here means no timeout. - req := gcs.NewCreateObjectRequest(uh.obj, uh.objectName, nil, 0) + req := gcs.NewCreateObjectRequest(uh.obj, uh.objectName, nil, uh.chunkTransferTimeout) // We need a new context here, since the first writeFile() call will be complete // (and context will be cancelled) by the time complete upload is done. var ctx context.Context diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 613e57f120..a7cbaf34bb 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -360,7 +360,7 @@ func (t *UploadHandlerTest) TestCreateObjectChunkWriterIsCalledWithCorrectReques *req.MetaGenerationPrecondition == t.uh.obj.MetaGeneration && req.ContentEncoding == t.uh.obj.ContentEncoding && req.ContentType == t.uh.obj.ContentType && - req.ChunkTransferTimeoutSecs == 0 + req.ChunkTransferTimeoutSecs == chunkTransferTimeoutSecs }), mock.Anything, mock.Anything).Return(writer, nil) @@ -386,7 +386,7 @@ func (t *UploadHandlerTest) TestCreateObjectChunkWriterIsCalledWithCorrectReques return req.Name == t.uh.objectName && *req.GenerationPrecondition == 0 && req.MetaGenerationPrecondition == nil && - req.ChunkTransferTimeoutSecs == 0 + req.ChunkTransferTimeoutSecs == chunkTransferTimeoutSecs }), mock.Anything, mock.Anything).Return(writer, nil) From 42f148b580c14bb2ec8e31ad677812d841c62228 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 19 May 2025 08:07:09 +0000 Subject: [PATCH 0440/1298] Limit blocks for streaming writes; fallback to staged writes (#3306) * limit block creation based on global max blocks * resource management update for sync file * lint fix --- internal/block/block_pool.go | 18 ++- internal/block/block_pool_test.go | 103 +++++++++++------- .../bufferedwrites/buffered_write_handler.go | 8 +- .../buffered_write_handler_test.go | 2 +- internal/fs/inode/file.go | 7 +- ...ile_handle_streaming_writes_common_test.go | 3 + 6 files changed, 92 insertions(+), 49 deletions(-) diff --git a/internal/block/block_pool.go b/internal/block/block_pool.go index 52b81e864b..7ba258ecc6 100644 --- a/internal/block/block_pool.go +++ b/internal/block/block_pool.go @@ -15,11 +15,14 @@ package block import ( + "errors" "fmt" "golang.org/x/sync/semaphore" ) +var CantAllocateAnyBlockError error = errors.New("cant allocate any streaming write block as global max blocks limit is reached") + // BlockPool handles the creation of blocks as per the user configuration. type BlockPool struct { // Channel holding free blocks. @@ -53,7 +56,12 @@ func NewBlockPool(blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphor totalBlocks: 0, globalMaxBlocksSem: globalMaxBlocksSem, } - return + semAcquired := bp.globalMaxBlocksSem.TryAcquire(1) + if !semAcquired { + return nil, CantAllocateAnyBlockError + } + + return bp, nil } // Get returns a block. It returns an existing block if it's ready for reuse or @@ -89,7 +97,8 @@ func (bp *BlockPool) canAllocateBlock() bool { return false } - // Always allow allocation if this is the first block for the file. + // Always allow allocation if this is the first block for the file since it has been reserved at + // the time of block pool creation. if bp.totalBlocks == 0 { return true } @@ -109,7 +118,7 @@ func (bp *BlockPool) BlockSize() int64 { return bp.blockSize } -func (bp *BlockPool) ClearFreeBlockChannel() error { +func (bp *BlockPool) ClearFreeBlockChannel(releaseLastBlock bool) error { for { select { case b := <-bp.freeBlocksCh: @@ -119,7 +128,8 @@ func (bp *BlockPool) ClearFreeBlockChannel() error { return fmt.Errorf("munmap error: %v", err) } bp.totalBlocks-- - if bp.totalBlocks != 0 { + // Release semaphore for last block iff releaseLastBlock is true. + if bp.totalBlocks != 0 || releaseLastBlock { bp.globalMaxBlocksSem.Release(1) } default: diff --git a/internal/block/block_pool_test.go b/internal/block/block_pool_test.go index 6c85eabbeb..c346fe7581 100644 --- a/internal/block/block_pool_test.go +++ b/internal/block/block_pool_test.go @@ -130,49 +130,78 @@ func (t *BlockPoolTest) TestBlockSize() { } func (t *BlockPoolTest) TestClearFreeBlockChannel() { - bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(3)) - require.Nil(t.T(), err) - blocks := make([]Block, 4) - for i := 0; i < 4; i++ { - blocks[i] = t.validateGetBlockIsNotBlocked(bp) + tests := []struct { + name string + releaseLastBlock bool + possibleSemaphoreAcquire int64 + }{ + { + name: "release_last_block_true", + releaseLastBlock: true, + possibleSemaphoreAcquire: 4, + }, + { + name: "release_last_block_false", + releaseLastBlock: false, + possibleSemaphoreAcquire: 3, + }, } - // Adding 2 blocks to freeBlocksCh - bp.freeBlocksCh <- blocks[0] - bp.freeBlocksCh <- blocks[1] - require.Equal(t.T(), int64(4), bp.totalBlocks) - err = bp.ClearFreeBlockChannel() + for _, tt := range tests { + t.Run(tt.name, func() { + bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(4)) + require.Nil(t.T(), err) + blocks := make([]Block, 4) + for i := 0; i < 4; i++ { + blocks[i] = t.validateGetBlockIsNotBlocked(bp) + } + // Adding all blocks to freeBlocksCh + for i := 0; i < 4; i++ { + bp.freeBlocksCh <- blocks[i] + } + require.Equal(t.T(), int64(4), bp.totalBlocks) - require.Nil(t.T(), err) - require.Equal(t.T(), int64(2), bp.totalBlocks) - require.Nil(t.T(), blocks[0].(*memoryBlock).buffer) - require.Nil(t.T(), blocks[1].(*memoryBlock).buffer) - require.NotNil(t.T(), blocks[2].(*memoryBlock).buffer) - require.NotNil(t.T(), blocks[3].(*memoryBlock).buffer) - // Check if semaphore is released correctly. - require.True(t.T(), bp.globalMaxBlocksSem.TryAcquire(2)) - require.False(t.T(), bp.globalMaxBlocksSem.TryAcquire(1)) + err = bp.ClearFreeBlockChannel(tt.releaseLastBlock) + + require.Nil(t.T(), err) + require.EqualValues(t.T(), 0, bp.totalBlocks) + for i := 0; i < 4; i++ { + require.Nil(t.T(), blocks[i].(*memoryBlock).buffer) + } + // Check if semaphore is released correctly. + require.True(t.T(), bp.globalMaxBlocksSem.TryAcquire(tt.possibleSemaphoreAcquire)) + require.False(t.T(), bp.globalMaxBlocksSem.TryAcquire(1)) + }) + } } -func (t *BlockPoolTest) TestFirstBlockIsCreatedWithoutAcquiringGlobalSem() { - bp, err := NewBlockPool(1024, 3, semaphore.NewWeighted(0)) +func (t *BlockPoolTest) TestBlockPoolCreationAcquiresGlobalSem() { + globalBlocksSem := semaphore.NewWeighted(1) + + bp, err := NewBlockPool(1024, 3, globalBlocksSem) + require.Nil(t.T(), err) + // Validate that semaphore got acquired. + acquired := globalBlocksSem.TryAcquire(1) + assert.False(t.T(), acquired) + // Validate that 1st block can be created as it was reserved. b1, err := bp.Get() require.Nil(t.T(), err) require.NotNil(t.T(), b1) - // Adding block to freeBlocksCh + // Validate that adding block to freeBlocksCh and clearing it up releases the semaphore bp.freeBlocksCh <- b1 require.Equal(t.T(), int64(1), bp.totalBlocks) - - err = bp.ClearFreeBlockChannel() - + err = bp.ClearFreeBlockChannel(true) require.Nil(t.T(), err) require.Equal(t.T(), int64(0), bp.totalBlocks) require.Nil(t.T(), b1.(*memoryBlock).buffer) + // Validate that semaphore can be acquired now. + acquired = globalBlocksSem.TryAcquire(1) + assert.True(t.T(), acquired) } func (t *BlockPoolTest) TestClearFreeBlockChannelWithMultipleBlockPools() { - globalMaxBlocksSem := semaphore.NewWeighted(1) + globalMaxBlocksSem := semaphore.NewWeighted(3) bp1, err := NewBlockPool(1024, 3, globalMaxBlocksSem) require.Nil(t.T(), err) bp2, err := NewBlockPool(1024, 3, globalMaxBlocksSem) @@ -187,7 +216,7 @@ func (t *BlockPoolTest) TestClearFreeBlockChannelWithMultipleBlockPools() { // Freeing up bp1. bp1.freeBlocksCh <- b1 bp1.freeBlocksCh <- b2 - err = bp1.ClearFreeBlockChannel() + err = bp1.ClearFreeBlockChannel(true) require.Nil(t.T(), err) require.Nil(t.T(), b1.(*memoryBlock).buffer) require.Nil(t.T(), b2.(*memoryBlock).buffer) @@ -199,33 +228,29 @@ func (t *BlockPoolTest) TestClearFreeBlockChannelWithMultipleBlockPools() { // Freeing up bp2. bp2.freeBlocksCh <- b3 bp2.freeBlocksCh <- b4 - err = bp2.ClearFreeBlockChannel() + err = bp2.ClearFreeBlockChannel(true) require.Nil(t.T(), err) require.Nil(t.T(), b3.(*memoryBlock).buffer) require.Nil(t.T(), b4.(*memoryBlock).buffer) } -func (t *BlockPoolTest) TestGetWhenGlobalMaxBlocksIsZero() { +func (t *BlockPoolTest) TestBlockPoolCreationFailsWhenGlobalMaxBlocksIsZero() { bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(0)) - require.Nil(t.T(), err) - // First block is allowed even with globalMaxBlocks being zero. - b1, err := bp.Get() - require.Nil(t.T(), err) - require.NotNil(t.T(), b1) - // We shouldn't be allowed to create another block. - t.validateGetBlockIsBlocked(bp) + require.Error(t.T(), err) + assert.Nil(t.T(), bp) + assert.ErrorContains(t.T(), err, CantAllocateAnyBlockError.Error()) } func (t *BlockPoolTest) TestGetWhenLimitedByGlobalBlocks() { bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(2)) require.Nil(t.T(), err) - // 3 blocks can be created. - for i := 0; i < 3; i++ { + // 2 blocks can be created. + for i := 0; i < 2; i++ { _ = t.validateGetBlockIsNotBlocked(bp) } - require.Equal(t.T(), int64(3), bp.totalBlocks) + require.Equal(t.T(), int64(2), bp.totalBlocks) t.validateGetBlockIsBlocked(bp) } diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index ceea019f02..e8a6c27d02 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -206,7 +206,7 @@ func (wh *bufferedWriteHandlerImpl) Sync() (o *gcs.MinObject, err error) { } } // Release memory used by buffers. - err = wh.blockPool.ClearFreeBlockChannel() + err = wh.blockPool.ClearFreeBlockChannel(false) if err != nil { // Only logging an error in case of resource leak as upload succeeded. logger.Errorf("blockPool.ClearFreeBlockChannel() failed during sync: %v", err) @@ -245,7 +245,7 @@ func (wh *bufferedWriteHandlerImpl) Flush() (*gcs.MinObject, error) { return nil, fmt.Errorf("BufferedWriteHandler.Flush(): %w", err) } - err = wh.blockPool.ClearFreeBlockChannel() + err = wh.blockPool.ClearFreeBlockChannel(true) if err != nil { // Only logging an error in case of resource leak as upload succeeded. logger.Errorf("blockPool.ClearFreeBlockChannel() failed: %v", err) @@ -276,7 +276,7 @@ func (wh *bufferedWriteHandlerImpl) WriteFileInfo() WriteFileInfo { func (wh *bufferedWriteHandlerImpl) Destroy() error { wh.uploadHandler.Destroy() - return wh.blockPool.ClearFreeBlockChannel() + return wh.blockPool.ClearFreeBlockChannel(true) } func (wh *bufferedWriteHandlerImpl) writeDataForTruncatedSize() error { @@ -303,7 +303,7 @@ func (wh *bufferedWriteHandlerImpl) writeDataForTruncatedSize() error { func (wh *bufferedWriteHandlerImpl) Unlink() { wh.uploadHandler.CancelUpload() - err := wh.blockPool.ClearFreeBlockChannel() + err := wh.blockPool.ClearFreeBlockChannel(true) if err != nil { // Only logging an error in case of resource leak. logger.Errorf("blockPool.ClearFreeBlockChannel() failed: %v", err) diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 9a4495781d..ddcaca500e 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -333,7 +333,7 @@ func (testSuite *BufferedWriteTest) TestSyncPartialBlockTableDriven() { err = testSuite.bwh.Write(buffer, 0) require.Nil(testSuite.T(), err) - // Wait for 3 blocks to upload successfully. + // Wait for blocks to upload successfully. o, err := testSuite.bwh.Sync() require.NoError(testSuite.T(), err) diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 53b9910f3c..5243ff41fb 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -24,6 +24,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/block" "github.com/googlecloudplatform/gcsfuse/v2/internal/bufferedwrites" "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" @@ -620,7 +621,7 @@ func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, o // Fall back to temp file for Out-Of-Order Writes. if errors.Is(err, bufferedwrites.ErrOutOfOrderWrite) { - logger.Infof("Out-of-order write detected. Falling back to temporary file on disk.") + logger.Infof("Falling back to staged writes on disk for file %s (inode %d) due to err: %v.", f.Name(), f.ID(), err.Error()) // Finalize the object. err = f.flushUsingBufferedWriteHandler() if err != nil { @@ -1003,6 +1004,10 @@ func (f *FileInode) InitBufferedWriteHandlerIfEligible(ctx context.Context) (boo GlobalMaxBlocksSem: f.globalMaxWriteBlocksSem, ChunkTransferTimeoutSecs: f.config.GcsRetries.ChunkTransferTimeoutSecs, }) + if errors.Is(err, block.CantAllocateAnyBlockError) { + logger.Warnf("writes will fall back to staged writes due to err: %v. Please increase block limit using --write-global-max-blocks mount option.", block.CantAllocateAnyBlockError.Error()) + return false, nil + } if err != nil { return false, fmt.Errorf("failed to create bufferedWriteHandler: %w", err) } diff --git a/internal/fs/stale_file_handle_streaming_writes_common_test.go b/internal/fs/stale_file_handle_streaming_writes_common_test.go index 04b70e1164..c917595d6a 100644 --- a/internal/fs/stale_file_handle_streaming_writes_common_test.go +++ b/internal/fs/stale_file_handle_streaming_writes_common_test.go @@ -15,6 +15,8 @@ package fs_test import ( + "math" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" @@ -40,6 +42,7 @@ func (t *staleFileHandleStreamingWritesCommon) SetupSuite() { serverCfg.Write.EnableStreamingWrites = true serverCfg.Write.BlockSizeMb = operations.MiB serverCfg.Write.MaxBlocksPerFile = 1 + serverCfg.Write.GlobalMaxBlocks = math.MaxInt t.serverCfg.NewConfig = serverCfg t.mountCfg.DisableWritebackCaching = true From addae8be927920358562fae41b5e3a0137b2c497 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Mon, 19 May 2025 13:49:32 +0530 Subject: [PATCH 0441/1298] Adding takeover API implementation to fake bucket (#3290) * updating createAppendableObjectWriter method definition * adding takeover impl to fake bucket  Conflicts:  internal/storage/fake/bucket.go * content type bucket tests for appendable writer * prefix bucket tests success case * prefix and content type bkt implementation * removing references to Finalized Attributes * nits --- internal/gcsx/content_type_bucket.go | 9 +++- internal/gcsx/content_type_bucket_test.go | 29 +++++++++++ internal/gcsx/prefix_bucket.go | 13 ++++- internal/gcsx/prefix_bucket_test.go | 59 +++++++++++++++++++++++ internal/storage/fake/bucket.go | 10 +++- 5 files changed, 114 insertions(+), 6 deletions(-) diff --git a/internal/gcsx/content_type_bucket.go b/internal/gcsx/content_type_bucket.go index ebd8395841..ac81f39748 100644 --- a/internal/gcsx/content_type_bucket.go +++ b/internal/gcsx/content_type_bucket.go @@ -69,6 +69,11 @@ func (b contentTypeBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs } func (b contentTypeBucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (gcs.Writer, error) { - //TODO: Implementation - return nil, nil + // Guess a content type if necessary. + if req.ContentType == "" { + req.ContentType = mime.TypeByExtension(path.Ext(req.Name)) + } + + // Pass on the request. + return b.Bucket.CreateAppendableObjectWriter(ctx, req) } diff --git a/internal/gcsx/content_type_bucket_test.go b/internal/gcsx/content_type_bucket_test.go index 7ac918fe23..3aff9319b2 100644 --- a/internal/gcsx/content_type_bucket_test.go +++ b/internal/gcsx/content_type_bucket_test.go @@ -129,6 +129,35 @@ func TestContentTypeBucket_CreateObjectChunkWriter(t *testing.T) { } } +func TestContentTypeBucket_CreateAppendableObjectWriter(t *testing.T) { + for i, tc := range contentTypeBucketTestCases { + // Set up a bucket. + bucket := gcsx.NewContentTypeBucket( + fake.NewFakeBucket(timeutil.RealClock(), "", gcs.BucketType{})) + + // Create the object writer request. + req := &gcs.CreateObjectChunkWriterRequest{ + CreateObjectRequest: gcs.CreateObjectRequest{ + Name: tc.name, + ContentType: tc.request, + }, + Offset: 100, + ChunkSize: 1024, + } + + w, err := bucket.CreateAppendableObjectWriter(context.Background(), req) + if err != nil { + t.Fatalf("Test case %d: CreateObjectChunkWriter: %v", i, err) + } + + // Check the content type. + writerImpl := w.(*fake.FakeObjectWriter) + if got, want := writerImpl.ContentType, tc.expected; got != want { + t.Errorf("Test case %d: o.ContentType is %q, want %q", i, got, want) + } + } +} + func TestContentTypeBucket_ComposeObjects(t *testing.T) { var err error ctx := context.Background() diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index 60dd5282ff..b1fdeadb31 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -111,8 +111,17 @@ func (b *prefixBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cre } func (b *prefixBucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (gcs.Writer, error) { - //TODO: Implementation - return nil, nil + // Modify the request and call through. + mReq := new(gcs.CreateObjectChunkWriterRequest) + *mReq = *req + mReq.Name = b.wrappedName(req.Name) + + wc, err := b.wrapped.CreateAppendableObjectWriter(ctx, mReq) + if err != nil { + return nil, err + } + + return wc, err } func (b *prefixBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { diff --git a/internal/gcsx/prefix_bucket_test.go b/internal/gcsx/prefix_bucket_test.go index 60105a64fd..aaa31b2cb7 100644 --- a/internal/gcsx/prefix_bucket_test.go +++ b/internal/gcsx/prefix_bucket_test.go @@ -364,6 +364,65 @@ func (t *PrefixBucketTest) TestCreateObjectChunkWriterAndFlushPendingWrites() { assert.Equal(t.T(), string(content), string(actual)) } +func (t *PrefixBucketTest) TestCreateAppendableObjectWriterAndFlush() { + var err error + suffix := "taco" + content := []byte("foobar") + + // Create the object writer. + w, err := t.bucket.CreateAppendableObjectWriter( + t.ctx, + &gcs.CreateObjectChunkWriterRequest{ + CreateObjectRequest: gcs.CreateObjectRequest{ + Name: suffix, + }, + ChunkSize: 1024, + Offset: 10, + }) + assert.NoError(t.T(), err) + assert.NotNil(t.T(), w) + _, err = w.Write(content) + assert.NoError(t.T(), err) + o, err := t.bucket.FlushPendingWrites(t.ctx, w) + + assert.NoError(t.T(), err) + assert.EqualValues(t.T(), int64(len(content)), o.Size) + // Read it through the back door. + actual, err := storageutil.ReadObject(t.ctx, t.wrapped, t.prefix+suffix) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), string(content), string(actual)) +} + +func (t *PrefixBucketTest) TestCreateAppendableObjectWriterAndClose() { + var err error + suffix := "taco" + content := []byte("foobar") + + // Create the object writer. + w, err := t.bucket.CreateAppendableObjectWriter( + t.ctx, + &gcs.CreateObjectChunkWriterRequest{ + CreateObjectRequest: gcs.CreateObjectRequest{ + Name: suffix, + }, + ChunkSize: 1024, + Offset: 10, + }) + assert.NoError(t.T(), err) + assert.NotNil(t.T(), w) + _, err = w.Write(content) + assert.NoError(t.T(), err) + o, err := t.bucket.FinalizeUpload(t.ctx, w) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), suffix, o.Name) + + // Read it through the back door. + actual, err := storageutil.ReadObject(t.ctx, t.wrapped, t.prefix+suffix) + assert.Equal(t.T(), nil, err) + assert.Equal(t.T(), string(content), string(actual)) +} + func (t *PrefixBucketTest) Test_CopyObject() { var err error suffix := "taco" diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index 486bfa23c4..bb0d2f9eae 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -692,8 +692,14 @@ func (b *bucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.CreateObj } func (b *bucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (gcs.Writer, error) { - //TODO implement it - return nil, nil + index := b.objects.find(req.Name) + if index != len(b.objects) { + obj := b.objects[index] + if obj.metadata.Generation == 0 { + return nil, fmt.Errorf("storage: ObjectHandle.Generation must be set to use NewWriterFromAppendableObject") + } + } + return NewFakeObjectWriter(b, &req.CreateObjectRequest) } func (b *bucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (*gcs.MinObject, error) { From 65df5cdea3ed0d265acfececf718380164393684 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 19 May 2025 15:39:15 +0530 Subject: [PATCH 0442/1298] update file name (#3322) --- .../gcp_ubuntu/micro_benchmarks/{continous.cfg => continuous.cfg} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/{continous.cfg => continuous.cfg} (100%) diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/continous.cfg b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/continuous.cfg similarity index 100% rename from perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/continous.cfg rename to perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/continuous.cfg From e20336301911162815bb881e8318a3720ae412e3 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 20 May 2025 09:22:59 +0530 Subject: [PATCH 0443/1298] Extend inactive-reader-timeout for http (#3325) * Extend inactive-reader-timeout for http * lint fix --- cfg/rationalize.go | 8 -------- cfg/rationalize_test.go | 45 ----------------------------------------- cmd/root_test.go | 2 +- 3 files changed, 1 insertion(+), 54 deletions(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index f95b5cfabd..a49dd797d5 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -121,13 +121,6 @@ func resolveParallelDownloadsValue(v isSet, fc *FileCacheConfig, c *Config) { } } -func resolveReadConfig(c *Config, r *ReadConfig) { - // Only enable for GRPC client protocol. - if c.GcsConnection.ClientProtocol != GRPC { - r.InactiveStreamTimeout = 0 - } -} - // Rationalize updates the config fields based on the values of other fields. func Rationalize(v isSet, c *Config, optimizedFlags []string) error { var err error @@ -148,7 +141,6 @@ func Rationalize(v isSet, c *Config, optimizedFlags []string) error { resolveStatCacheMaxSizeMB(v, &c.MetadataCache, optimizedFlags) resolveCloudMetricsUploadIntervalSecs(&c.Metrics) resolveParallelDownloadsValue(v, &c.FileCache, c) - resolveReadConfig(c, &c.Read) return nil } diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 5b62d96186..e142ee1a82 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -20,7 +20,6 @@ import ( "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) type mockIsSet struct{} @@ -580,47 +579,3 @@ func TestRationalize_ParallelDownloadsConfig(t *testing.T) { }) } } - -func TestRationalizeReadInactiveTimeoutConfig(t *testing.T) { - t.Parallel() - testCases := []struct { - name string - config *Config - expectedTimeout time.Duration - }{ - { - name: "http_client_protocol", - config: &Config{ - Read: ReadConfig{ - InactiveStreamTimeout: 60 * time.Second, - }, - GcsConnection: GcsConnectionConfig{ - ClientProtocol: HTTP1, - }, - }, - expectedTimeout: 0, - }, - { - name: "grpc_client_protocol", - config: &Config{ - Read: ReadConfig{ - InactiveStreamTimeout: 60 * time.Second, - }, - GcsConnection: GcsConnectionConfig{ - ClientProtocol: GRPC, - }, - }, - expectedTimeout: 60 * time.Second, - }, - } - - for _, tc := range testCases { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - require.NoError(t, Rationalize(&mockIsSet{}, tc.config, []string{})) - assert.EqualValues(t, tc.expectedTimeout, tc.config.Read.InactiveStreamTimeout) - }) - } -} diff --git a/cmd/root_test.go b/cmd/root_test.go index b22b7b4774..9f355f443c 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1297,7 +1297,7 @@ func TestArgsParsing_ReadInactiveTimeoutConfig(t *testing.T) { { name: "override_default", cfgFile: "override.yaml", - expectedTimeout: 0, + expectedTimeout: 30 * time.Second, }, { name: "override_with_grpc", From 0e15123c232873807eb3dcbad0b863252d7f0c81 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 20 May 2025 09:49:18 +0530 Subject: [PATCH 0444/1298] E2E test for inactiveStreamTimeout Support (#3285) * E2E test for inactiveStreamTimeout Support * review comments --- .../inactive_stream_timeout/setup_test.go | 202 ++++++++++++++++++ .../with_timeout_test.go | 141 ++++++++++++ .../without_timeout_test.go | 112 ++++++++++ .../run_tests_mounted_directory.sh | 26 +++ .../read_logs/json_read_log_parser.go | 20 ++ .../json_parser/read_logs/structure.go | 10 + 6 files changed, 511 insertions(+) create mode 100644 tools/integration_tests/inactive_stream_timeout/setup_test.go create mode 100644 tools/integration_tests/inactive_stream_timeout/with_timeout_test.go create mode 100644 tools/integration_tests/inactive_stream_timeout/without_timeout_test.go diff --git a/tools/integration_tests/inactive_stream_timeout/setup_test.go b/tools/integration_tests/inactive_stream_timeout/setup_test.go new file mode 100644 index 0000000000..2ef759bcd1 --- /dev/null +++ b/tools/integration_tests/inactive_stream_timeout/setup_test.go @@ -0,0 +1,202 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package inactive_stream_timeout + +import ( + "context" + "fmt" + "io" + "log" + "os" + "path" + "strings" + "testing" + "time" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/stretchr/testify/require" +) + +const ( + kTestDirName = "inactiveReadTimeoutTest" + kOnlyDirMounted = "onlyDirInactiveReadTimeout" + kFileSize = 10 * 1024 * 1024 // 10 MiB + kChunkSizeToRead = 128 * 1024 // 64 KiB + kTestFileName = "foo" + kDefaultInactiveReadTimeoutInSeconds = 1 // A short timeout for testing + kLogFileNameForMountedDirectoryTests = "/tmp/inactive_stream_timeout_logs/log.json" + kHTTP1ClientProtocol = "http1" + kGRPCClientProtocol = "grpc" +) + +var ( + // Stores test directory path in the mounted path. + gTestDirPath string + + // Used to run the test for different types of mount by modify this function. + gMountFunc func([]string) error + + // Actual mounted directory, for dynamic mount it becomes gRootDir/ + gMountDir string + + // Root directory which is mounted by gcsfuse. + gRootDir string + + // Clients to create the object in GCS. + gStorageClient *storage.Client + gCtx context.Context +) + +type gcsfuseTestFlags struct { + cliFlags []string + inactiveReadTimeout time.Duration + fileName string + clientProtocol string +} + +func setupFile(ctx context.Context, storageClient *storage.Client, fileName string, fileSize int, t *testing.T) string { + t.Helper() + client.SetupFileInTestDirectory(ctx, storageClient, kTestDirName, fileName, int64(fileSize), t) + return path.Join(gTestDirPath, fileName) +} + +func loadLogLines(reader io.Reader) ([]string, error) { + content, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + return strings.Split(string(content), "\n"), nil +} + +// validateInactiveReaderClosedLog checks if the "Closing reader for object ... due to inactivity" +// log message is present (or absent) for the given objectName, b/w the [startTime, endTime] interval. +// Also expects based on the shouldBePresent value. +func validateInactiveReaderClosedLog(t *testing.T, logFile, objectName string, shouldBePresent bool, startTime, endTime time.Time) { + t.Helper() + // Be specific about the object name in the expected message. + expectedMsgSubstring := fmt.Sprintf("Closing reader for object %q due to inactivity.", objectName) + + file, err := os.Open(logFile) + require.NoError(t, err, "Failed to open log file") + defer file.Close() + + logLines, err := loadLogLines(file) + require.NoError(t, err, "Failed to read log file") + + found := false + for _, line := range logLines { + logEntry, err := read_logs.ParseJsonLogLineIntoLogEntryStruct(line) // Assuming read_logs can parse general log lines too or a more generic parser is available. + // If parsing fails, it might be a non-JSON line or a different structured log. + // For this specific message, we expect it to be in the "Message" field of a structured log. + + if err == nil && logEntry != nil { + // Check if the log entry's timestamp is within the expected window. + if (logEntry.Timestamp.After(startTime) || logEntry.Timestamp.Equal(startTime)) && + (logEntry.Timestamp.Before(endTime) || logEntry.Timestamp.Equal(endTime)) { + if strings.Contains(logEntry.Message, expectedMsgSubstring) { + found = true + break + } + } + } + } + + if shouldBePresent { + require.True(t, found, "Expected log message substring '%s' not found between %v and %v", expectedMsgSubstring, startTime, endTime) + } else { + require.False(t, found, "Unexpected log message substring '%s' found between %v and %v", expectedMsgSubstring, startTime, endTime) + } +} + +func mountGCSFuseAndSetupTestDir(ctx context.Context, flags []string, storageClient *storage.Client, testDirName string) { + setup.MountGCSFuseWithGivenMountFunc(flags, gMountFunc) + setup.SetMntDir(gMountDir) + gTestDirPath = client.SetupTestDirectory(ctx, storageClient, testDirName) +} + +// createConfigFile generate mount config.yaml. +func createConfigFile(flags *gcsfuseTestFlags) string { + mountConfig := map[string]interface{}{ + "read": map[string]interface{}{ + "inactive-stream-timeout": flags.inactiveReadTimeout.String(), + }, + "gcs-connection": map[string]interface{}{ + "client-protocol": flags.clientProtocol, + }, + "logging": map[string]interface{}{ + "file-path": setup.LogFile(), + "format": "json", // Ensure JSON logs for easier parsing + }, + } + return setup.YAMLConfigFile(mountConfig, flags.fileName) +} + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + // Create common storage client to be used in test. + gCtx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&gCtx, &gStorageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() + + setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + + if setup.MountedDirectory() != "" { + gMountDir = setup.MountedDirectory() + setup.SetLogFile(kLogFileNameForMountedDirectoryTests) + // Run tests for mounted directory if the flag is set. + os.Exit(m.Run()) + } + + // Else run tests for testBucket. + setup.SetUpTestDirForTestBucketFlag() + + log.Println("Running static mounting tests...") + // MountDir and RootDir will be same for static mount. + gMountDir, gRootDir = setup.MntDir(), setup.MntDir() + gMountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() + + if successCode == 0 { + log.Println("Running dynamic mounting tests...") + // For dyanamic mount - gMountDir = gRootDir + + gMountDir = path.Join(setup.MntDir(), setup.TestBucket()) + gMountFunc = dynamic_mounting.MountGcsfuseWithDynamicMounting + successCode = m.Run() + } + + if successCode == 0 { + log.Println("Running only dir mounting tests...") + setup.SetOnlyDirMounted(kOnlyDirMounted + "/") + gMountDir = gRootDir + gMountFunc = only_dir_mounting.MountGcsfuseWithOnlyDir + successCode = m.Run() + setup.CleanupDirectoryOnGCS(gCtx, gStorageClient, path.Join(setup.TestBucket(), setup.OnlyDirMounted(), kTestDirName)) + } + + setup.CleanupDirectoryOnGCS(gCtx, gStorageClient, path.Join(setup.TestBucket(), kTestDirName)) + os.Exit(successCode) +} diff --git a/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go b/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go new file mode 100644 index 0000000000..fd54003a2b --- /dev/null +++ b/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go @@ -0,0 +1,141 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package inactive_stream_timeout + +import ( + "context" + "log" + "path" + "testing" + "time" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/require" +) + +const DefaultSequentialReadSizeMb = 5 + +type timeoutEnabledSuite struct { + flags []string + storageClient *storage.Client + ctx context.Context +} + +func (s *timeoutEnabledSuite) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(s.ctx, s.flags, s.storageClient, kTestDirName) +} + +func (s *timeoutEnabledSuite) Teardown(t *testing.T) { + setup.SaveGCSFuseLogFileInCaseOfFailure(t) + if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory + setup.UnmountGCSFuseAndDeleteLogFile(gRootDir) + } +} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +func (s *timeoutEnabledSuite) TestReaderCloses(t *testing.T) { + timeoutDuration := kDefaultInactiveReadTimeoutInSeconds * time.Second + gcsFileName := path.Join(kTestDirName, kTestFileName) + mountFilePath := setupFile(s.ctx, s.storageClient, kTestFileName, kFileSize, t) + + // 1. Open file. + fileHandle, err := operations.OpenFileAsReadonly(mountFilePath) + require.NoError(t, err) + defer fileHandle.Close() + + // 2. Read small chunk from 0 offset. + buff := make([]byte, kChunkSizeToRead) + _, err = fileHandle.ReadAt(buff, 0) + require.NoError(t, err) + endTimeRead := time.Now() + + // 3. Wait for timeout + time.Sleep(2*timeoutDuration + 1*time.Second) // Add buffer + endTimeWait := time.Now() + + // 4. "Closing reader" log should be present. + validateInactiveReaderClosedLog(t, setup.LogFile(), gcsFileName, true, endTimeRead, endTimeWait) + + // 5. Further reads should work as it is, yeah it will create a new reader. + _, err = fileHandle.ReadAt(buff, 8) + require.NoError(t, err) +} + +func (s *timeoutEnabledSuite) TestReaderStaysOpenWithinTimeout(t *testing.T) { + timeoutDuration := kDefaultInactiveReadTimeoutInSeconds * time.Second + gcsFileName := path.Join(kTestDirName, kTestFileName) + localFilePath := setupFile(s.ctx, s.storageClient, kTestFileName, kFileSize, t) + + fileHandle, err := operations.OpenFileAsReadonly(localFilePath) + require.NoError(t, err) + defer fileHandle.Close() + + // 1. First read. + buff := make([]byte, kChunkSizeToRead) + _, err = fileHandle.ReadAt(buff, 0) + require.NoError(t, err) + endTimeRead1 := time.Now() + + // 2. Wait for a period SHORTER than the timeout. + time.Sleep(timeoutDuration / 2) + startTimeRead2 := time.Now() + + // 3. Second read. + _, err = fileHandle.ReadAt(buff, int64(kChunkSizeToRead)) // Read the next chunk + require.NoError(t, err, "Second read within timeout failed") + + // 4. Check log: "Closing reader for object..." should NOT be present for this object + // between the first read's end and the second read's start. + validateInactiveReaderClosedLog(t, setup.LogFile(), gcsFileName, false, endTimeRead1, startTimeRead2) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestTimeoutEnabledSuite(t *testing.T) { + ts := &timeoutEnabledSuite{ctx: context.Background(), storageClient: gStorageClient} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + flagsSet := []gcsfuseTestFlags{ + { // Test with timeout enabled and grpc client protocol + inactiveReadTimeout: kDefaultInactiveReadTimeoutInSeconds * time.Second, + fileName: "timeout_with_grpc.yaml", + clientProtocol: kGRPCClientProtocol, + }, + } + + for _, flags := range flagsSet { + configFilePath := createConfigFile(&flags) + ts.flags = []string{"--config-file=" + configFilePath} + if flags.cliFlags != nil { + ts.flags = append(ts.flags, flags.cliFlags...) + } + log.Printf("Running inactive_read_timeout tests with flags: %s", ts.flags) + + test_setup.RunTests(t, ts) + } +} diff --git a/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go b/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go new file mode 100644 index 0000000000..d3cd2112a0 --- /dev/null +++ b/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go @@ -0,0 +1,112 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package inactive_stream_timeout + +import ( + "context" + "log" + "path" + "testing" + "time" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/require" +) + +type timeoutDisabledSuite struct { + flags []string + storageClient *storage.Client + ctx context.Context +} + +func (s *timeoutDisabledSuite) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(s.ctx, s.flags, s.storageClient, kTestDirName) +} + +func (s *timeoutDisabledSuite) Teardown(t *testing.T) { + setup.SaveGCSFuseLogFileInCaseOfFailure(t) + if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory + setup.UnmountGCSFuseAndDeleteLogFile(gRootDir) + } +} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +func (s *timeoutDisabledSuite) TestNoReaderCloser(t *testing.T) { + timeoutDuration := kDefaultInactiveReadTimeoutInSeconds * time.Second + gcsFileName := path.Join(kTestDirName, kTestFileName) + mountFilePath := setupFile(s.ctx, s.storageClient, kTestFileName, kFileSize, t) + + // 1. Open file. + fileHandle, err := operations.OpenFileAsReadonly(mountFilePath) + require.NoError(t, err) + defer fileHandle.Close() + + // 2. Read small chunk from 0 offset. + buff := make([]byte, kChunkSizeToRead) + _, err = fileHandle.ReadAt(buff, 0) + require.NoError(t, err) + endTimeRead := time.Now() + + // 3. Wait for timeout + time.Sleep(2*timeoutDuration + 1*time.Second) // Add buffer + endTimeWait := time.Now() + + // 4. Shouldn't be any `Close reader logs...`. + validateInactiveReaderClosedLog(t, setup.LogFile(), gcsFileName, false, endTimeRead, endTimeWait) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestTimeoutDisabledSuite(t *testing.T) { + ts := &timeoutDisabledSuite{ctx: context.Background(), storageClient: gStorageClient} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + flagsSet := []gcsfuseTestFlags{ + { // Test with timeout enabled and http1 client protocol + inactiveReadTimeout: kDefaultInactiveReadTimeoutInSeconds * time.Second, + fileName: "timeout_with_http.yaml", + clientProtocol: kHTTP1ClientProtocol, + }, + { // Test with timeout disabled + inactiveReadTimeout: 0 * time.Second, // Disable timeout + clientProtocol: kHTTP1ClientProtocol, + fileName: "zero_timeout.yaml", + }, + } + + for _, flags := range flagsSet { + configFilePath := createConfigFile(&flags) + ts.flags = []string{"--config-file=" + configFilePath} + if flags.cliFlags != nil { + ts.flags = append(ts.flags, flags.cliFlags...) + } + log.Printf("Running inactive_read_timeout tests with flags: %s", ts.flags) + + test_setup.RunTests(t, ts) + } +} diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index 744000522b..2fcc7eaa8d 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -626,3 +626,29 @@ sudo umount $MOUNT_DIR gcsfuse --enable-streaming-writes=true $TEST_BUCKET_NAME $MOUNT_DIR GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/write_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR + +# Test package: inactive_stream_timeout +# Run tests when timeout is disabled. +log_dir="/tmp/inactive_stream_timeout_logs" +mkdir -p $log_dir +log_file="$log_dir/log.json" +gcsfuse --read-inactive-stream-timeout=0s --log-file $log_file --log-severity=trace --log-format=json $TEST_BUCKET_NAME $MOUNT_DIR +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/inactive_stream_timeout/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME -run "TestTimeoutDisabledSuite" +sudo umount $MOUNT_DIR +rm -rf $log_dir + +# Run tests when timeout is enabled. +test_cases=( + "TestTimeoutEnabledSuite/TestReaderCloses" + "TestTimeoutEnabledSuite/TestReaderStaysOpenWithinTimeout" +) +for test_case in "${test_cases[@]}"; do + log_dir="/tmp/inactive_stream_timeout_logs" + mkdir -p $log_dir + log_file="$log_dir/log.json" + gcsfuse --read-inactive-stream-timeout=1s --client-protocol grpc --log-file $log_file --log-severity=trace --log-format=json $TEST_BUCKET_NAME $MOUNT_DIR + GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/inactive_stream_timeout/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME -run $test_case + sudo umount $MOUNT_DIR + rm -rf $log_dir +done + diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go index 23dfecfdcd..940da88a94 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go @@ -23,6 +23,7 @@ import ( "sort" "strings" "testing" + "time" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" ) @@ -149,3 +150,22 @@ func GetStructuredLogsSortedByTimestamp(logFilePath string, t *testing.T) []*Str return structuredReadLogs } + +func ParseJsonLogLineIntoLogEntryStruct(jsonLogEntry string) (*LogEntry, error) { + entry := &LogEntry{} + jsonLog := make(map[string]interface{}) + err := json.Unmarshal([]byte(jsonLogEntry), &jsonLog) + if err != nil { + return entry, nil + } + + // Get timestamp from the jsonLog + timestampSeconds := int64(jsonLog["timestamp"].(map[string]interface{})["seconds"].(float64)) + timestampNanos := int64(jsonLog["timestamp"].(map[string]interface{})["nanos"].(float64)) + entry.Timestamp = time.Unix(timestampSeconds, timestampNanos) + + // Normalize whitespace in the log message. + entry.Message = strings.TrimSpace(regexp.MustCompile(`\s+`).ReplaceAllString(jsonLog["message"].(string), " ")) + + return entry, nil +} diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go index 0d1e85d8b8..373bf348a5 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go @@ -14,6 +14,10 @@ package read_logs +import ( + "time" +) + // StructuredReadLogEntry stores the structured format to be created from logs. type StructuredReadLogEntry struct { Handle int64 @@ -64,3 +68,9 @@ type handleAndChunkIndex struct { handle int64 chunkIndex int } + +// LogEntry struct to match the JSON structure +type LogEntry struct { + Timestamp time.Time `json:"time"` + Message string `json:"message"` +} From bd5e05990b0547bedfa3189f138b3fd9b2767c21 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 20 May 2025 05:36:27 +0000 Subject: [PATCH 0445/1298] add tests for fall back to disk case when streaming writes are limited by global blocks (#3312) * add IT * minor typo --- ... => common_streaming_writes_suite_test.go} | 29 ++++++---- ...cs_file_test.go => empty_gcs_file_test.go} | 16 +++--- ..._local_file_test.go => local_file_test.go} | 56 +++++++++++-------- .../streaming_writes/read_file_test.go | 12 ++-- .../streaming_writes/rename_file_test.go | 4 +- ...streaming_writes_test.go => setup_test.go} | 4 +- .../streaming_writes/symlink_file_test.go | 4 +- .../streaming_writes/truncate_file_test.go | 20 ++++--- .../util/client/storage_client.go | 3 + 9 files changed, 84 insertions(+), 64 deletions(-) rename tools/integration_tests/streaming_writes/{default_mount_test.go => common_streaming_writes_suite_test.go} (73%) rename tools/integration_tests/streaming_writes/{default_mount_empty_gcs_file_test.go => empty_gcs_file_test.go} (78%) rename tools/integration_tests/streaming_writes/{default_mount_local_file_test.go => local_file_test.go} (57%) rename tools/integration_tests/streaming_writes/{streaming_writes_test.go => setup_test.go} (89%) diff --git a/tools/integration_tests/streaming_writes/default_mount_test.go b/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go similarity index 73% rename from tools/integration_tests/streaming_writes/default_mount_test.go rename to tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go index 35a6e68726..0f2e55d1df 100644 --- a/tools/integration_tests/streaming_writes/default_mount_test.go +++ b/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go @@ -16,46 +16,51 @@ package streaming_writes import ( "os" + "slices" + "syscall" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_suite" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -type defaultMountCommonTest struct { +type StreamingWritesSuite struct { f1 *os.File fileName string // filePath of the above file in the mounted directory. - filePath string - data string + filePath string + data string + fallbackToDiskCase bool test_suite.TestifySuite } -func (t *defaultMountCommonTest) SetupSuite() { - // TODO(mohitkyadav): Make these part of test suite after refactoring. - SetCtx(ctx) - SetStorageClient(storageClient) - SetTestDirName(testDirName) - +func (t *StreamingWritesSuite) SetupSuite() { + if slices.Contains(flags, "--write-global-max-blocks=0") { + t.fallbackToDiskCase = true + } setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) testDirPath = setup.SetupTestDirectory(testDirName) t.data = setup.GenerateRandomString(5 * util.MiB) } -func (t *defaultMountCommonTest) TearDownSuite() { +func (t *StreamingWritesSuite) TearDownSuite() { setup.UnmountGCSFuse(rootDir) setup.SaveGCSFuseLogFileInCaseOfFailure(t.T()) } -func (t *defaultMountCommonTest) validateReadCall(filePath string) { +func (t *StreamingWritesSuite) validateReadCall(filePath string) { _, err := os.ReadFile(filePath) if setup.IsZonalBucketRun() { // TODO(b/410698332): Remove skip condition once reads start working. t.T().Skip("Skipping Zonal Bucket Read tests.") require.NoError(t.T(), err) + } + if t.fallbackToDiskCase { + require.NoError(t.T(), err) } else { require.Error(t.T(), err) + assert.ErrorContains(t.T(), err, syscall.ENOTSUP.Error()) } } diff --git a/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go b/tools/integration_tests/streaming_writes/empty_gcs_file_test.go similarity index 78% rename from tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go rename to tools/integration_tests/streaming_writes/empty_gcs_file_test.go index edfc8d525e..f0e3864d76 100644 --- a/tools/integration_tests/streaming_writes/default_mount_empty_gcs_file_test.go +++ b/tools/integration_tests/streaming_writes/empty_gcs_file_test.go @@ -24,20 +24,20 @@ import ( "github.com/stretchr/testify/suite" ) -type defaultMountEmptyGCSFile struct { - defaultMountCommonTest +type streamingWritesEmptyGCSFileTestSuite struct { + StreamingWritesSuite suite.Suite } -func (t *defaultMountEmptyGCSFile) SetupTest() { +func (t *streamingWritesEmptyGCSFileTestSuite) SetupTest() { t.createEmptyGCSFile() } -func (t *defaultMountEmptyGCSFile) SetupSubTest() { +func (t *streamingWritesEmptyGCSFileTestSuite) SetupSubTest() { t.createEmptyGCSFile() } -func (t *defaultMountEmptyGCSFile) createEmptyGCSFile() { +func (t *streamingWritesEmptyGCSFileTestSuite) createEmptyGCSFile() { t.fileName = FileName1 + setup.GenerateRandomString(5) // Create an empty file on GCS. CreateObjectInGCSTestDir(ctx, storageClient, testDirName, t.fileName, "", t.T()) @@ -47,8 +47,8 @@ func (t *defaultMountEmptyGCSFile) createEmptyGCSFile() { } // Executes all tests that run with single streamingWrites configuration for empty GCS Files. -func TestDefaultMountEmptyGCSFileTest(t *testing.T) { - s := new(defaultMountEmptyGCSFile) - s.defaultMountCommonTest.TestifySuite = &s.Suite +func TestEmptyGCSFileTestSuiteTest(t *testing.T) { + s := new(streamingWritesEmptyGCSFileTestSuite) + s.StreamingWritesSuite.TestifySuite = &s.Suite suite.Run(t, s) } diff --git a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go b/tools/integration_tests/streaming_writes/local_file_test.go similarity index 57% rename from tools/integration_tests/streaming_writes/default_mount_local_file_test.go rename to tools/integration_tests/streaming_writes/local_file_test.go index 39e6315a31..7d6cb18860 100644 --- a/tools/integration_tests/streaming_writes/default_mount_local_file_test.go +++ b/tools/integration_tests/streaming_writes/local_file_test.go @@ -22,30 +22,23 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_suite" "github.com/stretchr/testify/suite" ) -type defaultMountCommonLocalFile struct { - defaultMountCommonTest +type streamingWritesLocalFileTestSuite struct { + StreamingWritesSuite suite.Suite } -type defaultMountRegionalBucketLocalFile struct { - CommonLocalFileTestSuite - defaultMountCommonLocalFile - test_suite.TestifySuite -} - -func (t *defaultMountCommonLocalFile) SetupTest() { +func (t *streamingWritesLocalFileTestSuite) SetupTest() { t.createLocalFile() } -func (t *defaultMountCommonLocalFile) SetupSubTest() { +func (t *streamingWritesLocalFileTestSuite) SetupSubTest() { t.createLocalFile() } -func (t *defaultMountCommonLocalFile) createLocalFile() { +func (t *streamingWritesLocalFileTestSuite) createLocalFile() { t.fileName = FileName1 + setup.GenerateRandomString(5) t.filePath = path.Join(testDirPath, t.fileName) // Create a local file with O_DIRECT. @@ -53,16 +46,35 @@ func (t *defaultMountCommonLocalFile) createLocalFile() { } // Executes all tests that run with single streamingWrites configuration for localFiles. -func TestDefaultMountLocalFileTest(t *testing.T) { - if setup.IsZonalBucketRun() { - s := new(defaultMountCommonLocalFile) - s.defaultMountCommonTest.TestifySuite = &s.Suite - suite.Run(t, s) - } else { - s := new(defaultMountRegionalBucketLocalFile) - s.defaultMountCommonTest.TestifySuite = &s.defaultMountCommonLocalFile.Suite - s.CommonLocalFileTestSuite.TestifySuite = &s.defaultMountCommonLocalFile.Suite - s.TestifySuite = &s.defaultMountCommonLocalFile.Suite +func TestStreamingWritesLocalFileTestSuite(t *testing.T) { + s := new(streamingWritesLocalFileTestSuite) + s.StreamingWritesSuite.TestifySuite = &s.Suite + suite.Run(t, s) +} + +type existingLocalFileTestSuite struct { + CommonLocalFileTestSuite + suite.Suite +} + +func (t *existingLocalFileTestSuite) SetupSuite() { + SetCtx(ctx) + SetStorageClient(storageClient) + SetTestDirName(testDirName) + + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) +} + +func (t *existingLocalFileTestSuite) TearDownSuite() { + setup.UnmountGCSFuse(rootDir) + setup.SaveGCSFuseLogFileInCaseOfFailure(t.T()) +} + +// Executes all tests that run with single streamingWrites configuration for localFiles. +func TestExistingLocalFileTest(t *testing.T) { + if !setup.IsZonalBucketRun() { + s := new(existingLocalFileTestSuite) + s.CommonLocalFileTestSuite.TestifySuite = &s.Suite suite.Run(t, s) } } diff --git a/tools/integration_tests/streaming_writes/read_file_test.go b/tools/integration_tests/streaming_writes/read_file_test.go index 07f1a91f94..0d8e20ef49 100644 --- a/tools/integration_tests/streaming_writes/read_file_test.go +++ b/tools/integration_tests/streaming_writes/read_file_test.go @@ -23,7 +23,7 @@ import ( "github.com/stretchr/testify/require" ) -func (t *defaultMountCommonTest) TestReadFileAfterSync() { +func (t *StreamingWritesSuite) TestReadFileAfterSync() { // Write some content to the file. _, err := t.f1.WriteAt([]byte(t.data), 0) assert.NoError(t.T(), err) @@ -36,22 +36,18 @@ func (t *defaultMountCommonTest) TestReadFileAfterSync() { CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data, t.T()) } -func (t *defaultMountCommonTest) TestReadBeforeFileIsFlushed() { +func (t *StreamingWritesSuite) TestReadBeforeFileIsFlushed() { // Write data to file. operations.WriteAt(t.data, 0, t.f1, t.T()) // Try to read the file. - _, err := t.f1.Seek(0, 0) - require.NoError(t.T(), err) - buf := make([]byte, len(t.data)) - _, err = t.f1.Read(buf) + t.validateReadCall(t.filePath) - require.Error(t.T(), err, "input/output error") // Validate if correct content is uploaded to GCS after read error. CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data, t.T()) } -func (t *defaultMountCommonTest) TestReadAfterFlush() { +func (t *StreamingWritesSuite) TestReadAfterFlush() { // Write data to file and flush. operations.WriteAt(t.data, 0, t.f1, t.T()) CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data, t.T()) diff --git a/tools/integration_tests/streaming_writes/rename_file_test.go b/tools/integration_tests/streaming_writes/rename_file_test.go index c8935cad87..fbb07fa4c8 100644 --- a/tools/integration_tests/streaming_writes/rename_file_test.go +++ b/tools/integration_tests/streaming_writes/rename_file_test.go @@ -22,7 +22,7 @@ import ( "github.com/stretchr/testify/require" ) -func (t *defaultMountCommonTest) TestRenameBeforeFileIsFlushed() { +func (t *StreamingWritesSuite) TestRenameBeforeFileIsFlushed() { operations.WriteWithoutClose(t.f1, t.data, t.T()) operations.WriteWithoutClose(t.f1, t.data, t.T()) operations.VerifyStatFile(t.filePath, int64(2*len(t.data)), FilePerms, t.T()) @@ -42,7 +42,7 @@ func (t *defaultMountCommonTest) TestRenameBeforeFileIsFlushed() { ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) } -func (t *defaultMountCommonTest) TestSyncAfterRenameSucceeds() { +func (t *StreamingWritesSuite) TestSyncAfterRenameSucceeds() { _, err := t.f1.WriteAt([]byte(t.data), 0) require.NoError(t.T(), err) operations.VerifyStatFile(t.filePath, int64(len(t.data)), FilePerms, t.T()) diff --git a/tools/integration_tests/streaming_writes/streaming_writes_test.go b/tools/integration_tests/streaming_writes/setup_test.go similarity index 89% rename from tools/integration_tests/streaming_writes/streaming_writes_test.go rename to tools/integration_tests/streaming_writes/setup_test.go index 2cfbd81915..5b96493a7b 100644 --- a/tools/integration_tests/streaming_writes/streaming_writes_test.go +++ b/tools/integration_tests/streaming_writes/setup_test.go @@ -66,10 +66,10 @@ func TestMain(m *testing.M) { // Define flag set to run the tests. flagsSet := [][]string{ + {"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2", "--write-global-max-blocks=0"}, + {"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2", "--client-protocol=grpc"}, {"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"}, } - // Run all tests for GRPC. - setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc", "") log.Println("Running static mounting tests...") mountFunc = static_mounting.MountGcsfuseWithStaticMounting diff --git a/tools/integration_tests/streaming_writes/symlink_file_test.go b/tools/integration_tests/streaming_writes/symlink_file_test.go index 3cb8384f85..e3bc919616 100644 --- a/tools/integration_tests/streaming_writes/symlink_file_test.go +++ b/tools/integration_tests/streaming_writes/symlink_file_test.go @@ -24,7 +24,7 @@ import ( "github.com/stretchr/testify/assert" ) -func (t *defaultMountCommonTest) TestCreateSymlinkForLocalFileAndReadFromSymlink() { +func (t *StreamingWritesSuite) TestCreateSymlinkForLocalFileAndReadFromSymlink() { // Create Symlink. symlink := path.Join(testDirPath, setup.GenerateRandomString(5)) operations.CreateSymLink(t.filePath, symlink, t.T()) @@ -40,7 +40,7 @@ func (t *defaultMountCommonTest) TestCreateSymlinkForLocalFileAndReadFromSymlink CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data, t.T()) } -func (t *defaultMountCommonTest) TestReadingFromSymlinkForDeletedLocalFile() { +func (t *StreamingWritesSuite) TestReadingFromSymlinkForDeletedLocalFile() { // Create Symlink. symlink := path.Join(testDirPath, setup.GenerateRandomString(5)) operations.CreateSymLink(t.filePath, symlink, t.T()) diff --git a/tools/integration_tests/streaming_writes/truncate_file_test.go b/tools/integration_tests/streaming_writes/truncate_file_test.go index 37e17b41f3..7d69ed90aa 100644 --- a/tools/integration_tests/streaming_writes/truncate_file_test.go +++ b/tools/integration_tests/streaming_writes/truncate_file_test.go @@ -21,7 +21,7 @@ import ( "github.com/stretchr/testify/require" ) -func (t *defaultMountCommonTest) TestTruncate() { +func (t *StreamingWritesSuite) TestTruncate() { truncateSize := 2 * 1024 * 1024 err := t.f1.Truncate(int64(truncateSize)) @@ -34,7 +34,7 @@ func (t *defaultMountCommonTest) TestTruncate() { CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, string(data[:]), t.T()) } -func (t *defaultMountCommonTest) TestWriteAfterTruncate() { +func (t *StreamingWritesSuite) TestWriteAfterTruncate() { truncateSize := 10 testCases := []struct { @@ -83,7 +83,7 @@ func (t *defaultMountCommonTest) TestWriteAfterTruncate() { } -func (t *defaultMountCommonTest) TestWriteAndTruncate() { +func (t *StreamingWritesSuite) TestWriteAndTruncate() { var truncateSize int64 = 20 operations.WriteWithoutClose(t.f1, FileContents, t.T()) operations.VerifyStatFile(t.filePath, int64(len(FileContents)), FilePerms, t.T()) @@ -98,7 +98,7 @@ func (t *defaultMountCommonTest) TestWriteAndTruncate() { CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents+string(data[:]), t.T()) } -func (t *defaultMountCommonTest) TestWriteTruncateWrite() { +func (t *StreamingWritesSuite) TestWriteTruncateWrite() { truncateSize := 30 // Write @@ -117,14 +117,18 @@ func (t *defaultMountCommonTest) TestWriteTruncateWrite() { CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents+FileContents+string(data[:]), t.T()) } -func (t *defaultMountCommonTest) TestTruncateToLowerSizeAfterWrite() { +func (t *StreamingWritesSuite) TestTruncateToLowerSizeAfterWrite() { // Write operations.WriteWithoutClose(t.f1, FileContents+FileContents, t.T()) operations.VerifyStatFile(t.filePath, int64(2*len(FileContents)), FilePerms, t.T()) // Perform truncate err := t.f1.Truncate(int64(5)) - // Truncating to lower size after writes are not allowed. - require.Error(t.T(), err) - operations.VerifyStatFile(t.filePath, int64(2*len(FileContents)), FilePerms, t.T()) + if t.fallbackToDiskCase { + require.NoError(t.T(), err) + } else { + // Truncating to lower size after writes are not allowed if buffered writes are used. + require.Error(t.T(), err) + operations.VerifyStatFile(t.filePath, int64(2*len(FileContents)), FilePerms, t.T()) + } } diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 1f902a880a..01e6b63339 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -93,6 +93,9 @@ func getTokenSrc(path string) (tokenSrc oauth2.TokenSource, err error) { func ReadObjectFromGCS(ctx context.Context, client *storage.Client, object string) (string, error) { bucket, object := setup.GetBucketAndObjectBasedOnTypeOfMount(object) + if client == nil { + return "", fmt.Errorf("client is nil") + } // Create storage reader to read from GCS. rc, err := client.Bucket(bucket).Object(object).NewReader(ctx) if err != nil { From 84dc1a39e58235d92b18eddf58ade8fa5e0f1217 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 20 May 2025 11:08:04 +0530 Subject: [PATCH 0446/1298] Adding mock bucket changes for takeover API (#3291) * adding takeover impl to fake bucket * removing references to Finalized Attributes * adding implementation  Conflicts:  internal/storage/mock_bucket.go * fast stat bucket tests * fast stat bkt impl --- internal/storage/caching/fast_stat_bucket.go | 3 +- .../storage/caching/fast_stat_bucket_test.go | 66 +++++++++++++++++++ internal/storage/mock_bucket.go | 26 +++++++- 3 files changed, 92 insertions(+), 3 deletions(-) diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index 9ad3737d14..ee8a73fc69 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -241,8 +241,7 @@ func (b *fastStatBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.C } func (b *fastStatBucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (gcs.Writer, error) { - //TODO: implementation - return nil, nil + return b.wrapped.CreateAppendableObjectWriter(ctx, req) } func (b *fastStatBucket) FinalizeUpload(ctx context.Context, writer gcs.Writer) (*gcs.MinObject, error) { diff --git a/internal/storage/caching/fast_stat_bucket_test.go b/internal/storage/caching/fast_stat_bucket_test.go index 90f3ed632e..41f40516fe 100644 --- a/internal/storage/caching/fast_stat_bucket_test.go +++ b/internal/storage/caching/fast_stat_bucket_test.go @@ -210,6 +210,72 @@ func (t *CreateObjectChunkWriterTest) WrappedSucceeds() { ExpectEq(wr, gotWr) } +//////////////////////////////////////////////////////////////////////// +// CreateAppendableObjectWriterTest +//////////////////////////////////////////////////////////////////////// + +type CreateAppendableObjectWriterTest struct { + fastStatBucketTest +} + +func init() { RegisterTestSuite(&CreateAppendableObjectWriterTest{}) } + +func (t *CreateAppendableObjectWriterTest) CallsWrappedWithExpectedParameters() { + const name = "taco" + const offset int64 = 10 + const chunkSize = 1024 + ctx := context.TODO() + // Wrapped + var wrappedReq *gcs.CreateObjectChunkWriterRequest + ExpectCall(t.wrapped, "CreateAppendableObjectWriter")(Any(), Any()). + WillOnce(DoAll(SaveArg(1, &wrappedReq), Return(nil, errors.New("")))) + // Call + req := &gcs.CreateObjectChunkWriterRequest{ + CreateObjectRequest: gcs.CreateObjectRequest{ + Name: name, + }, + Offset: offset, + ChunkSize: chunkSize, + } + + _, _ = t.bucket.CreateAppendableObjectWriter(ctx, req) + + AssertNe(nil, wrappedReq) + ExpectEq(req, wrappedReq) +} + +func (t *CreateAppendableObjectWriterTest) WrappedFails() { + ctx := context.TODO() + req := &gcs.CreateObjectChunkWriterRequest{} + var err error + // Wrapped + ExpectCall(t.wrapped, "CreateAppendableObjectWriter")(Any(), Any()). + WillOnce(Return(nil, errors.New("taco"))) + + // Call + _, err = t.bucket.CreateAppendableObjectWriter(ctx, req) + + ExpectThat(err, Error(HasSubstr("taco"))) +} + +func (t *CreateAppendableObjectWriterTest) WrappedSucceeds() { + ctx := context.TODO() + req := &gcs.CreateObjectChunkWriterRequest{} + var err error + // Wrapped + wr := &storage.ObjectWriter{ + Writer: &gostorage.Writer{}, + } + ExpectCall(t.wrapped, "CreateAppendableObjectWriter")(Any(), Any()). + WillOnce(Return(wr, nil)) + + // Call + gotWr, err := t.bucket.CreateAppendableObjectWriter(ctx, req) + + AssertEq(nil, err) + ExpectEq(wr, gotWr) +} + //////////////////////////////////////////////////////////////////////// // FinalizeUpload //////////////////////////////////////////////////////////////////////// diff --git a/internal/storage/mock_bucket.go b/internal/storage/mock_bucket.go index c524e8bcdc..97b89c4943 100644 --- a/internal/storage/mock_bucket.go +++ b/internal/storage/mock_bucket.go @@ -161,7 +161,31 @@ func (m *mockBucket) CreateObjectChunkWriter(p0 context.Context, p1 *gcs.CreateO } func (m *mockBucket) CreateAppendableObjectWriter(p0 context.Context, p1 *gcs.CreateObjectChunkWriterRequest) (o0 gcs.Writer, o1 error) { - //TODO + // Get a file name and line number for the caller. + _, file, line, _ := runtime.Caller(1) + + // Hand the call off to the controller, which does most of the work. + retVals := m.controller.HandleMethodCall( + m, + "CreateAppendableObjectWriter", + file, + line, + []interface{}{p0, p1}) + + if len(retVals) != 2 { + panic(fmt.Sprintf("mockBucket.CreateAppendableObjectWriter: invalid return values: %v", retVals)) + } + + // o0 storageWriter + if retVals[0] != nil { + o0 = retVals[0].(gcs.Writer) + } + + // o1 error + if retVals[1] != nil { + o1 = retVals[1].(error) + } + return } From cde56af9adfc3c792b5ea5e791a7caa8267f102c Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 20 May 2025 11:37:34 +0530 Subject: [PATCH 0447/1298] adding flag to enable/disable append support (#3323) --- cfg/config.go | 12 ++++ cfg/params.yaml | 7 ++ cmd/root_test.go | 173 ++++++++++++++++++++++++++--------------------- 3 files changed, 115 insertions(+), 77 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index be5a8adf55..2835559f0c 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -256,6 +256,8 @@ type WriteConfig struct { EnableStreamingWrites bool `yaml:"enable-streaming-writes"` + ExperimentalEnableRapidAppends bool `yaml:"experimental-enable-rapid-appends"` + GlobalMaxBlocks int64 `yaml:"global-max-blocks"` MaxBlocksPerFile int64 `yaml:"max-blocks-per-file"` @@ -591,6 +593,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } + flagSet.BoolP("write-experimental-enable-rapid-appends", "", false, "Enables support for appends to unfinalized object using streaming writes") + + if err := flagSet.MarkHidden("write-experimental-enable-rapid-appends"); err != nil { + return err + } + flagSet.IntP("write-global-max-blocks", "", -1, "Specifies the maximum number of blocks to be used by all files for streaming writes. The value should be >= 0 (1 block per file is not counted towards this limit) or -1 (for infinite blocks).") if err := flagSet.MarkHidden("write-global-max-blocks"); err != nil { @@ -952,6 +960,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("write.experimental-enable-rapid-appends", flagSet.Lookup("write-experimental-enable-rapid-appends")); err != nil { + return err + } + if err := v.BindPFlag("write.global-max-blocks", flagSet.Lookup("write-global-max-blocks")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index b0fb1a0708..4e315a2d5c 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -687,6 +687,13 @@ default: false hide-flag: false +- config-path: "write.experimental-enable-rapid-appends" + flag-name: "write-experimental-enable-rapid-appends" + type: "bool" + usage: "Enables support for appends to unfinalized object using streaming writes" + default: false + hide-flag: true + - config-path: "write.global-max-blocks" flag-name: "write-global-max-blocks" type: "int" diff --git a/cmd/root_test.go b/cmd/root_test.go index 9f355f443c..c1c73f3982 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -252,85 +252,104 @@ func TestArgsParsing_ImplicitDirsFlag(t *testing.T) { } func TestArgsParsing_WriteConfigFlags(t *testing.T) { tests := []struct { - name string - args []string - expectedCreateEmptyFile bool - expectedEnableStreamingWrites bool - expectedWriteBlockSizeMB int64 - expectedWriteGlobalMaxBlocks int64 - expectedWriteMaxBlocksPerFile int64 + name string + args []string + expectedCreateEmptyFile bool + expectedEnableStreamingWrites bool + expectedExperimentalEnableRapidAppends bool + expectedWriteBlockSizeMB int64 + expectedWriteGlobalMaxBlocks int64 + expectedWriteMaxBlocksPerFile int64 }{ { - name: "Test create-empty-file flag true.", - args: []string{"gcsfuse", "--create-empty-file=true", "abc", "pqr"}, - expectedCreateEmptyFile: true, - expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test create-empty-file flag false.", - args: []string{"gcsfuse", "--create-empty-file=false", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test default flags.", - args: []string{"gcsfuse", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test enable-streaming-writes flag true.", - args: []string{"gcsfuse", "--enable-streaming-writes", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test enable-streaming-writes flag false.", - args: []string{"gcsfuse", "--enable-streaming-writes=false", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test positive write-block-size-mb flag.", - args: []string{"gcsfuse", "--enable-streaming-writes", "--write-block-size-mb=10", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 10 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test positive write-global-max-blocks flag.", - args: []string{"gcsfuse", "--enable-streaming-writes", "--write-global-max-blocks=10", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: 10, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test positive write-max-blocks-per-file flag.", - args: []string{"gcsfuse", "--enable-streaming-writes", "--write-max-blocks-per-file=10", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, - expectedWriteMaxBlocksPerFile: 10, + name: "Test create-empty-file flag true.", + args: []string{"gcsfuse", "--create-empty-file=true", "abc", "pqr"}, + expectedCreateEmptyFile: true, + expectedEnableStreamingWrites: false, + expectedExperimentalEnableRapidAppends: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test create-empty-file flag false.", + args: []string{"gcsfuse", "--create-empty-file=false", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: false, + expectedExperimentalEnableRapidAppends: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test default flags.", + args: []string{"gcsfuse", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: false, + expectedExperimentalEnableRapidAppends: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test enable-streaming-writes flag true.", + args: []string{"gcsfuse", "--enable-streaming-writes", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedExperimentalEnableRapidAppends: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test enable-streaming-writes flag false.", + args: []string{"gcsfuse", "--enable-streaming-writes=false", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: false, + expectedExperimentalEnableRapidAppends: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test experimental-enable-rapid-appends flag true.", + args: []string{"gcsfuse", "--write-experimental-enable-rapid-appends", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: false, + expectedExperimentalEnableRapidAppends: true, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test positive write-block-size-mb flag.", + args: []string{"gcsfuse", "--enable-streaming-writes", "--write-block-size-mb=10", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedExperimentalEnableRapidAppends: false, + expectedWriteBlockSizeMB: 10 * util.MiB, + expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test positive write-global-max-blocks flag.", + args: []string{"gcsfuse", "--enable-streaming-writes", "--write-global-max-blocks=10", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedExperimentalEnableRapidAppends: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 10, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test positive write-max-blocks-per-file flag.", + args: []string{"gcsfuse", "--enable-streaming-writes", "--write-max-blocks-per-file=10", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedExperimentalEnableRapidAppends: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteMaxBlocksPerFile: 10, }, } From 67cb4a74a47774bcd9669f884d8e7047da19578f Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 20 May 2025 14:41:06 +0530 Subject: [PATCH 0448/1298] Run inactive_stream_timeout test as part of CI/CD (#3326) --- .../inactive_stream_timeout/with_timeout_test.go | 5 +++++ .../inactive_stream_timeout/without_timeout_test.go | 5 ----- tools/integration_tests/run_e2e_tests.sh | 1 + 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go b/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go index fd54003a2b..87b9d352e7 100644 --- a/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go +++ b/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go @@ -121,6 +121,11 @@ func TestTimeoutEnabledSuite(t *testing.T) { } flagsSet := []gcsfuseTestFlags{ + { // Test with timeout enabled and http1 client protocol + inactiveReadTimeout: kDefaultInactiveReadTimeoutInSeconds * time.Second, + fileName: "timeout_with_http.yaml", + clientProtocol: kHTTP1ClientProtocol, + }, { // Test with timeout enabled and grpc client protocol inactiveReadTimeout: kDefaultInactiveReadTimeoutInSeconds * time.Second, fileName: "timeout_with_grpc.yaml", diff --git a/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go b/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go index d3cd2112a0..2a58671d54 100644 --- a/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go +++ b/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go @@ -87,11 +87,6 @@ func TestTimeoutDisabledSuite(t *testing.T) { } flagsSet := []gcsfuseTestFlags{ - { // Test with timeout enabled and http1 client protocol - inactiveReadTimeout: kDefaultInactiveReadTimeoutInSeconds * time.Second, - fileName: "timeout_with_http.yaml", - clientProtocol: kHTTP1ClientProtocol, - }, { // Test with timeout disabled inactiveReadTimeout: 0 * time.Second, // Disable timeout clientProtocol: kHTTP1ClientProtocol, diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 4644b399a5..5ba29cfac1 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -106,6 +106,7 @@ TEST_DIR_PARALLEL=( "stale_handle" "negative_stat_cache" "streaming_writes" + "inactive_stream_timeout" ) # These tests never become parallel as it is changing bucket permissions. From 654419230c7bf8df8c2b21c94040b69c4743a6dd Mon Sep 17 00:00:00 2001 From: codechanges Date: Tue, 20 May 2025 15:53:13 +0530 Subject: [PATCH 0449/1298] Add support for using single GCSFuse build for all test packages (#3319) * Add support for using single GCSFuse build for all test packages * Corrected comment * minor change * changed build method * converted warning into an error * Added a comment * Return error --- tools/integration_tests/run_e2e_tests.sh | 79 +++++++++++++++++++-- tools/integration_tests/util/setup/setup.go | 39 ++++++++-- 2 files changed, 107 insertions(+), 11 deletions(-) diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 5ba29cfac1..1263c075af 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -52,6 +52,16 @@ if [[ $# -ge 6 ]] ; then fi fi +# 7th parameter is to determine whether we want to disable build by the script +# and let every test package build its own GCSFuse binary. +BUILD_BINARY_IN_SCRIPT=true +if [[ $# -ge 7 ]] ; then + if [[ "$7" == "false" ]]; then + BUILD_BINARY_IN_SCRIPT=false + fi +fi + + if ${RUN_TESTS_WITH_ZONAL_BUCKET}; then if [ "${BUCKET_LOCATION}" != "us-west4" ] && [ "${BUCKET_LOCATION}" != "us-central1" ]; then >&2 echo "For enabling zonal bucket run, BUCKET_LOCATION should be one of: us-west4, us-central1; passed: ${BUCKET_LOCATION}" @@ -156,6 +166,52 @@ TEST_DIR_NON_PARALLEL_FOR_ZB=( # Create a temporary file to store the log file name. TEST_LOGS_FILE=$(mktemp) +# This variable will store the path if the script builds GCSFuse binaries (gcsfuse, mount.gcsfuse) +BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR="" +# This variable will hold flag and its value to be passed to GCSFuse tests (--gcsfuse_prebuilt_dir=...) +USE_PREBUILT_GCSFUSE_BINARY="" + +build_gcsfuse_once() { + local build_output_dir # For the final gcsfuse binaries + build_output_dir=$(mktemp -d -t gcsfuse_e2e_run_build_XXXXXX) + echo "GCSFuse binaries will be built in ${build_output_dir}..." + + local gcsfuse_src_dir + # Determine GCSFuse source directory + # If this script is in tools/integration_tests, project root is ../../ + SCRIPT_DIR_REALPATH=$(realpath "$(dirname "${BASH_SOURCE[0]}")") + gcsfuse_src_dir=$(realpath "${SCRIPT_DIR_REALPATH}/../../") + + if [[ ! -f "${gcsfuse_src_dir}/go.mod" ]]; then + echo "Error: Could not reliably determine GCSFuse project root from ${SCRIPT_DIR_REALPATH}. Expected go.mod at ${gcsfuse_src_dir}" >&2 + rm -rf "${build_output_dir}" + exit 1 + fi + echo "Using GCSFuse source directory: ${gcsfuse_src_dir}" + + echo "Building GCSFuse using 'go run ./tools/build_gcsfuse/main.go'..." + (cd "${gcsfuse_src_dir}" && go run ./tools/build_gcsfuse/main.go . "${build_output_dir}" "e2e-$(date +%s)") + if [ $? -ne 0 ]; then + echo "Error building GCSFuse binaries using 'go run ./tools/build_gcsfuse/main.go'." + rm -rf "${build_output_dir}" # Clean up created temp dir + return 1 + fi + + # Set the directory path for use by the script (to form the go test flag) + BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR="${build_output_dir}" + echo "GCSFuse binaries built by script in: ${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" + echo "GCSFuse executable: ${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}/bin/gcsfuse" + return 0 +} + + +cleanup_gcsfuse_once() { + if [ -n "${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" ] && [ -d "${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" ]; then + echo "Cleaning up GCSFuse build directory created by script: ${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" + rm -rf "${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" + fi +} + # Delete contents of the buckets (and then the buckets themselves) whose names are in the passed file. # Args: function delete_buckets_listed_in_file() { @@ -259,7 +315,7 @@ function run_non_parallel_tests() { # Executing integration tests echo "Running test package in non-parallel (with zonal=${zonal}): ${test_dir_np} ..." - GODEBUG=asyncpreemptoff=1 go test $test_path_non_parallel -p 1 $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${zonal} --integrationTest -v --testbucket=$bucket_name_non_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 + GODEBUG=asyncpreemptoff=1 go test $test_path_non_parallel -p 1 $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${zonal} --integrationTest -v --testbucket=$bucket_name_non_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE $USE_PREBUILT_GCSFUSE_BINARY -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 exit_code_non_parallel=$? if [ $exit_code_non_parallel != 0 ]; then exit_code=$exit_code_non_parallel @@ -299,7 +355,7 @@ function run_parallel_tests() { echo $log_file >> $TEST_LOGS_FILE # Executing integration tests echo "Queueing up test package in parallel (with zonal=${zonal}): ${test_dir_p} ..." - GODEBUG=asyncpreemptoff=1 go test $test_path_parallel $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${zonal} $benchmark_flags -p 1 --integrationTest -v --testbucket=$bucket_name_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & + GODEBUG=asyncpreemptoff=1 go test $test_path_parallel $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=${zonal} $benchmark_flags -p 1 --integrationTest -v --testbucket=$bucket_name_parallel --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE $USE_PREBUILT_GCSFUSE_BINARY -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & pid=$! # Store the PID of the background process echo "Queued up test package in parallel (with zonal=${zonal}): ${test_dir_p} with pid=${pid}" pids[${test_dir_p}]=${pid} # Optionally add the PID to an array for later @@ -439,7 +495,7 @@ function run_e2e_tests_for_tpc() { gcloud --verbosity=error storage rm -r gs://"$bucket"/* # Run Operations e2e tests in TPC to validate all the functionality. - GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... --testOnTPCEndPoint=$RUN_TEST_ON_TPC_ENDPOINT $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=false -p 1 --integrationTest -v --testbucket="$bucket" --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE -timeout $INTEGRATION_TEST_TIMEOUT + GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/operations/... --testOnTPCEndPoint=$RUN_TEST_ON_TPC_ENDPOINT $GO_TEST_SHORT_FLAG $PRESUBMIT_RUN_FLAG --zonal=false -p 1 --integrationTest -v --testbucket="$bucket" --testInstalledPackage=$RUN_E2E_TESTS_ON_PACKAGE $USE_PREBUILT_GCSFUSE_BINARY -timeout $INTEGRATION_TEST_TIMEOUT exit_code=$? set -e @@ -463,7 +519,8 @@ function main(){ # buckets to be cleaned-up while exiting this program. bucketNamesFile=$(realpath ./bucketNames)"-"$(tr -dc 'a-z0-9' < /dev/urandom | head -c $RANDOM_STRING_LENGTH) # Delete all these buckets when the program exits. - trap "delete_buckets_listed_in_file ${bucketNamesFile}" EXIT + # Cleanup fuse build folder if created + trap "cleanup_gcsfuse_once; delete_buckets_listed_in_file ${bucketNamesFile}" EXIT set -e @@ -473,6 +530,20 @@ function main(){ set +e + # Decide whether to build GCSFuse based on RUN_E2E_TESTS_ON_PACKAGE + if [ "$RUN_E2E_TESTS_ON_PACKAGE" != "true" ] && [ "$BUILD_BINARY_IN_SCRIPT" == "true" ]; then + echo "RUN_E2E_TESTS_ON_PACKAGE is not 'true' (value: '${RUN_E2E_TESTS_ON_PACKAGE}') and BUILD_BINARY_IN_SCRIPT is 'true'. Building GCSFuse..." + build_gcsfuse_once + if [ $? -ne 0 ]; then + echo "build_gcsfuse_once failed. Exiting." + # The trap will handle cleanup + exit 1 + fi + + USE_PREBUILT_GCSFUSE_BINARY="--gcsfuse_prebuilt_dir=${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" + echo "Script built GCSFuse at: ${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" + fi + #run integration tests exit_code=0 diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 79041b4246..e02c227181 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -43,6 +43,7 @@ var mountedDirectory = flag.String("mountedDirectory", "", "The GCSFuse mounted var integrationTest = flag.Bool("integrationTest", false, "Run tests only when the flag value is true.") var testInstalledPackage = flag.Bool("testInstalledPackage", false, "[Optional] Run tests on the package pre-installed on the host machine. By default, integration tests build a new package to run the tests.") var testOnTPCEndPoint = flag.Bool("testOnTPCEndPoint", false, "Run tests on TPC endpoint only when the flag value is true.") +var gcsfusePreBuiltDir = flag.String("gcsfuse_prebuilt_dir", "", "Path to the pre-built GCSFuse directory containing bin/gcsfuse and sbin/mount.gcsfuse.") const ( FilePermission_0600 = 0600 @@ -168,7 +169,35 @@ func SetUpTestDir() error { return fmt.Errorf("TempDir: %w", err) } - if !TestInstalledPackage() { + // Order of priority to choose GCSFuse installation to run the tests + // 1. Installed package if explicitly asked to + // 2. Prebuilt GCSFuse dir if the said flag is passed + // 3. Build it yourself + if TestInstalledPackage() { + // when testInstalledPackage flag is set, gcsfuse is preinstalled on the + // machine. Hence, here we are overwriting binFile to gcsfuse. + log.Printf("Using GCSFuse installed on the target machine") + binFile = "gcsfuse" + sbinFile = "mount.gcsfuse" + } else if *gcsfusePreBuiltDir != "" { + prebuiltDir := *gcsfusePreBuiltDir + log.Printf("Using GCSFuse from pre-built directory specified by --gcsfuse_prebuilt_dir flag: %s", prebuiltDir) + binFile = filepath.Join(prebuiltDir, "bin/gcsfuse") + sbinFile = filepath.Join(prebuiltDir, "sbin/mount.gcsfuse") + + if _, statErr := os.Stat(binFile); statErr != nil { + return fmt.Errorf("gcsfuse binary from --gcsfuse_prebuilt_dir not found at %s: %w", binFile, statErr) + } + if _, statErr := os.Stat(sbinFile); statErr != nil { + return fmt.Errorf("mount helper from --gcsfuse_prebuilt_dir not found at %s: %w", sbinFile, statErr) + } + // Set PATH to include the bin directory of the pre-built gcsfuse + err = os.Setenv(PathEnvVariable, filepath.Dir(binFile)+string(filepath.ListSeparator)+os.Getenv(PathEnvVariable)) + if err != nil { + return fmt.Errorf("error setting PATH for --gcsfuse_prebuilt_dir: %v", err.Error()) + } + } else { + log.Printf("Building GCSFuse from source in the dir: %s ...", testDir) err = util.BuildGcsfuse(testDir) if err != nil { return fmt.Errorf("BuildGcsfuse(%q): %w", TestDir(), err) @@ -181,14 +210,10 @@ func SetUpTestDir() error { // Setting PATH so that executable is found in test directory. err := os.Setenv(PathEnvVariable, path.Join(TestDir(), "bin")+string(filepath.ListSeparator)+os.Getenv(PathEnvVariable)) if err != nil { - log.Printf("Error in setting PATH environment variable: %v", err.Error()) + return fmt.Errorf("error in setting PATH environment variable: %v", err.Error()) } - } else { - // when testInstalledPackage flag is set, gcsfuse is preinstalled on the - // machine. Hence, here we are overwriting binFile to gcsfuse. - binFile = "gcsfuse" - sbinFile = "mount.gcsfuse" } + logFile = path.Join(TestDir(), "gcsfuse.log") mntDir = path.Join(TestDir(), "mnt") From 96b772ec78982911781c86b345bdb5b3c0636c9f Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 20 May 2025 19:02:29 +0530 Subject: [PATCH 0450/1298] [Random Reader Refactoring] GCS Reader implementation - 2 (#3320) * adding tests * small fix * review comments --- .../gcsx/client_readers/gcs_reader_test.go | 220 +++++++++++++++++- 1 file changed, 219 insertions(+), 1 deletion(-) diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go index 561e5b8165..669d37641d 100644 --- a/internal/gcsx/client_readers/gcs_reader_test.go +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -34,6 +34,12 @@ import ( "github.com/stretchr/testify/suite" ) +const ( + sequential = "Sequential" + random = "Random" + sequentialReadSizeInMb = 22 +) + //////////////////////////////////////////////////////////////////////// // Helpers //////////////////////////////////////////////////////////////////////// @@ -67,7 +73,7 @@ func (t *gcsReaderTest) SetupTest() { Generation: 1234, } t.mockBucket = new(storage.TestifyMockBucket) - t.gcsReader = NewGCSReader(t.object, t.mockBucket, common.NewNoopMetrics(), nil, 200) + t.gcsReader = NewGCSReader(t.object, t.mockBucket, common.NewNoopMetrics(), nil, sequentialReadSizeInMb) t.ctx = context.Background() } @@ -93,6 +99,37 @@ func (t *gcsReaderTest) Test_NewGCSReader() { assert.Equal(t.T(), testUtil.Sequential, gcsReader.readType) } +func (t *gcsReaderTest) Test_ReadAt_InvalidOffset() { + testCases := []struct { + name string + objectSize int + start int + }{ + { + name: "InvalidOffset", + objectSize: 50, + start: 68, + }, + { + name: "NegativeOffset", + objectSize: 100, + start: -50, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.gcsReader.rangeReader.reader = nil + t.object.Size = uint64(tc.objectSize) + buf := make([]byte, tc.objectSize) + + _, err := t.gcsReader.ReadAt(t.ctx, buf, int64(tc.start)) + + assert.Error(t.T(), err) + }) + } +} + func (t *gcsReaderTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedDataSize() { t.object.Size = 10 // Simulate an existing reader. @@ -269,3 +306,184 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { }) } } + +func (t *gcsReaderTest) Test_ReadAt_PropagatesCancellation() { + // Set up a blocking reader + finishRead := make(chan struct{}) + blocking := &blockingReader{c: finishRead} + rc := io.NopCloser(blocking) + // Assign it to the rangeReader + t.gcsReader.rangeReader.reader = &fake.FakeReader{ReadCloser: rc} + t.gcsReader.rangeReader.start = 0 + t.gcsReader.rangeReader.limit = 2 + // Track cancel invocation + cancelCalled := make(chan struct{}) + t.gcsReader.rangeReader.cancel = func() { close(cancelCalled) } + // Controlled context + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + // Channel to track read completion + readReturned := make(chan struct{}) + var err error + + go func() { + _, err = t.gcsReader.ReadAt(ctx, make([]byte, 2), 0) + + assert.Error(t.T(), err) + + close(readReturned) + }() + + // Wait a bit to ensure ReadAt is blocking + select { + case <-readReturned: + t.T().Fatal("Read returned early — cancellation did not propagate properly.") + case <-time.After(100 * time.Millisecond): + // OK: Still blocked + } + // Cancel the context to trigger cancellation + cancel() + // Expect rr.cancel to be called + select { + case <-cancelCalled: + // Pass + case <-time.After(100 * time.Millisecond): + t.T().Fatal("Expected rr.cancel to be called on ctx cancellation.") + } + // Unblock the reader so the read can complete + close(finishRead) + // Ensure read completes + select { + case <-readReturned: + // Pass + case <-time.After(100 * time.Millisecond): + t.T().Fatal("Expected read to return after unblocking.") + } +} + +func (t *gcsReaderTest) Test_ReadInfo_WithInvalidInput() { + t.object.Size = 10 * MiB + testCases := []struct { + name string + start int64 + size int64 + }{ + { + name: "startLessThanZero", + start: -1, + size: 10, + }, + { + name: "sizeLessThanZero", + start: 0, + size: -1, + }, + { + name: "startGreaterThanObjectSize", + start: int64(t.object.Size + 1), + size: int64(t.object.Size), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + _, err := t.gcsReader.getReadInfo(tc.start, tc.size) + + assert.Error(t.T(), err) + }) + } +} + +func (t *gcsReaderTest) Test_ReadInfo_Sequential() { + testCases := []struct { + name string + start int64 + objectSize uint64 + expectedEnd int64 + }{ + { + name: "ExactSizeRead", // start 0, object = 10MB + start: 0, + objectSize: 10 * MiB, + expectedEnd: 10 * MiB, + }, + { + name: "ReadSizeGreaterThanObjectSize", // start near end, should clamp to objectSize + start: int64(10*MiB - 1), + objectSize: 10 * MiB, + expectedEnd: 10 * MiB, + }, + { + name: "ObjectSizeGreaterThanReadSize", // default read size applies + start: 0, + objectSize: 50 * MiB, + expectedEnd: 22 * MB, // equals to sequentialReadSizeInMb + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.SetupTest() + t.object.Size = tc.objectSize + + end, err := t.gcsReader.getReadInfo(tc.start, int64(tc.objectSize)) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), sequential, t.gcsReader.readType) + assert.Equal(t.T(), tc.expectedEnd, end) + }) + } +} + +func (t *gcsReaderTest) Test_ReadInfo_Random() { + t.gcsReader.seeks = 2 + testCases := []struct { + name string + start int64 + objectSize uint64 + totalReadBytes uint64 + expectedEnd int64 + }{ + { + name: "RangeBetween1And8MB", + start: 0, + objectSize: 50 * MiB, + totalReadBytes: 10 * MiB, + expectedEnd: 6 * MiB, + }, + { + name: "ReadSizeLessThan1MB", + start: 0, + objectSize: 50 * MiB, + totalReadBytes: 1 * MiB, // avg = 0.5MB + expectedEnd: MB, // equals to minReadSize + }, + { + name: "ReadSizeGreaterThan8MB", + start: 0, + objectSize: 50 * MiB, + totalReadBytes: 20 * MiB, + expectedEnd: 22 * MB, // equals to sequentialReadSizeInMb + }, + { + name: "ReadSizeGreaterThan8MB", + start: 5*MiB - 1, + objectSize: 5 * MiB, + totalReadBytes: 2 * MiB, + expectedEnd: 5 * MiB, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.object.Size = tc.objectSize + t.gcsReader.totalReadBytes = tc.totalReadBytes + + end, err := t.gcsReader.getReadInfo(tc.start, int64(tc.objectSize)) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), random, t.gcsReader.readType) + assert.Equal(t.T(), tc.expectedEnd, end) + }) + } +} From f855365c35f8185ad5a2f9cb6b9ad99281606d91 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Wed, 21 May 2025 11:36:27 +0530 Subject: [PATCH 0451/1298] skip on draft (#3334) --- .github/workflows/shadow.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/shadow.yml b/.github/workflows/shadow.yml index 91517b7b2e..cccbb6d3e5 100644 --- a/.github/workflows/shadow.yml +++ b/.github/workflows/shadow.yml @@ -4,12 +4,15 @@ on: pull_request: types: - opened + - ready_for_review + - reopened branches: - master jobs: shadow-reviewer: runs-on: ubuntu-latest + if: github.event.pull_request.draft == false timeout-minutes: 15 steps: - name: Add shadow reviewer From cdc860b824a84dfc3b44ea82cd0b67864f2f1319 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 21 May 2025 12:12:48 +0530 Subject: [PATCH 0452/1298] Refactor list_large_dir e2e tests to replace gcloud with go sdk (#3196) * Refactor list_large_dir e2e tests to replace gcloud with go sdk * updated comment * Use runtime.NumCPU()/2 instead of 16 for concurrency * Reuse WriteToObject method in CreateGcsDir method * fixed formatting --- ...ist_dir_with_twelve_thousand_files_test.go | 89 ++++++++++++------- .../testdata/create_implicit_dir.sh | 30 ------- .../testdata/upload_files_to_bucket.sh | 25 ------ .../util/client/storage_client.go | 23 ++++- 4 files changed, 77 insertions(+), 90 deletions(-) delete mode 100755 tools/integration_tests/list_large_dir/testdata/create_implicit_dir.sh delete mode 100755 tools/integration_tests/list_large_dir/testdata/upload_files_to_bucket.sh diff --git a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go index 7f8326d297..5eafe85aab 100644 --- a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go +++ b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go @@ -21,6 +21,7 @@ import ( "os" "path" "path/filepath" + "runtime" "strconv" "strings" "sync" @@ -32,6 +33,7 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "golang.org/x/sync/errgroup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" @@ -65,15 +67,16 @@ func validateDirectory(t *testing.T, objs []os.DirEntry, expectExplicitDirs, exp ) for _, obj := range objs { - if !obj.IsDir() { - numberOfFiles++ - checkIfObjNameIsCorrect(t, obj.Name(), prefixFileInDirectoryWithTwelveThousandFiles, numberOfFilesInDirectoryWithTwelveThousandFiles) - } else if strings.Contains(obj.Name(), prefixExplicitDirInLargeDirListTest) { + + if strings.Contains(obj.Name(), prefixExplicitDirInLargeDirListTest) { numberOfExplicitDirs++ checkIfObjNameIsCorrect(t, obj.Name(), prefixExplicitDirInLargeDirListTest, numberOfExplicitDirsInDirectoryWithTwelveThousandFiles) } else if strings.Contains(obj.Name(), prefixImplicitDirInLargeDirListTest) { numberOfImplicitDirs++ checkIfObjNameIsCorrect(t, obj.Name(), prefixImplicitDirInLargeDirListTest, numberOfImplicitDirsInDirectoryWithTwelveThousandFiles) + } else { + numberOfFiles++ + checkIfObjNameIsCorrect(t, obj.Name(), prefixFileInDirectoryWithTwelveThousandFiles, numberOfFilesInDirectoryWithTwelveThousandFiles) } } @@ -104,7 +107,7 @@ func checkIfObjNameIsCorrect(t *testing.T, objName string, prefix string, maxNum } } -// This is needed for ZB which is not supported by gcloud storage cp command yet. +// testdataUploadFilesToBucket uploads matching files from a local directory to a specified path in a GCS bucket. func testdataUploadFilesToBucket(ctx context.Context, t *testing.T, storageClient *storage.Client, bucketNameWithDirPath, dirWith12KFiles, filesPrefix string) { t.Helper() @@ -120,7 +123,7 @@ func testdataUploadFilesToBucket(ctx context.Context, t *testing.T, storageClien srcLocalFilePath string dstGCSObjectPath string } - channel := make(chan copyRequest) + channel := make(chan copyRequest, len(matches)) // Copy request producer. go func() { @@ -131,12 +134,12 @@ func testdataUploadFilesToBucket(ctx context.Context, t *testing.T, storageClien channel <- req } } - // close the channel to let the go-routines know that there is no more object to be copied. + // Close the channel to let the go-routines know that there is no more object to be copied. close(channel) }() // Copy request consumers. - numCopyGoroutines := 16 + numCopyGoroutines := runtime.NumCPU() / 2 var wg sync.WaitGroup for range numCopyGoroutines { wg.Add(1) @@ -162,21 +165,7 @@ func createFilesAndUpload(t *testing.T, dirPath string) { operations.CreateDirectoryWithNFiles(numberOfFilesInDirectoryWithTwelveThousandFiles, localDirPath, prefixFileInDirectoryWithTwelveThousandFiles, t) defer os.RemoveAll(localDirPath) - if setup.IsZonalBucketRun() { - testdataUploadFilesToBucket(ctx, t, storageClient, dirPath, localDirPath, prefixFileInDirectoryWithTwelveThousandFiles) - } else { - setup.RunScriptForTestData("testdata/upload_files_to_bucket.sh", dirPath, localDirPath, prefixFileInDirectoryWithTwelveThousandFiles) - } -} - -// createExplicitDirs creates empty explicit directories in the specified directory. -func createExplicitDirs(t *testing.T, dirPath string) { - t.Helper() - - for i := 1; i <= numberOfExplicitDirsInDirectoryWithTwelveThousandFiles; i++ { - subDirPath := path.Join(dirPath, fmt.Sprintf("%s%d", prefixExplicitDirInLargeDirListTest, i)) - operations.CreateDirectoryWithNFiles(0, subDirPath, "", t) - } + testdataUploadFilesToBucket(ctx, t, storageClient, dirPath, localDirPath, prefixFileInDirectoryWithTwelveThousandFiles) } // listDirTime measures the time taken to list a directory with and without cache. @@ -210,9 +199,8 @@ func listDirTime(t *testing.T, dirPath string, expectExplicitDirs bool, expectIm return firstListTime, minSecondListTime } -// This function is equivalent to testdata/create_implicit_dir.sh to replace gcloud with storage-client -// This is needed for ZB which is not supported by gcloud storage cp command yet. -func testdataCreateImplicitDir(ctx context.Context, t *testing.T, storageClient *storage.Client, bucketNameWithDirPath, prefixImplicitDirInLargeDirListTest string, numberOfImplicitDirsInDirectory int) { +// testdataCreateImplicitDir creates implicit directories by uploading files with nested paths. +func testdataCreateImplicitDir(t *testing.T, ctx context.Context, storageClient *storage.Client, bucketNameWithDirPath string) { t.Helper() bucketName, dirPathInBucket := operations.SplitBucketNameAndDirPath(t, bucketNameWithDirPath) @@ -221,8 +209,45 @@ func testdataCreateImplicitDir(ctx context.Context, t *testing.T, storageClient if err != nil { t.Fatalf("Failed to create local file for creating copies ...") } - for suffix := 1; suffix <= numberOfImplicitDirsInDirectory; suffix++ { - client.CopyFileInBucket(ctx, storageClient, testFile, path.Join(dirPathInBucket, fmt.Sprintf("%s%d", prefixImplicitDirInLargeDirListTest, suffix), testFile), bucketName) + + var wg sync.WaitGroup + sem := make(chan struct{}, runtime.NumCPU()/2) // Concurrency limiter + + for suffix := 1; suffix <= numberOfImplicitDirsInDirectoryWithTwelveThousandFiles; suffix++ { + objectPath := path.Join(dirPathInBucket, fmt.Sprintf("%s%d", prefixImplicitDirInLargeDirListTest, suffix), testFile) + + wg.Add(1) + go func(destinationPath string) { + defer wg.Done() + sem <- struct{}{} // acquire semaphore + defer func() { <-sem }() // release semaphore + + client.CopyFileInBucket(ctx, storageClient, testFile, destinationPath, bucketName) + }(objectPath) + } + + wg.Wait() +} + +// testdataCreateExplicitDir creates explicit directories (trailing slash objects) in the bucket. +func testdataCreateExplicitDir(t *testing.T, ctx context.Context, storageClient *storage.Client, bucketNameWithDirPath string) { + t.Helper() + + bucketName, dirPathInBucket := operations.SplitBucketNameAndDirPath(t, bucketNameWithDirPath) + + g, ctx := errgroup.WithContext(ctx) + g.SetLimit(runtime.NumCPU() / 2) // Concurrency limiter + + for dirIndex := 1; dirIndex <= numberOfExplicitDirsInDirectoryWithTwelveThousandFiles; dirIndex++ { + capturedIndex := dirIndex + g.Go(func() error { + dirName := fmt.Sprintf("%s%d", prefixExplicitDirInLargeDirListTest, capturedIndex) + return client.CreateGcsDir(ctx, storageClient, dirName, bucketName, dirPathInBucket) + }) + } + + if err := g.Wait(); err != nil { + t.Fatalf("Failed to create explicit dirs: %v", err) } } @@ -241,15 +266,11 @@ func prepareTestDirectory(t *testing.T, withExplicitDirs bool, withImplicitDirs createFilesAndUpload(t, testDirPathOnBucket) if withExplicitDirs { - createExplicitDirs(t, testDirPath) + testdataCreateExplicitDir(t, ctx, storageClient, testDirPathOnBucket) } if withImplicitDirs { - if setup.IsZonalBucketRun() { - testdataCreateImplicitDir(ctx, t, storageClient, testDirPathOnBucket, prefixImplicitDirInLargeDirListTest, numberOfImplicitDirsInDirectoryWithTwelveThousandFiles) - } else { - setup.RunScriptForTestData("testdata/create_implicit_dir.sh", testDirPathOnBucket, prefixImplicitDirInLargeDirListTest, strconv.Itoa(numberOfImplicitDirsInDirectoryWithTwelveThousandFiles)) - } + testdataCreateImplicitDir(t, ctx, storageClient, testDirPathOnBucket) } return testDirPath diff --git a/tools/integration_tests/list_large_dir/testdata/create_implicit_dir.sh b/tools/integration_tests/list_large_dir/testdata/create_implicit_dir.sh deleted file mode 100755 index d8d5828c07..0000000000 --- a/tools/integration_tests/list_large_dir/testdata/create_implicit_dir.sh +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -# $1 testbucket -# $2 PrefixImplicitDirInLargeDirListTest -# $3 NumberOfImplicitDirsInDirectoryWithTwelveThousandFiles - 100 -TEST_BUCKET=$1 -IMPLICIT_DIR=$2 -NUMBER_OF_FILES=$3 - -a=1 -#Iterate the loop until a greater than 100 -touch testFile.txt -while [ $a -le $NUMBER_OF_FILES ] -do - dir=$IMPLICIT_DIR$a - a=`expr $a + 1` - gcloud storage cp testFile.txt gs://$TEST_BUCKET/$dir/ -done diff --git a/tools/integration_tests/list_large_dir/testdata/upload_files_to_bucket.sh b/tools/integration_tests/list_large_dir/testdata/upload_files_to_bucket.sh deleted file mode 100755 index 71a2158c97..0000000000 --- a/tools/integration_tests/list_large_dir/testdata/upload_files_to_bucket.sh +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -set -e -# $1 testbucket -# $2 DirectoryWithTwelveThousandFiles -# $3 PrefixFileInDirectoryWithTwelveThousandFiles -TEST_BUCKET=$1 -DIR_WITH_TWELVE_THOUSAND_FILES=$2 -FILES=$3 - -cd $DIR_WITH_TWELVE_THOUSAND_FILES -gcloud storage mv $FILES* gs://$TEST_BUCKET/ -cd ../ -rm -r $DIR_WITH_TWELVE_THOUSAND_FILES diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 01e6b63339..1e34f752a9 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -21,8 +21,10 @@ import ( "io" "log" "os" + "path" "reflect" "runtime" + "strings" "sync" "testing" "time" @@ -316,7 +318,7 @@ func UploadGcsObject(ctx context.Context, client *storage.Client, localPath, buc obj := client.Bucket(bucketName).Object(objectName) w, err := NewWriter(ctx, obj, client) if err != nil { - return fmt.Errorf("Failed to open writer for GCS object gs://%s/%s: %w", bucketName, objectName, err) + return fmt.Errorf("failed to open writer for GCS object gs://%s/%s: %w", bucketName, objectName, err) } defer func() { if err := w.Close(); err != nil { @@ -430,3 +432,22 @@ func AppendableWriter(ctx context.Context, client *storage.Client, object string } return wc, nil } + +// CreateGcsDir creates a GCS object with trailing slash "/" to simulate a directory. +func CreateGcsDir(ctx context.Context, client *storage.Client, dirName, bucketName, objectName string) error { + // Combine objectName and dirName to form the full GCS object path + fullObjectPath := path.Join(objectName, dirName) + + // Ensure fullObjectPath ends with a "/" + if !strings.HasSuffix(fullObjectPath, "/") { + fullObjectPath += "/" + } + + // Create an empty object with the directory path + err := WriteToObject(ctx, client, fullObjectPath, "", storage.Conditions{}) + if err != nil { + return fmt.Errorf("failed to create GCS directory object %q in bucket %q: %w", fullObjectPath, bucketName, err) + } + + return nil +} From 743bf9d4b8c4cba0b31c6d1cae0bfa108f1f9b77 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 21 May 2025 12:42:57 +0530 Subject: [PATCH 0453/1298] feat: cloud profiler support for cpu/heap/goroutines/mutex profiling (#3331) * Cloud profiler support in GCSFuse * better name * lint and test fixes * lint fix * added success log * review comments * minor usage doc update --- cfg/config.go | 88 +++++++++++++++++++++++ cfg/params.yaml | 51 +++++++++++++ cmd/legacy_main.go | 6 ++ cmd/root_test.go | 92 ++++++++++++++++++++++++ go.mod | 2 + go.sum | 4 ++ internal/profiler/cloud_profiler.go | 55 ++++++++++++++ internal/profiler/cloud_profiler_test.go | 86 ++++++++++++++++++++++ 8 files changed, 384 insertions(+) create mode 100644 internal/profiler/cloud_profiler.go create mode 100644 internal/profiler/cloud_profiler_test.go diff --git a/cfg/config.go b/cfg/config.go index 2835559f0c..9f924d68d4 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -66,6 +66,8 @@ type Config struct { OnlyDir string `yaml:"only-dir"` + Profiling ProfilingConfig `yaml:"profiling"` + Read ReadConfig `yaml:"read"` Write WriteConfig `yaml:"write"` @@ -231,6 +233,22 @@ type MonitoringConfig struct { ExperimentalTracingSamplingRatio float64 `yaml:"experimental-tracing-sampling-ratio"` } +type ProfilingConfig struct { + AllocatedHeap bool `yaml:"allocated-heap"` + + Cpu bool `yaml:"cpu"` + + Enabled bool `yaml:"enabled"` + + Goroutines bool `yaml:"goroutines"` + + Heap bool `yaml:"heap"` + + Label string `yaml:"label"` + + Mutex bool `yaml:"mutex"` +} + type ReadConfig struct { InactiveStreamTimeout time.Duration `yaml:"inactive-stream-timeout"` } @@ -341,6 +359,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } + flagSet.BoolP("enable-cloud-profiling", "", false, "Enables cloud profiling, by default disabled.") + + if err := flagSet.MarkHidden("enable-cloud-profiling"); err != nil { + return err + } + flagSet.BoolP("enable-empty-managed-folders", "", false, "This handles the corner case in listing managed folders. There are two corner cases (a) empty managed folder (b) nested managed folder which doesn't contain any descendent as object. This flag always works in conjunction with --implicit-dirs flag. (a) If only ImplicitDirectories is true, all managed folders are listed other than above two mentioned cases. (b) If both ImplicitDirectories and EnableEmptyManagedFolders are true, then all the managed folders are listed including the above-mentioned corner case. (c) If ImplicitDirectories is false then no managed folders are listed irrespective of enable-empty-managed-folders flag.") if err := flagSet.MarkHidden("enable-empty-managed-folders"); err != nil { @@ -507,6 +531,42 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } + flagSet.BoolP("profiling-allocated-heap", "", true, "Enables allocated heap (HeapProfileAllocs) profiling. This only works when --enable-cloud-profiling is set to true.") + + if err := flagSet.MarkHidden("profiling-allocated-heap"); err != nil { + return err + } + + flagSet.BoolP("profiling-cpu", "", true, "Enables cpu profiling. This only works when --enable-cloud-profiling is set to true.") + + if err := flagSet.MarkHidden("profiling-cpu"); err != nil { + return err + } + + flagSet.BoolP("profiling-goroutines", "", false, "Enables goroutines profiling. This only works when --enable-cloud-profiling is set to true.") + + if err := flagSet.MarkHidden("profiling-goroutines"); err != nil { + return err + } + + flagSet.BoolP("profiling-heap", "", true, "Enables heap profiling. This only works when --enable-cloud-profiling is set to true.") + + if err := flagSet.MarkHidden("profiling-heap"); err != nil { + return err + } + + flagSet.StringP("profiling-label", "", "gcsfuse-0.0.0", "Allow setting a profile label to uniquely identify and compare profiling data with other profiles. This only works when --enable-cloud-profiling is set to true. ") + + if err := flagSet.MarkHidden("profiling-label"); err != nil { + return err + } + + flagSet.BoolP("profiling-mutex", "", false, "Enables mutex profiling. This only works when --enable-cloud-profiling is set to true.") + + if err := flagSet.MarkHidden("profiling-mutex"); err != nil { + return err + } + flagSet.IntP("prometheus-port", "", 0, "Expose Prometheus metrics endpoint on this port and a path of /metrics.") flagSet.DurationP("read-inactive-stream-timeout", "", 0*time.Nanosecond, "Duration of inactivity after which an open GCS read stream is automatically closed. This helps conserve resources when a file handle remains open without active Read calls. A value of '0s' disables this timeout. Note: Currently only applies when using the 'grpc' client-protocol.") @@ -684,6 +744,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("profiling.enabled", flagSet.Lookup("enable-cloud-profiling")); err != nil { + return err + } + if err := v.BindPFlag("list.enable-empty-managed-folders", flagSet.Lookup("enable-empty-managed-folders")); err != nil { return err } @@ -876,6 +940,30 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("profiling.allocated-heap", flagSet.Lookup("profiling-allocated-heap")); err != nil { + return err + } + + if err := v.BindPFlag("profiling.cpu", flagSet.Lookup("profiling-cpu")); err != nil { + return err + } + + if err := v.BindPFlag("profiling.goroutines", flagSet.Lookup("profiling-goroutines")); err != nil { + return err + } + + if err := v.BindPFlag("profiling.heap", flagSet.Lookup("profiling-heap")); err != nil { + return err + } + + if err := v.BindPFlag("profiling.label", flagSet.Lookup("profiling-label")); err != nil { + return err + } + + if err := v.BindPFlag("profiling.mutex", flagSet.Lookup("profiling-mutex")); err != nil { + return err + } + if err := v.BindPFlag("metrics.prometheus-port", flagSet.Lookup("prometheus-port")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 4e315a2d5c..8db325dd59 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -654,6 +654,57 @@ usage: "Mount only a specific directory within the bucket. See docs/mounting for more information" default: "" +- config-path: "profiling.allocated-heap" + flag-name: "profiling-allocated-heap" + type: "bool" + usage: "Enables allocated heap (HeapProfileAllocs) profiling. This only works when --enable-cloud-profiling is set to true." + default: true + hide-flag: true + +- config-path: "profiling.cpu" + flag-name: "profiling-cpu" + type: "bool" + usage: "Enables cpu profiling. This only works when --enable-cloud-profiling is set to true." + default: true + hide-flag: true + +- config-path: "profiling.enabled" + flag-name: "enable-cloud-profiling" + type: "bool" + usage: "Enables cloud profiling, by default disabled." + default: false + hide-flag: true + +- config-path: "profiling.goroutines" + flag-name: "profiling-goroutines" + type: "bool" + usage: "Enables goroutines profiling. This only works when --enable-cloud-profiling is set to true." + default: false + hide-flag: true + +- config-path: "profiling.heap" + flag-name: "profiling-heap" + type: "bool" + usage: "Enables heap profiling. This only works when --enable-cloud-profiling is set to true." + default: true + hide-flag: true + +- config-path: "profiling.label" + flag-name: "profiling-label" + type: "string" + usage: >- + Allow setting a profile label to uniquely identify and compare profiling data with other profiles. + This only works when --enable-cloud-profiling is set to true. + default: "gcsfuse-0.0.0" + hide-flag: true + +- config-path: "profiling.mutex" + flag-name: "profiling-mutex" + type: "bool" + usage: "Enables mutex profiling. This only works when --enable-cloud-profiling is set to true." + default: false + hide-flag: true + - config-path: "read.inactive-stream-timeout" flag-name: "read-inactive-stream-timeout" type: "duration" diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index d8d6f45f3d..9a43620b4f 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -37,6 +37,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor" "github.com/googlecloudplatform/gcsfuse/v2/internal/mount" + "github.com/googlecloudplatform/gcsfuse/v2/internal/profiler" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v2/internal/util" @@ -394,6 +395,11 @@ func Mount(newConfig *cfg.Config, bucketName, mountPoint string) (err error) { shutdownTracingFn := monitor.SetupTracing(ctx, newConfig) shutdownFn := common.JoinShutdownFunc(metricExporterShutdownFn, shutdownTracingFn) + // No-op if profiler is disabled. + if err := profiler.SetupCloudProfiler(&newConfig.Profiling); err != nil { + logger.Warnf("Failed to setup cloud profiler: %v", err) + } + // Mount, writing information about our progress to the writer that package // daemonize gives us and telling it about the outcome. var mfs *fuse.MountedFileSystem diff --git a/cmd/root_test.go b/cmd/root_test.go index c1c73f3982..3700f705bb 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1302,6 +1302,98 @@ func TestArgParsing_GCSRetries(t *testing.T) { } } +func TestArgsParsing_ProfilerFlags(t *testing.T) { + tests := []struct { + name string + args []string + expectedConfig cfg.ProfilingConfig + }{ + { + name: "Default profiler config (disabled)", + args: []string{"gcsfuse", "bucket", "mountpoint"}, + expectedConfig: cfg.ProfilingConfig{ + Enabled: false, // Profiler is disabled by default + Label: "gcsfuse-0.0.0", + Mutex: false, // Default for --profiling-mutex + Cpu: true, // Default for --profiling-cpu + AllocatedHeap: true, // Default for --profiling-allocated-heap + Heap: true, // Default for --profiling-heap + Goroutines: false, // Default for --profiling-goroutines + }, + }, + { + name: "Profiler enabled, sub-profilers default", + args: []string{"gcsfuse", "--enable-cloud-profiling", "bucket", "mountpoint"}, + expectedConfig: cfg.ProfilingConfig{ + Enabled: true, + Label: "gcsfuse-0.0.0", + Mutex: false, + Cpu: true, + AllocatedHeap: true, + Heap: true, + Goroutines: false, + }, + }, + { + name: "Profiler enabled, all sub-profilers explicitly true and label set", + args: []string{"gcsfuse", "--enable-cloud-profiling", "--profiling-label=v1.0.0", "--profiling-mutex=true", "--profiling-cpu=true", "--profiling-allocated-heap=true", "--profiling-heap=true", "--profiling-goroutines=true", "bucket", "mountpoint"}, + expectedConfig: cfg.ProfilingConfig{ + Enabled: true, + Label: "v1.0.0", + Mutex: true, + Cpu: true, + AllocatedHeap: true, + Heap: true, + Goroutines: true, + }, + }, + { + name: "Profiler enabled, all sub-profilers explicitly false", + args: []string{"gcsfuse", "--enable-cloud-profiling", "--profiling-mutex=false", "--profiling-cpu=false", "--profiling-allocated-heap=false", "--profiling-heap=false", "--profiling-goroutines=false", "bucket", "mountpoint"}, + expectedConfig: cfg.ProfilingConfig{ + Enabled: true, + Label: "gcsfuse-0.0.0", + Mutex: false, + Cpu: false, + AllocatedHeap: false, + Heap: false, + Goroutines: false, + }, + }, + { + name: "Profiler explicitly disabled, some sub-profiler flags set", + args: []string{"gcsfuse", "--enable-cloud-profiling=false", "--profiling-mutex=true", "--profiling-cpu=false", "bucket", "mountpoint"}, + expectedConfig: cfg.ProfilingConfig{ + Enabled: false, // Master switch is off + Label: "gcsfuse-0.0.0", + Mutex: true, // Flag was parsed + Cpu: false, // Flag was parsed + AllocatedHeap: true, // Default for its flag + Heap: true, // Default for its flag + Goroutines: false, // Default for its flag + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var gotProfilerConfig cfg.ProfilingConfig + cmd, err := newRootCmd(func(c *cfg.Config, _, _ string) error { + gotProfilerConfig = c.Profiling + return nil + }) + require.Nil(t, err) + cmd.SetArgs(convertToPosixArgs(tc.args, cmd)) + + err = cmd.Execute() + + if assert.NoError(t, err) { + assert.Equal(t, tc.expectedConfig, gotProfilerConfig) + } + }) + } +} + func TestArgsParsing_ReadInactiveTimeoutConfig(t *testing.T) { tests := []struct { name string diff --git a/go.mod b/go.mod index 926fa47e0b..c9de8b8ee3 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/longrunning v0.6.6 // indirect cloud.google.com/go/monitoring v1.24.0 // indirect + cloud.google.com/go/profiler v0.4.2 // indirect cloud.google.com/go/pubsub v1.47.0 // indirect cloud.google.com/go/trace v1.11.4 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect @@ -73,6 +74,7 @@ require ( github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect + github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect diff --git a/go.sum b/go.sum index a966839e3e..746da2a5f1 100644 --- a/go.sum +++ b/go.sum @@ -19,6 +19,8 @@ cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM= cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= +cloud.google.com/go/profiler v0.4.2 h1:KojCmZ+bEPIQrd7bo2UFvZ2xUPLHl55KzHl7iaR4V2I= +cloud.google.com/go/profiler v0.4.2/go.mod h1:7GcWzs9deJHHdJ5J9V1DzKQ9JoIoTGhezwlLbwkOoCs= cloud.google.com/go/pubsub v1.47.0 h1:Ou2Qu4INnf7ykrFjGv2ntFOjVo8Nloh/+OffF4mUu9w= cloud.google.com/go/pubsub v1.47.0/go.mod h1:LaENesmga+2u0nDtLkIOILskxsfvn/BXX9Ak1NFxOs8= cloud.google.com/go/secretmanager v1.14.6 h1:/ooktIMSORaWk9gm3vf8+Mg+zSrUplJFKBztP993oL0= @@ -111,6 +113,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= diff --git a/internal/profiler/cloud_profiler.go b/internal/profiler/cloud_profiler.go new file mode 100644 index 0000000000..3aa0f026eb --- /dev/null +++ b/internal/profiler/cloud_profiler.go @@ -0,0 +1,55 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package profiler + +import ( + cloudprofiler "cloud.google.com/go/profiler" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "google.golang.org/api/option" +) + +type startFunctionType func(cloudprofiler.Config, ...option.ClientOption) error + +// SetupCloudProfiler initializes and starts the Cloud Profiler based on the application configuration. +func SetupCloudProfiler(mpc *cfg.ProfilingConfig) error { + return setupCloudProfiler(mpc, cloudprofiler.Start) +} + +// setupCloudProfiler is an internal helper function with a configurable start function +// for testing purposes. +func setupCloudProfiler(mpc *cfg.ProfilingConfig, startFunc startFunctionType) error { + if !mpc.Enabled { + return nil + } + + pConfig := cloudprofiler.Config{ + Service: "gcsfuse", + ServiceVersion: mpc.Label, + MutexProfiling: mpc.Mutex, + NoCPUProfiling: !mpc.Cpu, + NoAllocProfiling: !mpc.AllocatedHeap, + NoHeapProfiling: !mpc.Heap, + NoGoroutineProfiling: !mpc.Goroutines, + AllocForceGC: true, + } + + if err := startFunc(pConfig); err != nil { + return err + } + + logger.Info("Cloud Profiler started successfully.") + return nil +} diff --git a/internal/profiler/cloud_profiler_test.go b/internal/profiler/cloud_profiler_test.go new file mode 100644 index 0000000000..d3ee732e30 --- /dev/null +++ b/internal/profiler/cloud_profiler_test.go @@ -0,0 +1,86 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package profiler + +import ( + "errors" + "testing" + + cloudprofiler "cloud.google.com/go/profiler" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/api/option" +) + +func TestSetupCloudProfiler_Disabled(t *testing.T) { + mockProfilerStartCalled := false + profilerStart := func(_ cloudprofiler.Config, _ ...option.ClientOption) error { + mockProfilerStartCalled = true + return nil + } + profilingConfig := &cfg.ProfilingConfig{ + Enabled: false, + } + + err := setupCloudProfiler(profilingConfig, profilerStart) + + require.NoError(t, err, "SetupCloudProfiler should not return an error") + assert.False(t, mockProfilerStartCalled, "profilerStart should not be called when profiler is disabled") +} + +func TestSetupCloudProfiler_EnabledSuccess(t *testing.T) { + var capturedProfilerConfig cloudprofiler.Config + mockProfilerStartCalled := false + profilerStart := func(pcfg cloudprofiler.Config, _ ...option.ClientOption) error { + mockProfilerStartCalled = true + capturedProfilerConfig = pcfg + return nil + } + profilingConfig := &cfg.ProfilingConfig{ + Enabled: true, + Label: "v1.2.3", + Mutex: true, + Cpu: true, + AllocatedHeap: false, + Heap: true, + Goroutines: false, + } + + err := setupCloudProfiler(profilingConfig, profilerStart) + + require.NoError(t, err, "SetupCloudProfiler should not return an error") + require.True(t, mockProfilerStartCalled, "profilerStart should be called") + assert.Equal(t, "gcsfuse", capturedProfilerConfig.Service) + assert.Equal(t, "v1.2.3", capturedProfilerConfig.ServiceVersion) + assert.Equal(t, true, capturedProfilerConfig.MutexProfiling) + assert.Equal(t, false, capturedProfilerConfig.NoCPUProfiling) + assert.Equal(t, true, capturedProfilerConfig.NoAllocProfiling) + assert.Equal(t, false, capturedProfilerConfig.NoHeapProfiling) + assert.Equal(t, true, capturedProfilerConfig.NoGoroutineProfiling) + assert.True(t, capturedProfilerConfig.AllocForceGC) +} + +func TestSetupCloudProfiler_EnabledStartFails(t *testing.T) { + expectedErr := errors.New("profiler failed to start") + profilerStart := func(_ cloudprofiler.Config, _ ...option.ClientOption) error { return expectedErr } + profilingConfig := &cfg.ProfilingConfig{ + Enabled: true, + } + + err := setupCloudProfiler(profilingConfig, profilerStart) + + assert.EqualError(t, err, expectedErr.Error()) +} From cbe3ac77b9d71726e6896f005921f9188e2f1d6b Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 21 May 2025 20:53:53 +0530 Subject: [PATCH 0454/1298] Move load_test_scripts to gcsfuse-tools repo (#3339) --- .../scripts/load_tests/python/README.md | 146 ----- .../scripts/load_tests/python/__init__.py | 13 - .../python/load_generator/__init__.py | 13 - .../python/load_generator/constants.py | 43 -- .../python/load_generator/load_generator.py | 392 ------------ .../load_tests/python/load_generator/task.py | 89 --- .../scripts/load_tests/python/load_test.py | 360 ----------- .../scripts/load_tests/python/requirements.in | 15 - .../load_tests/python/requirements.txt | 582 ------------------ .../load_tests/python/sample_tasks.yaml | 44 -- .../load_tests/python/tasks/__init__.py | 13 - .../load_tests/python/tasks/python_os.py | 107 ---- .../load_tests/python/tasks/tf_data.py | 141 ----- .../load_tests/python/tasks/tf_gfile.py | 103 ---- 14 files changed, 2061 deletions(-) delete mode 100644 perfmetrics/scripts/load_tests/python/README.md delete mode 100644 perfmetrics/scripts/load_tests/python/__init__.py delete mode 100644 perfmetrics/scripts/load_tests/python/load_generator/__init__.py delete mode 100644 perfmetrics/scripts/load_tests/python/load_generator/constants.py delete mode 100644 perfmetrics/scripts/load_tests/python/load_generator/load_generator.py delete mode 100644 perfmetrics/scripts/load_tests/python/load_generator/task.py delete mode 100644 perfmetrics/scripts/load_tests/python/load_test.py delete mode 100644 perfmetrics/scripts/load_tests/python/requirements.in delete mode 100644 perfmetrics/scripts/load_tests/python/requirements.txt delete mode 100644 perfmetrics/scripts/load_tests/python/sample_tasks.yaml delete mode 100644 perfmetrics/scripts/load_tests/python/tasks/__init__.py delete mode 100644 perfmetrics/scripts/load_tests/python/tasks/python_os.py delete mode 100644 perfmetrics/scripts/load_tests/python/tasks/tf_data.py delete mode 100644 perfmetrics/scripts/load_tests/python/tasks/tf_gfile.py diff --git a/perfmetrics/scripts/load_tests/python/README.md b/perfmetrics/scripts/load_tests/python/README.md deleted file mode 100644 index 928d00a008..0000000000 --- a/perfmetrics/scripts/load_tests/python/README.md +++ /dev/null @@ -1,146 +0,0 @@ -# Python load testing tool - -Python load testing tool is to generate load on the machine it is run using a -given task and then report the latencies of performing the task over the span -of load test. -A task is a piece of python code that is executed in multiple threads -in multiple processes. Users can define their own tasks, pass them to the tool -and pass different flags to configure the load testing. - -Example usage: -``` -python3 load_test.py --tasks-python-file-path /path/to/task/module.py ---num-processes 32 --num-threads 2 --output-dir /dir/for/test/output ---run-time 60 -``` -In the above example usage, two threads are spawned in each of the 32 processes -where each thread runs the task defined in /path/to/task/module.py in a -continuous loop till 60 seconds. After 60 seconds, the results containing -latencies are saved in /dir/for/test/output. - -# Prerequisites - -* python3 -* python packages mentioned in [requirements.txt] - -[requirements.txt]: ./requirements.txt - -# Supported flags -[load_test.py] is the script used that can be used to run load test. The script -accepts many flags to customize load testing configuration. Below are some -important flags that can be passed to the script: -* ```--tasks-python-file-path```: Path to python module (file) containing task -classes implementing task.LoadTestTask. -* ```--tasks-yaml-file-path```: Path to yaml file containing configurations for -tasks. Note: Configurations in this file can only be of defined and recognised -tasks. To know about the recognises types, see [sample_tasks.yaml]. -* ```--num-processes```: Number of processes to spawn in load tests with - --num-threads threads where each thread runs the task. -* ```--num-threads-per-process```: Number of threads to run in each process -spawned for load test. Each thread runs the task in a loop depending and -terminate depending upon other flags. -* ```--run-time```: Duration in seconds for which to run the load test. -* ```--output-dir```: Path to directory where you want to save the output of -load tests. One file is created for each task with which load test is performed. - -For more details on the supported flags, their default values and uses, please -run load_test.py script with ```--help``` flag. - -[load_test.py]: ./load_test.py -[sample_tasks.yaml]: ./sample_tasks.yaml - -# Output metrics -The output of load test contains the following metrics: -* General: Start time, end time, actual run time, tasks count. -* Latencies: Min, mean, max latencies and 25th, 50th, 95th and 99th percentiles -of latencies of task performed over span of load test. - -The output of load test performed using task with name SampleTask is saved -in the file ```output-dir/SampleTask.json```. - -# How to run - -## Custom task -Let's say we want to run a CPU intensive task parallely with 40 processes for 5 -minutes (300s) and save the result in file: ```~/output/CPUTask.json``` - -### Steps: -* Make sure the prerequisites are installed. -* Set ```PYTHONPATH = gcsfuse/perfmetrics/scripts/load_tests/python/``` -* Create a module for task class and implement LoadTestTask class in it i.e. -define task method. E.g. -``` -from load_generator import task - -class CPUTask(task.LoadTestTask): - - def task(self, process_id, thread_id): - s = 0 - for i in range(1000000): - s = s + process_id * thread_id - return s -``` -cpu_task.py -* Run the following command: -``` -python3 load_test.py --tasks-python-file-path cpu_task.py --num-processes 40 ---output-dir ~/output --run-time 300 -``` -* The latencies of CPU task performed over the span of load test is saved in -```~/output/CPUTask.json```. - -## Predefined tasks -The following [tasks] are predefined in tasks directory: -* python_os.py: Tasks to read files from disk python's native open api. Can be -used with GCSFuse if disk is mounted using GCSFuse. -* tf_gfile.py: Tasks to read files from GCS using tf's tf.io.gfile.Gfile api. -Can be used with GCSFuse or GCS files. -* tf_data.py: Tasks to read files from GCS using tf's tf.data api. Can be used -with GCSFuse or GCS files. - -For more details on the tasks, please refer to the module level -description of files. - -### Steps: -* Make sure the prerequisites are installed. -* Set ```PYTHONPATH = gcsfuse/perfmetrics/scripts/load_tests/python/``` -* Create a yaml file containing configs for predefined tasks. E.g. -``` ---- -200mb_os: - task_type: python_os_read - file_path_format: ./gcs/200mb/read.{process_id} - file_size: 200M - -200mb_tf_data: - task_type: tf_data_read - file_path_format: gs://load-test-bucket-gcs/200mb/read.{file_num}.tfrecord - file_size: 200M - num_files: 3072 -``` -read_tasks.yaml. - -For more details on the supported parameters in configs of predefined tasks, -please refer to [sample_tasks.yaml] -* Run the following command: -``` -python3 load_test.py --tasks-yaml-file-path read_tasks.yaml --num-processes 40 ---output-dir ~/output --run-time 300 -``` -* The latencies of read tasks performed over the span of load test is saved in - ```~/output/200mb_os.json``` & ```~/output/200mb_tf_data.json```. - -[sample_tasks.yaml]: ./sample_tasks.yaml -[tasks]: tasks - -# Miscellaneous -* GCSFuse has to be mounted for using with [python_os.py] tasks. -* It is recommended to keep --num-processes and --num-threads as 1 for -[tf_data.py] tasks as the parallelism is inside those tasks. -* All the tasks defined under [tasks] directory are marked as read/write tasks. -So, load_test.py script tries to create files before running actual load tests. -* Task using tf apis require gcloud login on machine. - -[python_os.py]: tasks/python_os.py -[tf_data.py]: tasks/tf_data.py -[tasks]: tasks diff --git a/perfmetrics/scripts/load_tests/python/__init__.py b/perfmetrics/scripts/load_tests/python/__init__.py deleted file mode 100644 index 1dc90d1848..0000000000 --- a/perfmetrics/scripts/load_tests/python/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/perfmetrics/scripts/load_tests/python/load_generator/__init__.py b/perfmetrics/scripts/load_tests/python/load_generator/__init__.py deleted file mode 100644 index 1dc90d1848..0000000000 --- a/perfmetrics/scripts/load_tests/python/load_generator/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/perfmetrics/scripts/load_tests/python/load_generator/constants.py b/perfmetrics/scripts/load_tests/python/load_generator/constants.py deleted file mode 100644 index a738df22e5..0000000000 --- a/perfmetrics/scripts/load_tests/python/load_generator/constants.py +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Module for constants to be used in load generator module and script. -""" -# Sizes -KB = 1024 -MB = 1024 * KB -GB = 1024 * MB - -# Used in showing percentage of load test completed (time-wise). e.g 0.25 -# represents load test has run 25% of its total run time. -TIME_LOADING_PERCENTAGES = (0.25, 0.50, 0.75) - -# Metrics names -START_TIME = 'start_time' -END_TIME = 'end_time' -TASKS_RESULTS = 'tasks_results' -PRE_TASKS_RESULTS = 'pre_tasks_results' -POST_TASKS_RESULTS = 'post_tasks_results' -TASKS_LAT_STATS = 'tasks_lat_stats' -PRE_TASKS_LAT_STATS = 'pre_tasks_lat_stats' -POST_TASKS_LAT_STATS = 'post_tasks_lat_stats' -MIN = 'min' -MAX = 'max' -MEAN = 'mean' -PER_25 = 'per_25' -PER_50 = 'per_50' -PER_90 = 'per_90' -PER_95 = 'per_95' -PER_99 = 'per_99' -TASKS_COUNT = 'tasks_count' -ACTUAL_RUN_TIME = 'actual_run_time' diff --git a/perfmetrics/scripts/load_tests/python/load_generator/load_generator.py b/perfmetrics/scripts/load_tests/python/load_generator/load_generator.py deleted file mode 100644 index df50b7ae55..0000000000 --- a/perfmetrics/scripts/load_tests/python/load_generator/load_generator.py +++ /dev/null @@ -1,392 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Load Generator for performing load test with a given task (in python). - -Contains the class for generating load with the help of a task written in -python. - -Example: - - task_obj = ReadTask() - lg_obj = LoadGenerator(...) - lg_obj.pre_load_generation(...) - observations = lg_obj.generate_load(task_obj) - metrics = lg_obj.post_load_generation(observations, ...) -""" -import logging -import sys -import threading -import time -import json -import multiprocessing -import numpy as np -from dataclasses import dataclass -from typing import Any -from load_generator import constants as lg_const - - -@dataclass(frozen=True) -class TaskExecutionResult: - """Dataclass for a single task (pre, task & post) execution result. - - process_id: Integer value denoting the process id in which the task was run. - thread_id: Integer value denoting the thread id in process_id in which the - task was run. - start_time: Float value denoting the time when the task was started. - end_time: Float value denoting the time when the task was ended. - result: Any type of value that the task returns - """ - process_id: int - thread_id: int - start_time: float - end_time: float - result: Any - - -class LoadGenerator: - """Generates load using a given task. - - Generates load on CPU and other resources depending upon the given task. This - class also provides default implementation of post load test task to get - latencies of tasks. Classes derived from this class can define - their own pre- and post-load test tasks and run before and after actual load - test. - - Args: - num_processes: An integer defining the number of processes to run during - load test. - num_threads_per_process: An integer defining the number of threads to run - in each process during load test. - run_time: An integer defining the number of seconds to run the load test - for. - num_executions_per_thread: An integer defining the number of times the given - task to be run inside each thread of each process during load test. - """ - - def __init__(self, - num_processes, - num_threads_per_process, - run_time=sys.maxsize, - num_executions_per_thread=sys.maxsize): - self.num_processes = num_processes - self.num_threads_per_process = num_threads_per_process - self.run_time = min(sys.maxsize, run_time) - self.num_executions_per_thread = min(sys.maxsize, num_executions_per_thread) - - if (self.run_time == sys.maxsize) & \ - (self.num_executions_per_thread == sys.maxsize): - raise ValueError('Out of run_time and num_executions_per_thread ' - 'arguments, one has to be passed.') - - self.total_num_tasks = sys.maxsize - if self.num_executions_per_thread != sys.maxsize: - self.total_num_tasks = self.num_executions_per_thread * \ - self.num_threads_per_process * self.num_processes - - def pre_load_generation(self): - """Task to perform before running load test. - """ - pass - - def generate_load(self, task): - """Performs load test using the given task. - - The load is generated on CPU and other resources (depending upon the task - used) by running process(s) and thread(s) where task runs inside thread(s) - and thread(s) runs inside process(s). - - Args: - task: Implementation of task.LoadTestTask. - - Returns: - Returns start_time, end_time of load test, latencies and results of all - the tasks performed over the span of load test. - - Raises: - RuntimeError: If the given task is not completed even once during the - course of load test. - """ - tasks_results_queue = multiprocessing.Manager().Queue() - pre_tasks_results_queue = multiprocessing.Manager().Queue() - post_tasks_results_queue = multiprocessing.Manager().Queue() - - processes = [] - for process_id in range(self.num_processes): - process = multiprocessing.Process( - target=LoadGenerator._process_task, - args=(task, process_id, self.num_threads_per_process, - self.num_executions_per_thread, pre_tasks_results_queue, - tasks_results_queue, post_tasks_results_queue)) - processes.append(process) - - # Initialize checkpoints to show completion of load test i.e. 25%, 50% etc. - # Note: Completion checkpoints are shown only when self.run_time is set. - # E.g. if self.run_time is 60 then the load test will inform that 50% of - # load test is completed after 30 seconds. - log_loading = self.run_time != sys.maxsize - loading_checkpoints = list( - map(lambda t: (t * self.run_time), lg_const.TIME_LOADING_PERCENTAGES)) - curr_loading_idx = 0 - - for process in processes: - process.start() - logging.debug('%s number of processes started for task %s', len(processes), - task.task_name) - - start_time = curr_time = time.time() - loading_checkpoints = [t + start_time for t in loading_checkpoints] - # Loop till the condition of termination of load test is not met. The - # condition is either the load test has run for self.run_time or completed - # the total number of tasks assigned. - while ((curr_time - start_time) < self.run_time) & \ - (tasks_results_queue.qsize() < self.total_num_tasks): - # Sleep so that the looping is not very fast. 0.1 is decided on - # discretion with the intention that time duration shouldn't be very - # small or shouldn't be very large. - time.sleep(0.1) - curr_time = time.time() - if log_loading & (curr_loading_idx < len(loading_checkpoints)) and ( - curr_time >= loading_checkpoints[curr_loading_idx]): - logging.info('Load test completed %s%% for task: %s', - lg_const.TIME_LOADING_PERCENTAGES[curr_loading_idx] * 100, - task.task_name) - curr_loading_idx = curr_loading_idx + 1 - logging.info('Load test completed 100%% for task: %s', task.task_name) - - for process in processes: - process.terminate() - logging.debug('%s number of processes terminated for task %s', - len(processes), task.task_name) - - # Raise error if not even a single task is completed - if tasks_results_queue.qsize() < 1: - raise RuntimeError('Not even a single task is completed. Pass higher ' - 'value to --run-time flag or check the task.') - return { - lg_const.START_TIME: - start_time, - lg_const.END_TIME: - curr_time, - lg_const.TASKS_RESULTS: - self._convert_multiprocessing_queue_to_list(tasks_results_queue), - lg_const.PRE_TASKS_RESULTS: - self._convert_multiprocessing_queue_to_list(pre_tasks_results_queue - ), - lg_const.POST_TASKS_RESULTS: - self._convert_multiprocessing_queue_to_list(post_tasks_results_queue - ), - } - - def post_load_generation(self, - observations, - output_file=None, - print_metrics=True): - """Task to perform after load testing. - - In this default implementation, latency metrics are computed. It can also - dump and print the metrics. - - Args: - observations: Observations collected during load generation. - output_file: String path where the metrics are dumped in JSON. - print_metrics: Bool, whether to print the metrics on console or not. - - Returns: - Default metrics (latencies of tasks) in a dictionary. - """ - metrics = self._compute_default_post_test_metrics(observations) - - # Dump metrics. - if output_file: - self._dump_metrics_into_json(metrics, output_file) - - # Print metrics on console - if print_metrics: - self._print_default_metrics(metrics) - - return metrics - - @staticmethod - def _thread_task(task, process_id, thread_id, num_executions_per_thread, - pre_tasks_results_queue, tasks_results_queue, - post_tasks_results_queue): - """Task run in threads spawned during the load test. - - The task used for the load test is run inside this thread. Pre- and - post-task are also run inside this thread. - This method is kept as protected as it is not used in other classes and - static because class methods can't be passed as target of thread. - """ - cnt = 0 - tasks = [task.pre_task, task.task, task.post_task] - queues = [ - pre_tasks_results_queue, tasks_results_queue, post_tasks_results_queue - ] - while cnt < num_executions_per_thread: - for curr_task, curr_queue in zip(tasks, queues): - start_time = time.time() - result = curr_task(process_id, thread_id) - end_time = time.time() - curr_queue.put( - TaskExecutionResult( - process_id=process_id, - thread_id=thread_id, - start_time=start_time, - end_time=end_time, - result=result)) - cnt = cnt + 1 - - @staticmethod - def _process_task(task, process_id, num_threads_per_process, - num_executions_per_thread, pre_tasks_results_queue, - tasks_results_queue, post_tasks_results_queue): - """Task run in processes spawned during the load test. - - It spawns num_threads_per_process number of threads where each thread runs - the task of load test. - This method is kept as protected as it is not used in other classes and - static because class methods can't be passed as target of process. - """ - # Spawn threads that will run task of load test. - threads = [] - for thread_num in range(num_threads_per_process): - threads.append( - threading.Thread( - target=LoadGenerator._thread_task, - args=(task, process_id, thread_num, num_executions_per_thread, - pre_tasks_results_queue, tasks_results_queue, - post_tasks_results_queue))) - - for thread in threads: - # Thread is kept as daemon, so that it is killed when the parent process - # is killed. - thread.daemon = True - thread.start() - logging.debug('Threads started for process number: %s', process_id) - - for thread in threads: - thread.join() - logging.debug('Threads tasks completed for process number: %s', process_id) - - def _convert_multiprocessing_queue_to_list(self, mp_queue): - """Converts the multiprocessing queue to list. - """ - queue_size = mp_queue.qsize() - return [mp_queue.get() for _ in range(queue_size)] - - def _compute_percentiles(self, data_pts): - """Compute percentiles for given data points. - - Args: - data_pts: List of integer data points. - - Returns: - Dictionary containing 25, 50, 90, 95 & 99 percentiles along with min, - max and mean. - """ - np_array = np.array(data_pts) - return { - lg_const.MIN: min(data_pts), - lg_const.MEAN: np.mean(np_array), - lg_const.MAX: max(data_pts), - lg_const.PER_25: np.percentile(np_array, 25), - lg_const.PER_50: np.percentile(np_array, 50), - lg_const.PER_90: np.percentile(np_array, 90), - lg_const.PER_95: np.percentile(np_array, 95), - lg_const.PER_99: np.percentile(np_array, 99) - } - - def _compute_default_post_test_metrics(self, observations): - """Computes default post load test metrics using observations. - - Computes latency related metrics (percentiles, min, max and mean) of tasks - performed over the course of load test. - """ - # Time stamps - start_time = observations[lg_const.START_TIME] - end_time = observations[lg_const.END_TIME] - actual_run_time = end_time - start_time - - # Latency stats - latency_stats_names = [ - lg_const.PRE_TASKS_LAT_STATS, lg_const.TASKS_LAT_STATS, - lg_const.POST_TASKS_LAT_STATS - ] - result_names = [ - lg_const.PRE_TASKS_RESULTS, lg_const.TASKS_RESULTS, - lg_const.POST_TASKS_RESULTS - ] - latency_stats = {} - for stat_name, result_name in zip(latency_stats_names, result_names): - lat_pts = [ - result.end_time - result.start_time - for result in observations[result_name] - ] - lat_pers = self._compute_percentiles(lat_pts) - latency_stats[stat_name] = lat_pers - - metrics = { - lg_const.START_TIME: start_time, - lg_const.END_TIME: end_time, - lg_const.ACTUAL_RUN_TIME: actual_run_time, - lg_const.TASKS_COUNT: len(observations[lg_const.TASKS_RESULTS]) - } - metrics.update(latency_stats) - return metrics - - def _dump_metrics_into_json(self, metrics, output_file): - """Dumps given metrics as JSON file in UTF-8 format given output directory. - """ - with open(output_file, 'w', encoding='utf-8') as f_p: - json.dump(metrics, f_p) - - def _print_default_metrics(self, metrics): - """Prints given default metrics to console. - """ - actual_run_time = metrics[lg_const.ACTUAL_RUN_TIME] - latency_stats_names = [ - lg_const.PRE_TASKS_LAT_STATS, lg_const.TASKS_LAT_STATS, - lg_const.POST_TASKS_LAT_STATS - ] - task_type_names = ['Pre', 'Task', 'Post'] - - # Time metrics - print('\nTime: ') - print('\tStart time (epoch): ', metrics[lg_const.START_TIME]) - print('\tEnd time (epoch): ', metrics[lg_const.END_TIME]) - print('\tActual run time (in seconds): ', actual_run_time) - - # Task related - print('\nTasks: ') - print('\tTasks count: ', metrics[lg_const.TASKS_COUNT]) - print('\tTasks per sec: ', metrics[lg_const.TASKS_COUNT] / actual_run_time) - - # Latency metrics - print('\nTasks latencies: ') - for task_type, latency_stat_name in zip(task_type_names, - latency_stats_names): - print('\t', task_type, ': ') - print('\t\tMin (in seconds): ', metrics[latency_stat_name][lg_const.MIN]) - print('\t\tMean (in seconds): ', metrics[latency_stat_name][lg_const.MAX]) - print('\t\t25th Percentile (in seconds): ', - metrics[latency_stat_name][lg_const.PER_25]) - print('\t\t50th Percentile (in seconds): ', - metrics[latency_stat_name][lg_const.PER_50]) - print('\t\t90th Percentile (in seconds): ', - metrics[latency_stat_name][lg_const.PER_90]) - print('\t\t95th Percentile (in seconds): ', - metrics[latency_stat_name][lg_const.PER_95]) - print('\t\t99th Percentile (in seconds): ', - metrics[latency_stat_name][lg_const.PER_99]) - print('\t\tMax (in seconds): ', metrics[latency_stat_name][lg_const.MAX]) diff --git a/perfmetrics/scripts/load_tests/python/load_generator/task.py b/perfmetrics/scripts/load_tests/python/load_generator/task.py deleted file mode 100644 index 946a8cae4f..0000000000 --- a/perfmetrics/scripts/load_tests/python/load_generator/task.py +++ /dev/null @@ -1,89 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Contains abstract class to represent task in load test. - -Example: - - lg_obj = lg.LoadGenerator(...) - task_obj = TaskImplementingLoadTestTask(...) - metrics = lg_obj.generate_load(task_obj) -""" -from abc import ABC, abstractmethod - - -class LoadTestTask(ABC): - """Abstract class to represent task in load test. - - pre_task represents the task to be performed before performing the actual - task and post_task represents the task to be performed after the actual task. - However, pre_task and post_task are not mandatory for a task in a typical load - test. - """ - - def __init__(self): - # A name given to task. It is recommended to keep a unique name for every - # task implemented in same module. - self.task_name = self.__class__.__name__ - - def pre_task(self, process_id, thread_id): - """Task to be always performed before the actual task in a load test. - - process_id & thread_id are kept as arguments in case the task logic - requires them. - - Args: - process_id: The process number assigned by the load generator to - the process running this task. - thread_id: The process number assigned by the load generator to - the thread running this task. - - Returns: - Result of the task. Type can be anything depending upon the use-case. - """ - pass - - @abstractmethod - def task(self, process_id, thread_id): - """Actual task in a load test. - - process_id & thread_id are kept as arguments in case the task logic - requires them. - - Args: - process_id: The process number assigned by the load generator to - the process running this task. - thread_id: The process number assigned by the load generator to - the thread running this task. - - Returns: - Result of the task. Type can be anything depending upon the use-case. - """ - pass - - def post_task(self, process_id, thread_id): - """Task to be always performed after the actual task in a load test. - - process_id & thread_id are kept as arguments in case the task logic - requires them. - - Args: - process_id: The process number assigned by the load generator to - the process running this task. - thread_id: The process number assigned by the load generator to - the thread running this task. - - Returns: - Result of the task. Type can be anything depending upon the use-case. - """ - pass diff --git a/perfmetrics/scripts/load_tests/python/load_test.py b/perfmetrics/scripts/load_tests/python/load_test.py deleted file mode 100644 index e864716276..0000000000 --- a/perfmetrics/scripts/load_tests/python/load_test.py +++ /dev/null @@ -1,360 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Script to run load tests with given tasks and configuration. - -The script requires tasks to run in load tests which can be written in a python -module or YAML file and then path of that file can be passed to this load script -using --tasks-python-file-path or --tasks-yaml-file path flag. -Apart from the tasks, the load test can also be customized in multiple ways -like number of processes, threads inside each process and run time. -To know more about the flags supported in script, run: - python3 load_test.py --help - -Example: - python3 load_test.py --task-python-file-path tasks/python_os.py - --num-processes 40 --num-threads 1 --run-time 90 --output-dir ~/output -""" -import logging -import importlib -import importlib.machinery -import importlib.util -import inspect -import sys -import os -import time -import argparse -import yaml -import re - -from load_generator import load_generator as lg -from load_generator import task -from load_generator import constants as lg_const -from tasks import python_os -from tasks import tf_data -from tasks import tf_gfile - -READ_WRITE_TASK_TYPES = [ - python_os.PYTHON_OS_READ, tf_data.TF_DATA_READ, tf_gfile.TF_GFILE_READ -] -KNOWN_TASK_TYPES = READ_WRITE_TASK_TYPES -SIZE_ABBREV_TO_SIZE = {'K': lg_const.KB, 'M': lg_const.MB, 'G': lg_const.GB} - - -def parse_args(): - """Parses the command line arguments and returns them. - - Returns: - Arguments passed through command line set as attributes in args object. - """ - parser = argparse.ArgumentParser( - description='Script to do Load testing with ' - 'given task using multiprocessing and ' - 'multithreading on CPU and ' - 'other resources.', - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - group = parser.add_mutually_exclusive_group(required=True) - group.add_argument( - '--tasks-python-file-path', - type=str, - help='Path to python module (file) containing task classes implementing ' - 'the base task.LoadTestTask') - group.add_argument( - '--tasks-yaml-file-path', - type=str, - help='Path to yaml file containing configs for known (predefined) ' - f'tasks: {KNOWN_TASK_TYPES}') - parser.add_argument( - '--task-names', - type=str, - default='', - help='Comma separated name(s) of tasks (task.LoadTestTask.task_name) in ' - 'the --tasks-python-file-path or --tasks-yaml-file-path. Load test is ' - 'conducted only with those tasks. If empty string or nothing is passed ' - "then load test is conducted with all tasks. E.g. 'TaskA, TaskB', " - "'TaskA,TaskB'.") - parser.add_argument( - '--output-dir', - type=str, - default=None, - help='Path to directory where you want to save the output of load tests. ' - ) - parser.add_argument( - '--num-processes', - type=int, - default=1, - help='Number of processes to spawn in load tests with ' - '--num-threads-per-process threads where each thread runs the task.') - parser.add_argument( - '--num-threads-per-process', - type=int, - default=1, - help='Number of threads to run in each process spawned for load test. ' - 'Each thread runs the task in a loop and terminate depending upon other ' - 'flags.') - parser.add_argument( - '--run-time', - type=int, - default=600, - help='Duration in seconds for which to run the load test. Note: The load ' - 'test may terminate before depending upon value of ' - '--num-executions-per-thread flags passed.') - parser.add_argument( - '--num-executions-per-thread', - type=int, - default=sys.maxsize, - help='Total number of times the given task to be performed inside each ' - 'thread of each process of load test. Note: It is possible that the task ' - 'is not performed given number of times if --run-time is not enough.') - parser.add_argument( - '--start-delay', - type=int, - default=5, - help='Time in seconds to wait before conducting load test on a task.') - parser.add_argument( - '--debug', - action='store_true', - help='Prints debug logs along with info logs.') - args = parser.parse_args() - args.task_names = args.task_names.replace(' ', '').split(',') - args.task_names = [el for el in args.task_names if len(el)] - return args - - -def import_module_using_src_code_path(src_code_path): - """Imports the python module from its path to source code at runtime. - - Args: - src_code_path: String path to source code of module. - - Returns: - Imported python module - """ - module_name = src_code_path.split('/')[-1].replace('.py', '') - loader = importlib.machinery.SourceFileLoader(module_name, src_code_path) - spec = importlib.util.spec_from_loader(loader.name, loader) - mod = importlib.util.module_from_spec(spec) - loader.exec_module(mod) - return mod - - -def get_tasks_from_python_file_path(python_file_path): - """Get tasks defined in given python module from its file path. - - Args: - python_file_path: String file path to python module. - - Returns: - List of task objects (task.LoadTestTask) - """ - mod = import_module_using_src_code_path(python_file_path) - task_objs = [] - for _, cls in inspect.getmembers(mod, inspect.isclass): - # Skip classes imported in the task file - if cls.__module__ != mod.__name__: - continue - # Skip classes that are not of type task.LoadTestTask or don't - # have implementation. - if (not issubclass(cls, task.LoadTestTask)) or (inspect.isabstract(cls)): - continue - task_objs.append(cls()) - return task_objs - - -def parse_file_size_str(file_size_str): - """Gives file size in bytes from size string. - - Args: - file_size_str: String file size. - - Returns: - Size of file in bytes. - - Raises: - ValueError: If file size string is not of recognised format. - """ - if not bool(re.fullmatch(r'[0-9]+[kKmMgB]', file_size_str)): - raise ValueError('The file size str set in config is not of recognised ' - f'format: {file_size_str}') - return int(file_size_str[:-1]) * \ - SIZE_ABBREV_TO_SIZE[file_size_str[-1].upper()] - - -def get_task_from_config(task_name, config): - """Identify, creates and returns task from config defining task. - - Args: - task_name: String name of task. - config: Dictionary defining config for task. - - Returns: - Task object instantiated using config. - - Raises: - ValueError: If the task_type in config is not recognised and defined. - """ - task_type = config['task_type'] - config.pop('task_type') - config['file_size'] = parse_file_size_str(config['file_size']) - if 'block_size' in config: - config['block_size'] = parse_file_size_str(config['block_size']) - task_cls = None - if task_type == python_os.PYTHON_OS_READ: - task_cls = python_os.OSRead - elif task_type == tf_data.TF_DATA_READ: - task_cls = tf_data.TFDataRead - elif task_type == tf_gfile.TF_GFILE_READ: - task_cls = tf_gfile.TFGFileRead - else: - raise ValueError(f'Given task type {task_type} is not in known types ' - '{KNOWN_TASK_TYPES}') - return task_cls(task_name=task_name, **config) - - -def get_tasks_from_yaml_file_path(yaml_file_path): - """Gives tasks corresponding to configs in given yaml from its file path. - - Args: - yaml_file_path: String path to yaml file. - - Returns: - Task objects instantiated using configs defined in yaml file. - """ - task_configs = {} - with open(yaml_file_path, 'r', encoding='utf-8') as yaml_fh: - task_configs = yaml.safe_load(yaml_fh) - - task_objs = [] - for task_name, config in task_configs.items(): - task_objs.append(get_task_from_config(task_name, config)) - return task_objs - - -class LoadGeneratorWithFileCreation(lg.LoadGenerator): - """Custom load generator derived from load_generator.LoadGenerator. - - This implementation has pre- and post- load test methods for read and write - files task types. (READ_WRITE_TASK_TYPES) - See base class for more details. - """ - - def pre_load_generation(self, task_obj): - """Pre- load test function to create files for read and write tasks. - - Calls task_obj.create_files method if task has create_files method - implemented. create_files method is implemented in case of - READ_WRITE_TASK_TYPES. - - Args: - task_obj: Task object on which load testing to be performed. - - Returns: - None - """ - if getattr(task_obj, 'task_type', '') in READ_WRITE_TASK_TYPES and \ - hasattr(task_obj, 'create_files'): - task_obj.create_files(self.num_processes) - - def post_load_generation(self, observations, output_file, print_metrics, - task_obj): - """Custom implementation of post-load test function. - - This function adds avg_computed_net_bw on top of default post load test - implementation. - avg_computed_net_bw = (SUM(values returned by task.LoadGenerator.task()) / - actual_run_time). - See base class implementation for more details. - """ - metrics = super().post_load_generation(observations, output_file, - print_metrics) - # only run custom logic for read and write tasks - if getattr(task_obj, 'task_type', '') not in READ_WRITE_TASK_TYPES: - return metrics - - # compute bandwidth from task results - total_io_bytes = sum( - (task_result.result - for task_result in observations[lg_const.TASKS_RESULTS])) - avg_computed_net_bw = total_io_bytes / metrics[lg_const.ACTUAL_RUN_TIME] - avg_computed_net_bw = avg_computed_net_bw / lg_const.MB - metrics.update({'avg_computed_net_bw': avg_computed_net_bw}) - - # Re-dump the metrics to same file. - if output_file: - self._dump_metrics_into_json(metrics, output_file) - - if print_metrics: - # print additional metrics - print('\nNetwork bandwidth (computed by Sum(task response) / actual ' - 'run time):') - print('\tAvg. bandwidth (MiB/sec): ', metrics['avg_computed_net_bw'], - '\n') - return metrics - - -def main(): - args = parse_args() - - logging.getLogger().setLevel(logging.INFO) - if args.debug: - logging.getLogger().setLevel(logging.DEBUG) - - logging.info('Initialising Load Generator...') - lg_obj = LoadGeneratorWithFileCreation( - num_processes=args.num_processes, - num_threads_per_process=args.num_threads_per_process, - run_time=args.run_time, - num_executions_per_thread=args.num_executions_per_thread) - - task_objs = [] - if args.tasks_python_file_path: - task_objs = get_tasks_from_python_file_path(args.tasks_python_file_path) - else: - task_objs = get_tasks_from_yaml_file_path(args.tasks_yaml_file_path) - - # keep only those tasks passed in args.task_names. Note: If task_names = '' - # then all tasks are kept. - filtered_task_objs = [] - for task_obj in task_objs: - if len(args.task_names) == 0 or task_obj.task_name in args.task_names: - filtered_task_objs.append(task_obj) - - logging.info('Starting load generation...') - load_test_results = [] - for task_obj in filtered_task_objs: - logging.info('\nSleeping for: %s seconds', args.start_delay) - time.sleep(args.start_delay) - - logging.info('\nRunning pre load test task for: %s', task_obj.task_name) - lg_obj.pre_load_generation(task_obj=task_obj) - - logging.info('Generating load for: %s', task_obj.task_name) - observations = lg_obj.generate_load(task_obj) - - output_file = None - if args.output_dir and (not os.path.exists(args.output_dir)): - os.makedirs(args.output_dir) - output_file = os.path.join(args.output_dir, f'{task_obj.task_name}.json') - - logging.info('Running post load test task for: %s', task_obj.task_name) - metrics = lg_obj.post_load_generation( - observations, - output_file=output_file, - print_metrics=True, - task_obj=task_obj) - load_test_results.append((task_obj.task_name, metrics)) - logging.info('Load test completed for task: %s', task_obj.task_name) - - -if __name__ == '__main__': - main() diff --git a/perfmetrics/scripts/load_tests/python/requirements.in b/perfmetrics/scripts/load_tests/python/requirements.in deleted file mode 100644 index dbe870d622..0000000000 --- a/perfmetrics/scripts/load_tests/python/requirements.in +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -tensorflow==2.17.0 - diff --git a/perfmetrics/scripts/load_tests/python/requirements.txt b/perfmetrics/scripts/load_tests/python/requirements.txt deleted file mode 100644 index 256fd54c16..0000000000 --- a/perfmetrics/scripts/load_tests/python/requirements.txt +++ /dev/null @@ -1,582 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --generate-hashes ./requirements.in -# -absl-py==1.4.0 \ - --hash=sha256:0d3fe606adfa4f7db64792dd4c7aee4ee0c38ab75dfd353b7a83ed3e957fcb47 \ - --hash=sha256:d2c244d01048ba476e7c080bd2c6df5e141d211de80223460d5b3b8a2a58433d - # via - # keras - # tensorboard - # tensorflow -astunparse==1.6.3 \ - --hash=sha256:5ad93a8456f0d084c3456d059fd9a92cce667963232cbf763eac3bc5b7940872 \ - --hash=sha256:c2652417f2c8b5bb325c885ae329bdf3f86424075c4fd1a128674bc6fba4b8e8 - # via tensorflow -certifi==2024.7.4 \ - --hash=sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b \ - --hash=sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90 - # via requests -charset-normalizer==3.1.0 \ - --hash=sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6 \ - --hash=sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1 \ - --hash=sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e \ - --hash=sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373 \ - --hash=sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62 \ - --hash=sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230 \ - --hash=sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be \ - --hash=sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c \ - --hash=sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0 \ - --hash=sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448 \ - --hash=sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f \ - --hash=sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649 \ - --hash=sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d \ - --hash=sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0 \ - --hash=sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706 \ - --hash=sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a \ - --hash=sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59 \ - --hash=sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23 \ - --hash=sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5 \ - --hash=sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb \ - --hash=sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e \ - --hash=sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e \ - --hash=sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c \ - --hash=sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28 \ - --hash=sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d \ - --hash=sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41 \ - --hash=sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974 \ - --hash=sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce \ - --hash=sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f \ - --hash=sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1 \ - --hash=sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d \ - --hash=sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8 \ - --hash=sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017 \ - --hash=sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31 \ - --hash=sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7 \ - --hash=sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8 \ - --hash=sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e \ - --hash=sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14 \ - --hash=sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd \ - --hash=sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d \ - --hash=sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795 \ - --hash=sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b \ - --hash=sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b \ - --hash=sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b \ - --hash=sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203 \ - --hash=sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f \ - --hash=sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19 \ - --hash=sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1 \ - --hash=sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a \ - --hash=sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac \ - --hash=sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9 \ - --hash=sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0 \ - --hash=sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137 \ - --hash=sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f \ - --hash=sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6 \ - --hash=sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5 \ - --hash=sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909 \ - --hash=sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f \ - --hash=sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0 \ - --hash=sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324 \ - --hash=sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755 \ - --hash=sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb \ - --hash=sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854 \ - --hash=sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c \ - --hash=sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60 \ - --hash=sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84 \ - --hash=sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0 \ - --hash=sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b \ - --hash=sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1 \ - --hash=sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531 \ - --hash=sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1 \ - --hash=sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11 \ - --hash=sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326 \ - --hash=sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df \ - --hash=sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab - # via requests -flatbuffers==24.3.25 \ - --hash=sha256:8dbdec58f935f3765e4f7f3cf635ac3a77f83568138d6a2311f524ec96364812 \ - --hash=sha256:de2ec5b203f21441716617f38443e0a8ebf3d25bf0d9c0bb0ce68fa00ad546a4 - # via tensorflow -gast==0.4.0 \ - --hash=sha256:40feb7b8b8434785585ab224d1568b857edb18297e5a3047f1ba012bc83b42c1 \ - --hash=sha256:b7adcdd5adbebf1adf17378da5ba3f543684dbec47b1cda1f3997e573cd542c4 - # via tensorflow -google-pasta==0.2.0 \ - --hash=sha256:4612951da876b1a10fe3960d7226f0c7682cf901e16ac06e473b267a5afa8954 \ - --hash=sha256:b32482794a366b5366a32c92a9a9201b107821889935a02b3e51f6b432ea84ed \ - --hash=sha256:c9f2c8dfc8f96d0d5808299920721be30c9eec37f2389f28904f454565c8a16e - # via tensorflow -grpcio==1.63.0 \ - --hash=sha256:01799e8649f9e94ba7db1aeb3452188048b0019dc37696b0f5ce212c87c560c3 \ - --hash=sha256:0697563d1d84d6985e40ec5ec596ff41b52abb3fd91ec240e8cb44a63b895094 \ - --hash=sha256:08e1559fd3b3b4468486b26b0af64a3904a8dbc78d8d936af9c1cf9636eb3e8b \ - --hash=sha256:166e5c460e5d7d4656ff9e63b13e1f6029b122104c1633d5f37eaea348d7356d \ - --hash=sha256:1ff737cf29b5b801619f10e59b581869e32f400159e8b12d7a97e7e3bdeee6a2 \ - --hash=sha256:219bb1848cd2c90348c79ed0a6b0ea51866bc7e72fa6e205e459fedab5770172 \ - --hash=sha256:259e11932230d70ef24a21b9fb5bb947eb4703f57865a404054400ee92f42f5d \ - --hash=sha256:2e93aca840c29d4ab5db93f94ed0a0ca899e241f2e8aec6334ab3575dc46125c \ - --hash=sha256:3a6d1f9ea965e750db7b4ee6f9fdef5fdf135abe8a249e75d84b0a3e0c668a1b \ - --hash=sha256:50344663068041b34a992c19c600236e7abb42d6ec32567916b87b4c8b8833b3 \ - --hash=sha256:56cdf96ff82e3cc90dbe8bac260352993f23e8e256e063c327b6cf9c88daf7a9 \ - --hash=sha256:5c039ef01516039fa39da8a8a43a95b64e288f79f42a17e6c2904a02a319b357 \ - --hash=sha256:6426e1fb92d006e47476d42b8f240c1d916a6d4423c5258ccc5b105e43438f61 \ - --hash=sha256:65bf975639a1f93bee63ca60d2e4951f1b543f498d581869922910a476ead2f5 \ - --hash=sha256:6a1a3642d76f887aa4009d92f71eb37809abceb3b7b5a1eec9c554a246f20e3a \ - --hash=sha256:6ef0ad92873672a2a3767cb827b64741c363ebaa27e7f21659e4e31f4d750280 \ - --hash=sha256:756fed02dacd24e8f488f295a913f250b56b98fb793f41d5b2de6c44fb762434 \ - --hash=sha256:75f701ff645858a2b16bc8c9fc68af215a8bb2d5a9b647448129de6e85d52bce \ - --hash=sha256:8064d986d3a64ba21e498b9a376cbc5d6ab2e8ab0e288d39f266f0fca169b90d \ - --hash=sha256:878b1d88d0137df60e6b09b74cdb73db123f9579232c8456f53e9abc4f62eb3c \ - --hash=sha256:8f3f6883ce54a7a5f47db43289a0a4c776487912de1a0e2cc83fdaec9685cc9f \ - --hash=sha256:91b73d3f1340fefa1e1716c8c1ec9930c676d6b10a3513ab6c26004cb02d8b3f \ - --hash=sha256:93a46794cc96c3a674cdfb59ef9ce84d46185fe9421baf2268ccb556f8f81f57 \ - --hash=sha256:93f45f27f516548e23e4ec3fbab21b060416007dbe768a111fc4611464cc773f \ - --hash=sha256:9e350cb096e5c67832e9b6e018cf8a0d2a53b2a958f6251615173165269a91b0 \ - --hash=sha256:a2d60cd1d58817bc5985fae6168d8b5655c4981d448d0f5b6194bbcc038090d2 \ - --hash=sha256:a3abfe0b0f6798dedd2e9e92e881d9acd0fdb62ae27dcbbfa7654a57e24060c0 \ - --hash=sha256:a44624aad77bf8ca198c55af811fd28f2b3eaf0a50ec5b57b06c034416ef2d0a \ - --hash=sha256:a7b19dfc74d0be7032ca1eda0ed545e582ee46cd65c162f9e9fc6b26ef827dc6 \ - --hash=sha256:ad2ac8903b2eae071055a927ef74121ed52d69468e91d9bcbd028bd0e554be6d \ - --hash=sha256:b005292369d9c1f80bf70c1db1c17c6c342da7576f1c689e8eee4fb0c256af85 \ - --hash=sha256:b2e44f59316716532a993ca2966636df6fbe7be4ab6f099de6815570ebe4383a \ - --hash=sha256:b3afbd9d6827fa6f475a4f91db55e441113f6d3eb9b7ebb8fb806e5bb6d6bd0d \ - --hash=sha256:b416252ac5588d9dfb8a30a191451adbf534e9ce5f56bb02cd193f12d8845b7f \ - --hash=sha256:b5194775fec7dc3dbd6a935102bb156cd2c35efe1685b0a46c67b927c74f0cfb \ - --hash=sha256:cacdef0348a08e475a721967f48206a2254a1b26ee7637638d9e081761a5ba86 \ - --hash=sha256:cd1e68776262dd44dedd7381b1a0ad09d9930ffb405f737d64f505eb7f77d6c7 \ - --hash=sha256:cdcda1156dcc41e042d1e899ba1f5c2e9f3cd7625b3d6ebfa619806a4c1aadda \ - --hash=sha256:cf8dae9cc0412cb86c8de5a8f3be395c5119a370f3ce2e69c8b7d46bb9872c8d \ - --hash=sha256:d2497769895bb03efe3187fb1888fc20e98a5f18b3d14b606167dacda5789434 \ - --hash=sha256:e3b77eaefc74d7eb861d3ffbdf91b50a1bb1639514ebe764c47773b833fa2d91 \ - --hash=sha256:e48cee31bc5f5a31fb2f3b573764bd563aaa5472342860edcc7039525b53e46a \ - --hash=sha256:e4cbb2100ee46d024c45920d16e888ee5d3cf47c66e316210bc236d5bebc42b3 \ - --hash=sha256:f28f8b2db7b86c77916829d64ab21ff49a9d8289ea1564a2b2a3a8ed9ffcccd3 \ - --hash=sha256:f3023e14805c61bc439fb40ca545ac3d5740ce66120a678a3c6c2c55b70343d1 \ - --hash=sha256:fdf348ae69c6ff484402cfdb14e18c1b0054ac2420079d575c53a60b9b2853ae - # via - # tensorboard - # tensorflow -h5py==3.11.0 \ - --hash=sha256:083e0329ae534a264940d6513f47f5ada617da536d8dccbafc3026aefc33c90e \ - --hash=sha256:1625fd24ad6cfc9c1ccd44a66dac2396e7ee74940776792772819fc69f3a3731 \ - --hash=sha256:21dbdc5343f53b2e25404673c4f00a3335aef25521bd5fa8c707ec3833934892 \ - --hash=sha256:52c416f8eb0daae39dabe71415cb531f95dce2d81e1f61a74537a50c63b28ab3 \ - --hash=sha256:55106b04e2c83dfb73dc8732e9abad69d83a436b5b82b773481d95d17b9685e1 \ - --hash=sha256:67462d0669f8f5459529de179f7771bd697389fcb3faab54d63bf788599a48ea \ - --hash=sha256:6c4b760082626120031d7902cd983d8c1f424cdba2809f1067511ef283629d4b \ - --hash=sha256:731839240c59ba219d4cb3bc5880d438248533366f102402cfa0621b71796b62 \ - --hash=sha256:754c0c2e373d13d6309f408325343b642eb0f40f1a6ad21779cfa9502209e150 \ - --hash=sha256:75bd7b3d93fbeee40860fd70cdc88df4464e06b70a5ad9ce1446f5f32eb84007 \ - --hash=sha256:77b19a40788e3e362b54af4dcf9e6fde59ca016db2c61360aa30b47c7b7cef00 \ - --hash=sha256:7b7e8f78072a2edec87c9836f25f34203fd492a4475709a18b417a33cfb21fa9 \ - --hash=sha256:8ec9df3dd2018904c4cc06331951e274f3f3fd091e6d6cc350aaa90fa9b42a76 \ - --hash=sha256:a76cae64080210389a571c7d13c94a1a6cf8cb75153044fd1f822a962c97aeab \ - --hash=sha256:aa6ae84a14103e8dc19266ef4c3e5d7c00b68f21d07f2966f0ca7bdb6c2761fb \ - --hash=sha256:bbd732a08187a9e2a6ecf9e8af713f1d68256ee0f7c8b652a32795670fb481ba \ - --hash=sha256:c072655ad1d5fe9ef462445d3e77a8166cbfa5e599045f8aa3c19b75315f10e5 \ - --hash=sha256:d9c944d364688f827dc889cf83f1fca311caf4fa50b19f009d1f2b525edd33a3 \ - --hash=sha256:ef4e2f338fc763f50a8113890f455e1a70acd42a4d083370ceb80c463d803972 \ - --hash=sha256:f3736fe21da2b7d8a13fe8fe415f1272d2a1ccdeff4849c1421d2fb30fd533bc \ - --hash=sha256:f4e025e852754ca833401777c25888acb96889ee2c27e7e629a19aee288833f0 - # via - # keras - # tensorflow -idna==3.7 \ - --hash=sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc \ - --hash=sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0 - # via requests -keras==3.5.0 \ - --hash=sha256:53ae4f9472ec9d9c6941c82a3fda86969724ace3b7630a94ba0a1f17ba1065c3 \ - --hash=sha256:d37a3c623935713473ceb25241b52bce9d1e0ff5b36e5d0f6f47ed55f8500c9a - # via tensorflow -libclang==16.0.0 \ - --hash=sha256:2adce42ae652f312245b8f4eda6f30b4076fb61f7619f2dfd0a0c31dee4c32b9 \ - --hash=sha256:65258a6bb3e7dc31dc9b26f8d42f53c9d3b959643ade291fcd1aef4855303ca6 \ - --hash=sha256:7b6686b67a0daa84b4c614bcc119578329fc4fbb52b919565b7376b507c4793b \ - --hash=sha256:a043138caaf2cb076ebb060c6281ec95612926645d425c691991fc9df00e8a24 \ - --hash=sha256:af55a4aa86fdfe6b2ec68bc8cfe5fdac6c448d591ca7648be86ca17099b41ca8 \ - --hash=sha256:bf4628fc4da7a1dd06a244f9b8e121c5ec68076a763c59d6b13cbb103acc935b \ - --hash=sha256:eb59652cb0559c0e71784ff4c8ba24c14644becc907b1446563ecfaa622d523b \ - --hash=sha256:ee20bf93e3dd330f71fc50cdbf13b92ced0aec8e540be64251db53502a9b33f7 - # via tensorflow -markdown==3.4.3 \ - --hash=sha256:065fd4df22da73a625f14890dd77eb8040edcbd68794bcd35943be14490608b2 \ - --hash=sha256:8bf101198e004dc93e84a12a7395e31aac6a9c9942848ae1d99b9d72cf9b3520 - # via tensorboard -markdown-it-py==3.0.0 \ - --hash=sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1 \ - --hash=sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb - # via rich -markupsafe==2.1.2 \ - --hash=sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed \ - --hash=sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc \ - --hash=sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2 \ - --hash=sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460 \ - --hash=sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7 \ - --hash=sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0 \ - --hash=sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1 \ - --hash=sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa \ - --hash=sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03 \ - --hash=sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323 \ - --hash=sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65 \ - --hash=sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013 \ - --hash=sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036 \ - --hash=sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f \ - --hash=sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4 \ - --hash=sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419 \ - --hash=sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2 \ - --hash=sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619 \ - --hash=sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a \ - --hash=sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a \ - --hash=sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd \ - --hash=sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7 \ - --hash=sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666 \ - --hash=sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65 \ - --hash=sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859 \ - --hash=sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625 \ - --hash=sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff \ - --hash=sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156 \ - --hash=sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd \ - --hash=sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba \ - --hash=sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f \ - --hash=sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1 \ - --hash=sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094 \ - --hash=sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a \ - --hash=sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513 \ - --hash=sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed \ - --hash=sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d \ - --hash=sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3 \ - --hash=sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147 \ - --hash=sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c \ - --hash=sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603 \ - --hash=sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601 \ - --hash=sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a \ - --hash=sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1 \ - --hash=sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d \ - --hash=sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3 \ - --hash=sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54 \ - --hash=sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2 \ - --hash=sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6 \ - --hash=sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58 - # via werkzeug -mdurl==0.1.2 \ - --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \ - --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba - # via markdown-it-py -ml-dtypes==0.4.0 \ - --hash=sha256:03e7cda6ef164eed0abb31df69d2c00c3a5ab3e2610b6d4c42183a43329c72a5 \ - --hash=sha256:2bb83fd064db43e67e67d021e547698af4c8d5c6190f2e9b1c53c09f6ff5531d \ - --hash=sha256:3b67ec73a697c88c1122038e0de46520e48dc2ec876d42cf61bc5efe3c0b7675 \ - --hash=sha256:41affb38fdfe146e3db226cf2953021184d6f0c4ffab52136613e9601706e368 \ - --hash=sha256:43cf4356a0fe2eeac6d289018d0734e17a403bdf1fd911953c125dd0358edcc0 \ - --hash=sha256:723af6346447268a3cf0b7356e963d80ecb5732b5279b2aa3fa4b9fc8297c85e \ - --hash=sha256:75b4faf99d0711b81f393db36d210b4255fd419f6f790bc6c1b461f95ffb7a9e \ - --hash=sha256:93afe37f3a879d652ec9ef1fc47612388890660a2657fbb5747256c3b818fd81 \ - --hash=sha256:a15d96d090aebb55ee85173d1775ae325a001aab607a76c8ea0b964ccd6b5364 \ - --hash=sha256:ad6849a2db386b38e4d54fe13eb3293464561780531a918f8ef4c8169170dd49 \ - --hash=sha256:bdf689be7351cc3c95110c910c1b864002f113e682e44508910c849e144f3df1 \ - --hash=sha256:c83e4d443962d891d51669ff241d5aaad10a8d3d37a81c5532a45419885d591c \ - --hash=sha256:e1e2f4237b459a63c97c2c9f449baa637d7e4c20addff6a9bac486f22432f3b6 \ - --hash=sha256:eaa32979ebfde3a0d7c947cafbf79edc1ec77ac05ad0780ee86c1d8df70f2259 \ - --hash=sha256:eaf197e72f4f7176a19fe3cb8b61846b38c6757607e7bf9cd4b1d84cd3e74deb \ - --hash=sha256:ee9f91d4c4f9959a7e1051c141dc565f39e54435618152219769e24f5e9a4d06 \ - --hash=sha256:f1724ddcdf5edbaf615a62110af47407f1719b8d02e68ccee60683acb5f74da1 - # via - # keras - # tensorflow -namex==0.0.8 \ - --hash=sha256:32a50f6c565c0bb10aa76298c959507abdc0e850efe085dc38f3440fcb3aa90b \ - --hash=sha256:7ddb6c2bb0e753a311b7590f84f6da659dd0c05e65cb89d519d54c0a250c0487 - # via keras -numpy==1.23.5 \ - --hash=sha256:01dd17cbb340bf0fc23981e52e1d18a9d4050792e8fb8363cecbf066a84b827d \ - --hash=sha256:06005a2ef6014e9956c09ba07654f9837d9e26696a0470e42beedadb78c11b07 \ - --hash=sha256:09b7847f7e83ca37c6e627682f145856de331049013853f344f37b0c9690e3df \ - --hash=sha256:0aaee12d8883552fadfc41e96b4c82ee7d794949e2a7c3b3a7201e968c7ecab9 \ - --hash=sha256:0cbe9848fad08baf71de1a39e12d1b6310f1d5b2d0ea4de051058e6e1076852d \ - --hash=sha256:1b1766d6f397c18153d40015ddfc79ddb715cabadc04d2d228d4e5a8bc4ded1a \ - --hash=sha256:33161613d2269025873025b33e879825ec7b1d831317e68f4f2f0f84ed14c719 \ - --hash=sha256:5039f55555e1eab31124a5768898c9e22c25a65c1e0037f4d7c495a45778c9f2 \ - --hash=sha256:522e26bbf6377e4d76403826ed689c295b0b238f46c28a7251ab94716da0b280 \ - --hash=sha256:56e454c7833e94ec9769fa0f86e6ff8e42ee38ce0ce1fa4cbb747ea7e06d56aa \ - --hash=sha256:58f545efd1108e647604a1b5aa809591ccd2540f468a880bedb97247e72db387 \ - --hash=sha256:5e05b1c973a9f858c74367553e236f287e749465f773328c8ef31abe18f691e1 \ - --hash=sha256:7903ba8ab592b82014713c491f6c5d3a1cde5b4a3bf116404e08f5b52f6daf43 \ - --hash=sha256:8969bfd28e85c81f3f94eb4a66bc2cf1dbdc5c18efc320af34bffc54d6b1e38f \ - --hash=sha256:92c8c1e89a1f5028a4c6d9e3ccbe311b6ba53694811269b992c0b224269e2398 \ - --hash=sha256:9c88793f78fca17da0145455f0d7826bcb9f37da4764af27ac945488116efe63 \ - --hash=sha256:a7ac231a08bb37f852849bbb387a20a57574a97cfc7b6cabb488a4fc8be176de \ - --hash=sha256:abdde9f795cf292fb9651ed48185503a2ff29be87770c3b8e2a14b0cd7aa16f8 \ - --hash=sha256:af1da88f6bc3d2338ebbf0e22fe487821ea4d8e89053e25fa59d1d79786e7481 \ - --hash=sha256:b2a9ab7c279c91974f756c84c365a669a887efa287365a8e2c418f8b3ba73fb0 \ - --hash=sha256:bf837dc63ba5c06dc8797c398db1e223a466c7ece27a1f7b5232ba3466aafe3d \ - --hash=sha256:ca51fcfcc5f9354c45f400059e88bc09215fb71a48d3768fb80e357f3b457e1e \ - --hash=sha256:ce571367b6dfe60af04e04a1834ca2dc5f46004ac1cc756fb95319f64c095a96 \ - --hash=sha256:d208a0f8729f3fb790ed18a003f3a57895b989b40ea4dce4717e9cf4af62c6bb \ - --hash=sha256:dbee87b469018961d1ad79b1a5d50c0ae850000b639bcb1b694e9981083243b6 \ - --hash=sha256:e9f4c4e51567b616be64e05d517c79a8a22f3606499941d97bb76f2ca59f982d \ - --hash=sha256:f063b69b090c9d918f9df0a12116029e274daf0181df392839661c4c7ec9018a \ - --hash=sha256:f9a909a8bae284d46bbfdefbdd4a262ba19d3bc9921b1e76126b1d21c3c34135 - # via - # h5py - # keras - # ml-dtypes - # opt-einsum - # tensorboard - # tensorflow -opt-einsum==3.3.0 \ - --hash=sha256:2455e59e3947d3c275477df7f5205b30635e266fe6dc300e3d9f9646bfcea147 \ - --hash=sha256:59f6475f77bbc37dcf7cd748519c0ec60722e91e63ca114e68821c0c54a46549 - # via tensorflow -optree==0.12.1 \ - --hash=sha256:06d6ef39b3ef9920d6cdb6d3d1d2804a37092d24dc406c4cb9b46cd6c9a44e89 \ - --hash=sha256:154738def491199d3fbcd919437315728e0a1caeaf4ec06688c76ef9d56e5ed6 \ - --hash=sha256:1b2fe5c04c218698a53ed2d4b7372f1989df8cf0a61d616e6f384770d8a5fb1c \ - --hash=sha256:1d76905bced5cf569d23dc4890341fae2fa257cce58a492a1603afcdc5969ae7 \ - --hash=sha256:1f8baf0ad6b58843d24fa8caf079cf1f0c33cc3658263cff960b5c1d0cc53bc8 \ - --hash=sha256:23afe4aae42336bdf8cf4fba35c56593405bf8f8e163627f722205b3bf0d9310 \ - --hash=sha256:24d74a9d97d7bdbdbb30356850f204950c39ab8fad7f273ed29d1feda19060b2 \ - --hash=sha256:27ae426745931ae1c2ccd7a78b27f9b7402167e0600fa62e2ef1cd58727e7b94 \ - --hash=sha256:2a1a9905d2d917d5aff775283e0a59be2c6b529a219241c248d50b3ad51c6cce \ - --hash=sha256:2d4d8e024b841f99907b2340fee7ac9994fbe300383a9af6c93578d12861a969 \ - --hash=sha256:2de1297b2bf019379ab86103e31caa97c8a08628f0c8b58cd7709f9048c589eb \ - --hash=sha256:349aafac463642979f7fe7ca3aa9e2fa8a5a0f81ef7af6946a075b797673e600 \ - --hash=sha256:35ca77b810cf5959e6930d56534ecbecc4300f5e5fa14b977030265c1c8eab6c \ - --hash=sha256:3e323744d083bd8b4648c9ff2383f01bfbc33098656d56fdd984b2263ef905f3 \ - --hash=sha256:404cf2decd8fb6a1a8f6fef623c98873cdf7ae086aeb8909d104cd321d829ba0 \ - --hash=sha256:409ef6f3656299923d722509849d83607bb3e5c621dcfe6aa90ace85665e9b54 \ - --hash=sha256:411a21eca034ddb98eb80e6c4bf552fc46b8d8ab7c4d250446d74d31a251a684 \ - --hash=sha256:42025da0bac19cc6de756fe64511f15baffb3fa7d8402e54aab035c02903eb5c \ - --hash=sha256:47db001a224382493ae7a8df16e7a9668e971fc129970d137995421aa6b06f8f \ - --hash=sha256:4b32f39988bfe6e76eeefb335da529e614145f7f1dfa8583fbc4aca8a72f504b \ - --hash=sha256:4ee926120887404e92877c99714b960bc29f572e8db69fd2e934022d80452f91 \ - --hash=sha256:50893bd088bdb3e2f07ee481dafd848b483bea1a19cc978f2309139314e5bc7d \ - --hash=sha256:509bddd38dae8c4e8d6b988f514b7a9fe803ca916b11af67b40520f0b1eeeaef \ - --hash=sha256:562036d3de15204ed1a88d9fc262a7e1c20964d22ef132069e20dbd88215f983 \ - --hash=sha256:5bfe3d3e47e10b528f9324d446c871bfad7d0be8c2bd2a2fbc3ddf1600ae8558 \ - --hash=sha256:5c2f2e0e3978558bc8f7df8c5a999674097dd0dc71363210783eb8d7a6da8ef9 \ - --hash=sha256:5f24b0a8b181a90a778cadc942a79336d29f0c164704d58cd20989bf7d0bea1c \ - --hash=sha256:606983f4696d81128e205a1c34d0c9f3fe6ae12f6c26ed5e8ab3722d6f378ec2 \ - --hash=sha256:62d232a344c14b8e94fdd6de1acf2c0b05954b05d6bb346bddb13c38be37dc09 \ - --hash=sha256:646842f8a2de2caaacc32a8c91f8031a93eda145ac9c915bb0fd2ad5249c14b7 \ - --hash=sha256:6d90fb28d52725352858013cafe34d98d90ab1bb86b5d8dc29d420e9bbc5706b \ - --hash=sha256:76a2240e7482355966a73c6c701e3d1f148420a77849c78d175d3b08bf06ff36 \ - --hash=sha256:7a71dd58522cd6258b61b639092ac7a2631d881f039ef968b31dfd555e513591 \ - --hash=sha256:7e79eedd9406c59d542482768e490795dc6b6f1a014c7852d29d9fd61749bf94 \ - --hash=sha256:80e0d4eba4a65d4c6f2002ed949142a40933b8185523894659c26c34693c4086 \ - --hash=sha256:8513d6dd71807abb1037a5b5bc66b45c21afb42e9c90961fa5e762cea3943ab2 \ - --hash=sha256:88d01ce6f78f209cad8dc4cf2d3222d7056cac93612abfd6beb40ab43a131769 \ - --hash=sha256:9280452c11da0872ec57be5d8f153207d6303b3cbf26115b2bf6d2b8157a5343 \ - --hash=sha256:9c473988b2d8fd7edc3958e6c7cb1d3f92afb7bcaff53b76a8f41cf4f3a24709 \ - --hash=sha256:a11e58d7c0a71a48d74ca0a6715f4c0932c6f9409ba93d600e3326df4cf778ae \ - --hash=sha256:a49d3cfec1a51463b63e11c889bb00207c4e040016833cd202871ad946116925 \ - --hash=sha256:a55a79c1c72f73259532e4cbe9ff65bed9663064747db02591fb4714fe742d2e \ - --hash=sha256:a67842cd1c5c83d74863872f06fe6ed64e44279c0378267a9805567fe3c38591 \ - --hash=sha256:aadb26d68f1d7871507f84846d8844aa94f47402d5277ce19cfe5102bb5df9e9 \ - --hash=sha256:afa0051335c6032ee4dfc212952dcfb3b23fe59bcd70f56d25a214e7585cd62c \ - --hash=sha256:b1ca00bdfe4da8068c2773b7ac4c8c96d3f61b8d21eba6a8642dab23ee631b0d \ - --hash=sha256:b43c09cf9dd28aed2efc163f4bb4808d7fad54250812960bf349399ba6972e16 \ - --hash=sha256:b890ba0a21049addf589c314c85e98a68d3dfc84e3954491e9ce60f60cb7b0e7 \ - --hash=sha256:ba6aed8b9684c5804a5e2d6b246c3b4a68bab793b6829d369ba1c53734852a0c \ - --hash=sha256:bd207b43e71fb3f8c315e2e4a5444f48317b2108889e96279d5426bca730a47e \ - --hash=sha256:c987931bd31d0f28cbff28925a875639170534a36ce178a40020aca0769d9549 \ - --hash=sha256:ce7cb233e87a2dc127b8ec82bd61f098e6ff1e57d0a09dc110a17b38bfd73034 \ - --hash=sha256:cefd4f4c7596cdd4c95dca431bc41284a43ebd7056e739480f157789aa34579d \ - --hash=sha256:d0950ee245db2c40824362def1efc15621a6492419628cec1fac0061818420f7 \ - --hash=sha256:d313303a1ce36ea55c3a96fc375c5cc64a9ab814ab2677ce64e4a7d755a9b1d0 \ - --hash=sha256:d913122454d0e3f10dc25a1b598eaf588d225372f41ece3ad4d508bddd363e4d \ - --hash=sha256:da37e6cc669a9840844722edb3f8dd5b4f07e99b0e8c9196089cb49af70c7b75 \ - --hash=sha256:e124f30daf79d51b1bbbda7e74d01e637fa47aff4aa64cb082b88057535daa64 \ - --hash=sha256:e2027217c3acaf44e5f5aabe01ba0cbf33066f3f6df870881ddf597965f80db0 \ - --hash=sha256:e20b5569369a5f1e8faa2604799b91a1941fe17b5de8afc84c8c23ff66d8e585 \ - --hash=sha256:e8046cbbcd5f7494ba7c6811e44a6d2867216f2bdb7cef980a9a62e31d39270c \ - --hash=sha256:eb968d3cc1db8944f220f1a67c9db043b86b47ace90ce3cfd23f3e6500baeb65 \ - --hash=sha256:efffa3814ab8e3aaf7bf88495e4b6d263de9689d6f02dfa4490f8f64736806ac \ - --hash=sha256:f0460f025bf1c08f2c008b5e3628d849fcb5810345222e57879cd248fec7f9f7 \ - --hash=sha256:f65a31d7cfab2fed2bc29ab6eabcf4205dec6e0ee3cfb7006336c4f76d78fb0e \ - --hash=sha256:f6b98b80b1259e9817aca701beba616ce33e43e856e7d644f7e0f582b8e45565 \ - --hash=sha256:fc1ec38d1ec43bb8358ab058c3220a70b7bfb56f2bb625f41cb09d117a0d6150 \ - --hash=sha256:fd3ead0c64d22d692284d96c27d5091e682b002ffe5a52afacc9f1fcc8ae3180 - # via keras -packaging==23.1 \ - --hash=sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61 \ - --hash=sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f - # via - # keras - # tensorboard - # tensorflow -protobuf==4.23.0 \ - --hash=sha256:03eee35b60317112a72d19c54d0bff7bc58ff12fea4cd7b018232bd99758ffdf \ - --hash=sha256:2b94bd6df92d71bd1234a2ffe7ce96ddf6d10cf637a18d6b55ad0a89fbb7fc21 \ - --hash=sha256:36f5370a930cb77c8ad2f4135590c672d0d2c72d4a707c7d0058dce4b4b4a598 \ - --hash=sha256:5f1eba1da2a2f3f7df469fccddef3cc060b8a16cfe3cc65961ad36b4dbcf59c5 \ - --hash=sha256:6c16657d6717a0c62d5d740cb354fbad1b0d8cb811669e06fc1caa0ff4799ddd \ - --hash=sha256:6fe180b56e1169d72ecc4acbd39186339aed20af5384531b8e8979b02bbee159 \ - --hash=sha256:7cb5b9a05ce52c6a782bb97de52679bd3438ff2b7460eff5da348db65650f227 \ - --hash=sha256:9744e934ea5855d12191040ea198eaf704ac78665d365a89d9572e3b627c2688 \ - --hash=sha256:9f5a0fbfcdcc364f3986f9ed9f8bb1328fb84114fd790423ff3d7fdb0f85c2d1 \ - --hash=sha256:baca40d067dddd62141a129f244703160d278648b569e90bb0e3753067644711 \ - --hash=sha256:d5a35ff54e3f62e8fc7be02bb0d2fbc212bba1a5a9cc2748090690093996f07b \ - --hash=sha256:e62fb869762b4ba18666370e2f8a18f17f8ab92dd4467295c6d38be6f8fef60b \ - --hash=sha256:ebde3a023b8e11bfa6c890ef34cd6a8b47d586f26135e86c21344fe433daf2e2 - # via - # tensorboard - # tensorflow -pygments==2.18.0 \ - --hash=sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199 \ - --hash=sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a - # via rich -requests==2.31.0 \ - --hash=sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f \ - --hash=sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1 - # via tensorflow -rich==13.8.0 \ - --hash=sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc \ - --hash=sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4 - # via keras -six==1.16.0 \ - --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \ - --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254 - # via - # astunparse - # google-pasta - # tensorboard - # tensorflow -tensorboard==2.17.1 \ - --hash=sha256:253701a224000eeca01eee6f7e978aea7b408f60b91eb0babdb04e78947b773e - # via tensorflow -tensorboard-data-server==0.7.0 \ - --hash=sha256:64aa1be7c23e80b1a42c13b686eb0875bb70f5e755f4d2b8de5c1d880cf2267f \ - --hash=sha256:753d4214799b31da7b6d93837959abebbc6afa86e69eacf1e9a317a48daa31eb \ - --hash=sha256:eb7fa518737944dbf4f0cf83c2e40a7ac346bf91be2e6a0215de98be74e85454 - # via tensorboard -tensorflow==2.17.0 \ - --hash=sha256:0ad7bfea6afb4ded3928ca5b24df9fda876cea4904c103a5163fcc0c3483e7a4 \ - --hash=sha256:147c93ded4cb7e500a65d3c26d74744ff41660db7a8afe2b00d1d08bf329b4ec \ - --hash=sha256:278bc80642d799adf08dc4e04f291aab603bba7457d50c1f9bc191ebbca83f43 \ - --hash=sha256:4ae8e6746deb2ec807b902ba26d62fcffb6a6b53555a1a5906ec00416c5e4175 \ - --hash=sha256:515fe5ae8a9bc50312575412b08515f3ca66514c155078e0707bdffbea75d783 \ - --hash=sha256:72adfef0ee39dd641627906fd7b244fcf21bdd8a87216a998ed74d9c74653aff \ - --hash=sha256:8339777b1b5ebd8ffadaa8196f786e65fbb081a371d8e87b52f24563392d8552 \ - --hash=sha256:8f80d11ad3766570deb6ff47d2bed2d166f51399ca08205e38ef024345571d6f \ - --hash=sha256:97f89e95d68b4b46e1072243b9f315c3b340e27cc07b1e1988e2ca97ad844305 \ - --hash=sha256:b36683ac28af20abc3a548c72bf4537b00df1b1f3dd39d59df3873fefaf26f15 \ - --hash=sha256:ca82f98ea38fa6c9e08ccc69eb6c2fab5b35b30a8999115b8b63b6f02fc69d9d \ - --hash=sha256:dde37cff74ed22b8fa2eea944805b001ae38e96adc989666422bdea34f4e2d47 \ - --hash=sha256:e46090587f69e33637d17d7c3d94a790cac7d4bc5ff5ecbf3e71fdc6982fe96e \ - --hash=sha256:e8d26d6c24ccfb139db1306599257ca8f5cfe254ef2d023bfb667f374a17a64d \ - --hash=sha256:ee18b4fcd627c5e872eabb25092af6c808b6ec77948662c88fc5c89a60eb0211 \ - --hash=sha256:ef615c133cf4d592a073feda634ccbeb521a554be57de74f8c318d38febbeab5 - # via -r requirements.in -tensorflow-io-gcs-filesystem==0.32.0 \ - --hash=sha256:045d51bba586390d0545fcd8a18727d62b175eb142f6f4c6d719d39de40774cd \ - --hash=sha256:05e65d3cb6c93a7929b384d86c6369c63cbbab8a770440a3d95e094878403f9f \ - --hash=sha256:122be149e5f6a030f5c2901be0cc3cb07619232f7b03889e2cdf3da1c0d4f92f \ - --hash=sha256:1ce80e1555d6ee88dda67feddf366cc8b30252b5837a7a17303df7b06a71fc2e \ - --hash=sha256:21de7dcc06eb1e7de3c022b0072d90ba35ef886578149663437aa7a6fb5bf6b3 \ - --hash=sha256:28202492d904a6e280cf27560791e87ac1c7566000db82065d63a70c27008af2 \ - --hash=sha256:336d9b3fe6b55aea149c4f6aa1fd6ffaf27d4e5c37e55a182340b47caba38846 \ - --hash=sha256:5635df0bbe40f971dc1b946e3372744b0bdfda45c38ffcd28ef53a32bb8da4da \ - --hash=sha256:74a7e25e83d4117a7ebb09a3f247553a5497393ab48c3ee0cf0d17b405026817 \ - --hash=sha256:79fdd02103b8ae9f8b89af41f744c013fa1caaea709de19833917795e3063857 \ - --hash=sha256:7f15fd22e592661b10de317be2f42a0f84be7bfc5e6a565fcfcb04b60d625b78 \ - --hash=sha256:8214cdf85bea694160f9035ff395221c1e25e119784ccb4c104919b1f5dec84e \ - --hash=sha256:842f5f09cd756bdb3b4d0b5571b3a6f72fd534d42da938b9acf0ef462995eada \ - --hash=sha256:db682e9a510c27dd35710ba5a2c62c371e25b727741b2fe3a920355fa501e947 - # via tensorflow -termcolor==2.3.0 \ - --hash=sha256:3afb05607b89aed0ffe25202399ee0867ad4d3cb4180d98aaf8eefa6a5f7d475 \ - --hash=sha256:b5b08f68937f138fe92f6c089b99f1e2da0ae56c52b78bf7075fd95420fd9a5a - # via tensorflow -typing-extensions==4.5.0 \ - --hash=sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb \ - --hash=sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4 - # via - # optree - # tensorflow -urllib3==1.26.18 \ - --hash=sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07 \ - --hash=sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0 - # via requests -werkzeug==3.0.6 \ - --hash=sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17 \ - --hash=sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d - # via tensorboard -wheel==0.40.0 \ - --hash=sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873 \ - --hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247 - # via astunparse -wrapt==1.14.1 \ - --hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \ - --hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \ - --hash=sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4 \ - --hash=sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2 \ - --hash=sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656 \ - --hash=sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3 \ - --hash=sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff \ - --hash=sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310 \ - --hash=sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a \ - --hash=sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57 \ - --hash=sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069 \ - --hash=sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383 \ - --hash=sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe \ - --hash=sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87 \ - --hash=sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d \ - --hash=sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b \ - --hash=sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907 \ - --hash=sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f \ - --hash=sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0 \ - --hash=sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28 \ - --hash=sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1 \ - --hash=sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853 \ - --hash=sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc \ - --hash=sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3 \ - --hash=sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3 \ - --hash=sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164 \ - --hash=sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1 \ - --hash=sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c \ - --hash=sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1 \ - --hash=sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7 \ - --hash=sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1 \ - --hash=sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320 \ - --hash=sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed \ - --hash=sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1 \ - --hash=sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248 \ - --hash=sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c \ - --hash=sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456 \ - --hash=sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77 \ - --hash=sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef \ - --hash=sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1 \ - --hash=sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7 \ - --hash=sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86 \ - --hash=sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4 \ - --hash=sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d \ - --hash=sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d \ - --hash=sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8 \ - --hash=sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5 \ - --hash=sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471 \ - --hash=sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00 \ - --hash=sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68 \ - --hash=sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3 \ - --hash=sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d \ - --hash=sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735 \ - --hash=sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d \ - --hash=sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569 \ - --hash=sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7 \ - --hash=sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59 \ - --hash=sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5 \ - --hash=sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb \ - --hash=sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b \ - --hash=sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f \ - --hash=sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462 \ - --hash=sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015 \ - --hash=sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af - # via tensorflow - -# WARNING: The following packages were not pinned, but pip requires them to be -# pinned when the requirements file includes hashes and the requirement is not -# satisfied by a package already installed. Consider using the --allow-unsafe flag. -# setuptools diff --git a/perfmetrics/scripts/load_tests/python/sample_tasks.yaml b/perfmetrics/scripts/load_tests/python/sample_tasks.yaml deleted file mode 100644 index 6d34775fc1..0000000000 --- a/perfmetrics/scripts/load_tests/python/sample_tasks.yaml +++ /dev/null @@ -1,44 +0,0 @@ -# Sample YAML file containing example configs for recognised and predefined -# tasks. - ---- -# Sample task for reading file using Python OS native open api. -256kb_os: # [Required] Name of the task (task_name). - # Task type. Fixed for reading file using python os native open api. - task_type: python_os_read # [Required] - # Local file path. Can only contain {process_id} and {thread_id} in format. - file_path_format: ./gcs/256kb/read.{process_id} # [Required] - # K for 1024, M for 1024 * K, G for 1024 * M. - file_size: 256K # [Required] - block_size: 16K # [Optional] [Default = file_size] - -# Sample task for reading file using tensorflow's tf.io.gfile.GFile api. -1mb_tf_gfile: # [Required] Name of the task (task_name). - # Task type. Fixed for reading file using tensorflow's tf.io.gfile.GFile api. - task_type: tf_gfile_read # [Required] - # Local file path/GCS path (gs://). Can only contain {process_id} and - # {thread_id} in format. - file_path_format: gs://load-test-bucket/1mb/read.{process_id} # [Required] - # K for 1024, M for 1024 * K, G for 1024 * M. - file_size: 1M # [Required] - block_size: 16K # [Optional] [Default = file_size] - -# Sample task for reading file using tensorflow's tf.data api. -# For parallelism in tf.data, tweak num_parallel_calls in task file and always -# pass 1 to --num-processes & --num-threads -100mb_tf_data: # [Required] Name of the task (task_name). - # Task type. Fixed for reading file using tensorflow's tf.data api. - task_type: tf_data_read # [Required] - # Local file path/GCS path (gs://). Can only contain {file_num} in path. - file_path_format: ./gcs/100mb/read.{file_num}.tfrecord # [Required] - # K for 1024, M for 1024 * K, G for 1024 * M. - file_size: 100M # [Required] - # Number of files to read in one task. - num_files: 3 # [Required] - # Prefetch value in tf.data call. - prefetch: 50 # [Optional] [Default = -1 (AUTOTUNE)] - # Parallelism in tf.data calls. - num_parallel_calls: 100 # [Optional] [Default = -1 (AUTOTUNE)] - # Shard value in tf.data call - shard: 100 # [Optional] [Default = 1] - diff --git a/perfmetrics/scripts/load_tests/python/tasks/__init__.py b/perfmetrics/scripts/load_tests/python/tasks/__init__.py deleted file mode 100644 index 1dc90d1848..0000000000 --- a/perfmetrics/scripts/load_tests/python/tasks/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. diff --git a/perfmetrics/scripts/load_tests/python/tasks/python_os.py b/perfmetrics/scripts/load_tests/python/tasks/python_os.py deleted file mode 100644 index 1e2cba273d..0000000000 --- a/perfmetrics/scripts/load_tests/python/tasks/python_os.py +++ /dev/null @@ -1,107 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Contains task which reads data from files using python's native open api. - -Example: - - lg_obj = load_generator.LoadGenerator(...) - task_obj = python_os.OSRead256KB(...) - observations = lg_obj.generate_load(task_obj) -""" -import os -import logging -from load_generator import task - -PYTHON_OS_READ = 'python_os_read' - - -class OSRead(task.LoadTestTask): - """Task class for reading file from disk using python's native open api. - - The task reads file with os.O_DIRECT i.e. bypassing page cache. - - Args: - task_name: String name assigned to task. - file_path_format: String format of file path. The format can contain - 'process_id' and 'thread_id' to be filled. E.g. gcs/256kb/read.{process_id} - file_size: Integer size of file in bytes to be read. - block_size: Integer size of block in bytes. File is read block by block. - If block size is not passed or -1, then by default it takes file size as - block size. - """ - - def __init__(self, task_name, file_path_format, file_size, block_size=-1): - super().__init__() - self.task_type = PYTHON_OS_READ - self.task_name = task_name - self.file_path_format = file_path_format - self.file_size = file_size - # Keep file size as default block size - self.block_size = block_size - if self.block_size == -1: - self.block_size = file_size - - def task(self, process_id, thread_id): - """Reads file of given size from given file path and with given block size. - - See base class for more details. - - Returns: - Integer denoting the size of content read in bytes. - """ - file_path = self.file_path_format.format( - process_id=process_id, thread_id=thread_id) - my_file = os.open(file_path, os.O_DIRECT) - content_len = 0 - with open(my_file, 'rb') as f_p: - for _ in range(0, self.file_size, self.block_size): - content = f_p.read(self.block_size) - content_len = content_len + len(content) - f_p.close() - return content_len - - def create_files(self, num_processes): - """Creates num_processes number of files to be used in load testing. - - Args: - num_processes: Integer denoting number of processes in load test. - - Returns: - None - - Raises: - RuntimeError: If the path under which the files to be created doesn't - exist. - """ - # Create one file per process for read and write tasks. - if not os.path.exists(os.path.dirname(self.file_path_format)): - raise RuntimeError('Directory containing files for task not exists.') - - logging.info( - 'One file is created per process of size %s using the format ' - '%s', self.file_size, self.file_path_format) - for process_id in range(num_processes): - file_path = self.file_path_format.format(process_id=process_id) - self._create_binary_file(file_path, self.file_size) - - def _create_binary_file(self, file_path, file_size): - """Creates binary file of given file size in bytes at given path. - - Doesn't create a file if file is already present and has same file size. - """ - if os.path.exists(file_path) and os.path.getsize(file_path) == file_size: - return - logging.info('Creating file %s of size %s.', file_path, file_size) - with open(file_path, 'wb') as f_p: - f_p.truncate(file_size) diff --git a/perfmetrics/scripts/load_tests/python/tasks/tf_data.py b/perfmetrics/scripts/load_tests/python/tasks/tf_data.py deleted file mode 100644 index 471c01e0f2..0000000000 --- a/perfmetrics/scripts/load_tests/python/tasks/tf_data.py +++ /dev/null @@ -1,141 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Contains tasks which reads data from files using tensorflow's tf.data api. - -Example: - - lg_obj = load_generator.LoadGenerator(...) - task_obj = tf_data.TFDataRead(...) - observations = lg_obj.generate_load(task_obj) -""" -import os -import logging -import tensorflow as tf -from load_generator import task - -# .tfrecord file contains multiple TFRecords in it. This defines the size of -# that single TFRecord in bytes. -SINGLE_RECORD_SIZE = 256 * 1024 -TF_DATA_READ = 'tf_data_read' - - -class TFDataRead(task.LoadTestTask): - """Task class for reading tfrecord file using tensorflow's tf.data api. - - tf.data: https://www.tensorflow.org/guide/data - tf.data.TFRecordDataset.shard: - https://www.tensorflow.org/api_docs/python/tf/data/Dataset#shard - Note: The same class can be used with tf's internal GCS client if format of - file path starts with gs:// and with GCSFuse if local GCSFuse mounted path - is passed. - - For details on prefetch, num_parallel_calls & shard arguments, please refer - to tf.data public documentation. - - Args: - task_name: String name assigned to task. - file_path_format: String format of file path. Must contain 'file_num' to be - formatted. E.g. gcs/256kb/read.{file_num}. - file_size: Integer size of file in bytes to be read. - num_files: Integer number of files to be read in one task. - prefetch: Integer value of prefetch. Default to AUTOTUNE(-1). - num_parallel_calls = Integer value of num_parallel_calls. Default to - AUTOTUNE(-1). - shard: Integer value of shard. Default to AUTOTUNE(-1). - """ - - def __init__(self, - task_name, - file_path_format, - file_size, - num_files, - prefetch=-1, - num_parallel_calls=-1, - shard=1): - super().__init__() - self.task_type = TF_DATA_READ - self.task_name = task_name - self.file_path_format = file_path_format - self.file_size = file_size - self.num_files = num_files - self.prefetch = prefetch - self.num_parallel_calls = num_parallel_calls - self.shard = shard - self.file_names = [ - self.file_path_format.format(file_num=file_num) - for file_num in range(self.num_files) - ] - - def pre_task(self, process_id, thread_id): #pylint: disable=unused-argument - """Pre-task to clear OS's page caches in load testing. - """ - # Clear the page cache as there is no way to bypass cache in tf.data. - # Note: This takes time and hence decrease the average bandwidth. - os.system("sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'") - - def task(self, process_id, thread_id): #pylint: disable=unused-argument - """Reads TFRecord files using tensorflow's tf.data method. - - See base class for more details. - - Returns: - Integer denoting the size of content read in bytes. - """ - content_len = 0 - files_dataset = tf.data.Dataset.from_tensor_slices(self.file_names) - - def tfrecord(path): - return tf.data.TFRecordDataset(path).prefetch(self.prefetch).shard( - self.shard, 0) - - dataset = files_dataset.interleave( - tfrecord, - cycle_length=self.num_parallel_calls, - num_parallel_calls=self.num_parallel_calls) - for record in dataset: - content_len = content_len + len(record.numpy()) - - return content_len * self.shard - - def create_files(self, num_processes): #pylint: disable=unused-argument - """Creates self.num_files number of files to be used in load testing. - """ - logging.info('Creating %s bytes TFRecord files using the format ' - '%s', self.file_size, self.file_path_format) - - default_file_path = self.file_path_format.format(file_num=0) - self._create_tfrecord_file(default_file_path, self.file_size) - for file_num in range(1, self.num_files): - file_path = self.file_path_format.format(file_num=file_num) - if not tf.io.gfile.exists(file_path): - logging.info('Creating TFRecord file %s of size %s.', file_path, - self.file_size) - tf.io.gfile.copy(default_file_path, file_path) - - def _create_tfrecord_file(self, file_path, file_size): - """Creates .tfrecord file of given file size in bytes at given file path. - - Doesn't create a file if file is already present. - """ - # We only check existence in this case because actual TFRecord's file size - # is not exactly equal to file_size - if tf.io.gfile.exists(file_path): - return - - logging.info('Creating TFRecord file %s of size %s.', file_path, file_size) - content = b'\t' * SINGLE_RECORD_SIZE - writer = tf.io.TFRecordWriter(file_path) - for _ in range(0, file_size, len(content)): - writer.write(content) - writer.close() diff --git a/perfmetrics/scripts/load_tests/python/tasks/tf_gfile.py b/perfmetrics/scripts/load_tests/python/tasks/tf_gfile.py deleted file mode 100644 index e5294f51e3..0000000000 --- a/perfmetrics/scripts/load_tests/python/tasks/tf_gfile.py +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Contains tasks that read data from files using tensorflow's tf.io.gfile api. - -Example: - - lg_obj = load_generator.LoadGenerator(...) - task_obj = python_os.TFGFileRead(...) - observations = lg_obj.generate_load(task_obj) -""" -import logging -import tensorflow as tf -from load_generator import task - -TF_GFILE_READ = 'tf_gfile_read' - - -class TFGFileRead(task.LoadTestTask): - """Task class for reading GCS file using tensorflow's tf.io.gfile api. - - tf.io.gfile: https://www.tensorflow.org/api_docs/python/tf/io/gfile/GFile - Note: The same class can be used with tf's internal GCS client if format of - file path starts with gs:// and with GCSFuse if local GCSFuse mounted path - is passed. - - Args: - task_name: String name assigned to task. - file_path_format: String format of file path. The format can contain - 'process_id' and 'thread_id' to be filled. E.g. gcs/256kb/read.{process_id} - file_size: Integer size of file in bytes to be read. - block_size: Integer size of block in bytes. File is read block by block. - If block size is not passed or -1, then by default it takes file size as - block size - """ - - def __init__(self, task_name, file_path_format, file_size, block_size=-1): - super().__init__() - self.task_type = TF_GFILE_READ - self.task_name = task_name - self.file_path_format = file_path_format - self.file_size = file_size - # Keep file size as default block size - self.block_size = block_size - if self.block_size == -1: - self.block_size = file_size - - def task(self, process_id, thread_id): - """Reads file of given size from given GCS file path and block size. - - See base class for more details. - - Returns: - Integer denoting the size of content read in bytes. - """ - file_path = self.file_path_format.format( - process_id=process_id, thread_id=thread_id) - content_len = 0 - with tf.io.gfile.GFile(file_path, 'rb') as fp: - for _ in range(0, self.file_size, self.block_size): - content = fp.read(self.block_size) - content_len = content_len + len(content) - fp.close() - return content_len - - def create_files(self, num_processes): - """Creates num_processes number of GCS files to be used in load testing. - - Args: - num_processes: Integer denoting number of processes in load test. - - Returns: - None - """ - logging.info( - 'One file is created per process of size %s using the format ' - '%s', self.file_size, self.file_path_format) - for process_id in range(num_processes): - file_path = self.file_path_format.format(process_id=process_id) - self._create_binary_file(file_path, self.file_size) - - def _create_binary_file(self, file_path, file_size): - """Creates binary file of given file size in bytes at given GCS path. - - Doesn't create a file if file is already present and has same file size. - """ - if tf.io.gfile.exists(file_path) and tf.io.gfile.stat( - file_path).length == file_size: - return - logging.info('Creating file %s of size %s.', file_path, file_size) - with tf.io.gfile.GFile(file_path, 'wb') as f_p: - content = b'\t' * file_size - f_p.write(content) From 323e00ce80260ec58cbf1ccc63d7db05987449be Mon Sep 17 00:00:00 2001 From: codechanges Date: Thu, 22 May 2025 09:42:04 +0530 Subject: [PATCH 0455/1298] Encapsulate global vars in a struct for a few e2e tests (#3341) * Encapsulate global vars in a struct * update explicit_dir test --- .../explicit_dir/explicit_dir_test.go | 16 ++++-- .../explicit_dir/list_test.go | 2 +- .../implicit_dir/delete_test.go | 12 ++--- .../implicit_dir/implicit_dir_test.go | 19 ++++--- .../implicit_dir/list_test.go | 2 +- .../implicit_dir/local_file_test.go | 52 +++++++++---------- 6 files changed, 56 insertions(+), 47 deletions(-) diff --git a/tools/integration_tests/explicit_dir/explicit_dir_test.go b/tools/integration_tests/explicit_dir/explicit_dir_test.go index 4fa4eaad29..42e9ed94dc 100644 --- a/tools/integration_tests/explicit_dir/explicit_dir_test.go +++ b/tools/integration_tests/explicit_dir/explicit_dir_test.go @@ -29,16 +29,22 @@ import ( const DirForExplicitDirTests = "dirForExplicitDirTests" -var ( +// IMPORTANT: To prevent global variable pollution, enhance code clarity, +// and avoid inadvertent errors. We strongly suggest that, all new package-level +// variables (which would otherwise be declared with `var` at the package root) should +// be added as fields to this 'env' struct instead. +type env struct { storageClient *storage.Client ctx context.Context -) +} + +var testEnv env func TestMain(m *testing.M) { setup.ParseSetUpFlags() // Create storage client before running tests. - ctx = context.Background() - closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + testEnv.ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&testEnv.ctx, &testEnv.storageClient) defer func() { err := closeStorageClient() if err != nil { @@ -48,7 +54,7 @@ func TestMain(m *testing.M) { // These tests will not run on HNS buckets because the "--implicit-dirs=false" flag does not function similarly to how it does on FLAT buckets. // Note that HNS buckets do not have the concept of implicit directories. - if setup.IsHierarchicalBucket(ctx, storageClient) { + if setup.IsHierarchicalBucket(testEnv.ctx, testEnv.storageClient) { log.Println("These tests will not run on HNS buckets.") return } diff --git a/tools/integration_tests/explicit_dir/list_test.go b/tools/integration_tests/explicit_dir/list_test.go index 1cbac8f12a..605ebc15a6 100644 --- a/tools/integration_tests/explicit_dir/list_test.go +++ b/tools/integration_tests/explicit_dir/list_test.go @@ -40,7 +40,7 @@ func TestListOnlyExplicitObjectsFromBucket(t *testing.T) { // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, DirForExplicitDirTests) + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(testEnv.ctx, t, testEnv.storageClient, DirForExplicitDirTests) } else { implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(DirForExplicitDirTests) } diff --git a/tools/integration_tests/implicit_dir/delete_test.go b/tools/integration_tests/implicit_dir/delete_test.go index 94f676dee9..7e81fa110a 100644 --- a/tools/integration_tests/implicit_dir/delete_test.go +++ b/tools/integration_tests/implicit_dir/delete_test.go @@ -34,7 +34,7 @@ func TestDeleteNonEmptyImplicitDir(t *testing.T) { testDirPath := setupTestDir(testDirName) // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(testEnv.ctx, t, testEnv.storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) } @@ -54,7 +54,7 @@ func TestDeleteNonEmptyImplicitSubDir(t *testing.T) { testDirPath := setupTestDir(testDirName) // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(testEnv.ctx, t, testEnv.storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) } @@ -76,7 +76,7 @@ func TestDeleteImplicitDirWithExplicitSubDir(t *testing.T) { testDirPath := setupTestDir(testDirName) // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(testEnv.ctx, t, testEnv.storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) } @@ -102,7 +102,7 @@ func TestDeleteImplicitDirWithImplicitSubDirContainingExplicitDir(t *testing.T) testDirPath := setupTestDir(testDirName) // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(testEnv.ctx, t, testEnv.storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) } @@ -129,7 +129,7 @@ func TestDeleteImplicitDirInExplicitDir(t *testing.T) { testDirPath := setupTestDir(testDirName) // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { - implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructureUsingStorageClient(testEnv.ctx, t, testEnv.storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName), t) } @@ -153,7 +153,7 @@ func TestDeleteExplicitDirContainingImplicitSubDir(t *testing.T) { testDirPath := setupTestDir(testDirName) // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { - implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructureUsingStorageClient(testEnv.ctx, t, testEnv.storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { implicit_and_explicit_dir_setup.CreateImplicitDirectoryInExplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName), t) } diff --git a/tools/integration_tests/implicit_dir/implicit_dir_test.go b/tools/integration_tests/implicit_dir/implicit_dir_test.go index da6490f851..d7410cec45 100644 --- a/tools/integration_tests/implicit_dir/implicit_dir_test.go +++ b/tools/integration_tests/implicit_dir/implicit_dir_test.go @@ -36,10 +36,17 @@ const NumberOfFilesInExplicitDirInImplicitSubDir = 1 const NumberOfFilesInExplicitDirInImplicitDir = 1 const DirForImplicitDirTests = "dirForImplicitDirTests" -var ( +// IMPORTANT: To prevent global variable pollution, enhance code clarity, +// and avoid inadvertent errors. We strongly suggest that, all new package-level +// variables (which would otherwise be declared with `var` at the package root) should +// be added as fields to this 'env' struct instead. +type env struct { storageClient *storage.Client ctx context.Context -) + testDirPath string +} + +var testEnv env func setupTestDir(dirName string) string { dir := setup.SetupTestDirectory(DirForImplicitDirTests) @@ -55,8 +62,8 @@ func TestMain(m *testing.M) { setup.ParseSetUpFlags() // Create storage client before running tests. - ctx = context.Background() - closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + testEnv.ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&testEnv.ctx, &testEnv.storageClient) defer func() { err := closeStorageClient() if err != nil { @@ -66,7 +73,7 @@ func TestMain(m *testing.M) { flagsSet := [][]string{{"--implicit-dirs"}} - if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(ctx, storageClient); err == nil { + if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(testEnv.ctx, testEnv.storageClient); err == nil { flagsSet = append(flagsSet, hnsFlagSet) } @@ -77,6 +84,6 @@ func TestMain(m *testing.M) { successCode := implicit_and_explicit_dir_setup.RunTestsForImplicitDirAndExplicitDir(flagsSet, m) // Clean up test directory created. - setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) + setup.CleanupDirectoryOnGCS(testEnv.ctx, testEnv.storageClient, path.Join(setup.TestBucket(), testDirName)) os.Exit(successCode) } diff --git a/tools/integration_tests/implicit_dir/list_test.go b/tools/integration_tests/implicit_dir/list_test.go index 660c4f8e0b..aa018eb5b3 100644 --- a/tools/integration_tests/implicit_dir/list_test.go +++ b/tools/integration_tests/implicit_dir/list_test.go @@ -42,7 +42,7 @@ func TestListImplicitObjectsFromBucket(t *testing.T) { // TODO: Remove the condition and keep the storage-client flow for non-ZB too. if setup.IsZonalBucketRun() { - implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(ctx, t, storageClient, path.Join(DirForImplicitDirTests, testDirName)) + implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructureUsingStorageClient(testEnv.ctx, t, testEnv.storageClient, path.Join(DirForImplicitDirTests, testDirName)) } else { implicit_and_explicit_dir_setup.CreateImplicitDirectoryStructure(path.Join(DirForImplicitDirTests, testDirName)) } diff --git a/tools/integration_tests/implicit_dir/local_file_test.go b/tools/integration_tests/implicit_dir/local_file_test.go index 34bb8448c7..c94a64b03c 100644 --- a/tools/integration_tests/implicit_dir/local_file_test.go +++ b/tools/integration_tests/implicit_dir/local_file_test.go @@ -28,39 +28,35 @@ const ( testDirName = "ImplicitDirTest" ) -var ( - testDirPath string -) - // ////////////////////////////////////////////////////////////////////// // Tests // ////////////////////////////////////////////////////////////////////// func TestNewFileUnderImplicitDirectoryShouldNotGetSyncedToGCSTillClose(t *testing.T) { testBaseDirName := path.Join(testDirName, operations.GetRandomName(t)) - testDirPath = setup.SetupTestDirectoryRecursive(testBaseDirName) - CreateImplicitDir(ctx, storageClient, testBaseDirName, t) + testEnv.testDirPath = setup.SetupTestDirectoryRecursive(testBaseDirName) + CreateImplicitDir(testEnv.ctx, testEnv.storageClient, testBaseDirName, t) fileName := path.Join(ImplicitDirName, FileName1) - _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName, t) + _, fh := CreateLocalFileInTestDir(testEnv.ctx, testEnv.storageClient, testEnv.testDirPath, fileName, t) operations.WriteWithoutClose(fh, FileContents, t) - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testBaseDirName, fileName, t) + ValidateObjectNotFoundErrOnGCS(testEnv.ctx, testEnv.storageClient, testBaseDirName, fileName, t) // Validate. - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testBaseDirName, fileName, FileContents, t) + CloseFileAndValidateContentFromGCS(testEnv.ctx, testEnv.storageClient, fh, testBaseDirName, fileName, FileContents, t) } func TestReadDirForImplicitDirWithLocalFile(t *testing.T) { testBaseDirName := path.Join(testDirName, operations.GetRandomName(t)) - testDirPath = setup.SetupTestDirectoryRecursive(testBaseDirName) - CreateImplicitDir(ctx, storageClient, testBaseDirName, t) + testEnv.testDirPath = setup.SetupTestDirectoryRecursive(testBaseDirName) + CreateImplicitDir(testEnv.ctx, testEnv.storageClient, testBaseDirName, t) fileName1 := path.Join(ImplicitDirName, FileName1) fileName2 := path.Join(ImplicitDirName, FileName2) - _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName1, t) - _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName2, t) + _, fh1 := CreateLocalFileInTestDir(testEnv.ctx, testEnv.storageClient, testEnv.testDirPath, fileName1, t) + _, fh2 := CreateLocalFileInTestDir(testEnv.ctx, testEnv.storageClient, testEnv.testDirPath, fileName2, t) // Attempt to list implicit directory. - entries := operations.ReadDirectory(path.Join(testDirPath, ImplicitDirName), t) + entries := operations.ReadDirectory(path.Join(testEnv.testDirPath, ImplicitDirName), t) // Verify entries received successfully. operations.VerifyCountOfDirectoryEntries(3, len(entries), t) @@ -68,8 +64,8 @@ func TestReadDirForImplicitDirWithLocalFile(t *testing.T) { operations.VerifyFileEntry(entries[1], FileName2, 0, t) operations.VerifyFileEntry(entries[2], ImplicitFileName1, GCSFileSize, t) // Close the local files. - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh1, testBaseDirName, fileName1, "", t) - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testBaseDirName, fileName2, "", t) + CloseFileAndValidateContentFromGCS(testEnv.ctx, testEnv.storageClient, fh1, testBaseDirName, fileName1, "", t) + CloseFileAndValidateContentFromGCS(testEnv.ctx, testEnv.storageClient, fh2, testBaseDirName, fileName2, "", t) } func TestRecursiveListingWithLocalFiles(t *testing.T) { @@ -83,20 +79,20 @@ func TestRecursiveListingWithLocalFiles(t *testing.T) { // mntDir/implicit/implicitFile1 --- file testBaseDirName := path.Join(testDirName, operations.GetRandomName(t)) - testDirPath = setup.SetupTestDirectoryRecursive(testBaseDirName) + testEnv.testDirPath = setup.SetupTestDirectoryRecursive(testBaseDirName) fileName2 := path.Join(ExplicitDirName, ExplicitFileName1) fileName3 := path.Join(ImplicitDirName, FileName2) // Create local file in mnt/ dir. - _, fh1 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t) + _, fh1 := CreateLocalFileInTestDir(testEnv.ctx, testEnv.storageClient, testEnv.testDirPath, FileName1, t) // Create explicit dir with 1 local file. - operations.CreateDirectory(path.Join(testDirPath, ExplicitDirName), t) - _, fh2 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName2, t) + operations.CreateDirectory(path.Join(testEnv.testDirPath, ExplicitDirName), t) + _, fh2 := CreateLocalFileInTestDir(testEnv.ctx, testEnv.storageClient, testEnv.testDirPath, fileName2, t) // Create implicit dir with 1 local file1 and 1 synced file. - CreateImplicitDir(ctx, storageClient, testBaseDirName, t) - _, fh3 := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName3, t) + CreateImplicitDir(testEnv.ctx, testEnv.storageClient, testBaseDirName, t) + _, fh3 := CreateLocalFileInTestDir(testEnv.ctx, testEnv.storageClient, testEnv.testDirPath, fileName3, t) // Recursively list mntDir/ directory. - err := filepath.WalkDir(testDirPath, + err := filepath.WalkDir(testEnv.testDirPath, func(walkPath string, dir fs.DirEntry, err error) error { if err != nil { return err @@ -118,14 +114,14 @@ func TestRecursiveListingWithLocalFiles(t *testing.T) { } // Check if mntDir/explicitFoo/ has correct objects. - if walkPath == path.Join(testDirPath, ExplicitDirName) { + if walkPath == path.Join(testEnv.testDirPath, ExplicitDirName) { // numberOfObjects = 1 operations.VerifyCountOfDirectoryEntries(1, len(objs), t) operations.VerifyFileEntry(objs[0], ExplicitFileName1, 0, t) } // Check if mntDir/implicitFoo/ has correct objects. - if walkPath == path.Join(testDirPath, ImplicitDirName) { + if walkPath == path.Join(testEnv.testDirPath, ImplicitDirName) { // numberOfObjects = 2 operations.VerifyCountOfDirectoryEntries(2, len(objs), t) operations.VerifyFileEntry(objs[0], FileName2, 0, t) @@ -138,7 +134,7 @@ func TestRecursiveListingWithLocalFiles(t *testing.T) { if err != nil { t.Errorf("filepath.WalkDir() err: %v", err) } - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh1, testBaseDirName, FileName1, "", t) - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh2, testBaseDirName, fileName2, "", t) - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh3, testBaseDirName, fileName3, "", t) + CloseFileAndValidateContentFromGCS(testEnv.ctx, testEnv.storageClient, fh1, testBaseDirName, FileName1, "", t) + CloseFileAndValidateContentFromGCS(testEnv.ctx, testEnv.storageClient, fh2, testBaseDirName, fileName2, "", t) + CloseFileAndValidateContentFromGCS(testEnv.ctx, testEnv.storageClient, fh3, testBaseDirName, fileName3, "", t) } From 45ade3ce7dfad88a35f575e93cd235231f4d60ee Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Thu, 22 May 2025 10:37:14 +0530 Subject: [PATCH 0456/1298] Help doc update for inactive stream timeout (#3342) --- cfg/config.go | 2 +- cfg/params.yaml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 9f924d68d4..5d0b527f44 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -569,7 +569,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.IntP("prometheus-port", "", 0, "Expose Prometheus metrics endpoint on this port and a path of /metrics.") - flagSet.DurationP("read-inactive-stream-timeout", "", 0*time.Nanosecond, "Duration of inactivity after which an open GCS read stream is automatically closed. This helps conserve resources when a file handle remains open without active Read calls. A value of '0s' disables this timeout. Note: Currently only applies when using the 'grpc' client-protocol.") + flagSet.DurationP("read-inactive-stream-timeout", "", 0*time.Nanosecond, "Duration of inactivity after which an open GCS read stream is automatically closed. This helps conserve resources when a file handle remains open without active Read calls. A value of '0s' disables this timeout.") if err := flagSet.MarkHidden("read-inactive-stream-timeout"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 8db325dd59..65687d5feb 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -712,7 +712,6 @@ Duration of inactivity after which an open GCS read stream is automatically closed. This helps conserve resources when a file handle remains open without active Read calls. A value of '0s' disables this timeout. - Note: Currently only applies when using the 'grpc' client-protocol. default: "0s" hide-flag: true From 3865ba831589f58049bcb251ae8971b4a74dcd7d Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 22 May 2025 11:34:27 +0530 Subject: [PATCH 0457/1298] Enable reads when writes are in progress with streaming writes for Regional Buckets. (#3336) * enable reads while writes are in progress for regional bucket * fix test * add streaming writes flag to interrupt flag for regional bucket * refactor code * revert comment changes * fix review comments * modify comment * Enable local file read tests for streaming writes * Uncomment assertion --- internal/fs/fs.go | 11 +++++++++- internal/fs/handle/file.go | 17 ++++---------- internal/fs/inode/file.go | 11 +++++++--- .../fs/inode/file_streaming_writes_test.go | 6 +++++ .../interrupt/interrupt_test.go | 5 ++++- .../integration_tests/local_file/read_file.go | 2 +- .../integration_tests/local_file/sym_link.go | 4 ++-- .../stale_file_handle_synced_file_test.go | 4 +++- .../common_streaming_writes_suite_test.go | 22 +++++++++---------- .../streaming_writes/read_file_test.go | 16 ++++++++++++-- .../streaming_writes/symlink_file_test.go | 8 +++++-- .../util/operations/file_operations.go | 14 ------------ 12 files changed, 68 insertions(+), 52 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 0b10f46bc4..73dc1872d4 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2595,8 +2595,17 @@ func (fs *fileSystem) ReadFile( fs.mu.Unlock() fh.Lock() + fh.Inode().Lock() defer fh.Unlock() - + // TODO(b/417136852): Remove bucket type check when we start leaving zonal bucket objects unfinalized. + // Flush Pending streaming writes file for regional bucket and issue read within same inode lock. + if fh.Inode().IsUsingBWH() && !fh.Inode().Bucket().BucketType().Zonal { + err = fs.flushFile(ctx, fh.Inode()) + if err != nil { + fh.Inode().Unlock() + return err + } + } // Serve the read. op.Dst, op.BytesRead, err = fh.Read(ctx, op.Dst, op.Offset, fs.sequentialReadSizeMb) diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index d92ce9c28e..a675ba37de 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -106,22 +106,13 @@ func (fh *FileHandle) Unlock() { // Equivalent to locking fh.Inode() and calling fh.Inode().Read, but may be // more efficient. // -// LOCKS_REQUIRED(fh) -// LOCKS_EXCLUDED(fh.inode) +// LOCKS_REQUIRED(fh.mu) +// LOCKS_REQUIRED(fh.inode.mu) +// UNLOCK_FUNCTION(fh.inode.mu) func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequentialReadSizeMb int32) (output []byte, n int, err error) { - // Lock the inode and attempt to ensure that we have a reader for its current + // fh.inode.mu is already locked to ensure that we have a reader for its current // state, or clear fh.reader if it's not possible to create one (probably // because the inode is dirty). - fh.inode.Lock() - // Ensure all pending writes to Zonal Buckets are flushed before issuing a read. - // Updating inode state is not required here because inode state for Zonal Buckets will - // be updated at time of BWH creation. - _, err = fh.inode.SyncPendingBufferedWrites() - if err != nil { - fh.inode.Unlock() - err = fmt.Errorf("fh.inode.SyncPendingBufferedWrites: %w", err) - return - } err = fh.tryEnsureReader(ctx, sequentialReadSizeMb) if err != nil { fh.inode.Unlock() diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 5243ff41fb..e942004342 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -364,6 +364,13 @@ func (f *FileInode) Unlink() { } } +// Returns true if the fileInode is using Buffered Write Handler. +// +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) IsUsingBWH() bool { + return f.bwh != nil +} + // Source returns a record for the GCS object from which this inode is branched. The // record is guaranteed not to be modified, and users must not modify it. // @@ -543,9 +550,7 @@ func (f *FileInode) Read( ctx context.Context, dst []byte, offset int64) (n int, err error) { - // It is not nil when streaming writes are enabled in 2 scenarios: - // 1. Local file - // 2. Empty GCS files and writes are triggered via buffered flow. + // It is not nil when streaming writes are enabled and bucket type is Zonal. if f.bwh != nil { err = fmt.Errorf("cannot read a file when upload in progress: %w", syscall.ENOTSUP) return diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index f245cd7f86..dd1ce1fa99 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -167,6 +167,12 @@ func (t *FileStreamingWritesCommon) createInode(fileName string, fileType string // Common Tests //////////////////////////////////////////////////////////////////////// +func (t *FileStreamingWritesCommon) TestIsUsingBWH() { + assert.False(t.T(), t.in.IsUsingBWH()) + t.createBufferedWriteHandler() + assert.True(t.T(), t.in.IsUsingBWH()) +} + func (t *FileStreamingWritesCommon) TestflushUsingBufferedWriteHandlerOnZeroSizeRecreatesBwhOnInitAgain() { t.createBufferedWriteHandler() err := t.in.flushUsingBufferedWriteHandler() diff --git a/tools/integration_tests/interrupt/interrupt_test.go b/tools/integration_tests/interrupt/interrupt_test.go index 8158ac4f79..a80a5c82f2 100644 --- a/tools/integration_tests/interrupt/interrupt_test.go +++ b/tools/integration_tests/interrupt/interrupt_test.go @@ -65,7 +65,10 @@ func TestMain(m *testing.M) { {"--implicit-dirs=true"}, {"--config-file=" + setup.YAMLConfigFile(yamlContent1, "ignore_interrupts.yaml")}, {"--config-file=" + setup.YAMLConfigFile(yamlContent2, "default_ignore_interrupts.yaml")}} - + // TODO(b/417136852): Enable this test for Zonal Bucket also once read start working. + if !setup.IsZonalBucketRun() { + flags = append(flags, []string{"--enable-streaming-writes=true", "--implicit-dirs=true"}) + } successCode := static_mounting.RunTests(flags, m) // Clean up test directory created. diff --git a/tools/integration_tests/local_file/read_file.go b/tools/integration_tests/local_file/read_file.go index d6e2ec6046..0741ece184 100644 --- a/tools/integration_tests/local_file/read_file.go +++ b/tools/integration_tests/local_file/read_file.go @@ -24,7 +24,7 @@ import ( // Tests //////////////////////////////////////////////////////////////////////// -func (t *localFileTestSuite) TestReadLocalFile() { +func (t *CommonLocalFileTestSuite) TestReadLocalFile() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. _, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) diff --git a/tools/integration_tests/local_file/sym_link.go b/tools/integration_tests/local_file/sym_link.go index 440d29b8db..a74d692475 100644 --- a/tools/integration_tests/local_file/sym_link.go +++ b/tools/integration_tests/local_file/sym_link.go @@ -42,13 +42,13 @@ func createAndVerifySymLink(t *testing.T) (filePath, symlink string, fh *os.File return } -func (t *localFileTestSuite) TestCreateSymlinkForLocalFile() { +func (t *CommonLocalFileTestSuite) TestCreateSymlinkForLocalFile() { _, _, fh := createAndVerifySymLink(t.T()) CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, FileContents, t.T()) } -func (t *localFileTestSuite) TestReadSymlinkForDeletedLocalFile() { +func (t *CommonLocalFileTestSuite) TestReadSymlinkForDeletedLocalFile() { filePath, symlink, fh := createAndVerifySymLink(t.T()) // Remove filePath and then close the fileHandle to avoid syncing to GCS. operations.RemoveFile(filePath) diff --git a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go index 40e4a5e227..aa6a789929 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go @@ -65,7 +65,9 @@ func (s *staleFileHandleEmptyGcsFile) TestClobberedFileReadThrowsStaleFileHandle err = WriteToObject(ctx, storageClient, path.Join(testDirName, s.fileName), FileContents, storage.Conditions{}) assert.NoError(s.T(), err) - operations.ValidateReadGivenThatFileIsClobbered(s.T(), s.f1, s.isStreamingWritesEnabled, s.data) + buffer := make([]byte, len(s.data)) + _, err = s.f1.Read(buffer) + operations.ValidateESTALEError(s.T(), err) } func (s *staleFileHandleEmptyGcsFile) TestClobberedFileFirstWriteThrowsStaleFileHandleError() { diff --git a/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go b/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go index 0f2e55d1df..16813c6539 100644 --- a/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go +++ b/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go @@ -17,9 +17,9 @@ package streaming_writes import ( "os" "slices" - "syscall" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_suite" "github.com/stretchr/testify/assert" @@ -50,17 +50,15 @@ func (t *StreamingWritesSuite) TearDownSuite() { setup.SaveGCSFuseLogFileInCaseOfFailure(t.T()) } -func (t *StreamingWritesSuite) validateReadCall(filePath string) { - _, err := os.ReadFile(filePath) - if setup.IsZonalBucketRun() { - // TODO(b/410698332): Remove skip condition once reads start working. - t.T().Skip("Skipping Zonal Bucket Read tests.") - require.NoError(t.T(), err) - } - if t.fallbackToDiskCase { - require.NoError(t.T(), err) +func (t *StreamingWritesSuite) validateReadCall(fh *os.File, content string) { + readContent := make([]byte, len(content)) + n, err := fh.ReadAt(readContent, 0) + // TODO(b/417136852): Fix validation once zb reads start working. + if setup.IsZonalBucketRun() && !t.fallbackToDiskCase { + operations.ValidateEOPNOTSUPPError(t.T(), err) } else { - require.Error(t.T(), err) - assert.ErrorContains(t.T(), err, syscall.ENOTSUP.Error()) + require.NoError(t.T(), err) + assert.Equal(t.T(), len(content), n) + assert.Equal(t.T(), content, string(readContent)) } } diff --git a/tools/integration_tests/streaming_writes/read_file_test.go b/tools/integration_tests/streaming_writes/read_file_test.go index 0d8e20ef49..f32f6258aa 100644 --- a/tools/integration_tests/streaming_writes/read_file_test.go +++ b/tools/integration_tests/streaming_writes/read_file_test.go @@ -30,7 +30,7 @@ func (t *StreamingWritesSuite) TestReadFileAfterSync() { // Sync File to ensure buffers are flushed to GCS. operations.SyncFile(t.f1, t.T()) - t.validateReadCall(t.f1.Name()) + t.validateReadCall(t.f1, t.data) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data, t.T()) @@ -41,12 +41,24 @@ func (t *StreamingWritesSuite) TestReadBeforeFileIsFlushed() { operations.WriteAt(t.data, 0, t.f1, t.T()) // Try to read the file. - t.validateReadCall(t.filePath) + t.validateReadCall(t.f1, t.data) // Validate if correct content is uploaded to GCS after read error. CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data, t.T()) } +func (t *StreamingWritesSuite) TestReadBeforeSyncThenWriteAgainAndRead() { + // Write data to file. + operations.WriteAt(t.data, 0, t.f1, t.T()) + + t.validateReadCall(t.f1, t.data) + + operations.WriteAt(t.data, int64(len(t.data)), t.f1, t.T()) + t.validateReadCall(t.f1, t.data+t.data) + // Validate if correct content is uploaded to GCS after read. + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data+t.data, t.T()) +} + func (t *StreamingWritesSuite) TestReadAfterFlush() { // Write data to file and flush. operations.WriteAt(t.data, 0, t.f1, t.T()) diff --git a/tools/integration_tests/streaming_writes/symlink_file_test.go b/tools/integration_tests/streaming_writes/symlink_file_test.go index e3bc919616..da01f203d8 100644 --- a/tools/integration_tests/streaming_writes/symlink_file_test.go +++ b/tools/integration_tests/streaming_writes/symlink_file_test.go @@ -34,7 +34,9 @@ func (t *StreamingWritesSuite) TestCreateSymlinkForLocalFileAndReadFromSymlink() operations.VerifyReadLink(t.filePath, symlink, t.T()) // Validate read file from symlink. - t.validateReadCall(symlink) + symlink_fh := operations.OpenFile(symlink, t.T()) + defer operations.CloseFileShouldNotThrowError(t.T(), symlink_fh) + t.validateReadCall(symlink_fh, t.data) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, t.data, t.T()) @@ -50,7 +52,9 @@ func (t *StreamingWritesSuite) TestReadingFromSymlinkForDeletedLocalFile() { operations.VerifyReadLink(t.filePath, symlink, t.T()) // Validate read from symlink. - t.validateReadCall(symlink) + symlink_fh := operations.OpenFile(symlink, t.T()) + defer operations.CloseFileShouldNotThrowError(t.T(), symlink_fh) + t.validateReadCall(symlink_fh, t.data) // Remove filePath and then close the fileHandle to avoid syncing to GCS. operations.RemoveFile(t.filePath) diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index b4524ad5c8..f92c0d0ddd 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -816,20 +816,6 @@ func ValidateSyncGivenThatFileIsClobbered(t *testing.T, file *os.File, streaming } } -// This method validates read operation on file which has already been clobbered. -// 1. With streaming writes read operation is not supported. -// 2. Without streaming writes read operation returns ESTALE error encountered during reader creation. -func ValidateReadGivenThatFileIsClobbered(t *testing.T, file *os.File, streamingWrites bool, content string) { - t.Helper() - buffer := make([]byte, len(content)) - _, err := file.Read(buffer) - if streamingWrites { - ValidateEOPNOTSUPPError(t, err) - } else { - ValidateESTALEError(t, err) - } -} - // This method validates write operation on file which has been renamed. // 1. With streaming writes write operation returns ESTALE error as buffer upload fails. // 2. Without streaming writes write operation succeeds. From d1373b665b7f60e98856d2181f1193396ef16427 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 22 May 2025 16:46:32 +0530 Subject: [PATCH 0458/1298] Update direct dependencies. (#3343) Command used: `go get $(go list -f '{{if not (or .Main .Indirect)}}{{.Path}}{{end}}' -m all)` --- go.mod | 72 ++++++++++++++-------------- go.sum | 148 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 113 insertions(+), 107 deletions(-) diff --git a/go.mod b/go.mod index c9de8b8ee3..be050e59c7 100644 --- a/go.mod +++ b/go.mod @@ -3,16 +3,17 @@ module github.com/googlecloudplatform/gcsfuse/v2 go 1.24.0 require ( - cloud.google.com/go/compute/metadata v0.6.0 - cloud.google.com/go/iam v1.5.0 - cloud.google.com/go/secretmanager v1.14.6 - cloud.google.com/go/storage v1.52.0 + cloud.google.com/go/compute/metadata v0.7.0 + cloud.google.com/go/iam v1.5.2 + cloud.google.com/go/profiler v0.4.2 + cloud.google.com/go/secretmanager v1.14.7 + cloud.google.com/go/storage v1.54.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 github.com/fsouza/fake-gcs-server v1.52.2 github.com/go-viper/mapstructure/v2 v2.2.1 github.com/google/uuid v1.6.0 - github.com/googleapis/gax-go/v2 v2.14.1 + github.com/googleapis/gax-go/v2 v2.14.2 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec github.com/jacobsa/fuse v0.0.0-20250506064942-d10bfe4fb08e github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd @@ -22,30 +23,30 @@ require ( github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3 github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6 github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 - github.com/prometheus/client_golang v1.21.1 - github.com/prometheus/client_model v0.6.1 - github.com/prometheus/common v0.63.0 + github.com/prometheus/client_golang v1.22.0 + github.com/prometheus/client_model v0.6.2 + github.com/prometheus/common v0.64.0 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.6 - github.com/spf13/viper v1.20.0 + github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 go.opencensus.io v0.24.0 go.opentelemetry.io/contrib/detectors/gcp v1.35.0 - go.opentelemetry.io/otel v1.35.0 - go.opentelemetry.io/otel/exporters/prometheus v0.57.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 - go.opentelemetry.io/otel/metric v1.35.0 - go.opentelemetry.io/otel/sdk v1.35.0 - go.opentelemetry.io/otel/sdk/metric v1.35.0 - go.opentelemetry.io/otel/trace v1.35.0 - golang.org/x/net v0.39.0 - golang.org/x/oauth2 v0.29.0 - golang.org/x/sync v0.13.0 + go.opentelemetry.io/otel v1.36.0 + go.opentelemetry.io/otel/exporters/prometheus v0.58.0 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 + go.opentelemetry.io/otel/metric v1.36.0 + go.opentelemetry.io/otel/sdk v1.36.0 + go.opentelemetry.io/otel/sdk/metric v1.36.0 + go.opentelemetry.io/otel/trace v1.36.0 + golang.org/x/net v0.40.0 + golang.org/x/oauth2 v0.30.0 + golang.org/x/sync v0.14.0 golang.org/x/sys v0.33.0 - golang.org/x/text v0.24.0 + golang.org/x/text v0.25.0 golang.org/x/time v0.11.0 - google.golang.org/api v0.229.0 - google.golang.org/grpc v1.71.1 + google.golang.org/api v0.234.0 + google.golang.org/grpc v1.72.1 google.golang.org/protobuf v1.36.6 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 @@ -53,14 +54,13 @@ require ( require ( cel.dev/expr v0.22.0 // indirect - cloud.google.com/go v0.120.0 // indirect - cloud.google.com/go/auth v0.16.0 // indirect + cloud.google.com/go v0.121.0 // indirect + cloud.google.com/go/auth v0.16.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect - cloud.google.com/go/longrunning v0.6.6 // indirect - cloud.google.com/go/monitoring v1.24.0 // indirect - cloud.google.com/go/profiler v0.4.2 // indirect - cloud.google.com/go/pubsub v1.47.0 // indirect - cloud.google.com/go/trace v1.11.4 // indirect + cloud.google.com/go/longrunning v0.6.7 // indirect + cloud.google.com/go/monitoring v1.24.2 // indirect + cloud.google.com/go/pubsub v1.49.0 // indirect + cloud.google.com/go/trace v1.11.6 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -71,6 +71,7 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/go-jose/go-jose/v4 v4.0.4 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect @@ -81,25 +82,26 @@ require ( github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/klauspost/compress v1.18.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pkg/xattr v0.4.10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/sagikazarmark/locafero v0.7.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.12.0 // indirect github.com/spf13/cast v1.7.1 // indirect + github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.37.0 // indirect - google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e // indirect + golang.org/x/crypto v0.38.0 // indirect + google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect ) diff --git a/go.sum b/go.sum index 746da2a5f1..9e164368ae 100644 --- a/go.sum +++ b/go.sum @@ -1,34 +1,34 @@ cel.dev/expr v0.22.0 h1:+hFFhLPmquBImfs1BiN2PZmkr5ASse2ZOuaxIs9e4R8= cel.dev/expr v0.22.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.120.0 h1:wc6bgG9DHyKqF5/vQvX1CiZrtHnxJjBlKUyF9nP6meA= -cloud.google.com/go v0.120.0/go.mod h1:/beW32s8/pGRuj4IILWQNd4uuebeT4dkOhKmkfit64Q= -cloud.google.com/go/auth v0.16.0 h1:Pd8P1s9WkcrBE2n/PhAwKsdrR35V3Sg2II9B+ndM3CU= -cloud.google.com/go/auth v0.16.0/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= +cloud.google.com/go v0.121.0 h1:pgfwva8nGw7vivjZiRfrmglGWiCJBP+0OmDpenG/Fwg= +cloud.google.com/go v0.121.0/go.mod h1:rS7Kytwheu/y9buoDmu5EIpMMCI4Mb8ND4aeN4Vwj7Q= +cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= +cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -cloud.google.com/go/iam v1.5.0 h1:QlLcVMhbLGOjRcGe6VTGGTyQib8dRLK2B/kYNV0+2xs= -cloud.google.com/go/iam v1.5.0/go.mod h1:U+DOtKQltF/LxPEtcDLoobcsZMilSRwR7mgNL7knOpo= -cloud.google.com/go/kms v1.21.0 h1:x3EeWKuYwdlo2HLse/876ZrKjk2L5r7Uexfm8+p6mSI= -cloud.google.com/go/kms v1.21.0/go.mod h1:zoFXMhVVK7lQ3JC9xmhHMoQhnjEDZFoLAr5YMwzBLtk= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= +cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= +cloud.google.com/go/kms v1.21.2 h1:c/PRUSMNQ8zXrc1sdAUnsenWWaNXN+PzTXfXOcSFdoE= +cloud.google.com/go/kms v1.21.2/go.mod h1:8wkMtHV/9Z8mLXEXr1GK7xPSBdi6knuLXIhqjuWcI6w= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= -cloud.google.com/go/longrunning v0.6.6 h1:XJNDo5MUfMM05xK3ewpbSdmt7R2Zw+aQEMbdQR65Rbw= -cloud.google.com/go/longrunning v0.6.6/go.mod h1:hyeGJUrPHcx0u2Uu1UFSoYZLn4lkMrccJig0t4FI7yw= -cloud.google.com/go/monitoring v1.24.0 h1:csSKiCJ+WVRgNkRzzz3BPoGjFhjPY23ZTcaenToJxMM= -cloud.google.com/go/monitoring v1.24.0/go.mod h1:Bd1PRK5bmQBQNnuGwHBfUamAV1ys9049oEPHnn4pcsc= +cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= +cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= +cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= +cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= cloud.google.com/go/profiler v0.4.2 h1:KojCmZ+bEPIQrd7bo2UFvZ2xUPLHl55KzHl7iaR4V2I= cloud.google.com/go/profiler v0.4.2/go.mod h1:7GcWzs9deJHHdJ5J9V1DzKQ9JoIoTGhezwlLbwkOoCs= -cloud.google.com/go/pubsub v1.47.0 h1:Ou2Qu4INnf7ykrFjGv2ntFOjVo8Nloh/+OffF4mUu9w= -cloud.google.com/go/pubsub v1.47.0/go.mod h1:LaENesmga+2u0nDtLkIOILskxsfvn/BXX9Ak1NFxOs8= -cloud.google.com/go/secretmanager v1.14.6 h1:/ooktIMSORaWk9gm3vf8+Mg+zSrUplJFKBztP993oL0= -cloud.google.com/go/secretmanager v1.14.6/go.mod h1:0OWeM3qpJ2n71MGgNfKsgjC/9LfVTcUqXFUlGxo5PzY= -cloud.google.com/go/storage v1.52.0 h1:ROpzMW/IwipKtatA69ikxibdzQSiXJrY9f6IgBa9AlA= -cloud.google.com/go/storage v1.52.0/go.mod h1:4wrBAbAYUvYkbrf19ahGm4I5kDQhESSqN3CGEkMGvOY= -cloud.google.com/go/trace v1.11.4 h1:LKlhVyX6I4+heP31sWvERSKZZ9cPPEZumt7b4SKVK18= -cloud.google.com/go/trace v1.11.4/go.mod h1:lCSHzSPZC1TPwto7zhaRt3KtGYsXFyaErPQ18AUUeUE= +cloud.google.com/go/pubsub v1.49.0 h1:5054IkbslnrMCgA2MAEPcsN3Ky+AyMpEZcii/DoySPo= +cloud.google.com/go/pubsub v1.49.0/go.mod h1:K1FswTWP+C1tI/nfi3HQecoVeFvL4HUOB1tdaNXKhUY= +cloud.google.com/go/secretmanager v1.14.7 h1:VkscIRzj7GcmZyO4z9y1EH7Xf81PcoiAo7MtlD+0O80= +cloud.google.com/go/secretmanager v1.14.7/go.mod h1:uRuB4F6NTFbg0vLQ6HsT7PSsfbY7FqHbtJP1J94qxGc= +cloud.google.com/go/storage v1.54.0 h1:Du3XEyliAiftfyW0bwfdppm2MMLdpVAfiIg4T2nAI+0= +cloud.google.com/go/storage v1.54.0/go.mod h1:hIi9Boe8cHxTyaeqh7KMMwKg088VblFK46C2x/BWaZE= +cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= +cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= @@ -78,6 +78,8 @@ github.com/fsouza/fake-gcs-server v1.52.2 h1:j6ne83nqHrlX5EEor7WWVIKdBsztGtwJ1J2 github.com/fsouza/fake-gcs-server v1.52.2/go.mod h1:47HKyIkz6oLTes1R8vEaHLwXfzYsGfmDUk1ViHHAUsA= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= +github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -92,6 +94,8 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -124,8 +128,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= -github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= +github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= +github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -134,8 +138,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= -github.com/jacobsa/fuse v0.0.0-20250228153609-219b4762b40e h1:1k0eO4yveJsWQDzjA7YbnqDfT631ld5wGfr9uPN15N0= -github.com/jacobsa/fuse v0.0.0-20250228153609-219b4762b40e/go.mod h1:JYi9iIxdYNgxmMgLwtSHO/hmVnP2kfX1oc+mtx+XWLA= github.com/jacobsa/fuse v0.0.0-20250506064942-d10bfe4fb08e h1:l3GV2yC45OcDO/ErTSS/LnLQdytjUSmmBFcQCjWSUDY= github.com/jacobsa/fuse v0.0.0-20250506064942-d10bfe4fb08e/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= @@ -179,15 +181,15 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= -github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= -github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= +github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -205,8 +207,10 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= -github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= +github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= +github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -219,6 +223,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= +github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= go.einride.tech/aip v0.68.1 h1:16/AfSxcQISGN5z9C5lM+0mLYXihrHbQ1onvYTr93aQ= go.einride.tech/aip v0.68.1/go.mod h1:XaFtaj4HuA3Zwk9xoBtTWgNubZ0ZZXv9BZJCkuKuWbg= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= @@ -231,30 +237,30 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= -go.opentelemetry.io/otel/exporters/prometheus v0.57.0 h1:AHh/lAP1BHrY5gBwk8ncc25FXWm/gmmY3BX258z5nuk= -go.opentelemetry.io/otel/exporters/prometheus v0.57.0/go.mod h1:QpFWz1QxqevfjwzYdbMb4Y1NnlJvqSGwyuU0B4iuc9c= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/prometheus v0.58.0 h1:CJAxWKFIqdBennqxJyOgnt5LqkeFRT+Mz3Yjz3hL+h8= +go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0 h1:T0Ec2E+3YZf5bgTNQVet8iTDW7oIk03tXHq+wkwIDnE= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.35.0/go.mod h1:30v2gqH+vYGJsesLWFov8u47EpYTcIQcBjKpI6pJThg= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -265,29 +271,27 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= -golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -296,26 +300,26 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.229.0 h1:p98ymMtqeJ5i3lIBMj5MpR9kzIIgzpHHh8vQ+vgAzx8= -google.golang.org/api v0.229.0/go.mod h1:wyDfmq5g1wYJWn29O22FDWN48P7Xcz0xz+LBpptYvB0= +google.golang.org/api v0.234.0 h1:d3sAmYq3E9gdr2mpmiWGbm9pHsA/KJmyiLkwKfHBqU4= +google.golang.org/api v0.234.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf h1:114fkUG+I9ba4UmaoNZt0UtiRmBng3KJIB/E0avfYII= -google.golang.org/genproto v0.0.0-20250311190419-81fb87f6b8bf/go.mod h1:sAo5UzpjUwgFBCzupwhcLcxHVDK7vG5IqI30YnwX2eE= -google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e h1:UdXH7Kzbj+Vzastr5nVfccbmFsmYNygVLSPk1pEfDoY= -google.golang.org/genproto/googleapis/api v0.0.0-20250414145226-207652e42e2e/go.mod h1:085qFyf2+XaZlRdCgKNCIZ3afY2p4HHZdoIRpId8F4A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e h1:ztQaXfzEXTmCBvbtWYRhJxW+0iJcz2qXfd38/e9l7bA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250414145226-207652e42e2e/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= +google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= +google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0= +google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.71.1 h1:ffsFWr7ygTUscGPI0KKK6TLrGz0476KUvvsbqWK0rPI= -google.golang.org/grpc v1.71.1/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= +google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= +google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From 602c0452ee03af6479b71b304a809866b551f0be Mon Sep 17 00:00:00 2001 From: codechanges Date: Sun, 25 May 2025 04:16:25 +0530 Subject: [PATCH 0459/1298] Fixed flaky parallel download test (#3346) --- .../parallel_downloads_job_testify_test.go | 87 +++++++++++++++---- 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/internal/cache/file/downloader/parallel_downloads_job_testify_test.go b/internal/cache/file/downloader/parallel_downloads_job_testify_test.go index 8772b17a6e..2986189350 100644 --- a/internal/cache/file/downloader/parallel_downloads_job_testify_test.go +++ b/internal/cache/file/downloader/parallel_downloads_job_testify_test.go @@ -18,6 +18,7 @@ import ( "io" "os" "strings" + "sync" "testing" "github.com/googlecloudplatform/gcsfuse/v2/cfg" @@ -56,6 +57,7 @@ func (t *ParallelDownloaderJobTestifyTest) SetupTest() { func (t *ParallelDownloaderJobTestifyTest) Test_ParallelDownloadObjectToFile_NewReaderWithReadHandle() { objectName := "path/in/gcs/foo.txt" objectSize := 10 * util.MiB + chunkSize := 3 * util.MiB objectContent := testutil.GenerateRandomBytes(objectSize) t.initReadCacheTestifyTest(objectName, objectContent, DefaultSequentialReadSizeMb, uint64(2*objectSize), func() {}) t.job.cancelCtx, t.job.cancelFunc = context.WithCancel(context.Background()) @@ -72,27 +74,80 @@ func (t *ParallelDownloaderJobTestifyTest) Test_ParallelDownloadObjectToFile_New // DownloadChunkSizeMb = 3mb there will be one call to NewReaderWithReadHandle // with read handle. handle := []byte("opaque-handle") - rc1 := io.NopCloser(strings.NewReader(string(objectContent[0 : 3*util.MiB]))) - rd1 := &fake.FakeReader{ReadCloser: rc1, Handle: handle} - rc2 := io.NopCloser(strings.NewReader(string(objectContent[3*util.MiB : 6*util.MiB]))) - rd2 := &fake.FakeReader{ReadCloser: rc2, Handle: handle} - rc3 := io.NopCloser(strings.NewReader(string(objectContent[6*util.MiB : 9*util.MiB]))) - rd3 := &fake.FakeReader{ReadCloser: rc3, Handle: handle} - rc4 := io.NopCloser(strings.NewReader(string(objectContent[9*util.MiB : 10*util.MiB]))) - rd4 := &fake.FakeReader{ReadCloser: rc4, Handle: handle} + var ( + actualCallCount int64 + nilHandleCallCount int64 + mu sync.Mutex // To protect counter updates from concurrent mock calls + ) + // Reset counters + actualCallCount = 0 + nilHandleCallCount = 0 + + // Helper function to increment counters based on the actual request seen by the mock + incrementCounters := func(args mock.Arguments) { + req := args.Get(1).(*gcs.ReadObjectRequest) // Second arg to NewReaderWithReadHandle + mu.Lock() + actualCallCount++ + if req.ReadHandle == nil { + nilHandleCallCount++ + } + mu.Unlock() + } + + // Define ranges for clarity + rangeR1 := &gcs.ByteRange{Start: uint64(0 * chunkSize), Limit: uint64(1 * chunkSize)} + rangeR2 := &gcs.ByteRange{Start: uint64(1 * chunkSize), Limit: uint64(2 * chunkSize)} + rangeR3 := &gcs.ByteRange{Start: uint64(2 * chunkSize), Limit: uint64(3 * chunkSize)} + rangeR4 := &gcs.ByteRange{Start: uint64(3 * chunkSize), Limit: uint64(objectSize)} // Last chunk + + // Create FakeReaders for each chunk, all will return the same propagatedHandle + readerR1 := &fake.FakeReader{ReadCloser: io.NopCloser(strings.NewReader(string(objectContent[0*chunkSize : 1*chunkSize]))), Handle: handle} + readerR2 := &fake.FakeReader{ReadCloser: io.NopCloser(strings.NewReader(string(objectContent[1*chunkSize : 2*chunkSize]))), Handle: handle} + readerR3 := &fake.FakeReader{ReadCloser: io.NopCloser(strings.NewReader(string(objectContent[2*chunkSize : 3*chunkSize]))), Handle: handle} + readerR4 := &fake.FakeReader{ReadCloser: io.NopCloser(strings.NewReader(string(objectContent[3*chunkSize : objectSize]))), Handle: handle} + t.mockBucket.On("Name").Return(storage.TestBucketName) - readObjectReq := gcs.ReadObjectRequest{Name: objectName, Range: &gcs.ByteRange{Start: 0, Limit: 3 * util.MiB}, ReadHandle: nil} - t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, &readObjectReq).Return(rd1, nil).Times(1) - readObjectReq2 := gcs.ReadObjectRequest{Name: objectName, Range: &gcs.ByteRange{Start: 3 * util.MiB, Limit: 6 * util.MiB}, ReadHandle: nil} - t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, &readObjectReq2).Return(rd2, nil).Times(1) - readObjectReq3 := gcs.ReadObjectRequest{Name: objectName, Range: &gcs.ByteRange{Start: 6 * util.MiB, Limit: 9 * util.MiB}, ReadHandle: nil} - t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, &readObjectReq3).Return(rd3, nil).Times(1) - readObjectReq4 := gcs.ReadObjectRequest{Name: objectName, Range: &gcs.ByteRange{Start: 9 * util.MiB, Limit: 10 * util.MiB}, ReadHandle: handle} - t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, &readObjectReq4).Return(rd4, nil).Times(1) + + // Chunk 1 (R1): Must be ReadHandle: nil + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(req *gcs.ReadObjectRequest) bool { + return req.Range.Start == rangeR1.Start && req.Range.Limit == rangeR1.Limit && + req.ReadHandle == nil + })).Run(incrementCounters).Return(readerR1, nil).Once() + + // Chunk 2 (R2): ReadHandle can be nil or propagated. Match primarily on range. + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(req *gcs.ReadObjectRequest) bool { + return req.Range.Start == rangeR2.Start && req.Range.Limit == rangeR2.Limit + })).Run(incrementCounters).Return(readerR2, nil).Once() + + // Chunk 3 (R3): ReadHandle can be nil or propagated. Match primarily on range. + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(req *gcs.ReadObjectRequest) bool { + return req.Range.Start == rangeR3.Start && req.Range.Limit == rangeR3.Limit + })).Run(incrementCounters).Return(readerR3, nil).Once() + + // Chunk 4 (R4): ReadHandle should not be nil + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(req *gcs.ReadObjectRequest) bool { + return req.Range.Start == rangeR4.Start && req.Range.Limit == rangeR4.Limit && req.ReadHandle != nil + })).Run(incrementCounters).Return(readerR4, nil).Once() // Start download err = t.job.parallelDownloadObjectToFile(file) + assert.Equal(t.T(), nil, err, "parallelDownloadObjectToFile should not return an error") + assert.Equal(t.T(), int64(4), actualCallCount, "Total calls to NewReaderWithReadHandle should be 4") + // Assert the number of calls with ReadHandle: nil falls within the expected range. + // For ParallelDownloadsPerFile = 3 and 4 chunks: + // - nilHandleCallCount must be at least 1 (for the very first chunk processed by any worker). + // - nilHandleCallCount can be at most ParallelDownloadsPerFile (3), as each of the 3 launched workers + // uses a nil handle only for its first operation. + // 1 <= nilHandleCallCount <= 3. + minExpectedNilCalls := int64(1) + maxExpectedNilCalls := int64(3) + numberOfChunks := int64(4) // Based on objectSize and chunkSize + + assert.True(t.T(), nilHandleCallCount >= minExpectedNilCalls && nilHandleCallCount <= maxExpectedNilCalls, + "Expected nilHandleCallCount to be between %d and %d (inclusive), but got %d. ParallelDownloadsPerFile=%d, Chunks=%d", + minExpectedNilCalls, maxExpectedNilCalls, nilHandleCallCount, t.job.fileCacheConfig.ParallelDownloadsPerFile, numberOfChunks) + t.mockBucket.AssertExpectations(t.T()) assert.Equal(t.T(), nil, err) jobStatus, ok := <-notificationC From fec1603dea52c923c6dc636234d7ef842ef49b4f Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 26 May 2025 10:04:10 +0530 Subject: [PATCH 0460/1298] [Random Reader Refactoring] Read manager implementation - 1 (#3337) * read manager implementation 1 * test formating * remove variable * review comments * review comments --- internal/gcsx/read_manager/read_manager.go | 129 +++++++++++ .../gcsx/read_manager/read_manager_test.go | 200 ++++++++++++++++++ 2 files changed, 329 insertions(+) create mode 100644 internal/gcsx/read_manager/read_manager.go create mode 100644 internal/gcsx/read_manager/read_manager_test.go diff --git a/internal/gcsx/read_manager/read_manager.go b/internal/gcsx/read_manager/read_manager.go new file mode 100644 index 0000000000..1afbe8901d --- /dev/null +++ b/internal/gcsx/read_manager/read_manager.go @@ -0,0 +1,129 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_manager + +import ( + "context" + "errors" + "io" + + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + clientReaders "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx/client_readers" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" +) + +type ReadManager struct { + gcsx.ReadManager + object *gcs.MinObject + + // readers holds a list of data readers, prioritized for reading. + // e.g., File cache reader, GCS reader. + readers []gcsx.Reader +} + +// ReadManagerConfig holds the configuration parameters for creating a new ReadManager. +type ReadManagerConfig struct { + SequentialReadSizeMB int32 + FileCacheHandler *file.CacheHandler + CacheFileForRangeRead bool + MetricHandle common.MetricHandle + MrdWrapper *gcsx.MultiRangeDownloaderWrapper +} + +// NewReadManager creates a new ReadManager for the given GCS object, +// using the provided configuration. It initializes the manager with a +// file cache reader and a GCS reader, prioritizing the file cache reader if available. +func NewReadManager(object *gcs.MinObject, bucket gcs.Bucket, config *ReadManagerConfig) *ReadManager { + // Create a slice to hold all readers. The file cache reader will be added first if it exists. + var readers []gcsx.Reader + + // If a file cache handler is provided, initialize the file cache reader and add it to the readers slice first. + if config.FileCacheHandler != nil { + fileCacheReader := gcsx.NewFileCacheReader( + object, + bucket, + config.FileCacheHandler, + config.CacheFileForRangeRead, + config.MetricHandle, + ) + readers = append(readers, fileCacheReader) // File cache reader is prioritized. + } + + // Initialize the GCS reader, which is always present. + gcsReader := clientReaders.NewGCSReader( + object, + bucket, + config.MetricHandle, + config.MrdWrapper, + config.SequentialReadSizeMB, + ) + // Add the GCS reader as a fallback. + readers = append(readers, gcsReader) + + return &ReadManager{ + object: object, + readers: readers, // Readers are prioritized: file cache first, then GCS. + } +} + +func (rr *ReadManager) Object() *gcs.MinObject { + return rr.object +} + +func (rr *ReadManager) CheckInvariants() { + for _, r := range rr.readers { + r.CheckInvariants() + } +} + +// ReadAt attempts to read data from the provided offset, using the configured readers. +// It prioritizes readers in the order they are defined (file cache first, then GCS). +// If a reader returns a FallbackToAnotherReader error, it tries the next reader. +func (rr *ReadManager) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.ReaderResponse, error) { + if offset >= int64(rr.object.Size) { + return gcsx.ReaderResponse{}, io.EOF + } + + // empty read + if len(p) == 0 { + return gcsx.ReaderResponse{}, nil + } + + var readerResponse gcsx.ReaderResponse + var err error + for _, r := range rr.readers { + readerResponse, err = r.ReadAt(ctx, p, offset) + if err == nil { + return readerResponse, nil + } + if !errors.Is(err, gcsx.FallbackToAnotherReader) { + // Non-fallback error, return it. + return readerResponse, err + } + // Fallback to the next reader. + } + + // If all readers failed with FallbackToAnotherReader, return the last response and error. + // This case should not happen as the last reader should always succeed. + return readerResponse, err +} + +func (rr *ReadManager) Destroy() { + for _, r := range rr.readers { + r.Destroy() + } +} diff --git a/internal/gcsx/read_manager/read_manager_test.go b/internal/gcsx/read_manager/read_manager_test.go new file mode 100644 index 0000000000..fa0c5784e9 --- /dev/null +++ b/internal/gcsx/read_manager/read_manager_test.go @@ -0,0 +1,200 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_manager + +import ( + "context" + "errors" + "io" + "os" + "path" + "strings" + "testing" + "testing/iotest" + + "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + clientReaders "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx/client_readers" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" +) + +const ( + MiB = 1024 * 1024 + sequentialReadSizeInMb = 22 + cacheMaxSize = 2 * sequentialReadSizeInMb * MiB +) + +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func (t *readManagerTest) readManagerConfig(fileCacheEnable bool) *ReadManagerConfig { + config := &ReadManagerConfig{ + SequentialReadSizeMB: sequentialReadSizeInMb, + CacheFileForRangeRead: false, + MetricHandle: common.NewNoopMetrics(), + MrdWrapper: nil, + } + if fileCacheEnable { + cacheDir := path.Join(os.Getenv("HOME"), "test_cache_dir") + lruCache := lru.NewCache(cacheMaxSize) + jobManager := downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{EnableCrc: false}, common.NewNoopMetrics()) + config.FileCacheHandler = file.NewCacheHandler(lruCache, jobManager, cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) + } else { + config.FileCacheHandler = nil + } + return config +} + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type readManagerTest struct { + suite.Suite + object *gcs.MinObject + mockBucket *storage.TestifyMockBucket + readManager *ReadManager + ctx context.Context +} + +func TestReadManagerTestSuite(t *testing.T) { + suite.Run(t, new(readManagerTest)) +} + +func (t *readManagerTest) SetupTest() { + t.object = &gcs.MinObject{ + Name: "testObject", + Size: 17, + Generation: 1234, + } + t.mockBucket = new(storage.TestifyMockBucket) + t.ctx = context.Background() + t.readManager = NewReadManager(t.object, t.mockBucket, t.readManagerConfig(true)) +} + +func (t *readManagerTest) TearDownTest() { + t.readManager.Destroy() +} + +// ////////////////////////////////////////////////////////////////////// +// Tests +// ////////////////////////////////////////////////////////////////////// +func (t *readManagerTest) Test_NewReadManager_WithFileCacheHandler() { + config := t.readManagerConfig(true) + + rm := NewReadManager(t.object, t.mockBucket, config) + + assert.Equal(t.T(), t.object, rm.Object()) + assert.Len(t.T(), rm.readers, 2) + _, ok1 := rm.readers[0].(*gcsx.FileCacheReader) + _, ok2 := rm.readers[1].(*clientReaders.GCSReader) + assert.True(t.T(), ok1, "First reader should be FileCacheReader") + assert.True(t.T(), ok2, "Second reader should be GCSReader") +} + +func (t *readManagerTest) Test_NewReadManager_WithoutFileCacheHandler() { + config := t.readManagerConfig(false) + + rm := NewReadManager(t.object, t.mockBucket, config) + + assert.Equal(t.T(), t.object, rm.Object()) + assert.Len(t.T(), rm.readers, 1) + _, ok := rm.readers[0].(*clientReaders.GCSReader) + assert.True(t.T(), ok, "Only reader should be GCSReader") +} + +func (t *readManagerTest) Test_ReadAt_EmptyRead() { + // Nothing should happen. + readerResponse, err := t.readManager.ReadAt(t.ctx, make([]byte, 0), 0) + + assert.NoError(t.T(), err) + assert.Zero(t.T(), readerResponse.Size) +} + +func (t *readManagerTest) Test_ReadAt_InvalidOffset() { + tests := []struct { + name string + offset int64 + }{ + { + name: "ReadAtEndOfObject", + offset: int64(t.object.Size), + }, + { + name: "ReadPastEndOfObject", + offset: int64(t.object.Size) + 1, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func() { + readerResponse, err := t.readManager.ReadAt(t.ctx, make([]byte, 1), tc.offset) + + assert.Zero(t.T(), readerResponse.Size) + assert.True(t.T(), errors.Is(err, io.EOF), "expected %v error got %v", io.EOF, err) + }) + } +} + +func (t *readManagerTest) Test_ReadAt_NoExistingReader() { + // The bucket should be called to set up a new reader. + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(nil, errors.New("network error")) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + t.mockBucket.On("Name").Return("test-bucket") + + _, err := t.readManager.ReadAt(t.ctx, make([]byte, 1), 0) + + assert.Error(t.T(), err) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *readManagerTest) Test_ReadAt_ReaderFailsWithTimeout() { + t.readManager = NewReadManager(t.object, t.mockBucket, t.readManagerConfig(false)) + r := iotest.OneByteReader(iotest.TimeoutReader(strings.NewReader("xxx"))) + rc := &fake.FakeReader{ReadCloser: io.NopCloser(r)} + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(rc, nil).Once() + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + + _, err := t.readManager.ReadAt(t.ctx, make([]byte, 3), 0) + + assert.Error(t.T(), err) + assert.Contains(t.T(), err.Error(), "timeout") + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *readManagerTest) Test_ReadAt_FileClobbered() { + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(nil, &gcs.NotFoundError{}) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + t.mockBucket.On("Name").Return("test-bucket") + + _, err := t.readManager.ReadAt(t.ctx, make([]byte, 3), 1) + + assert.Error(t.T(), err) + var clobberedErr *gcsfuse_errors.FileClobberedError + assert.True(t.T(), errors.As(err, &clobberedErr)) + t.mockBucket.AssertExpectations(t.T()) +} From b912dceaf05d6173d7a552b1c97cd9dc8b1192cc Mon Sep 17 00:00:00 2001 From: codechanges Date: Mon, 26 May 2025 14:45:01 +0530 Subject: [PATCH 0461/1298] Enable optimized config for high perf node (#3354) --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 5d0b527f44..1bdbbde45c 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -341,7 +341,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.StringP("dir-mode", "", "0755", "Permissions bits for directories, in octal.") - flagSet.BoolP("disable-autoconfig", "", true, "Disable optimizing configuration automatically for a machine") + flagSet.BoolP("disable-autoconfig", "", false, "Disable optimizing configuration automatically for a machine") if err := flagSet.MarkHidden("disable-autoconfig"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 65687d5feb..345bba3751 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -75,7 +75,7 @@ flag-name: "disable-autoconfig" type: "bool" usage: "Disable optimizing configuration automatically for a machine" - default: true + default: false hide-flag: true - config-path: "enable-atomic-rename-object" From 29f664f51ca28c903c433ef4183e6263cf63d614 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 26 May 2025 19:27:29 +0530 Subject: [PATCH 0462/1298] [Random Reader Refactoring] Read manager implementation - 2 (#3353) * adding more unit tests * adding more unit tests * review comment and adding one more test * add mock reader --- internal/gcsx/file_cache_reader.go | 3 + internal/gcsx/mock_reader.go | 38 +++++++++ .../gcsx/read_manager/read_manager_test.go | 82 +++++++++++++++++-- 3 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 internal/gcsx/mock_reader.go diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index f33ad2cb14..edaaae963e 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -220,3 +220,6 @@ func (fc *FileCacheReader) Destroy() { fc.fileCacheHandle = nil } } + +func (fc *FileCacheReader) CheckInvariants() { +} diff --git a/internal/gcsx/mock_reader.go b/internal/gcsx/mock_reader.go new file mode 100644 index 0000000000..b4733faa84 --- /dev/null +++ b/internal/gcsx/mock_reader.go @@ -0,0 +1,38 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "context" + + "github.com/stretchr/testify/mock" +) + +type MockReader struct { + mock.Mock +} + +func (m *MockReader) ReadAt(ctx context.Context, p []byte, offset int64) (ReaderResponse, error) { + args := m.Called(ctx, p, offset) + return args.Get(0).(ReaderResponse), args.Error(1) +} + +func (m *MockReader) Destroy() { + m.Called() +} + +func (m *MockReader) CheckInvariants() { + m.Called() +} diff --git a/internal/gcsx/read_manager/read_manager_test.go b/internal/gcsx/read_manager/read_manager_test.go index fa0c5784e9..a6286a5e36 100644 --- a/internal/gcsx/read_manager/read_manager_test.go +++ b/internal/gcsx/read_manager/read_manager_test.go @@ -15,6 +15,7 @@ package read_manager import ( + "bytes" "context" "errors" "io" @@ -36,6 +37,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + testUtil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" @@ -69,6 +71,24 @@ func (t *readManagerTest) readManagerConfig(fileCacheEnable bool) *ReadManagerCo return config } +func (t *readManagerTest) mockNewReaderWithHandleCallForTestBucket(start uint64, limit uint64, rd gcs.StorageReader) { + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(rg *gcs.ReadObjectRequest) bool { + return rg != nil && (*rg.Range).Start == start && (*rg.Range).Limit == limit + })).Return(rd, nil).Once() +} + +func getReadCloser(content []byte) io.ReadCloser { + r := bytes.NewReader(content) + rc := io.NopCloser(r) + return rc +} + +func (t *readManagerTest) readAt(offset int64, size int64) (gcsx.ReaderResponse, error) { + t.readManager.CheckInvariants() + defer t.readManager.CheckInvariants() + return t.readManager.ReadAt(t.ctx, make([]byte, size), offset) +} + //////////////////////////////////////////////////////////////////////// // Boilerplate //////////////////////////////////////////////////////////////////////// @@ -129,7 +149,7 @@ func (t *readManagerTest) Test_NewReadManager_WithoutFileCacheHandler() { func (t *readManagerTest) Test_ReadAt_EmptyRead() { // Nothing should happen. - readerResponse, err := t.readManager.ReadAt(t.ctx, make([]byte, 0), 0) + readerResponse, err := t.readAt(0, 0) assert.NoError(t.T(), err) assert.Zero(t.T(), readerResponse.Size) @@ -152,7 +172,7 @@ func (t *readManagerTest) Test_ReadAt_InvalidOffset() { for _, tc := range tests { t.Run(tc.name, func() { - readerResponse, err := t.readManager.ReadAt(t.ctx, make([]byte, 1), tc.offset) + readerResponse, err := t.readAt(tc.offset, 1) assert.Zero(t.T(), readerResponse.Size) assert.True(t.T(), errors.Is(err, io.EOF), "expected %v error got %v", io.EOF, err) @@ -166,7 +186,7 @@ func (t *readManagerTest) Test_ReadAt_NoExistingReader() { t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) t.mockBucket.On("Name").Return("test-bucket") - _, err := t.readManager.ReadAt(t.ctx, make([]byte, 1), 0) + _, err := t.readAt(0, 1) assert.Error(t.T(), err) t.mockBucket.AssertExpectations(t.T()) @@ -179,7 +199,7 @@ func (t *readManagerTest) Test_ReadAt_ReaderFailsWithTimeout() { t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(rc, nil).Once() t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) - _, err := t.readManager.ReadAt(t.ctx, make([]byte, 3), 0) + _, err := t.readAt(0, 3) assert.Error(t.T(), err) assert.Contains(t.T(), err.Error(), "timeout") @@ -191,10 +211,62 @@ func (t *readManagerTest) Test_ReadAt_FileClobbered() { t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) t.mockBucket.On("Name").Return("test-bucket") - _, err := t.readManager.ReadAt(t.ctx, make([]byte, 3), 1) + _, err := t.readAt(1, 3) assert.Error(t.T(), err) var clobberedErr *gcsfuse_errors.FileClobberedError assert.True(t.T(), errors.As(err, &clobberedErr)) t.mockBucket.AssertExpectations(t.T()) } + +func (t *readManagerTest) Test_ReadAt_FullObjectFromCache() { + objectSize := int(t.object.Size) + expectedData := testUtil.GenerateRandomBytes(objectSize) + fakeReader := &fake.FakeReader{ + ReadCloser: getReadCloser(expectedData), + } + // Mock the reader that returns full object data + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, fakeReader) + t.mockBucket.On("Name").Return("test-bucket").Maybe() + + // Act: First read (expected to be served via GCS, populating the cache) + firstResp, err := t.readAt(0, int64(objectSize)) + + // Assert: First read succeeds and returns expected data + assert.NoError(t.T(), err, "First read should not return an error") + assert.Equal(t.T(), expectedData, firstResp.DataBuf, "First read should return expected data") + + // Act: Second read (should be served from cache) + secondResp, err := t.readAt(0, int64(objectSize)) + + // Assert: Second read also succeeds and returns the same cached data + assert.NoError(t.T(), err, "Second read (from cache) should not return an error") + assert.Equal(t.T(), expectedData, secondResp.DataBuf, "Second read should return cached data") + // Verify that bucket mock expectations are met + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *readManagerTest) Test_ReadAt_R1FailsR2Succeeds() { + offset := int64(0) + buf := make([]byte, 10) + expectedResp := gcsx.ReaderResponse{Size: 10} + mockReader1 := new(gcsx.MockReader) + mockReader2 := new(gcsx.MockReader) + t.readManager = &ReadManager{ + object: t.object, + readers: []gcsx.Reader{mockReader1, mockReader2}, + } + mockReader1.On("ReadAt", t.ctx, buf, offset).Return(gcsx.ReaderResponse{}, gcsx.FallbackToAnotherReader).Once() + mockReader1.On("CheckInvariants").Maybe() + mockReader1.On("Destroy").Maybe() + mockReader2.On("ReadAt", t.ctx, buf, offset).Return(expectedResp, nil).Once() + mockReader2.On("CheckInvariants").Maybe() + mockReader2.On("Destroy").Maybe() + + resp, err := t.readAt(offset, 10) + + assert.NoError(t.T(), err, "expected no error when second reader succeeds") + assert.Equal(t.T(), expectedResp, resp, "expected response from second reader") + mockReader1.AssertExpectations(t.T()) + mockReader2.AssertExpectations(t.T()) +} From 8809b26f211287996c7134d5bb78c9f28a9a3a0e Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 27 May 2025 08:59:33 +0530 Subject: [PATCH 0463/1298] E2E test for cloud profiler support (#3333) * E2E test for cloud profiler support in gcsfuse * fixing lint * fixing another lint issue * review comments * review comments * review comment * review comments * minor review comment --- .../cloud_profiler/cloud_profiler_test.go | 79 +++++++++++++ .../with_gcp_profiler_service_test.go | 110 ++++++++++++++++++ 2 files changed, 189 insertions(+) create mode 100644 tools/integration_tests/cloud_profiler/cloud_profiler_test.go create mode 100644 tools/integration_tests/cloud_profiler/with_gcp_profiler_service_test.go diff --git a/tools/integration_tests/cloud_profiler/cloud_profiler_test.go b/tools/integration_tests/cloud_profiler/cloud_profiler_test.go new file mode 100644 index 0000000000..0c71cafcc4 --- /dev/null +++ b/tools/integration_tests/cloud_profiler/cloud_profiler_test.go @@ -0,0 +1,79 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloud_profiler_test + +// Command to run the test from gcsfuse root directory: +// go test ./tools/integration_tests/cloud_profiler/... --integrationTest --testbucket -testInstalledPackage -v -timeout 20m + +import ( + "context" + "fmt" + "log" + "os" + "path" + "strings" + "testing" + + "cloud.google.com/go/storage" + "github.com/google/uuid" + "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" +) + +const ( + testDirName = "CloudProfilerTest" + testServiceName = "gcsfuse" +) + +var ( + testServiceVersion string +) + +//////////////////////////////////////////////////////////////////////// +// TestMain +//////////////////////////////////////////////////////////////////////// + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + var storageClient *storage.Client + ctx := context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() + + setup.RunTestsForMountedDirectoryFlag(m) + + // Else run tests for testBucket. + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + testServiceVersion = fmt.Sprintf("ve2e0.0.0-%s", strings.ReplaceAll(uuid.New().String(), "-", "")[:8]) + + flags := [][]string{ + {"--enable-cloud-profiling", "--profiling-cpu", "--profiling-heap", "--profiling-goroutines", "--profiling-mutex", "--profiling-allocated-heap", fmt.Sprintf("--profiling-label=%s", testServiceVersion)}, + } + logger.Infof("Enabling cloud profiler with version tag: %s", testServiceVersion) + successCode := static_mounting.RunTests(flags, m) + + // Clean up test directory created. + setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) + os.Exit(successCode) +} diff --git a/tools/integration_tests/cloud_profiler/with_gcp_profiler_service_test.go b/tools/integration_tests/cloud_profiler/with_gcp_profiler_service_test.go new file mode 100644 index 0000000000..f2948cb637 --- /dev/null +++ b/tools/integration_tests/cloud_profiler/with_gcp_profiler_service_test.go @@ -0,0 +1,110 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cloud_profiler_test + +import ( + "context" + "errors" + "fmt" + "os" + "testing" + "time" + + "cloud.google.com/go/compute/metadata" + gcpProfiler "google.golang.org/api/cloudprofiler/v2" + "google.golang.org/api/option" +) + +func getGCPProjectID(t *testing.T) string { + fetchProjectCtx, fetchProjectCancel := context.WithTimeout(context.Background(), 5*time.Second) + defer fetchProjectCancel() + + projectID, err := metadata.ProjectIDWithContext(fetchProjectCtx) // Reduced timeout, 5s is usually sufficient. + if err != nil { + t.Logf("metadata.ProjectIDWithContext failed: %v, try fetching from environment variable.", err) + projectID := os.Getenv("GOOGLE_CLOUD_PROJECT") + if projectID == "" { + t.Skip("Not able to fetch project ID from metadata server or GOOGLE_CLOUD_PROJECT environment variable. Skipping integration test.") + } + } + return projectID +} + +// checkIfProfileExistForServiceAndVersion queries the Cloud Profiler API for profiles +// returns true just after the first matching profile, false if no matching profile found. +// Ref: https://cloud.google.com/profiler/docs/reference/v2/rest +func checkIfProfileExistForServiceAndVersion( + ctx context.Context, + t *testing.T, // Pass testing.T for logging within the helper + profilerAPIClient *gcpProfiler.Service, + projectID string, +) bool { + + t.Logf("Querying profiles for service [%s] version [%s]", testServiceName, testServiceVersion) + + listCtx, listCancel := context.WithTimeout(ctx, 10*time.Minute) + defer listCancel() + + pagesFetched := 0 + completedErr := errors.New("completed") + listCall := profilerAPIClient.Projects.Profiles.List(fmt.Sprintf("projects/%s", projectID)) + err := listCall.Pages(listCtx, func(resp *gcpProfiler.ListProfilesResponse) error { + pagesFetched++ + t.Logf("Processing page %d of profiles, number of profiles in page: %d", pagesFetched, len(resp.Profiles)) + for _, p := range resp.Profiles { + if p.Deployment == nil || p.Deployment.Labels == nil { + continue + } + + // Return if matching profile found. + if p.Deployment.Target == testServiceName && p.Deployment.Labels["version"] == testServiceVersion { + t.Logf("Found matching profile: Type=%s, ID=%s", p.ProfileType, p.Deployment.Labels["version"]) + return completedErr + } + } + return nil // Continue to the next page + }) + if err != nil && err != completedErr { + t.Logf("while listing: %v", err) + return false + } + + if pagesFetched == 0 { + t.Logf("No profiles found") + return false + } + + return true +} + +func TestValidateProfilerWithActualService(t *testing.T) { + // GCSFuse process will be started as part of mount. + // Allow some time to export the profile data to GCP profiler service. + time.Sleep(2*time.Minute + 30*time.Second) + + // 1. Fetch GCP projectID. + // 2. Create a profiler service api client. + // 3. Make list call to the profiler service api client and fetch the profiles. + // 4. Filter and match if the right profile exists. + projectID := getGCPProjectID(t) + apiCtx := context.Background() + profilerAPIClient, err := gcpProfiler.NewService(apiCtx, option.WithScopes(gcpProfiler.CloudPlatformScope)) + if err != nil { + t.Fatalf("Failed to create Cloud Profiler API client: %v", err) + } + if !checkIfProfileExistForServiceAndVersion(apiCtx, t, profilerAPIClient, projectID) { + t.Errorf("No valid profile found for service [%s] and version [%s]", testServiceName, testServiceVersion) + } +} From 2d00aa62f7e250d9bfa2588d2540481b089b9f8a Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 27 May 2025 14:02:00 +0530 Subject: [PATCH 0464/1298] Adding cloud-profiler test as part of CI/CD (#3356) * Adding cloud-profiler test as part of CI/CD * fixing lint issue --- .../cloud_profiler/cloud_profiler_test.go | 8 +++++++- tools/integration_tests/run_e2e_tests.sh | 1 + tools/integration_tests/run_tests_mounted_directory.sh | 6 ++++++ tools/integration_tests/util/setup/setup.go | 6 ++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tools/integration_tests/cloud_profiler/cloud_profiler_test.go b/tools/integration_tests/cloud_profiler/cloud_profiler_test.go index 0c71cafcc4..ab0e977af6 100644 --- a/tools/integration_tests/cloud_profiler/cloud_profiler_test.go +++ b/tools/integration_tests/cloud_profiler/cloud_profiler_test.go @@ -60,7 +60,13 @@ func TestMain(m *testing.M) { } }() - setup.RunTestsForMountedDirectoryFlag(m) + if setup.MountedDirectory() != "" { + if setup.ProfileLabelForMountedDirTest() == "" { + log.Fatal("Profile label should have been provided for mounted directory test.") + } + testServiceVersion = setup.ProfileLabelForMountedDirTest() + setup.RunTestsForMountedDirectoryFlag(m) + } // Else run tests for testBucket. // Set up test directory. diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 1263c075af..12e4694fb1 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -117,6 +117,7 @@ TEST_DIR_PARALLEL=( "negative_stat_cache" "streaming_writes" "inactive_stream_timeout" + "cloud_profiler" ) # These tests never become parallel as it is changing bucket permissions. diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index 2fcc7eaa8d..1b45855d40 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -652,3 +652,9 @@ for test_case in "${test_cases[@]}"; do rm -rf $log_dir done +# Test package: cloud_profiler +# Run cloud_profiler tests. +random_profile_label="test" +gcsfuse --enable-cloud-profiling --profiling-goroutines --profiling-cpu --profiling-heap --profiling-allocated-heap --profiling-mutex --profiling-label $random_profile_label $TEST_BUCKET_NAME $MOUNT_DIR +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/cloud_profiler/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --profile_label=$random_profile_label +sudo umount $MOUNT_DIR diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index e02c227181..c5e2428e6c 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -44,6 +44,7 @@ var integrationTest = flag.Bool("integrationTest", false, "Run tests only when t var testInstalledPackage = flag.Bool("testInstalledPackage", false, "[Optional] Run tests on the package pre-installed on the host machine. By default, integration tests build a new package to run the tests.") var testOnTPCEndPoint = flag.Bool("testOnTPCEndPoint", false, "Run tests on TPC endpoint only when the flag value is true.") var gcsfusePreBuiltDir = flag.String("gcsfuse_prebuilt_dir", "", "Path to the pre-built GCSFuse directory containing bin/gcsfuse and sbin/mount.gcsfuse.") +var profileLabelForMountedDirTest = flag.String("profile_label", "", "To pass profile-label for the cloud-profile test.") const ( FilePermission_0600 = 0600 @@ -146,6 +147,11 @@ func DynamicBucketMounted() string { return dynamicBucketMounted } +// ProfileLabelForMountedDirTest returns the profile-label required for cloud-profiler test package. +func ProfileLabelForMountedDirTest() string { + return *profileLabelForMountedDirTest +} + // SetDynamicBucketMounted sets the name of the bucket in case of dynamic mount. func SetDynamicBucketMounted(dynamicBucketValue string) { dynamicBucketMounted = dynamicBucketValue From abfec4839a9c1b76cf051fa43937e9912d6b74f3 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 27 May 2025 14:04:48 +0530 Subject: [PATCH 0465/1298] [testing-on-gke] Refactor FIO output file parser (#3349) * Refactor FIO output file parser 1. Parse filesize, blocksize, read_type from FIO output file rather than from the output file path. 2. Add more error checks on FIO output files being malformed. 3. Support FIO output files with epoch numbers > 9. * mean_file_size -> file_size * fix logs * ignore FIO output files containing more than 1 job * more refactoring and error checks * fix a local variable name * fix logs --- .../examples/dlio/parse_logs.py | 21 +- .../examples/fio/fio_workload.py | 2 +- .../testing_on_gke/examples/fio/parse_logs.py | 202 +++++++++++------- .../testing_on_gke/examples/fio/run_tests.py | 2 +- .../testing_on_gke/examples/run-gke-tests.sh | 44 ++-- 5 files changed, 162 insertions(+), 109 deletions(-) diff --git a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py index 697c272d4f..60e532af3b 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/dlio/parse_logs.py @@ -269,12 +269,15 @@ def write_records_to_csv_output_file(output: dict, output_file_path: str): output = create_output_scenarios_from_downloaded_files(args) - output_file_path = args.output_file - # Create the parent directory of output_file_path if doesn't - # exist already. - ensure_directory_exists(os.path.dirname(output_file_path)) - write_records_to_csv_output_file(output, output_file_path) - print( - "\n\nSuccessfully published outputs of DLIO test runs to" - f" {output_file_path} !!!\n\n" - ) + if len(output) == 0: + print(f"\nNo DLIO outputs found for experiment_id {args.experiment_id} !\n") + else: + output_file_path = args.output_file + # Create the parent directory of output_file_path if doesn't + # exist already. + ensure_directory_exists(os.path.dirname(output_file_path)) + write_records_to_csv_output_file(output, output_file_path) + print( + "\n\nSuccessfully published outputs of DLIO test runs to" + f" {output_file_path} !!!\n\n" + ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py index 828872e200..4c5d54c5da 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/fio_workload.py @@ -162,7 +162,7 @@ def PPrint(self): f' blockSize:{self.blockSize}, filesPerThread:{self.filesPerThread},' f' numThreads:{self.numThreads}, bucket:{self.bucket},' f' readTypes:{self.readTypes}, gcsfuseMountOptions:' - f' {gcsfuseMountOptions}, numEpochs: {self.numEpochs}' + f' {self.gcsfuseMountOptions}, numEpochs: {self.numEpochs}' ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py index 73bf6edcba..2bda35337a 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/parse_logs.py @@ -17,8 +17,10 @@ # standard library imports import argparse +from collections.abc import Sequence import json import os +from os import path import pprint import re import subprocess @@ -32,7 +34,8 @@ from utils.utils import unix_to_timestamp, convert_size_to_bytes _LOCAL_LOGS_LOCATION = "../../bin/fio-logs" -_EPOCH_FILENAME_REGEX = "^epoch[0-9]+.json$" +_EPOCH_FILENAME_REGEX = "^epoch[1-9][0-9]*.json$" +_EPOCH_NUMBER_MATCH_REGEX = ".*epoch([1-9][0-9]*).json" record = { "pod_name": "", @@ -81,13 +84,18 @@ def download_fio_outputs(fioWorkloads: set, experimentID: str) -> int: <_LOCAL_LOGS_LOCATION>///----/gcsfuse-generic//gcsfuse_mount_options """ + # Find all the unique set of buckets, and then only + # download output files from them to avoid runtime + # wastage for multiple workloads using the same bucket. + buckets = set() for fioWorkload in fioWorkloads: - dstDir = ( - _LOCAL_LOGS_LOCATION + "/" + experimentID + "/" + fioWorkload.fileSize - ) + buckets.add(fioWorkload.bucket) + + for bucket in buckets: + dstDir = path.join(_LOCAL_LOGS_LOCATION, experimentID, bucket) ensure_directory_exists(dstDir) - srcObjects = f"gs://{fioWorkload.bucket}/fio-output/{experimentID}/*" + srcObjects = f"gs://{bucket}/fio-output/{experimentID}/*" print(f"Downloading FIO outputs from {srcObjects} ...") returncode, errorStr = download_gcs_objects(srcObjects, dstDir) if returncode < 0: @@ -108,8 +116,8 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: <_LOCAL_LOGS_LOCATION>///----/gcsfuse-generic//gcsfuse_mount_options Output dict structure: - "{read_type}-{mean_file_size}-{bs}-{numjobs}-{nrfiles}": - "mean_file_size": str + "{read_type}-{file_size}-{bs}-{numjobs}-{nrfiles}": + "file_size": str "read_type": str "records": "local-ssd": [record1, record2, record3, ...] @@ -151,7 +159,7 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: metadata_values[metadata] = f.read().strip() else: print( - f"Error: Directory {root} does not have file {metadata} in it" + f"Warning: Directory {root} does not have file {metadata} in it" " as expected, so skipping it." ) skip_this_directory = True @@ -168,64 +176,109 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: with open(per_epoch_output, "r") as f: try: per_epoch_output_data = json.load(f) - except: - print(f"failed to json-parse {per_epoch_output}, so skipping it.") + except Exception as e: + print( + f"Warning: failed to json-parse {per_epoch_output}, so skipping" + f" it. Error: {e}" + ) continue - # Confirm that the per_epoch_output_data has ["jobs"][0]["job options"]['"bs"] - # for determining blocksize. - if ( - not "jobs" in per_epoch_output_data - or not per_epoch_output_data["jobs"] - or not "job options" in per_epoch_output_data["jobs"][0] - or not "bs" in per_epoch_output_data["jobs"][0]["job options"] - ): - print( - 'Did not find "[jobs][0][job options][bs]" in' - f" {per_epoch_output}, so ignoring this file" - ) - continue - # Confirm that the per_epoch_output_data has ["global options"] for - # determining nrfiles and numjobs in it. - if "global options" not in per_epoch_output_data: - print(f"field: 'global options' missing in {per_epoch_output}") - continue - # This print is for debugging in case something goes wrong. print(f"Now parsing file {per_epoch_output} ...") - # Get fileSize, readType, echo number from the file path. + # Get epoch number, and key from the file path. root_split = root.split("/") - mean_file_size = root_split[-4] key = root_split[ -3 - ] # key is unique for a given combination of of fileSize,blockSize,numThreads(numjobs),filesPerThread(nrfiles). + ] # key is unique identifier for a single FIO workload. scenario = root_split[-2] - read_type = root_split[-1] - epoch = int(file.split(".")[0][-1]) + epoch = int( + re.match(_EPOCH_NUMBER_MATCH_REGEX, per_epoch_output).group(1) + ) - # Get nrfiles,numjobs, blocksize from ["global options"] and ["job options"]. + # Confirm that the per_epoch_output_data has all the required attributes. + for attr in {"global options", "jobs", "timestamp_ms"}: + if not attr in per_epoch_output_data: + print( + f"Warning: '{attr}' missing in FIO output file" + f" {per_epoch_output}, so skipping this file." + ) + continue global_options = per_epoch_output_data["global options"] + jobs = per_epoch_output_data["jobs"] + timestamp_ms = per_epoch_output_data["timestamp_ms"] + + for attr in {"nrfiles", "numjobs", "ioengine", "direct", "iodepth"}: + if not attr in global_options: + print( + f"Warning: '{attr}' missing in 'global options' in FIO output" + f" file {per_epoch_output}, so skipping this file." + ) + continue nrfiles = int(global_options["nrfiles"]) numjobs = int(global_options["numjobs"]) - job0 = per_epoch_output_data["jobs"][0] - bs = job0["job options"]["bs"] + if not isinstance(jobs, Sequence) or len(jobs) == 0: + print( + f"Warning: No jobs found in FIO output file {per_epoch_output}, so" + " skipping this file." + ) + continue + elif len(jobs) > 1: + print( + "Warning: More than 1 job found in FIO output file" + f" {per_epoch_output}, which is not supported, so skipping this" + " file." + ) + continue + job0 = jobs[0] + + for attr in ["job options", "read", "job_start"]: + if not attr in job0: + print( + f'Warning: Did not find "[jobs][0][{attr}]" in' + f" {per_epoch_output}, so skipping this file." + ) + continue + job0_options = job0["job options"] + job0_read_metrics = job0["read"] + job0_start = job0["job_start"] + + if not isinstance(job0_options, dict): + print( + 'Warning: "[jobs][0][job options]" is not of type dict in' + f" {per_epoch_output}, so skipping this file" + ) + continue + + for attr in ["bs", "filesize", "rw"]: + if not attr in job0_options: + print( + f'Warning: Did not find "[jobs][0][job options][{attr}]" in' + f" {per_epoch_output}, so skipping this file" + ) + continue + bs = job0_options["bs"] + file_size = job0_options["filesize"] + read_type = job0_options["rw"] # If the record for this key has not been added, create a new entry # for it. if key not in output: output[key] = { - "mean_file_size": mean_file_size, + "file_size": file_size, "read_type": read_type, "records": { - "local-ssd": [], - "gcsfuse-generic": [], + scenario: [], }, } - job0_read_metrics = job0["read"] - bs = job0["job options"]["bs"] + if not isinstance(job0_read_metrics, dict): + print( + "Warning: jobs[0]['read'] is not of type dict in" + f" {per_epoch_output}, so skipping this file." + ) + continue # Create a record for this key. r = record.copy() @@ -243,10 +296,10 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: r["throughput_mb_per_second"] = round( (r["throughput_bytes_per_second"] / 1e6), 2 ) - r["start_epoch"] = job0["job_start"] // 1000 - r["end_epoch"] = per_epoch_output_data["timestamp_ms"] // 1000 - r["start"] = unix_to_timestamp(job0["job_start"]) - r["end"] = unix_to_timestamp(per_epoch_output_data["timestamp_ms"]) + r["start_epoch"] = job0_start // 1000 + r["end_epoch"] = timestamp_ms // 1000 + r["start"] = unix_to_timestamp(job0_start) + r["end"] = unix_to_timestamp(timestamp_ms) fetch_cpu_memory_data(args=args, record=r) @@ -262,8 +315,9 @@ def create_output_scenarios_from_downloaded_files(args: dict) -> dict: r["e2e_latency_ns_p99.9"] = clat_ns_percentile["99.900000"] except Exception as e: print( - f"Failed to create following record with error: {e}, metadata:" - f" {repr(metadata_values)}" + f"Warning: failed to create record for key {key} for FIO output" + f" file {per_epoch_output} with error: {e}, metadata:" + f" {repr(metadata_values)}, so skipping this file." ) # This print is for debugging in case something goes wrong. pprint.pprint(r) @@ -331,7 +385,7 @@ def write_records_to_csv_output_file(output: dict, output_file_path: str): continue output_file_fwr.write( - f"{record_set['mean_file_size']},{record_set['read_type']},{scenario},{r['epoch']},{r['duration']},{r['throughput_mib_per_second']},{r['IOPS']},{r['throughput_over_local_ssd']},{r['lowest_memory']},{r['highest_memory']},{r['lowest_cpu']},{r['highest_cpu']},{r['pod_name']},{r['start']},{r['end']},\"{r['gcsfuse_mount_options']}\",{r['blockSize']},{r['filesPerThread']},{r['numThreads']},{args.experiment_id}," + f"{record_set['file_size']},{record_set['read_type']},{scenario},{r['epoch']},{r['duration']},{r['throughput_mib_per_second']},{r['IOPS']},{r['throughput_over_local_ssd']},{r['lowest_memory']},{r['highest_memory']},{r['lowest_cpu']},{r['highest_cpu']},{r['pod_name']},{r['start']},{r['end']},\"{r['gcsfuse_mount_options']}\",{r['blockSize']},{r['filesPerThread']},{r['numThreads']},{args.experiment_id}," ) output_file_fwr.write( f"{r['e2e_latency_ns_max']},{r['e2e_latency_ns_p50']},{r['e2e_latency_ns_p90']},{r['e2e_latency_ns_p99']},{r['e2e_latency_ns_p99.9']}," @@ -375,7 +429,7 @@ def write_records_to_bq_table( row.experiment_id = experiment_id row.epoch = r["epoch"] row.operation = record_set["read_type"] - row.file_size = record_set["mean_file_size"] + row.file_size = record_set["file_size"] row.file_size_in_bytes = convert_size_to_bytes(row.file_size) row.block_size = r["blockSize"] row.block_size_in_bytes = convert_size_to_bytes(row.block_size) @@ -429,26 +483,28 @@ def write_records_to_bq_table( download_fio_outputs(fioWorkloads, args.experiment_id) output = create_output_scenarios_from_downloaded_files(args) - - # Export output dict to CSV. - output_file_path = args.output_file - # Create the parent directory of output_file_path if doesn't exist already. - ensure_directory_exists(os.path.dirname(output_file_path)) - write_records_to_csv_output_file(output, output_file_path) - - # Export output dict to bigquery table. - if ( - args.bq_project_id - and args.bq_project_id.strip() - and args.bq_dataset_id - and args.bq_dataset_id.strip() - and args.bq_table_id - and args.bq_table_id.strip() - ): - write_records_to_bq_table( - output=output, - bq_project_id=args.bq_project_id, - bq_dataset_id=args.bq_dataset_id, - bq_table_id=args.bq_table_id, - experiment_id=args.experiment_id, - ) + if len(output) == 0: + print(f"\nNo FIO outputs found for experiment_id {args.experiment_id} !\n") + else: + # Export output dict to CSV. + output_file_path = args.output_file + # Create the parent directory of output_file_path if doesn't exist already. + ensure_directory_exists(os.path.dirname(output_file_path)) + write_records_to_csv_output_file(output, output_file_path) + + # Export output dict to bigquery table. + if ( + args.bq_project_id + and args.bq_project_id.strip() + and args.bq_dataset_id + and args.bq_dataset_id.strip() + and args.bq_table_id + and args.bq_table_id.strip() + ): + write_records_to_bq_table( + output=output, + bq_project_id=args.bq_project_id, + bq_dataset_id=args.bq_dataset_id, + bq_table_id=args.bq_table_id, + experiment_id=args.experiment_id, + ) diff --git a/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py b/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py index 3aa9fd8bcd..05d48f0f29 100644 --- a/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py +++ b/perfmetrics/scripts/testing_on_gke/examples/fio/run_tests.py @@ -94,7 +94,7 @@ def main(args) -> None: helmInstallCommands = createHelmInstallCommands( fioWorkloads, args.experiment_id, args.machine_type, args.custom_csi_driver ) - buckets = (fioWorkload.bucket for fioWorkload in fioWorkloads) + buckets = {fioWorkload.bucket for fioWorkload in fioWorkloads} role = 'roles/storage.objectUser' add_iam_role_for_buckets( buckets, diff --git a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh index 547616a964..d7bfe0a31c 100755 --- a/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh +++ b/perfmetrics/scripts/testing_on_gke/examples/run-gke-tests.sh @@ -780,15 +780,14 @@ function waitTillAllPodsComplete() { # given bucket and file-size. function downloadFioOutputsFromBucket() { local bucket=$1 - local fileSize=$2 - local mountpath=$3 + local mountpath=$2/${bucket}-mount - mkdir -pv $mountpath + mkdir -p $mountpath fusermount -uz $mountpath 2>/dev/null || true - echo "Mounting bucket \"${bucket}\" to ${mountpath} ... " + echo "Searching for FIO outputs for experiment ${experiment_id} in gs://${bucket} ..." cd $gcsfuse_src_dir - if ! go run $gcsfuse_src_dir --implicit-dirs $bucket $mountpath > /dev/null ; then + if ! go run $gcsfuse_src_dir --implicit-dirs --o ro $bucket $mountpath > /dev/null ; then # If fails to mount this bucket, # Return to original directory before exiting.. cd - >/dev/null @@ -802,30 +801,27 @@ function downloadFioOutputsFromBucket() { # If the given bucket has the fio outputs for the given experiment-id, then # copy/download them locally to the appropriate folder. src_dir="${mountpath}/fio-output/${experiment_id}" - dst_dir="${gcsfuse_src_dir}/perfmetrics/scripts/testing_on_gke/bin/fio-logs/${experiment_id}/${fileSize}" + dst_dir="${gcsfuse_src_dir}/perfmetrics/scripts/testing_on_gke/bin/fio-logs/${experiment_id}/${bucket}" if test -d "${src_dir}" ; then - mkdir -pv "${dst_dir}" - echo "Copying files of type ${fileSize}* from \"${src_dir}\" to \"${dst_dir}/\" ... " - cp -rfvu "${src_dir}"/${fileSize}* "${dst_dir}"/ + mkdir -p "${dst_dir}" + echo "Copying all files from \"${src_dir}\" to \"${dst_dir}/\" ... " + cp -rfu "${src_dir}"/* "${dst_dir}"/ fi - echo " Unmounting \"${bucket}\" from \"${mountpath}\" ... " fusermount -uz "${mountpath}" || true + rm -rf "${mountpath}" } function downloadFioOutputsFromAllBucketsInWorkloadConfig() { local mountpath=$(realpath mounted) # Using jquery, find out all the relevant buckets for non-disabled fio # workloads in the workload-config file and download fio outputs for them all. - cat ${workload_config} | jq 'select(.TestConfig.workloadConfig.workloads[].fioWorkload != null)' | jq -r '.TestConfig.workloadConfig.workloads[] | [.bucket, .fioWorkload.fileSize] | @csv' | grep -v " " | sort | uniq | while read bucket_size_combo; do - workload_bucket=$(echo ${bucket_size_combo} | cut -d, -f1 | tr -d \") - workload_filesize=$(echo ${bucket_size_combo} | cut -d, -f2 | tr -d \") - if [[ "${workload_bucket}" != "" && "${workload_filesize}" != "" ]]; then - downloadFioOutputsFromBucket ${workload_bucket} ${workload_filesize} ${mountpath} + cat ${workload_config} | jq 'select(.TestConfig.workloadConfig.workloads[].fioWorkload != null)' | jq -r '.TestConfig.workloadConfig.workloads[] | [.bucket] | @csv' | grep -v " " | sort | uniq | while read bucket; do + bucket=$(echo ${bucket} | tr -d \" ) + if [[ "${bucket}" != "" ]]; then + downloadFioOutputsFromBucket ${bucket} "${mountpath}" fi done - # Clean-up - fusermount -uz ${mountpath} || true rm -rf ${mountpath} } @@ -850,18 +846,22 @@ function fetchAndParseFioOutputs() { cd "${gke_testing_dir}"/examples/fio parse_logs_args="--project-number=${project_number} --workload-config ${workload_config} --experiment-id ${experiment_id} --output-file ${output_dir}/fio/output.csv --project-id=${project_id} --cluster-name=${cluster_name} --namespace-name=${appnamespace} --bq-project-id=${DEFAULT_BQ_PROJECT_ID} --bq-dataset-id=${DEFAULT_BQ_DATASET_ID} --bq-table-id=${DEFAULT_BQ_TABLE_ID}" if ${zonal}; then + # Download fio outputs from all buckets using gcsfuse because zonal buckets don't work with gcloud storage cp. + printf "\nDownloading all fio outputs using gcsfuse mount as there are zonal buckets involved ...\n\n" + downloadFioOutputsFromAllBucketsInWorkloadConfig + python3 parse_logs.py ${parse_logs_args} --predownloaded-output-files else python3 parse_logs.py ${parse_logs_args} fi - cd - + cd - >/dev/null } function fetchAndParseDlioOutputs() { printf "\nFetching and parsing dlio outputs ...\n\n" cd "${gke_testing_dir}"/examples/dlio python3 parse_logs.py --project-number=${project_number} --workload-config "${workload_config}" --experiment-id ${experiment_id} --output-file "${output_dir}"/dlio/output.csv --project-id=${project_id} --cluster-name=${cluster_name} --namespace-name=${appnamespace} - cd - + cd - >/dev/null } # prep @@ -902,12 +902,6 @@ waitTillAllPodsComplete # clean-up after run deleteAllPods -# Download fio outputs from all buckets using gcsfuse because zonal buckets don't work with gcloud storage cp. -if ${zonal}; then - printf "\nDownloading all fio outputs using gcsfuse mount as there are zonal buckets involved ...\n\n" - downloadFioOutputsFromAllBucketsInWorkloadConfig -fi - # parse outputs fetchAndParseFioOutputs fetchAndParseDlioOutputs From 49726fdd02463f9405bbb85b8282622cf8bb90b7 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Tue, 27 May 2025 10:03:50 +0000 Subject: [PATCH 0466/1298] Fix for updating seeks when reads are served using MRD (#3327) * Fixing MRD random read to sequential read conversion issue * removing unnecessary changes * fixed tests * fixing the test * Fixing the condition --- internal/gcsx/random_reader.go | 24 +++++--- internal/gcsx/random_reader_stretchr_test.go | 64 ++++++++++++++++++++ internal/gcsx/random_reader_test.go | 1 + 3 files changed, 82 insertions(+), 7 deletions(-) diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 5983333eca..136c9ac2e8 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -176,6 +176,10 @@ type randomReader struct { metricHandle common.MetricHandle config *cfg.ReadConfig + + // Specifies the next expected offset for the reads. Used to distinguish between + // sequential and random reads. + expectedOffset int64 } func (rr *randomReader) CheckInvariants() { @@ -336,7 +340,7 @@ func (rr *randomReader) ReadAt( // is a 15-20x improvement in throughput: 150-200 MiB/s instead of 10 MiB/s. if rr.reader != nil && rr.start < offset && offset-rr.start < maxReadSize { bytesToSkip := offset - rr.start - discardedBytes, copyError := io.CopyN(io.Discard, rr.reader, int64(bytesToSkip)) + discardedBytes, copyError := io.CopyN(io.Discard, rr.reader, bytesToSkip) // io.EOF is expected if the reader is shorter than the requested offset to read. if copyError != nil && !errors.Is(copyError, io.EOF) { logger.Warnf("Error while skipping reader bytes: %v", copyError) @@ -352,12 +356,6 @@ func (rr *randomReader) ReadAt( rr.closeReader() rr.reader = nil rr.cancel = nil - if rr.start != offset { - // We should only increase the seek count if we have to discard the reader when it's - // positioned at wrong place. Discarding it if can't serve the entire request would - // result in reader size not growing for random reads scenario. - rr.seeks++ - } } if rr.reader != nil { @@ -365,6 +363,12 @@ func (rr *randomReader) ReadAt( return } + // If the data can't be served from the existing reader, then we need to update the seeks. + // If current offset is not same as expected offset, its a random read. + if rr.expectedOffset != 0 && rr.expectedOffset != offset { + rr.seeks++ + } + // If we don't have a reader, determine whether to read from NewReader or MRR. end, err := rr.getReadInfo(offset, int64(len(p))) if err != nil { @@ -639,6 +643,7 @@ func (rr *randomReader) readFromRangeReader(ctx context.Context, p []byte, offse requestedDataSize := end - offset common.CaptureGCSReadMetrics(ctx, rr.metricHandle, readType, requestedDataSize) + rr.updateExpectedOffset(offset + int64(n)) return } @@ -655,6 +660,7 @@ func (rr *randomReader) readFromMultiRangeReader(ctx context.Context, p []byte, bytesRead, err = rr.mrdWrapper.Read(ctx, p, offset, end, timeout, rr.metricHandle) rr.totalReadBytes += uint64(bytesRead) + rr.updateExpectedOffset(offset + int64(bytesRead)) return } @@ -666,3 +672,7 @@ func (rr *randomReader) closeReader() { logger.Warnf("error while closing reader: %v", err) } } + +func (rr *randomReader) updateExpectedOffset(offset int64) { + rr.expectedOffset = offset +} diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 5c27038f4a..918aebe65f 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -288,6 +288,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenExistingReaderIs assert.Nil(t.T(), t.rr.wrapped.cancel) assert.Equal(t.T(), int64(5), t.rr.wrapped.start) assert.Equal(t.T(), int64(5), t.rr.wrapped.limit) + assert.Equal(t.T(), int64(t.object.Size), t.rr.wrapped.expectedOffset) }) } } @@ -314,6 +315,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenExistingReaderIs assert.Equal(t.T(), int64(8), t.rr.wrapped.start) assert.Equal(t.T(), int64(10), t.rr.wrapped.limit) assert.Equal(t.T(), uint64(8), t.rr.wrapped.totalReadBytes) + assert.Equal(t.T(), int64(8), t.rr.wrapped.expectedOffset) } func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenAllDataFromReaderIsRead() { @@ -357,6 +359,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenAllDataFromReade assert.Equal(t.T(), int64(10), t.rr.wrapped.start) assert.Equal(t.T(), int64(10), t.rr.wrapped.limit) assert.Equal(t.T(), uint64(10), t.rr.wrapped.totalReadBytes) + assert.Equal(t.T(), int64(10), t.rr.wrapped.expectedOffset) expectedReadHandle := tc.readHandle assert.Equal(t.T(), expectedReadHandle, t.rr.wrapped.readHandle) }) @@ -403,6 +406,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderHasLessDat assert.Equal(t.T(), int64(dataSize), t.rr.wrapped.start) assert.Equal(t.T(), int64(dataSize), t.rr.wrapped.limit) assert.Equal(t.T(), uint64(dataSize), t.rr.wrapped.totalReadBytes) + assert.Equal(t.T(), int64(dataSize), t.rr.wrapped.expectedOffset) expectedReadHandle := tc.readHandle assert.Equal(t.T(), expectedReadHandle, t.rr.wrapped.readHandle) }) @@ -447,6 +451,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderReturnedMo assert.Equal(t.T(), int64(-1), t.rr.wrapped.start) assert.Equal(t.T(), int64(-1), t.rr.wrapped.limit) assert.Equal(t.T(), uint64(8), t.rr.wrapped.totalReadBytes) + assert.Equal(t.T(), int64(0), t.rr.wrapped.expectedOffset) expectedReadHandle := tc.readHandle assert.Equal(t.T(), expectedReadHandle, t.rr.wrapped.readHandle) }) @@ -466,6 +471,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderReturnedEO _, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, 10, "unhandled") assert.True(t.T(), strings.Contains(err.Error(), "skipping 4 bytes")) + assert.Equal(t.T(), int64(0), t.rr.wrapped.expectedOffset) } func (t *RandomReaderStretchrTest) Test_ExistingReader_WrongOffset() { @@ -551,6 +557,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequ require.Equal(t.T(), requestSize, data.Size) require.Equal(t.T(), "abcdef", string(buf[:data.Size])) assert.Equal(t.T(), uint64(requestSize), t.rr.wrapped.totalReadBytes) + assert.Equal(t.T(), int64(2+requestSize), t.rr.wrapped.expectedOffset) assert.Equal(t.T(), expectedHandleInRequest, t.rr.wrapped.readHandle) } @@ -585,6 +592,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequ require.Equal(t.T(), int(t.object.Size), data.Size) require.Equal(t.T(), "abcde", string(buf[:data.Size])) assert.Equal(t.T(), t.object.Size, t.rr.wrapped.totalReadBytes) + assert.Equal(t.T(), int64(t.object.Size), t.rr.wrapped.expectedOffset) assert.Equal(t.T(), []byte(nil), t.rr.wrapped.readHandle) } @@ -626,6 +634,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { bucketType gcs.BucketType readRanges [][]int expectedReadTypes []string + expectedSeeks []int }{ { name: "SequentialReadFlat", @@ -633,6 +642,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Sequential, testutil.Sequential}, + expectedSeeks: []int{0, 0, 0, 0, 0}, }, { name: "SequentialReadZonal", @@ -640,6 +650,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Sequential, testutil.Sequential}, + expectedSeeks: []int{0, 0, 0, 0, 0}, }, { name: "RandomReadFlat", @@ -647,6 +658,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Random, testutil.Random, testutil.Random}, + expectedSeeks: []int{0, 1, 2, 2, 2}, }, { name: "RandomReadZonal", @@ -654,6 +666,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Random, testutil.Random, testutil.Random}, + expectedSeeks: []int{0, 1, 2, 2, 2}, }, } @@ -664,6 +677,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { t.rr.wrapped.isMRDInUse = false t.rr.wrapped.seeks = 0 t.rr.wrapped.readType = testutil.Sequential + t.rr.wrapped.expectedOffset = 0 t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) @@ -680,11 +694,55 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { assert.NoError(t.T(), err) assert.Equal(t.T(), tc.expectedReadTypes[i], t.rr.wrapped.readType) + assert.Equal(t.T(), int64(readRange[1]), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), uint64(tc.expectedSeeks[i]), t.rr.wrapped.seeks) } }) } } +// This test validates the bug fix where seeks are not updated correctly in case of zonal bucket random reads (b/410904634). +func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateZonalRandomReads() { + t.rr.wrapped.reader = nil + t.rr.wrapped.isMRDInUse = false + t.rr.wrapped.seeks = 0 + t.rr.wrapped.readType = testutil.Sequential + t.rr.wrapped.expectedOffset = 0 + t.rr.wrapped.totalReadBytes = 0 + t.object.Size = 20 * MiB + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}) + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + assert.Nil(t.T(), err, "Error in creating MRDWrapper") + t.rr.wrapped.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(&fake.FakeReader{ReadCloser: getReadCloser(testContent)}, nil).Twice() + buf := make([]byte, 3*MiB) + + // Sequential read #1 + _, err = t.rr.wrapped.ReadAt(t.rr.ctx, buf, 13*MiB) + assert.NoError(t.T(), err) + // Random read #1 + seeks := 1 + _, err = t.rr.wrapped.ReadAt(t.rr.ctx, buf, 12*MiB) + assert.NoError(t.T(), err) + assert.Equal(t.T(), uint64(seeks), t.rr.wrapped.seeks) + + readRanges := [][]int{{11 * MiB, 15 * MiB}, {12 * MiB, 14 * MiB}, {10 * MiB, 12 * MiB}, {9 * MiB, 11 * MiB}, {8 * MiB, 10 * MiB}} + // Series of random reads to check if seeks are updated correctly and MRD is invoked always + for _, readRange := range readRanges { + seeks++ + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)) + buf := make([]byte, readRange[1]-readRange[0]) + + _, err := t.rr.wrapped.ReadAt(t.rr.ctx, buf, int64(readRange[0])) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), testutil.Random, t.rr.wrapped.readType) + assert.Equal(t.T(), int64(readRange[1]), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), uint64(seeks), t.rr.wrapped.seeks) + } +} + func (t *RandomReaderStretchrTest) Test_ReadAt_MRDRead() { testCases := []struct { name string @@ -710,6 +768,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_MRDRead() { t.Run(tc.name, func() { t.rr.wrapped.reader = nil t.rr.wrapped.isMRDInUse = false + t.rr.wrapped.expectedOffset = 10 t.rr.wrapped.seeks = minSeeksForRandom + 1 t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) @@ -727,6 +786,9 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_MRDRead() { assert.Nil(t.T(), t.rr.wrapped.reader) assert.Equal(t.T(), tc.bytesToRead, objData.Size) assert.Equal(t.T(), testContent[tc.offset:tc.offset+tc.bytesToRead], objData.DataBuf[:objData.Size]) + if tc.bytesToRead != 0 { + assert.Equal(t.T(), int64(tc.offset+tc.bytesToRead), t.rr.wrapped.expectedOffset) + } }) } } @@ -767,6 +829,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadFull() { assert.NoError(t.T(), err) assert.Equal(t.T(), tc.dataSize, bytesRead) assert.Equal(t.T(), testContent[:tc.dataSize], buf[:bytesRead]) + assert.Equal(t.T(), int64(t.object.Size), t.rr.wrapped.expectedOffset) }) } } @@ -802,6 +865,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadChunk() { assert.NoError(t.T(), err) assert.Equal(t.T(), tc.end-tc.start, bytesRead) assert.Equal(t.T(), testContent[tc.start:tc.end], buf[:bytesRead]) + assert.Equal(t.T(), int64(tc.end), t.rr.wrapped.expectedOffset) } } diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index c7d32fde95..2c9c814a47 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -468,6 +468,7 @@ func (t *RandomReaderTest) UpgradeReadsToAverageSize() { t.rr.wrapped.cancel = func() {} t.rr.wrapped.start = 2 t.rr.wrapped.limit = 5 + t.rr.wrapped.expectedOffset = 2 // The bucket should be asked to read expectedBytesToRead bytes. r := strings.NewReader(strings.Repeat("x", expectedBytesToRead)) From 38fa18d04e7429db2a75c9e4a19a3e2be42fb25c Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 27 May 2025 11:11:17 +0000 Subject: [PATCH 0467/1298] set new defaults for write configs (#3309) * set defaults for global max blocks * set default for streaming writes * update tests to not use streaming writes flag * rebase changes * fix tests * remove hns flag as it is no longer used * fix only dir tests * fix readonly_creds tests * fix managed folder tests * fix UT * skip interrupt tests with zb as reads on unfinalized objects are not supported yet * update mounted directory tests script --- cfg/config.go | 8 +--- cfg/optimize.go | 1 + cfg/optimize_test.go | 2 + cfg/params.yaml | 12 ++--- cmd/config_validation_test.go | 5 +-- cmd/root_test.go | 45 +++++++++++++------ internal/gcsx/prefix_bucket.go | 7 ++- internal/gcsx/prefix_bucket_test.go | 2 +- internal/storage/bucket_handle.go | 1 + .../common_failure_test.go | 2 +- .../interrupt/interrupt_test.go | 8 ++-- .../local_file/setup_test.go | 9 ++-- .../managed_folders/view_permissions_test.go | 12 ++--- .../operations/operations_test.go | 3 ++ .../failure_during_file_sync_test.go | 24 +++++++--- .../run_tests_mounted_directory.sh | 4 +- .../stale_handle/setup_test.go | 4 +- .../stale_file_handle_local_file_test.go | 2 +- .../stale_file_handle_synced_file_test.go | 2 +- .../streaming_writes/buffer_size_test.go | 8 ++-- .../streaming_writes/setup_test.go | 6 +-- .../unfinalized_object_operations_test.go | 2 +- .../unfinalized_read_test.go | 2 +- .../write_large_files_test.go | 2 +- 24 files changed, 103 insertions(+), 70 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 1bdbbde45c..cd56f3b866 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -391,7 +391,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.BoolP("enable-streaming-writes", "", false, "Enables streaming uploads during write file operation.") + flagSet.BoolP("enable-streaming-writes", "", true, "Enables streaming uploads during write file operation.") flagSet.BoolP("experimental-enable-json-read", "", false, "By default, GCSFuse uses the GCS XML API to get and read objects. When this flag is specified, GCSFuse uses the GCS JSON API instead.\"") @@ -659,11 +659,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.IntP("write-global-max-blocks", "", -1, "Specifies the maximum number of blocks to be used by all files for streaming writes. The value should be >= 0 (1 block per file is not counted towards this limit) or -1 (for infinite blocks).") - - if err := flagSet.MarkHidden("write-global-max-blocks"); err != nil { - return err - } + flagSet.IntP("write-global-max-blocks", "", 4, "Specifies the maximum number of blocks available for streaming writes across all files. The value should be >= 0 or -1 (for infinite blocks). A value of 0 disables streaming writes.") flagSet.IntP("write-max-blocks-per-file", "", 1, "Specifies the maximum number of blocks to be used by a single file for streaming writes. The value should be >= 1 or -1 (for infinite blocks).") diff --git a/cfg/optimize.go b/cfg/optimize.go index fe88660ac7..682c3633ad 100644 --- a/cfg/optimize.go +++ b/cfg/optimize.go @@ -77,6 +77,7 @@ var ( { name: "high-performance", overrides: map[string]flagOverride{ + "write.global-max-blocks": {newValue: 1600}, "metadata-cache.negative-ttl-secs": {newValue: 0}, "metadata-cache.ttl-secs": {newValue: -1}, "metadata-cache.stat-cache-max-size-mb": {newValue: 1024}, diff --git a/cfg/optimize_test.go b/cfg/optimize_test.go index a215d0783c..272a581a01 100644 --- a/cfg/optimize_test.go +++ b/cfg/optimize_test.go @@ -392,6 +392,8 @@ func TestOptimize_Success(t *testing.T) { optimizedFlags, err := Optimize(cfg, isSet) require.NoError(t, err) + assert.True(t, isFlagPresent(optimizedFlags, "write.global-max-blocks")) + assert.EqualValues(t, 1600, cfg.Write.GlobalMaxBlocks) assert.True(t, isFlagPresent(optimizedFlags, "metadata-cache.negative-ttl-secs")) assert.EqualValues(t, 0, cfg.MetadataCache.NegativeTtlSecs) assert.EqualValues(t, -1, cfg.MetadataCache.TtlSecs) diff --git a/cfg/params.yaml b/cfg/params.yaml index 345bba3751..892cae639d 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -734,7 +734,7 @@ flag-name: "enable-streaming-writes" type: "bool" usage: "Enables streaming uploads during write file operation." - default: false + default: true hide-flag: false - config-path: "write.experimental-enable-rapid-appends" @@ -748,11 +748,11 @@ flag-name: "write-global-max-blocks" type: "int" usage: >- - Specifies the maximum number of blocks to be used by all files for - streaming writes. The value should be >= 0 (1 block per file is not counted - towards this limit) or -1 (for infinite blocks). - default: -1 - hide-flag: true + Specifies the maximum number of blocks available for streaming writes across all files. + The value should be >= 0 or -1 (for infinite blocks). + A value of 0 disables streaming writes. + default: 4 + hide-flag: false - config-path: "write.max-blocks-per-file" flag-name: "write-max-blocks-per-file" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index ff57147478..302245fdc3 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -16,7 +16,6 @@ package cmd import ( "fmt" - "math" "os" "path" "runtime" @@ -193,8 +192,8 @@ func TestValidateConfigFile_WriteConfig(t *testing.T) { Write: cfg.WriteConfig{ CreateEmptyFile: false, BlockSizeMb: 32 * util.MiB, - EnableStreamingWrites: false, - GlobalMaxBlocks: math.MaxInt64, + EnableStreamingWrites: true, + GlobalMaxBlocks: 4, MaxBlocksPerFile: 1}, }, }, diff --git a/cmd/root_test.go b/cmd/root_test.go index 3700f705bb..1c3cd1ca3d 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -16,7 +16,6 @@ package cmd import ( "fmt" - "math" "os" "path" "runtime" @@ -262,33 +261,33 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedWriteMaxBlocksPerFile int64 }{ { - name: "Test create-empty-file flag true.", - args: []string{"gcsfuse", "--create-empty-file=true", "abc", "pqr"}, + name: "Test create-empty-file flag true works when streaming writes are explicitly disabled.", + args: []string{"gcsfuse", "--create-empty-file=true", "--enable-streaming-writes=false", "abc", "pqr"}, expectedCreateEmptyFile: true, expectedEnableStreamingWrites: false, expectedExperimentalEnableRapidAppends: false, expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, { name: "Test create-empty-file flag false.", args: []string{"gcsfuse", "--create-empty-file=false", "abc", "pqr"}, expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: false, + expectedEnableStreamingWrites: true, expectedExperimentalEnableRapidAppends: false, expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, { name: "Test default flags.", args: []string{"gcsfuse", "abc", "pqr"}, expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: false, + expectedEnableStreamingWrites: true, expectedExperimentalEnableRapidAppends: false, expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, { @@ -298,7 +297,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedEnableStreamingWrites: true, expectedExperimentalEnableRapidAppends: false, expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, { @@ -308,17 +307,17 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedEnableStreamingWrites: false, expectedExperimentalEnableRapidAppends: false, expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, { name: "Test experimental-enable-rapid-appends flag true.", args: []string{"gcsfuse", "--write-experimental-enable-rapid-appends", "abc", "pqr"}, expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: false, + expectedEnableStreamingWrites: true, expectedExperimentalEnableRapidAppends: true, expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, { @@ -328,7 +327,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedEnableStreamingWrites: true, expectedExperimentalEnableRapidAppends: false, expectedWriteBlockSizeMB: 10 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, { @@ -348,9 +347,27 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedEnableStreamingWrites: true, expectedExperimentalEnableRapidAppends: false, expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: math.MaxInt64, + expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 10, }, + { + name: "Test high performance config values.", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "abc", "pqr"}, + expectedEnableStreamingWrites: true, + expectedExperimentalEnableRapidAppends: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 1600, + }, + { + name: "Test high performance config values with --write-global-max-blocks flag overriden.", + args: []string{"gcsfuse", "--write-global-max-blocks=2000", "--disable-autoconfig=false", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedExperimentalEnableRapidAppends: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 2000, + expectedWriteMaxBlocksPerFile: 1, + }, } for _, tc := range tests { diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index b1fdeadb31..0c96be7296 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -134,7 +134,12 @@ func (b *prefixBucket) FinalizeUpload(ctx context.Context, w gcs.Writer) (o *gcs } func (b *prefixBucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (o *gcs.MinObject, err error) { - return b.wrapped.FlushPendingWrites(ctx, w) + o, err = b.wrapped.FlushPendingWrites(ctx, w) + // Modify the returned object. + if o != nil { + o.Name = b.localName(o.Name) + } + return } func (b *prefixBucket) CopyObject( diff --git a/internal/gcsx/prefix_bucket_test.go b/internal/gcsx/prefix_bucket_test.go index aaa31b2cb7..fc63c823d0 100644 --- a/internal/gcsx/prefix_bucket_test.go +++ b/internal/gcsx/prefix_bucket_test.go @@ -329,7 +329,6 @@ func (t *PrefixBucketTest) TestCreateObjectChunkWriterAndFinalizeUpload() { assert.NoError(t.T(), err) assert.Equal(t.T(), suffix, o.Name) - //assert.Equal(t.T(), "en-GB", o.ContentLanguage) assert.Equal(t.T(), "gzip", o.ContentEncoding) // Read it through the back door. actual, err := storageutil.ReadObject(t.ctx, t.wrapped, t.prefix+suffix) @@ -358,6 +357,7 @@ func (t *PrefixBucketTest) TestCreateObjectChunkWriterAndFlushPendingWrites() { assert.NoError(t.T(), err) assert.EqualValues(t.T(), int64(len(content)), o.Size) + assert.Equal(t.T(), suffix, o.Name) // Read it through the back door. actual, err := storageutil.ReadObject(t.ctx, t.wrapped, t.prefix+suffix) assert.Equal(t.T(), nil, err) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index d6213de7f0..3ed5e92f57 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -228,6 +228,7 @@ func (bh *bucketHandle) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cr wc := &ObjectWriter{obj.NewWriter(ctx)} wc.ChunkSize = chunkSize wc.Writer = storageutil.SetAttrsInWriter(wc.Writer, req) + wc.ChunkTransferTimeout = time.Duration(req.ChunkTransferTimeoutSecs) * time.Second if callBack == nil { callBack = func(bytesUploadedSoFar int64) { logger.Tracef("gcs: Req %#16x: -- UploadBlock(%q): %20v bytes uploaded so far", ctx.Value(gcs.ReqIdField), req.Name, bytesUploadedSoFar) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go index 4150f8c1b3..1fe479a389 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go @@ -61,7 +61,7 @@ type gcsObjectValidator interface { // ////////////////////////////////////////////////////////////////////// func (t *commonFailureTestSuite) SetupSuite() { - t.flags = []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1"} + t.flags = []string{"--write-block-size-mb=1", "--write-max-blocks-per-file=1"} // Generate 5 MB random data. var err error t.data, err = operations.GenerateRandomData(5 * operations.MiB) diff --git a/tools/integration_tests/interrupt/interrupt_test.go b/tools/integration_tests/interrupt/interrupt_test.go index a80a5c82f2..497d6a6745 100644 --- a/tools/integration_tests/interrupt/interrupt_test.go +++ b/tools/integration_tests/interrupt/interrupt_test.go @@ -62,12 +62,12 @@ func TestMain(m *testing.M) { } yamlContent2 := map[string]interface{}{} // test default flags := [][]string{ - {"--implicit-dirs=true"}, - {"--config-file=" + setup.YAMLConfigFile(yamlContent1, "ignore_interrupts.yaml")}, - {"--config-file=" + setup.YAMLConfigFile(yamlContent2, "default_ignore_interrupts.yaml")}} + {"--implicit-dirs=true", "--enable-streaming-writes=false"}, + {"--config-file=" + setup.YAMLConfigFile(yamlContent1, "ignore_interrupts.yaml"), "--enable-streaming-writes=false"}, + {"--config-file=" + setup.YAMLConfigFile(yamlContent2, "default_ignore_interrupts.yaml"), "--enable-streaming-writes=false"}} // TODO(b/417136852): Enable this test for Zonal Bucket also once read start working. if !setup.IsZonalBucketRun() { - flags = append(flags, []string{"--enable-streaming-writes=true", "--implicit-dirs=true"}) + flags = append(flags, []string{"--enable-streaming-writes=true"}) } successCode := static_mounting.RunTests(flags, m) diff --git a/tools/integration_tests/local_file/setup_test.go b/tools/integration_tests/local_file/setup_test.go index 62d552c8be..835c262de0 100644 --- a/tools/integration_tests/local_file/setup_test.go +++ b/tools/integration_tests/local_file/setup_test.go @@ -64,13 +64,10 @@ func TestMain(m *testing.M) { // Set up flags to run tests on local file test suite. // Not setting config file explicitly with 'create-empty-file: false' as it is default. + // Running these tests with streaming writes disabled because local file tests are already running in streaming_writes test package. flagsSet := [][]string{ - {"--implicit-dirs=true", "--rename-dir-limit=3"}, - {"--implicit-dirs=false", "--rename-dir-limit=3"}} - - if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(ctx, storageClient); err == nil { - flagsSet = append(flagsSet, hnsFlagSet) - } + {"--implicit-dirs=true", "--rename-dir-limit=3", "--enable-streaming-writes=false"}, + {"--implicit-dirs=false", "--rename-dir-limit=3", "--enable-streaming-writes=false"}} if !testing.Short() { setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc") diff --git a/tools/integration_tests/managed_folders/view_permissions_test.go b/tools/integration_tests/managed_folders/view_permissions_test.go index 7f84bd1a97..88d68cb467 100644 --- a/tools/integration_tests/managed_folders/view_permissions_test.go +++ b/tools/integration_tests/managed_folders/view_permissions_test.go @@ -58,14 +58,14 @@ func (s *managedFoldersViewPermission) TestListNonEmptyManagedFolders(t *testing func (s *managedFoldersViewPermission) TestCreateObjectInManagedFolder(t *testing.T) { filePath := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder2, DestFile) + + // The error must happen either at file creation or file handle close. file, err := os.Create(filePath) - if err != nil { - t.Errorf("Error in creating file locally.") - } - t.Cleanup(func() { + if file != nil { err = file.Close() - operations.CheckErrorForReadOnlyFileSystem(t, err) - }) + } + + operations.CheckErrorForReadOnlyFileSystem(t, err) } func (s *managedFoldersViewPermission) TestDeleteObjectFromManagedFolder(t *testing.T) { diff --git a/tools/integration_tests/operations/operations_test.go b/tools/integration_tests/operations/operations_test.go index 9446f697dd..e92b44c198 100644 --- a/tools/integration_tests/operations/operations_test.go +++ b/tools/integration_tests/operations/operations_test.go @@ -125,6 +125,9 @@ func createMountConfigsAndEquivalentFlags() (flags [][]string) { "metadata-cache": map[string]interface{}{ "ttl-secs": 0, }, + "write": map[string]interface{}{ + "enable-streaming-writes": false, + }, } filePath3 := setup.YAMLConfigFile(mountConfig3, "config3.yaml") flags = append(flags, []string{"--config-file=" + filePath3}) diff --git a/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go b/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go index 8977a49d8a..86fe005ccf 100644 --- a/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go +++ b/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go @@ -23,6 +23,8 @@ import ( "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) //////////////////////////////////////////////////////////////////////// @@ -68,8 +70,13 @@ func (r *readOnlyCredsTest) assertFileSyncFailsWithPermissionError(fh *os.File, func (r *readOnlyCredsTest) TestEmptyCreateFileFails_FailedFileNotInListing(t *testing.T) { filePath := path.Join(r.testDirPath, testFileName) - fh := operations.CreateFile(filePath, operations.FilePermission_0777, t) - r.assertFileSyncFailsWithPermissionError(fh, t) + fh, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, operations.FilePermission_0777) + if setup.IsZonalBucketRun() { + require.Error(t, err) + assert.True(t, strings.Contains(err.Error(), permissionDeniedError)) + } else { + r.assertFileSyncFailsWithPermissionError(fh, t) + } r.assertFailedFileNotInListing(t) } @@ -77,10 +84,15 @@ func (r *readOnlyCredsTest) TestEmptyCreateFileFails_FailedFileNotInListing(t *t func (r *readOnlyCredsTest) TestNonEmptyCreateFileFails_FailedFileNotInListing(t *testing.T) { filePath := path.Join(r.testDirPath, testFileName) - fh := operations.CreateFile(filePath, operations.FilePermission_0777, t) - operations.WriteWithoutClose(fh, content, t) - operations.WriteWithoutClose(fh, content, t) - r.assertFileSyncFailsWithPermissionError(fh, t) + fh, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, operations.FilePermission_0777) + if setup.IsZonalBucketRun() { + require.Error(t, err) + assert.True(t, strings.Contains(err.Error(), permissionDeniedError)) + } else { + operations.WriteWithoutClose(fh, content, t) + operations.WriteWithoutClose(fh, content, t) + r.assertFileSyncFailsWithPermissionError(fh, t) + } r.assertFailedFileNotInListing(t) } diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index 1b45855d40..c38246bfb8 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -618,12 +618,12 @@ sudo umount $MOUNT_DIR # Test package: streaming_writes # Run streaming_writes tests. -gcsfuse --rename-dir-limit=3 --enable-streaming-writes=true --write-block-size-mb=1 --write-max-blocks-per-file=2 $TEST_BUCKET_NAME $MOUNT_DIR +gcsfuse --rename-dir-limit=3 --write-block-size-mb=1 --write-max-blocks-per-file=2 --write-global-max-blocks=-1 $TEST_BUCKET_NAME $MOUNT_DIR GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/streaming_writes/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run write_large_files tests with streaming writes enabled. -gcsfuse --enable-streaming-writes=true $TEST_BUCKET_NAME $MOUNT_DIR +gcsfuse $TEST_BUCKET_NAME $MOUNT_DIR GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/write_large_files/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR diff --git a/tools/integration_tests/stale_handle/setup_test.go b/tools/integration_tests/stale_handle/setup_test.go index 5bc945f991..0c9137f10c 100644 --- a/tools/integration_tests/stale_handle/setup_test.go +++ b/tools/integration_tests/stale_handle/setup_test.go @@ -70,8 +70,8 @@ func TestMain(m *testing.M) { rootDir = setup.MntDir() flagsSet = [][]string{ - {"--metadata-cache-ttl-secs=0"}, - {"--metadata-cache-ttl-secs=0", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=1"}, + {"--metadata-cache-ttl-secs=0", "--enable-streaming-writes=false"}, + {"--metadata-cache-ttl-secs=0", "--write-block-size-mb=1", "--write-max-blocks-per-file=1"}, } // Run all tests with GRPC. setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "--client-protocol=grpc", "") diff --git a/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go index a70d081659..892f7032c0 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go @@ -57,7 +57,7 @@ func TestStaleFileHandleLocalFileTest(t *testing.T) { for _, flags := range flagsSet { s := new(staleFileHandleLocalFile) s.flags = flags - s.isStreamingWritesEnabled = slices.Contains(s.flags, "--enable-streaming-writes=true") + s.isStreamingWritesEnabled = !slices.Contains(s.flags, "--enable-streaming-writes=false") suite.Run(t, s) } } diff --git a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go index aa6a789929..52b6a41911 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go @@ -124,7 +124,7 @@ func TestStaleFileHandleEmptyGcsFileTest(t *testing.T) { for _, flags := range flagsSet { s := new(staleFileHandleEmptyGcsFile) s.flags = flags - s.isStreamingWritesEnabled = slices.Contains(s.flags, "--enable-streaming-writes=true") + s.isStreamingWritesEnabled = !slices.Contains(s.flags, "--enable-streaming-writes=false") suite.Run(t, s) } } diff --git a/tools/integration_tests/streaming_writes/buffer_size_test.go b/tools/integration_tests/streaming_writes/buffer_size_test.go index b720cb3737..f97bd1097a 100644 --- a/tools/integration_tests/streaming_writes/buffer_size_test.go +++ b/tools/integration_tests/streaming_writes/buffer_size_test.go @@ -35,23 +35,23 @@ func TestWritesWithDifferentConfig(t *testing.T) { }{ { name: "BlockSizeGreaterThanFileSize", - flags: []string{"--enable-streaming-writes=true", "--write-block-size-mb=5", "--write-max-blocks-per-file=2"}, + flags: []string{"--write-block-size-mb=5", "--write-max-blocks-per-file=2"}, fileSize: 2 * 1024 * 1024, }, { name: "BlockSizeLessThanFileSize", - flags: []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=20"}, + flags: []string{"--write-block-size-mb=1", "--write-max-blocks-per-file=20"}, fileSize: 5 * 1024 * 1024, }, { // BlockSize*num_blocks < fileSize name: "NumberOfBlocksLessThanFileSize", - flags: []string{"--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"}, + flags: []string{"--write-block-size-mb=1", "--write-max-blocks-per-file=2"}, fileSize: 10 * 1024 * 1024, }, { name: "BlockSizeEqualToFileSize", - flags: []string{"--enable-streaming-writes=true", "--write-block-size-mb=5", "--write-max-blocks-per-file=2"}, + flags: []string{"--write-block-size-mb=5", "--write-max-blocks-per-file=2"}, fileSize: 5 * 1024 * 1024, }, } diff --git a/tools/integration_tests/streaming_writes/setup_test.go b/tools/integration_tests/streaming_writes/setup_test.go index 5b96493a7b..5f27f69aea 100644 --- a/tools/integration_tests/streaming_writes/setup_test.go +++ b/tools/integration_tests/streaming_writes/setup_test.go @@ -66,9 +66,9 @@ func TestMain(m *testing.M) { // Define flag set to run the tests. flagsSet := [][]string{ - {"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2", "--write-global-max-blocks=0"}, - {"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2", "--client-protocol=grpc"}, - {"--rename-dir-limit=3", "--enable-streaming-writes=true", "--write-block-size-mb=1", "--write-max-blocks-per-file=2"}, + {"--rename-dir-limit=3", "--write-block-size-mb=1", "--write-max-blocks-per-file=2", "--write-global-max-blocks=0"}, + {"--rename-dir-limit=3", "--write-block-size-mb=1", "--write-max-blocks-per-file=2", "--client-protocol=grpc", "--write-global-max-blocks=-1"}, + {"--rename-dir-limit=3", "--write-block-size-mb=1", "--write-max-blocks-per-file=2", "--write-global-max-blocks=-1"}, } log.Println("Running static mounting tests...") diff --git a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go index 69401971e8..e0f603b7fe 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go @@ -160,7 +160,7 @@ func TestUnfinalizedObjectOperationTest(t *testing.T) { // Define flag set to run the tests. flagsSet := [][]string{ - {"--enable-streaming-writes=true", "--metadata-cache-ttl-secs=0"}, + {"--metadata-cache-ttl-secs=0"}, } // Run tests. diff --git a/tools/integration_tests/unfinalized_object/unfinalized_read_test.go b/tools/integration_tests/unfinalized_object/unfinalized_read_test.go index 1096d6f392..873e3b2baf 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_read_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_read_test.go @@ -93,7 +93,7 @@ func TestUnfinalizedObjectReadTest(t *testing.T) { // Define flag set to run the tests. flagsSet := [][]string{ - {"--enable-streaming-writes=true", "--metadata-cache-ttl-secs=-1"}, + {"--metadata-cache-ttl-secs=-1"}, } // Run tests. diff --git a/tools/integration_tests/write_large_files/write_large_files_test.go b/tools/integration_tests/write_large_files/write_large_files_test.go index a286c1b3bc..c2f733e474 100644 --- a/tools/integration_tests/write_large_files/write_large_files_test.go +++ b/tools/integration_tests/write_large_files/write_large_files_test.go @@ -38,7 +38,7 @@ func TestMain(m *testing.M) { // with this config, we are giving 2 blocks to 2 files and 1 block to other file. flags := [][]string{ {"--enable-streaming-writes=false"}, - {"--enable-streaming-writes=true", "--write-max-blocks-per-file=2", "--write-global-max-blocks=2"}} + {"--write-max-blocks-per-file=2", "--write-global-max-blocks=2"}} setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() From fade2073173ab20c03baebe865ab899f6e1c6fce Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 28 May 2025 10:48:58 +0530 Subject: [PATCH 0468/1298] [Random Reader Refactoring] Adding inactive reader support (#3359) * adding inactive reader support * create config * making inactive reader public for testing * review comment --- internal/gcsx/client_readers/gcs_reader.go | 16 +++- .../gcsx/client_readers/gcs_reader_test.go | 88 ++++++++++++++++++- internal/gcsx/client_readers/range_reader.go | 40 ++++++--- .../gcsx/client_readers/range_reader_test.go | 4 +- internal/gcsx/inactive_timeout_reader.go | 22 ++--- internal/gcsx/inactive_timeout_reader_test.go | 22 ++--- internal/gcsx/random_reader_stretchr_test.go | 4 +- internal/gcsx/read_manager/read_manager.go | 11 ++- 8 files changed, 160 insertions(+), 47 deletions(-) diff --git a/internal/gcsx/client_readers/gcs_reader.go b/internal/gcsx/client_readers/gcs_reader.go index 4b738d9dca..11af1577d8 100644 --- a/internal/gcsx/client_readers/gcs_reader.go +++ b/internal/gcsx/client_readers/gcs_reader.go @@ -20,6 +20,7 @@ import ( "fmt" "io" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" @@ -71,13 +72,20 @@ type GCSReader struct { totalReadBytes uint64 } -func NewGCSReader(obj *gcs.MinObject, bucket gcs.Bucket, metricHandle common.MetricHandle, mrdWrapper *gcsx.MultiRangeDownloaderWrapper, sequentialReadSizeMb int32) *GCSReader { +type GCSReaderConfig struct { + MetricHandle common.MetricHandle + MrdWrapper *gcsx.MultiRangeDownloaderWrapper + SequentialReadSizeMb int32 + ReadConfig *cfg.ReadConfig +} + +func NewGCSReader(obj *gcs.MinObject, bucket gcs.Bucket, config *GCSReaderConfig) *GCSReader { return &GCSReader{ object: obj, bucket: bucket, - sequentialReadSizeMb: sequentialReadSizeMb, - rangeReader: NewRangeReader(obj, bucket, metricHandle), - mrr: NewMultiRangeReader(obj, metricHandle, mrdWrapper), + sequentialReadSizeMb: config.SequentialReadSizeMb, + rangeReader: NewRangeReader(obj, bucket, config.ReadConfig, config.MetricHandle), + mrr: NewMultiRangeReader(obj, config.MetricHandle, config.MrdWrapper), readType: util.Sequential, } } diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go index 669d37641d..b0913c64b5 100644 --- a/internal/gcsx/client_readers/gcs_reader_test.go +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -21,6 +21,7 @@ import ( "testing" "time" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" @@ -73,7 +74,12 @@ func (t *gcsReaderTest) SetupTest() { Generation: 1234, } t.mockBucket = new(storage.TestifyMockBucket) - t.gcsReader = NewGCSReader(t.object, t.mockBucket, common.NewNoopMetrics(), nil, sequentialReadSizeInMb) + t.gcsReader = NewGCSReader(t.object, t.mockBucket, &GCSReaderConfig{ + MetricHandle: common.NewNoopMetrics(), + MrdWrapper: nil, + SequentialReadSizeMb: sequentialReadSizeInMb, + ReadConfig: nil, + }) t.ctx = context.Background() } @@ -92,7 +98,12 @@ func (t *gcsReaderTest) Test_NewGCSReader() { Generation: 4321, } - gcsReader := NewGCSReader(object, t.mockBucket, common.NewNoopMetrics(), nil, 200) + gcsReader := NewGCSReader(object, t.mockBucket, &GCSReaderConfig{ + MetricHandle: common.NewNoopMetrics(), + MrdWrapper: nil, + SequentialReadSizeMb: 200, + ReadConfig: nil, + }) assert.Equal(t.T(), object, gcsReader.object) assert.Equal(t.T(), t.mockBucket, gcsReader.bucket) @@ -487,3 +498,76 @@ func (t *gcsReaderTest) Test_ReadInfo_Random() { }) } } + +// Validates: +// 1. No change in ReadAt behavior based inactiveStreamTimeout readConfig. +// 2. Valid timeout readConfig creates inactiveTimeoutReader instance of storage.Reader. +func (t *gcsReaderTest) Test_ReadAt_WithAndWithoutReadConfig() { + testCases := []struct { + name string + config *cfg.ReadConfig + expectInactiveTimeoutReader bool + }{ + { + name: "WithoutReadConfig", + config: nil, + expectInactiveTimeoutReader: false, + }, + { + name: "WithReadConfigAndZeroTimeout", + config: &cfg.ReadConfig{InactiveStreamTimeout: 0}, + expectInactiveTimeoutReader: false, + }, + { + name: "WithReadConfigAndPositiveTimeout", + config: &cfg.ReadConfig{InactiveStreamTimeout: 10 * time.Millisecond}, + expectInactiveTimeoutReader: true, + }, + } + + objectSize := uint64(20) + readOffset := int64(0) + readLength := 10 // Reading only 10 bytes from the complete object reader. + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.SetupTest() // Resets mockBucket, rr, etc. for each sub-test + defer t.TearDownTest() + + t.gcsReader.rangeReader.readConfig = tc.config + t.gcsReader.rangeReader.reader = nil // Ensure startRead path is taken in ReadAt + t.object.Size = objectSize + // Prepare fake content for the GCS object. + // startRead will attempt to read the entire object [0, objectSize) + // because objectSize is small compared to typical SequentialReadSizeMb. + fakeReaderContent := testUtil.GenerateRandomBytes(int(t.object.Size)) + rc := &fake.FakeReader{ReadCloser: getReadCloser(fakeReaderContent)} + expectedReadObjectRequest := &gcs.ReadObjectRequest{ + Name: t.object.Name, + Generation: t.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(readOffset), // Read from the beginning + Limit: t.object.Size, // getReadInfo will determine this limit + }, + ReadCompressed: t.object.HasContentEncodingGzip(), + ReadHandle: nil, // No existing read handle + } + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, expectedReadObjectRequest).Return(rc, nil).Once() + // BucketType is called by ReadAt -> getReadInfo -> readerType to determine reader strategy. + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: false}).Once() + + objectData, err := t.readAt(readOffset, int64(readLength)) + + t.mockBucket.AssertExpectations(t.T()) + assert.NoError(t.T(), err) + assert.Equal(t.T(), readLength, objectData.Size) + assert.Equal(t.T(), fakeReaderContent[:readLength], objectData.DataBuf[:objectData.Size]) // Ensure buffer is populated correctly + assert.NotNil(t.T(), t.gcsReader.rangeReader.reader, "Reader should be active as partial data read from the requested range.") + assert.NotNil(t.T(), t.gcsReader.rangeReader.cancel) + assert.Equal(t.T(), int64(readLength), t.gcsReader.rangeReader.start) + assert.Equal(t.T(), int64(t.object.Size), t.gcsReader.rangeReader.limit) + _, isInactiveTimeoutReader := t.gcsReader.rangeReader.reader.(*gcsx.InactiveTimeoutReader) + assert.Equal(t.T(), tc.expectInactiveTimeoutReader, isInactiveTimeoutReader) + }) + } +} diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index a6497ffb5b..e9bd80e625 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -21,6 +21,7 @@ import ( "io" "math" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" @@ -61,14 +62,16 @@ type RangeReader struct { cancel func() readType string + readConfig *cfg.ReadConfig metricHandle common.MetricHandle } -func NewRangeReader(object *gcs.MinObject, bucket gcs.Bucket, metricHandle common.MetricHandle) *RangeReader { +func NewRangeReader(object *gcs.MinObject, bucket gcs.Bucket, readConfig *cfg.ReadConfig, metricHandle common.MetricHandle) *RangeReader { return &RangeReader{ object: object, bucket: bucket, metricHandle: metricHandle, + readConfig: readConfig, start: -1, limit: -1, } @@ -222,22 +225,36 @@ func (rr *RangeReader) readFull(ctx context.Context, p []byte) (int, error) { // Ensure that rr.reader is set up for a range for which [start, start+size) is // a prefix. Irrespective of the size requested, we try to fetch more data -// from GCS defined by sequentialReadSizeMb flag to serve future read requests. +// from GCS defined by SequentialReadSizeMb flag to serve future read requests. func (rr *RangeReader) startRead(start int64, end int64) error { ctx, cancel := context.WithCancel(context.Background()) + var err error - rc, err := rr.bucket.NewReaderWithReadHandle( - ctx, - &gcs.ReadObjectRequest{ - Name: rr.object.Name, - Generation: rr.object.Generation, - Range: &gcs.ByteRange{ + if rr.readConfig != nil && rr.readConfig.InactiveStreamTimeout > 0 { + rr.reader, err = gcsx.NewInactiveTimeoutReader( + ctx, + rr.bucket, + rr.object, + rr.readHandle, + gcs.ByteRange{ Start: uint64(start), Limit: uint64(end), }, - ReadCompressed: rr.object.HasContentEncodingGzip(), - ReadHandle: rr.readHandle, - }) + rr.readConfig.InactiveStreamTimeout) + } else { + rr.reader, err = rr.bucket.NewReaderWithReadHandle( + ctx, + &gcs.ReadObjectRequest{ + Name: rr.object.Name, + Generation: rr.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(start), + Limit: uint64(end), + }, + ReadCompressed: rr.object.HasContentEncodingGzip(), + ReadHandle: rr.readHandle, + }) + } // If a file handle is open locally, but the corresponding object doesn't exist // in GCS, it indicates a file clobbering scenario. This likely occurred because: @@ -258,7 +275,6 @@ func (rr *RangeReader) startRead(start int64, end int64) error { return err } - rr.reader = rc rr.cancel = cancel rr.start = start rr.limit = end diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go index 8076fab981..59d82ac2f7 100644 --- a/internal/gcsx/client_readers/range_reader_test.go +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -64,7 +64,7 @@ func (t *rangeReaderTest) SetupTest() { Generation: 1234, } t.mockBucket = new(storage.TestifyMockBucket) - t.rangeReader = NewRangeReader(t.object, t.mockBucket, common.NewNoopMetrics()) + t.rangeReader = NewRangeReader(t.object, t.mockBucket, nil, common.NewNoopMetrics()) t.ctx = context.Background() } @@ -146,7 +146,7 @@ func (t *rangeReaderTest) Test_NewRangeReader() { Generation: 4321, } - reader := NewRangeReader(object, t.mockBucket, common.NewNoopMetrics()) + reader := NewRangeReader(object, t.mockBucket, nil, common.NewNoopMetrics()) assert.Equal(t.T(), object, reader.object) assert.Equal(t.T(), t.mockBucket, reader.bucket) diff --git a/internal/gcsx/inactive_timeout_reader.go b/internal/gcsx/inactive_timeout_reader.go index 2adc61d00d..7ac73b1133 100644 --- a/internal/gcsx/inactive_timeout_reader.go +++ b/internal/gcsx/inactive_timeout_reader.go @@ -27,7 +27,7 @@ import ( "golang.org/x/net/context" ) -// inactiveTimeoutReader is a wrapper over gcs.StorageReader that automatically +// InactiveTimeoutReader is a wrapper over gcs.StorageReader that automatically // closes the wrapped GCS reader connection after a specified period of // inactivity (timeout). When a read operation is attempted on an inactive // (closed) reader, it automatically attempts to reconnect using the last known @@ -46,7 +46,7 @@ import ( // relative to the background routine wake-up cycle. // - Thread Safety: The reader is safe for concurrent use by multiple goroutines, // protected by an internal mutex. -type inactiveTimeoutReader struct { +type InactiveTimeoutReader struct { object *gcs.MinObject bucket gcs.Bucket @@ -93,12 +93,12 @@ func NewInactiveTimeoutReaderWithClock(ctx context.Context, bucket gcs.Bucket, o return nil, ErrZeroInactivityTimeout } - itr := &inactiveTimeoutReader{ + itr := &InactiveTimeoutReader{ object: object, bucket: bucket, reqRange: byteRange, readHandle: readHandle, - mu: locker.New("inactiveTimeoutReader: "+object.Name, func() {}), + mu: locker.New("InactiveTimeoutReader: "+object.Name, func() {}), isActive: false, } itr.ctx, itr.cancel = context.WithCancel(ctx) @@ -115,7 +115,7 @@ func NewInactiveTimeoutReaderWithClock(ctx context.Context, bucket gcs.Bucket, o } // createGCSReader is a helper method to create the underlined reader from itr.start + itr.seen offset. -func (itr *inactiveTimeoutReader) createGCSReader() (gcs.StorageReader, error) { +func (itr *InactiveTimeoutReader) createGCSReader() (gcs.StorageReader, error) { reader, err := itr.bucket.NewReaderWithReadHandle( itr.ctx, &gcs.ReadObjectRequest{ @@ -145,7 +145,7 @@ func (itr *inactiveTimeoutReader) createGCSReader() (gcs.StorageReader, error) { // // Calling Read() after explicitly calling Close() is not supported and will // lead to undefined behavior. -func (itr *inactiveTimeoutReader) Read(p []byte) (n int, err error) { +func (itr *InactiveTimeoutReader) Read(p []byte) (n int, err error) { itr.mu.Lock() defer itr.mu.Unlock() @@ -165,7 +165,7 @@ func (itr *inactiveTimeoutReader) Read(p []byte) (n int, err error) { // Close explicitly closes the underlying gcs.StorageReader if it's currently open. // It also signals the background monitor goroutine to stop. // Returns an error if closing the underlying reader fails. -func (itr *inactiveTimeoutReader) Close() (err error) { +func (itr *InactiveTimeoutReader) Close() (err error) { itr.mu.Lock() defer itr.mu.Unlock() @@ -187,7 +187,7 @@ func (itr *inactiveTimeoutReader) Close() (err error) { // ReadHandle returns the read handle associated with the underlying GCS reader. // If the reader has been closed due to inactivity, it returns the handle // stored from the last active reader. -func (itr *inactiveTimeoutReader) ReadHandle() (rh storagev2.ReadHandle) { +func (itr *InactiveTimeoutReader) ReadHandle() (rh storagev2.ReadHandle) { itr.mu.Lock() defer itr.mu.Unlock() @@ -199,7 +199,7 @@ func (itr *inactiveTimeoutReader) ReadHandle() (rh storagev2.ReadHandle) { } // monitor runs in a background goroutine, and checks for inactivity. -func (itr *inactiveTimeoutReader) monitor(clock clock.Clock, timeout time.Duration) { +func (itr *InactiveTimeoutReader) monitor(clock clock.Clock, timeout time.Duration) { timer := clock.After(timeout) for { select { @@ -217,7 +217,7 @@ func (itr *inactiveTimeoutReader) monitor(clock clock.Clock, timeout time.Durati // If the reader was marked as active since the last check, it resets the // activity flag. If the reader was inactive, it closes the underlying GCS reader. // It always returns a new timer channel for the next fire. -func (itr *inactiveTimeoutReader) handleTimeout() { +func (itr *InactiveTimeoutReader) handleTimeout() { itr.mu.Lock() defer itr.mu.Unlock() @@ -230,7 +230,7 @@ func (itr *inactiveTimeoutReader) handleTimeout() { // closeGCSReader closes the wrapped gcsReader, itr.mu.Lock() should be taken // before calling this. -func (itr *inactiveTimeoutReader) closeGCSReader() { +func (itr *InactiveTimeoutReader) closeGCSReader() { if itr.gcsReader == nil { return } diff --git a/internal/gcsx/inactive_timeout_reader_test.go b/internal/gcsx/inactive_timeout_reader_test.go index 10e9c62f33..d74f4d5e4b 100644 --- a/internal/gcsx/inactive_timeout_reader_test.go +++ b/internal/gcsx/inactive_timeout_reader_test.go @@ -158,7 +158,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_NoReadCloserWithinTimeout() { n2, err2 := s.reader.Read(buf) - inactiveReader := s.reader.(*inactiveTimeoutReader) + inactiveReader := s.reader.(*InactiveTimeoutReader) s.True(inactiveReader.isActive) s.NoError(err2) s.Equal(6, n2) @@ -189,7 +189,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_ReconnectFails() { s.simulatedClock.AdvanceTime(s.timeout + time.Millisecond) // Wait for the monitor routine to make the read inactive. require.Eventually(s.T(), func() bool { - rr := s.reader.(*inactiveTimeoutReader) + rr := s.reader.(*InactiveTimeoutReader) rr.mu.Lock() defer rr.mu.Unlock() return !rr.isActive @@ -198,7 +198,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_ReconnectFails() { s.simulatedClock.AdvanceTime(s.timeout + time.Millisecond) // Wait for the monitor routine to close the wrapped reader. require.Eventually(s.T(), func() bool { - rr := s.reader.(*inactiveTimeoutReader) + rr := s.reader.(*InactiveTimeoutReader) rr.mu.Lock() defer rr.mu.Unlock() return (rr.gcsReader == nil) @@ -231,7 +231,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_TimeoutAndSuccessfulReconnect s.simulatedClock.AdvanceTime(s.timeout + time.Millisecond) // Wait for the monitor routine to make the read inactive. require.Eventually(s.T(), func() bool { - rr := s.reader.(*inactiveTimeoutReader) + rr := s.reader.(*InactiveTimeoutReader) rr.mu.Lock() defer rr.mu.Unlock() return !rr.isActive @@ -240,7 +240,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_TimeoutAndSuccessfulReconnect s.simulatedClock.AdvanceTime(s.timeout + time.Millisecond) // Wait for the monitor routine to close the wrapped reader. require.Eventually(s.T(), func() bool { - rr := s.reader.(*inactiveTimeoutReader) + rr := s.reader.(*InactiveTimeoutReader) rr.mu.Lock() defer rr.mu.Unlock() return (rr.gcsReader == nil) @@ -276,7 +276,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Close_ExplicitClose() { err := s.reader.Close() s.NoError(err) - s.Nil(s.reader.(*inactiveTimeoutReader).gcsReader) + s.Nil(s.reader.(*InactiveTimeoutReader).gcsReader) s.reader = nil // Prevent TearDownTest from closing again } @@ -287,7 +287,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_handleTimeout_InactiveClose() { s.setupReader(0) // Sets up s.reader and s.initialFakeReader expectedHandleAfterClose := []byte("handle-after-close") s.initialFakeReader.Handle = expectedHandleAfterClose - itr := s.reader.(*inactiveTimeoutReader) + itr := s.reader.(*InactiveTimeoutReader) itr.isActive = false // Simulate inactivity itr.handleTimeout() @@ -304,7 +304,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_handleTimeout_ActiveBecomeInactive s.setupReader(0) // Sets up s.reader and s.initialFakeReader expectedHandleAfterClose := []byte("handle-after-close") s.initialFakeReader.Handle = expectedHandleAfterClose - itr := s.reader.(*inactiveTimeoutReader) + itr := s.reader.(*InactiveTimeoutReader) itr.isActive = true itr.handleTimeout() @@ -318,7 +318,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_closeGCSReader_NilReader() { s.timeout = 50 * time.Millisecond s.readHandle = []byte("handle-before-close") s.setupReader(0) // Sets up s.reader and s.initialFakeReader - itr := s.reader.(*inactiveTimeoutReader) + itr := s.reader.(*InactiveTimeoutReader) itr.gcsReader = nil itr.closeGCSReader() @@ -334,7 +334,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_closeGCSReader_NonNilReader() { s.setupReader(0) // Sets up s.reader and s.initialFakeReader expectedHandleAfterClose := []byte("handle-after-close") s.initialFakeReader.Handle = expectedHandleAfterClose - itr := s.reader.(*inactiveTimeoutReader) + itr := s.reader.(*InactiveTimeoutReader) itr.closeGCSReader() @@ -370,7 +370,7 @@ func (s *InactiveTimeoutReaderTestSuite) TestRaceCondition() { go func() { defer wg.Done() for i := 0; i < 1000; i++ { - s.reader.(*inactiveTimeoutReader).handleTimeout() + s.reader.(*InactiveTimeoutReader).handleTimeout() } }() diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 918aebe65f..eb53f8848f 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -930,7 +930,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ValidateTimeout // Validates: // 1. No change in ReadAt behavior based inactiveStreamTimeout config. -// 2. Valid timeout config creates inactiveTimeoutReader instance of storage.Reader. +// 2. Valid timeout config creates InactiveTimeoutReader instance of storage.Reader. func (t *RandomReaderStretchrTest) Test_ReadAt_WithAndWithoutReadConfig() { testCases := []struct { name string @@ -996,7 +996,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_WithAndWithoutReadConfig() { assert.NotNil(t.T(), t.rr.wrapped.cancel) assert.Equal(t.T(), int64(readLength), t.rr.wrapped.start) assert.Equal(t.T(), int64(t.object.Size), t.rr.wrapped.limit) - _, isInactiveTimeoutReader := t.rr.wrapped.reader.(*inactiveTimeoutReader) + _, isInactiveTimeoutReader := t.rr.wrapped.reader.(*InactiveTimeoutReader) assert.Equal(t.T(), tc.expectInactiveTimeoutReader, isInactiveTimeoutReader) }) } diff --git a/internal/gcsx/read_manager/read_manager.go b/internal/gcsx/read_manager/read_manager.go index 1afbe8901d..de88685f07 100644 --- a/internal/gcsx/read_manager/read_manager.go +++ b/internal/gcsx/read_manager/read_manager.go @@ -19,6 +19,7 @@ import ( "errors" "io" + "github.com/googlecloudplatform/gcsfuse/v2/cfg" "github.com/googlecloudplatform/gcsfuse/v2/common" "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" @@ -42,6 +43,7 @@ type ReadManagerConfig struct { CacheFileForRangeRead bool MetricHandle common.MetricHandle MrdWrapper *gcsx.MultiRangeDownloaderWrapper + ReadConfig *cfg.ReadConfig } // NewReadManager creates a new ReadManager for the given GCS object, @@ -67,9 +69,12 @@ func NewReadManager(object *gcs.MinObject, bucket gcs.Bucket, config *ReadManage gcsReader := clientReaders.NewGCSReader( object, bucket, - config.MetricHandle, - config.MrdWrapper, - config.SequentialReadSizeMB, + &clientReaders.GCSReaderConfig{ + MetricHandle: config.MetricHandle, + MrdWrapper: config.MrdWrapper, + SequentialReadSizeMb: config.SequentialReadSizeMB, + ReadConfig: config.ReadConfig, + }, ) // Add the GCS reader as a fallback. readers = append(readers, gcsReader) From 1ff8ef608840a84766d651a641fc79fa932be084 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 28 May 2025 08:31:49 +0000 Subject: [PATCH 0469/1298] Upgrade to v3 (#3361) * upgrade to v3 * upgrade to v3 --- .../internal/percentile/duration_test.go | 2 +- benchmarks/read_full_file/main.go | 4 +-- benchmarks/read_within_file/main.go | 2 +- benchmarks/stat_files/main.go | 2 +- benchmarks/write_locally/main.go | 2 +- benchmarks/write_to_gcs/main.go | 2 +- cfg/constants.go | 2 +- cfg/rationalize.go | 2 +- cfg/types.go | 2 +- cfg/validate.go | 2 +- cfg/validate_test.go | 2 +- cmd/config_validation_test.go | 4 +-- cmd/datatypes_parsing_test.go | 2 +- cmd/legacy_main.go | 22 +++++++-------- cmd/legacy_main_test.go | 4 +-- cmd/mount.go | 16 +++++------ cmd/mount_test.go | 2 +- cmd/root.go | 6 ++-- cmd/root_test.go | 4 +-- common/version.go | 2 +- go.mod | 2 +- .../bufferedwrites/buffered_write_handler.go | 6 ++-- .../buffered_write_handler_test.go | 8 +++--- internal/bufferedwrites/upload_handler.go | 6 ++-- .../bufferedwrites/upload_handler_test.go | 6 ++-- internal/cache/file/cache_handle.go | 10 +++---- internal/cache/file/cache_handle_test.go | 22 +++++++-------- internal/cache/file/cache_handler.go | 14 +++++----- internal/cache/file/cache_handler_test.go | 22 +++++++-------- internal/cache/file/downloader/downloader.go | 14 +++++----- .../cache/file/downloader/downloader_test.go | 20 ++++++------- .../downloader/jm_parallel_downloads_test.go | 16 +++++------ internal/cache/file/downloader/job.go | 18 ++++++------ internal/cache/file/downloader/job_test.go | 14 +++++----- .../cache/file/downloader/job_testify_test.go | 18 ++++++------ .../file/downloader/parallel_downloads_job.go | 12 ++++---- .../downloader/parallel_downloads_job_test.go | 8 +++--- .../parallel_downloads_job_testify_test.go | 14 +++++----- internal/cache/file/downloader/test_util.go | 8 +++--- internal/cache/lru/lru.go | 2 +- internal/cache/lru/lru_test.go | 4 +-- internal/cache/metadata/stat_cache.go | 6 ++-- internal/cache/metadata/stat_cache_test.go | 8 +++--- internal/cache/metadata/type_cache.go | 4 +-- internal/cache/metadata/type_cache_test.go | 2 +- internal/cache/util/util.go | 4 +-- internal/cache/util/util_test.go | 6 ++-- internal/canned/canned.go | 4 +-- internal/contentcache/contentcache.go | 4 +-- internal/contentcache/contentcache_test.go | 2 +- internal/fs/all_buckets_test.go | 4 +-- internal/fs/caching_test.go | 18 ++++++------ internal/fs/foreign_modifications_test.go | 6 ++-- internal/fs/fs.go | 28 +++++++++---------- internal/fs/fs_test.go | 20 ++++++------- internal/fs/handle/dir_handle.go | 4 +-- internal/fs/handle/dir_handle_test.go | 14 +++++----- internal/fs/handle/file.go | 14 +++++----- internal/fs/hns_bucket_test.go | 14 +++++----- internal/fs/implicit_dirs_test.go | 4 +-- internal/fs/implicit_dirs_with_cache_test.go | 2 +- internal/fs/inode/base_dir.go | 8 +++--- internal/fs/inode/base_dir_test.go | 10 +++---- internal/fs/inode/core.go | 6 ++-- internal/fs/inode/core_test.go | 12 ++++---- internal/fs/inode/dir.go | 12 ++++---- internal/fs/inode/dir_test.go | 16 +++++------ internal/fs/inode/explicit_dir.go | 4 +-- internal/fs/inode/file.go | 18 ++++++------ internal/fs/inode/file_mock_bucket_test.go | 12 ++++---- .../fs/inode/file_streaming_writes_test.go | 20 ++++++------- internal/fs/inode/file_test.go | 16 +++++------ internal/fs/inode/hns_dir_test.go | 8 +++--- internal/fs/inode/inode.go | 2 +- internal/fs/inode/inode_test.go | 2 +- internal/fs/inode/name_test.go | 2 +- internal/fs/inode/symlink.go | 2 +- internal/fs/inode/symlink_test.go | 4 +-- .../kernel_list_cache_inifinite_ttl_test.go | 4 +-- internal/fs/kernel_list_cache_test.go | 6 ++-- .../fs/kernel_list_cache_zero_ttl_test.go | 4 +-- internal/fs/local_file_test.go | 10 +++---- internal/fs/local_modifications_test.go | 6 ++-- internal/fs/parallel_dirops_test.go | 4 +-- internal/fs/read_cache_test.go | 8 +++--- internal/fs/read_only_test.go | 2 +- internal/fs/server.go | 4 +-- internal/fs/stale_file_handle_common_test.go | 6 ++-- .../fs/stale_file_handle_local_file_test.go | 2 +- ...ile_handle_streaming_writes_common_test.go | 4 +-- ...handle_streaming_writes_local_file_test.go | 4 +-- ...andle_streaming_writes_synced_file_test.go | 4 +-- .../fs/stale_file_handle_synced_file_test.go | 4 +-- internal/fs/streaming_writes_common_test.go | 2 +- .../streaming_writes_empty_gcs_object_test.go | 4 +-- .../fs/streaming_writes_local_file_test.go | 4 +-- internal/fs/type_cache_test.go | 14 +++++----- internal/fs/wrappers/error_mapping.go | 4 +-- internal/fs/wrappers/error_mapping_test.go | 2 +- internal/fs/wrappers/monitoring.go | 2 +- internal/fs/zonal_bucket_test.go | 4 +-- internal/gcsx/bucket_manager.go | 20 ++++++------- internal/gcsx/bucket_manager_test.go | 8 +++--- internal/gcsx/client_readers/gcs_reader.go | 10 +++---- .../gcsx/client_readers/gcs_reader_test.go | 16 +++++------ .../gcsx/client_readers/multi_range_reader.go | 8 +++--- .../client_readers/multi_range_reader_test.go | 14 +++++----- internal/gcsx/client_readers/range_reader.go | 14 +++++----- .../gcsx/client_readers/range_reader_test.go | 14 +++++----- internal/gcsx/compose_object_creator.go | 2 +- internal/gcsx/compose_object_creator_test.go | 4 +-- internal/gcsx/content_type_bucket.go | 2 +- internal/gcsx/content_type_bucket_test.go | 6 ++-- internal/gcsx/file_cache_reader.go | 14 +++++----- internal/gcsx/file_cache_reader_test.go | 20 ++++++------- internal/gcsx/garbage_collect.go | 6 ++-- internal/gcsx/inactive_timeout_reader.go | 8 +++--- internal/gcsx/inactive_timeout_reader_test.go | 8 +++--- internal/gcsx/integration_test.go | 8 +++--- .../gcsx/multi_range_downloader_wrapper.go | 10 +++---- .../multi_range_downloader_wrapper_test.go | 12 ++++---- internal/gcsx/prefix_bucket.go | 2 +- internal/gcsx/prefix_bucket_test.go | 8 +++--- internal/gcsx/random_reader.go | 18 ++++++------ internal/gcsx/random_reader_stretchr_test.go | 22 +++++++-------- internal/gcsx/random_reader_test.go | 22 +++++++-------- internal/gcsx/read_manager/read_manager.go | 12 ++++---- .../gcsx/read_manager/read_manager_test.go | 26 ++++++++--------- internal/gcsx/reader.go | 2 +- internal/gcsx/syncer.go | 2 +- internal/gcsx/syncer_bucket.go | 2 +- internal/gcsx/syncer_test.go | 6 ++-- internal/gcsx/temp_file_test.go | 2 +- internal/locker/locker.go | 2 +- internal/locker/rw_locker.go | 2 +- internal/logger/logger.go | 2 +- internal/logger/logger_test.go | 2 +- internal/logger/slog_helper.go | 2 +- internal/monitor/bucket.go | 4 +-- internal/monitor/otelexporters.go | 6 ++-- internal/monitor/traceexporter.go | 6 ++-- internal/perf/cpu.go | 2 +- internal/perf/memory.go | 2 +- internal/perms/perms_test.go | 2 +- internal/profiler/cloud_profiler.go | 4 +-- internal/profiler/cloud_profiler_test.go | 2 +- internal/ratelimit/throttle_test.go | 2 +- internal/ratelimit/throttled_bucket.go | 2 +- internal/storage/bucket_handle.go | 6 ++-- internal/storage/bucket_handle_test.go | 4 +-- internal/storage/caching/fast_stat_bucket.go | 8 +++--- .../storage/caching/fast_stat_bucket_test.go | 8 +++--- internal/storage/caching/integration_test.go | 14 +++++----- .../mock_gcscaching/mock_stat_cache.go | 4 +-- internal/storage/debug_bucket.go | 4 +-- internal/storage/fake/bucket.go | 4 +-- internal/storage/fake/bucket_test.go | 4 +-- .../fake/fake_multi_range_downloader.go | 4 +-- internal/storage/fake/fake_object_writer.go | 4 +-- internal/storage/fake/testing/bucket_tests.go | 4 +-- .../fake/testing/register_bucket_tests.go | 2 +- internal/storage/fake_storage_util.go | 6 ++-- internal/storage/full_read_closer.go | 2 +- internal/storage/mock/testify_mock_bucket.go | 2 +- internal/storage/mock_bucket.go | 2 +- internal/storage/storage_handle.go | 8 +++--- internal/storage/storage_handle_test.go | 6 ++-- internal/storage/storageutil/client.go | 4 +-- .../storage/storageutil/control_client.go | 2 +- .../storageutil/create_empty_objects.go | 2 +- internal/storage/storageutil/create_object.go | 2 +- .../storage/storageutil/create_objects.go | 2 +- internal/storage/storageutil/custom_retry.go | 2 +- .../storage/storageutil/delete_all_objects.go | 2 +- internal/storage/storageutil/delete_object.go | 2 +- internal/storage/storageutil/list_all.go | 2 +- internal/storage/storageutil/list_prefix.go | 2 +- internal/storage/storageutil/object_attrs.go | 2 +- .../storage/storageutil/object_attrs_test.go | 2 +- internal/storage/storageutil/read_object.go | 2 +- internal/storage/storageutil/test_util.go | 2 +- .../unsupported_object_util_test.go | 2 +- internal/storage/testify_mock_bucket.go | 2 +- internal/util/sizeof.go | 2 +- internal/util/sizeof_test.go | 2 +- main.go | 8 +++--- tools/build_gcsfuse/main.go | 6 ++-- .../benchmarking/benchmark_delete_test.go | 4 +-- .../benchmarking/benchmark_rename_test.go | 4 +-- .../benchmarking/benchmark_stat_test.go | 6 ++-- .../benchmarking/setup_test.go | 8 +++--- .../cloud_profiler/cloud_profiler_test.go | 8 +++--- .../concurrent_listing_test.go | 6 ++-- .../concurrent_operations/setup_test.go | 10 +++---- .../emulator_tests/proxy_server/main_test.go | 2 +- .../read_stall/read_stall_test.go | 6 ++-- .../emulator_tests/read_stall/setup_test.go | 4 +-- .../common_failure_test.go | 8 +++--- .../empty_gcs_file_failure_test.go | 4 +-- .../new_local_file_failure_test.go | 2 +- .../streaming_writes_failure_test.go | 4 +-- .../emulator_tests/util/test_helper.go | 2 +- .../write_stall/write_stall_test.go | 4 +-- .../write_stall/writes_stall_on_sync_test.go | 6 ++-- .../explicit_dir/explicit_dir_test.go | 6 ++-- .../explicit_dir/list_test.go | 4 +-- .../grpc_validation/grpc_validation_test.go | 10 +++---- .../grpc_validation/setup_test.go | 4 +-- tools/integration_tests/gzip/gzip_test.go | 8 +++--- .../integration_tests/gzip/read_gzip_test.go | 6 ++-- .../integration_tests/gzip/write_gzip_test.go | 6 ++-- .../implicit_dir/delete_test.go | 6 ++-- .../implicit_dir/implicit_dir_test.go | 6 ++-- .../implicit_dir/list_test.go | 4 +-- .../implicit_dir/local_file_test.go | 6 ++-- .../inactive_stream_timeout/setup_test.go | 12 ++++---- .../with_timeout_test.go | 6 ++-- .../without_timeout_test.go | 6 ++-- .../interrupt/git_clone_test.go | 8 +++--- .../interrupt/interrupt_test.go | 6 ++-- .../disabled_kernel_list_cache_test.go | 8 +++--- .../finite_kernel_list_cache_test.go | 8 +++--- ...inite_kernel_list_cache_delete_dir_test.go | 8 +++--- .../infinite_kernel_list_cache_test.go | 8 +++--- .../kernel_list_cache/setup_test.go | 10 +++---- ...ist_dir_with_twelve_thousand_files_test.go | 8 +++--- .../list_large_dir/list_large_dir_test.go | 6 ++-- .../local_file/create_file.go | 6 ++-- .../local_file/edit_file_test.go | 6 ++-- .../local_file/local_file_helper.go | 4 +-- .../local_file/local_file_suite.go | 2 +- .../integration_tests/local_file/read_dir.go | 6 ++-- .../integration_tests/local_file/read_file.go | 4 +-- .../local_file/remove_dir.go | 6 ++-- tools/integration_tests/local_file/rename.go | 6 ++-- .../local_file/setup_test.go | 10 +++---- .../integration_tests/local_file/stat_file.go | 8 +++--- .../integration_tests/local_file/sym_link.go | 6 ++-- .../local_file/unlinked_file.go | 6 ++-- .../local_file/write_file.go | 6 ++-- .../log_rotation/log_rotation_test.go | 6 ++-- .../log_rotation/logrotate_logfile_test.go | 4 +-- .../managed_folders/admin_permissions_test.go | 10 +++---- .../list_empty_managed_folders_test.go | 8 +++--- .../managed_folders/managed_folders_test.go | 10 +++---- .../managed_folders/test_helper.go | 6 ++-- .../managed_folders/view_permissions_test.go | 8 +++--- .../integration_tests/monitoring/prom_test.go | 8 +++--- .../gcsfuse_mount_timeout_test.go | 8 +++--- .../mount_timeout/mount_timeout_test.go | 4 +-- .../mounting/gcsfuse_darwin_test.go | 4 +-- .../mounting/gcsfuse_linux_test.go | 4 +-- .../mounting/gcsfuse_test.go | 4 +-- tools/integration_tests/mounting/main_test.go | 4 +-- .../mounting/mount_helper_test.go | 4 +-- .../disabled_negative_stat_cache_test.go | 8 +++--- .../finite_int_negative_stat_cache_test.go | 8 +++--- .../infinite_negative_stat_cache_test.go | 8 +++--- .../negative_stat_cache/setup_test.go | 10 +++---- .../operations/copy_dir_test.go | 4 +-- .../operations/copy_file_test.go | 4 +-- .../operations/create_three_level_dir_test.go | 4 +-- .../operations/delete_dir_test.go | 4 +-- .../operations/delete_file_test.go | 4 +-- .../file_and_dir_attributes_test.go | 4 +-- .../operations/list_dir_test.go | 4 +-- .../operations/move_file_test.go | 4 +-- .../operations/operations_test.go | 14 +++++----- .../operations/parallel_dirops_test.go | 4 +-- .../integration_tests/operations/read_test.go | 4 +-- .../operations/rename_file_test.go | 4 +-- .../operations/stat_file_test.go | 2 +- .../operations/write_test.go | 10 +++---- .../cache_file_for_range_read_false_test.go | 10 +++---- .../cache_file_for_range_read_true_test.go | 10 +++---- .../read_cache/disabled_cache_ttl_test.go | 10 +++---- .../read_cache/helpers_test.go | 8 +++--- .../read_cache/job_chunk_test.go | 12 ++++---- .../read_cache/local_modification_test.go | 10 +++---- .../read_cache/range_read_test.go | 10 +++---- .../read_cache/read_only_test.go | 10 +++---- .../read_cache/remount_test.go | 12 ++++---- .../read_cache/setup_test.go | 12 ++++---- .../read_cache/small_cache_ttl_test.go | 10 +++---- .../concurrent_read_same_file_test.go | 4 +-- .../read_gcs_algo/read_gcs_algo_test.go | 4 +-- .../read_gcs_algo/seq_diff_block_size_test.go | 4 +-- .../seq_to_ran_to_seq_read_test.go | 4 +-- .../concurrent_read_files_test.go | 4 +-- .../random_read_large_file_test.go | 4 +-- .../read_large_files/read_large_files_test.go | 6 ++-- .../seq_read_large_file_test.go | 4 +-- .../readonly/copy_object_test.go | 4 +-- .../readonly/create_object_test.go | 4 +-- .../readonly/delete_object_test.go | 4 +-- .../readonly/list_objects_test.go | 2 +- tools/integration_tests/readonly/read_test.go | 4 +-- .../readonly/readonly_test.go | 10 +++---- .../readonly/rename_object_test.go | 4 +-- .../readonly/stat_object_test.go | 2 +- .../integration_tests/readonly/write_test.go | 4 +-- .../failure_during_file_sync_test.go | 6 ++-- .../readonly_creds/readonly_creds_test.go | 6 ++-- .../rename_dir_limit/move_dir_test.go | 4 +-- .../rename_dir_limit/rename_dir_limit_test.go | 10 +++---- .../rename_dir_limit/rename_dir_test.go | 4 +-- .../stale_handle/setup_test.go | 6 ++-- .../stale_file_handle_common_test.go | 8 +++--- .../stale_file_handle_local_file_test.go | 4 +-- .../stale_file_handle_synced_file_test.go | 6 ++-- .../streaming_writes/buffer_size_test.go | 6 ++-- .../common_streaming_writes_suite_test.go | 8 +++--- .../streaming_writes/empty_gcs_file_test.go | 6 ++-- .../streaming_writes/local_file_test.go | 8 +++--- .../streaming_writes/read_file_test.go | 4 +-- .../streaming_writes/rename_file_test.go | 4 +-- .../streaming_writes/setup_test.go | 6 ++-- .../streaming_writes/symlink_file_test.go | 6 ++-- .../streaming_writes/truncate_file_test.go | 4 +-- .../unfinalized_object/setup_test.go | 4 +-- .../unfinalized_object_operations_test.go | 6 ++-- .../unfinalized_read_test.go | 8 +++--- .../benchmark_setup/benchmark_setup_test.go | 2 +- .../util/client/control_client.go | 4 +-- .../util/client/gcs_helper.go | 4 +-- .../util/client/storage_client.go | 6 ++-- .../util/creds_tests/creds.go | 6 ++-- .../read_logs/json_job_log_parser.go | 2 +- .../read_logs/json_job_log_parser_test.go | 2 +- .../read_logs/json_read_log_parser.go | 2 +- .../read_logs/json_read_log_parser_test.go | 4 +-- .../dynamic_mounting/dynamic_mounting.go | 6 ++-- .../util/mounting/mounting.go | 4 +-- .../only_dir_mounting/only_dir_mounting.go | 6 ++-- .../perisistent_mounting.go | 4 +-- .../static_mounting/static_mounting.go | 4 +-- .../util/operations/file_operations.go | 2 +- .../util/operations/validation_helper.go | 6 ++-- .../implicit_and_explicit_dir_setup.go | 10 +++---- tools/integration_tests/util/setup/setup.go | 4 +-- .../util/test_setup/test_setup_test.go | 4 +-- .../concurrent_write_files_test.go | 4 +-- .../concurrent_write_to_same_file_test.go | 4 +-- .../random_write_large_file_test.go | 6 ++-- .../seq_write_large_file_test.go | 4 +-- .../write_large_files_test.go | 4 +-- tools/mount_gcsfuse/main.go | 4 +-- tools/prefetch_cache_gcsfuse/prefetch.go | 2 +- tools/util/build_gcsfuse.go | 4 +-- 349 files changed, 1111 insertions(+), 1111 deletions(-) diff --git a/benchmarks/internal/percentile/duration_test.go b/benchmarks/internal/percentile/duration_test.go index 2d699132f9..4c1efaa6cd 100644 --- a/benchmarks/internal/percentile/duration_test.go +++ b/benchmarks/internal/percentile/duration_test.go @@ -18,7 +18,7 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/benchmarks/internal/percentile" + "github.com/googlecloudplatform/gcsfuse/v3/benchmarks/internal/percentile" . "github.com/jacobsa/ogletest" ) diff --git a/benchmarks/read_full_file/main.go b/benchmarks/read_full_file/main.go index 5ad4cdc903..24542ea208 100644 --- a/benchmarks/read_full_file/main.go +++ b/benchmarks/read_full_file/main.go @@ -30,8 +30,8 @@ import ( "sort" "time" - "github.com/googlecloudplatform/gcsfuse/v2/benchmarks/internal/format" - "github.com/googlecloudplatform/gcsfuse/v2/benchmarks/internal/percentile" + "github.com/googlecloudplatform/gcsfuse/v3/benchmarks/internal/format" + "github.com/googlecloudplatform/gcsfuse/v3/benchmarks/internal/percentile" ) var fDir = flag.String("dir", "", "Directory within which to write the file.") diff --git a/benchmarks/read_within_file/main.go b/benchmarks/read_within_file/main.go index 2f480bc47b..68798d88db 100644 --- a/benchmarks/read_within_file/main.go +++ b/benchmarks/read_within_file/main.go @@ -24,7 +24,7 @@ import ( "os" "time" - "github.com/googlecloudplatform/gcsfuse/v2/benchmarks/internal/format" + "github.com/googlecloudplatform/gcsfuse/v3/benchmarks/internal/format" ) var fFile = flag.String("file", "", "Path to file to read.") diff --git a/benchmarks/stat_files/main.go b/benchmarks/stat_files/main.go index 1ab6f988b1..efb01fed24 100644 --- a/benchmarks/stat_files/main.go +++ b/benchmarks/stat_files/main.go @@ -29,7 +29,7 @@ import ( "golang.org/x/net/context" "golang.org/x/sync/errgroup" - "github.com/googlecloudplatform/gcsfuse/v2/benchmarks/internal/format" + "github.com/googlecloudplatform/gcsfuse/v3/benchmarks/internal/format" "github.com/jacobsa/fuse/fsutil" ) diff --git a/benchmarks/write_locally/main.go b/benchmarks/write_locally/main.go index b3f49d5707..eae51ef199 100644 --- a/benchmarks/write_locally/main.go +++ b/benchmarks/write_locally/main.go @@ -26,7 +26,7 @@ import ( "os" "time" - "github.com/googlecloudplatform/gcsfuse/v2/benchmarks/internal/format" + "github.com/googlecloudplatform/gcsfuse/v3/benchmarks/internal/format" ) var fDir = flag.String("dir", "", "Directory within which to write the file.") diff --git a/benchmarks/write_to_gcs/main.go b/benchmarks/write_to_gcs/main.go index 800daa1e49..db705064fd 100644 --- a/benchmarks/write_to_gcs/main.go +++ b/benchmarks/write_to_gcs/main.go @@ -26,7 +26,7 @@ import ( "os" "time" - "github.com/googlecloudplatform/gcsfuse/v2/benchmarks/internal/format" + "github.com/googlecloudplatform/gcsfuse/v3/benchmarks/internal/format" ) var fDir = flag.String("dir", "", "Directory within which to write the file.") diff --git a/cfg/constants.go b/cfg/constants.go index 076878e01e..2807261703 100644 --- a/cfg/constants.go +++ b/cfg/constants.go @@ -18,7 +18,7 @@ import ( "math" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) const ( diff --git a/cfg/rationalize.go b/cfg/rationalize.go index a49dd797d5..84e6743692 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -18,7 +18,7 @@ import ( "math" "net/url" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) // isSet interface is abstraction over the IsSet() method of viper, specially diff --git a/cfg/types.go b/cfg/types.go index 918e476496..91c00c7dac 100644 --- a/cfg/types.go +++ b/cfg/types.go @@ -20,7 +20,7 @@ import ( "strconv" "strings" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) // Octal is the datatype for params such as file-mode and dir-mode which accept a base-8 value. diff --git a/cfg/validate.go b/cfg/validate.go index 426e0218f7..e3c9fd581f 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -19,7 +19,7 @@ import ( "fmt" "math" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) const ( diff --git a/cfg/validate_test.go b/cfg/validate_test.go index 74877189e0..58cc5db1c0 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -18,7 +18,7 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" ) diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 302245fdc3..b9d90569ad 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -22,8 +22,8 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/cmd/datatypes_parsing_test.go b/cmd/datatypes_parsing_test.go index a3290a05f6..5932744386 100644 --- a/cmd/datatypes_parsing_test.go +++ b/cmd/datatypes_parsing_test.go @@ -19,7 +19,7 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 9a43620b4f..94db652b48 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -30,17 +30,17 @@ import ( "golang.org/x/sys/unix" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/canned" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor" - "github.com/googlecloudplatform/gcsfuse/v2/internal/mount" - "github.com/googlecloudplatform/gcsfuse/v2/internal/profiler" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/canned" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/monitor" + "github.com/googlecloudplatform/gcsfuse/v3/internal/mount" + "github.com/googlecloudplatform/gcsfuse/v3/internal/profiler" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/daemonize" "github.com/jacobsa/fuse" "github.com/kardianos/osext" diff --git a/cmd/legacy_main_test.go b/cmd/legacy_main_test.go index 4bbd4e0270..f89bcac212 100644 --- a/cmd/legacy_main_test.go +++ b/cmd/legacy_main_test.go @@ -20,8 +20,8 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/cmd/mount.go b/cmd/mount.go index aee2431cc6..588a2e46fb 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -19,16 +19,16 @@ import ( "os" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/mount" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/mount" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "golang.org/x/net/context" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/perms" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/perms" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fsutil" "github.com/jacobsa/timeutil" diff --git a/cmd/mount_test.go b/cmd/mount_test.go index 2f9a106c54..5c781ec36f 100644 --- a/cmd/mount_test.go +++ b/cmd/mount_test.go @@ -17,7 +17,7 @@ package cmd import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/stretchr/testify/assert" ) diff --git a/cmd/root.go b/cmd/root.go index 8f677f6160..7e6b8ef686 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,9 +21,9 @@ import ( "strings" "github.com/go-viper/mapstructure/v2" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" diff --git a/cmd/root_test.go b/cmd/root_test.go index 1c3cd1ca3d..d0c22e5726 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -22,8 +22,8 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/common/version.go b/common/version.go index 76e5817714..b1c6594d7e 100644 --- a/common/version.go +++ b/common/version.go @@ -19,7 +19,7 @@ import ( "runtime" ) -// Set with `-ldflags -X github.com/googlecloudplatform/gcsfuse/v2/common.gcsfuseVersion=1.2.3` +// Set with `-ldflags -X github.com/googlecloudplatform/gcsfuse/v3/common.gcsfuseVersion=1.2.3` // by tools/build_gcsfuse. If not defined, we use "unknown" in getVersion. var gcsfuseVersion string diff --git a/go.mod b/go.mod index be050e59c7..2f2cd26f18 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/googlecloudplatform/gcsfuse/v2 +module github.com/googlecloudplatform/gcsfuse/v3 go 1.24.0 diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index e8a6c27d02..3817a3fc58 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -20,9 +20,9 @@ import ( "math" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/block" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/sync/semaphore" ) diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index ddcaca500e..de98f012c2 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -21,10 +21,10 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/jacobsa/timeutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index d502ebfcac..eccc6c62b5 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -25,9 +25,9 @@ import ( "sync" "sync/atomic" - "github.com/googlecloudplatform/gcsfuse/v2/internal/block" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" ) // UploadHandler is responsible for synchronized uploads of the filled blocks diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index a7cbaf34bb..4408ae6a56 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -21,9 +21,9 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/block" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - storagemock "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/mock" + "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + storagemock "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/cache/file/cache_handle.go b/internal/cache/file/cache_handle.go index bd41bab21f..8cf3350299 100644 --- a/internal/cache/file/cache_handle.go +++ b/internal/cache/file/cache_handle.go @@ -20,11 +20,11 @@ import ( "io" "os" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" ) type CacheHandle struct { diff --git a/internal/cache/file/cache_handle_test.go b/internal/cache/file/cache_handle_test.go index b22c12bc54..f53b694af5 100644 --- a/internal/cache/file/cache_handle_test.go +++ b/internal/cache/file/cache_handle_test.go @@ -28,17 +28,17 @@ import ( "testing" "cloud.google.com/go/storage/control/apiv2/controlpb" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" diff --git a/internal/cache/file/cache_handler.go b/internal/cache/file/cache_handler.go index e56fb73c35..835dbd5e42 100644 --- a/internal/cache/file/cache_handler.go +++ b/internal/cache/file/cache_handler.go @@ -18,13 +18,13 @@ import ( "fmt" "os" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" ) // CacheHandler is responsible for creating CacheHandle and invalidating file cache diff --git a/internal/cache/file/cache_handler_test.go b/internal/cache/file/cache_handler_test.go index 2da69a15c1..4149b7998e 100644 --- a/internal/cache/file/cache_handler_test.go +++ b/internal/cache/file/cache_handler_test.go @@ -26,17 +26,17 @@ import ( "time" "cloud.google.com/go/storage/control/apiv2/controlpb" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/cache/file/downloader/downloader.go b/internal/cache/file/downloader/downloader.go index df1069bde8..f79d4a6529 100644 --- a/internal/cache/file/downloader/downloader.go +++ b/internal/cache/file/downloader/downloader.go @@ -18,13 +18,13 @@ import ( "math" "os" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/sync/semaphore" ) diff --git a/internal/cache/file/downloader/downloader_test.go b/internal/cache/file/downloader/downloader_test.go index 32ffc88429..38c9bbe2b3 100644 --- a/internal/cache/file/downloader/downloader_test.go +++ b/internal/cache/file/downloader/downloader_test.go @@ -23,16 +23,16 @@ import ( "time" "cloud.google.com/go/storage/control/apiv2/controlpb" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" . "github.com/jacobsa/ogletest" "github.com/stretchr/testify/mock" ) diff --git a/internal/cache/file/downloader/jm_parallel_downloads_test.go b/internal/cache/file/downloader/jm_parallel_downloads_test.go index cf73280ebf..8c63fd1720 100644 --- a/internal/cache/file/downloader/jm_parallel_downloads_test.go +++ b/internal/cache/file/downloader/jm_parallel_downloads_test.go @@ -23,14 +23,14 @@ import ( "time" "cloud.google.com/go/storage/control/apiv2/controlpb" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index 6b230e84dd..f1877faedd 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -24,15 +24,15 @@ import ( "reflect" "syscall" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - cacheutil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + cacheutil "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "golang.org/x/net/context" "golang.org/x/sync/semaphore" ) diff --git a/internal/cache/file/downloader/job_test.go b/internal/cache/file/downloader/job_test.go index dcfb0afd6e..7484b93b34 100644 --- a/internal/cache/file/downloader/job_test.go +++ b/internal/cache/file/downloader/job_test.go @@ -28,13 +28,13 @@ import ( "sync/atomic" "time" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" . "github.com/jacobsa/ogletest" "golang.org/x/sync/semaphore" ) diff --git a/internal/cache/file/downloader/job_testify_test.go b/internal/cache/file/downloader/job_testify_test.go index c6fff97f2d..7ce082b950 100644 --- a/internal/cache/file/downloader/job_testify_test.go +++ b/internal/cache/file/downloader/job_testify_test.go @@ -22,18 +22,18 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "golang.org/x/net/context" "golang.org/x/sync/semaphore" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" diff --git a/internal/cache/file/downloader/parallel_downloads_job.go b/internal/cache/file/downloader/parallel_downloads_job.go index 47014bf85f..e6affb23c0 100644 --- a/internal/cache/file/downloader/parallel_downloads_job.go +++ b/internal/cache/file/downloader/parallel_downloads_job.go @@ -21,12 +21,12 @@ import ( "io" "os" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - cacheutil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + cacheutil "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "golang.org/x/sync/errgroup" ) diff --git a/internal/cache/file/downloader/parallel_downloads_job_test.go b/internal/cache/file/downloader/parallel_downloads_job_test.go index 78b5f8c111..1a2d13381e 100644 --- a/internal/cache/file/downloader/parallel_downloads_job_test.go +++ b/internal/cache/file/downloader/parallel_downloads_job_test.go @@ -27,10 +27,10 @@ import ( "sync/atomic" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" . "github.com/jacobsa/ogletest" ) diff --git a/internal/cache/file/downloader/parallel_downloads_job_testify_test.go b/internal/cache/file/downloader/parallel_downloads_job_testify_test.go index 2986189350..733a2188a3 100644 --- a/internal/cache/file/downloader/parallel_downloads_job_testify_test.go +++ b/internal/cache/file/downloader/parallel_downloads_job_testify_test.go @@ -21,13 +21,13 @@ import ( "sync" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" diff --git a/internal/cache/file/downloader/test_util.go b/internal/cache/file/downloader/test_util.go index 3a71021c30..3703521a30 100644 --- a/internal/cache/file/downloader/test_util.go +++ b/internal/cache/file/downloader/test_util.go @@ -21,10 +21,10 @@ import ( "reflect" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/stretchr/testify/assert" ) diff --git a/internal/cache/lru/lru.go b/internal/cache/lru/lru.go index 84ede6021d..aa7f989dd8 100644 --- a/internal/cache/lru/lru.go +++ b/internal/cache/lru/lru.go @@ -21,7 +21,7 @@ import ( "reflect" "strings" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" ) // Predefined errors returned by the Cache. diff --git a/internal/cache/lru/lru_test.go b/internal/cache/lru/lru_test.go index bac7beb17c..05ea3482b7 100644 --- a/internal/cache/lru/lru_test.go +++ b/internal/cache/lru/lru_test.go @@ -20,8 +20,8 @@ import ( "sync" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" . "github.com/jacobsa/ogletest" ) diff --git a/internal/cache/metadata/stat_cache.go b/internal/cache/metadata/stat_cache.go index 96c88da9ce..db17c5b61d 100644 --- a/internal/cache/metadata/stat_cache.go +++ b/internal/cache/metadata/stat_cache.go @@ -18,9 +18,9 @@ import ( "math" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) // A cache mapping from name to most recent known record for the object of that diff --git a/internal/cache/metadata/stat_cache_test.go b/internal/cache/metadata/stat_cache_test.go index d96fa94098..2f4fcfda7d 100644 --- a/internal/cache/metadata/stat_cache_test.go +++ b/internal/cache/metadata/stat_cache_test.go @@ -18,10 +18,10 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/cache/metadata/type_cache.go b/internal/cache/metadata/type_cache.go index 35e34b3a82..ce9f4946e7 100644 --- a/internal/cache/metadata/type_cache.go +++ b/internal/cache/metadata/type_cache.go @@ -19,8 +19,8 @@ import ( "math" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) type Type int diff --git a/internal/cache/metadata/type_cache_test.go b/internal/cache/metadata/type_cache_test.go index c7268c836f..166ea19cd0 100644 --- a/internal/cache/metadata/type_cache_test.go +++ b/internal/cache/metadata/type_cache_test.go @@ -19,7 +19,7 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" . "github.com/jacobsa/ogletest" ) diff --git a/internal/cache/util/util.go b/internal/cache/util/util.go index dec74d2fd3..6cf8ee6c28 100644 --- a/internal/cache/util/util.go +++ b/internal/cache/util/util.go @@ -25,8 +25,8 @@ import ( "path/filepath" "unsafe" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" "github.com/jacobsa/fuse/fsutil" ) diff --git a/internal/cache/util/util_test.go b/internal/cache/util/util_test.go index ebc59a2bad..680c973a92 100644 --- a/internal/cache/util/util_test.go +++ b/internal/cache/util/util_test.go @@ -27,9 +27,9 @@ import ( "testing" "unsafe" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/data" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" . "github.com/jacobsa/ogletest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/internal/canned/canned.go b/internal/canned/canned.go index a0fd759b6a..663ecffdfa 100644 --- a/internal/canned/canned.go +++ b/internal/canned/canned.go @@ -21,8 +21,8 @@ import ( "log" "strings" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" "github.com/jacobsa/timeutil" diff --git a/internal/contentcache/contentcache.go b/internal/contentcache/contentcache.go index fef7554d13..4e84e893b8 100644 --- a/internal/contentcache/contentcache.go +++ b/internal/contentcache/contentcache.go @@ -26,8 +26,8 @@ import ( "regexp" "sync" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/jacobsa/timeutil" ) diff --git a/internal/contentcache/contentcache_test.go b/internal/contentcache/contentcache_test.go index 3c28c9fe72..e3e249d618 100644 --- a/internal/contentcache/contentcache_test.go +++ b/internal/contentcache/contentcache_test.go @@ -21,7 +21,7 @@ import ( "sync" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" "github.com/jacobsa/fuse/fsutil" . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" diff --git a/internal/fs/all_buckets_test.go b/internal/fs/all_buckets_test.go index 9912918611..804b498e5a 100644 --- a/internal/fs/all_buckets_test.go +++ b/internal/fs/all_buckets_test.go @@ -19,8 +19,8 @@ import ( "os" "path" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" diff --git a/internal/fs/caching_test.go b/internal/fs/caching_test.go index 73ecc71f9b..406cce4821 100644 --- a/internal/fs/caching_test.go +++ b/internal/fs/caching_test.go @@ -20,15 +20,15 @@ import ( "path" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/caching" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/caching" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" diff --git a/internal/fs/foreign_modifications_test.go b/internal/fs/foreign_modifications_test.go index fbb25bb44c..748e922699 100644 --- a/internal/fs/foreign_modifications_test.go +++ b/internal/fs/foreign_modifications_test.go @@ -29,10 +29,10 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 73dc1872d4..b0ad87b611 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -30,20 +30,20 @@ import ( "golang.org/x/sync/semaphore" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - cacheutil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/handle" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + cacheutil "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/handle" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index b217493374..a1fce02c7d 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -29,16 +29,16 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/perms" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/perms" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/ogletest" diff --git a/internal/fs/handle/dir_handle.go b/internal/fs/handle/dir_handle.go index 86b05117b6..e4c6b42257 100644 --- a/internal/fs/handle/dir_handle.go +++ b/internal/fs/handle/dir_handle.go @@ -18,8 +18,8 @@ import ( "fmt" "sort" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" diff --git a/internal/fs/handle/dir_handle_test.go b/internal/fs/handle/dir_handle_test.go index 74e29ea2bb..f24b37b4f0 100644 --- a/internal/fs/handle/dir_handle_test.go +++ b/internal/fs/handle/dir_handle_test.go @@ -20,13 +20,13 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" . "github.com/jacobsa/ogletest" diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index a675ba37de..8d978a8b49 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -19,13 +19,13 @@ import ( "fmt" "io" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/syncutil" "golang.org/x/net/context" ) diff --git a/internal/fs/hns_bucket_test.go b/internal/fs/hns_bucket_test.go index 13f3ed1160..c84378f50e 100644 --- a/internal/fs/hns_bucket_test.go +++ b/internal/fs/hns_bucket_test.go @@ -23,13 +23,13 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/caching" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/caching" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/jacobsa/timeutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/internal/fs/implicit_dirs_test.go b/internal/fs/implicit_dirs_test.go index dc5d20d635..8e58a9defc 100644 --- a/internal/fs/implicit_dirs_test.go +++ b/internal/fs/implicit_dirs_test.go @@ -24,8 +24,8 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" diff --git a/internal/fs/implicit_dirs_with_cache_test.go b/internal/fs/implicit_dirs_with_cache_test.go index 23d4ba6a4f..b3d8cdcb56 100644 --- a/internal/fs/implicit_dirs_with_cache_test.go +++ b/internal/fs/implicit_dirs_with_cache_test.go @@ -24,7 +24,7 @@ import ( "path" "time" - "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v3/common" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" ) diff --git a/internal/fs/inode/base_dir.go b/internal/fs/inode/base_dir.go index 9bd89d52c3..562cb47cb7 100644 --- a/internal/fs/inode/base_dir.go +++ b/internal/fs/inode/base_dir.go @@ -18,13 +18,13 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/jacobsa/fuse" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" "golang.org/x/net/context" diff --git a/internal/fs/inode/base_dir_test.go b/internal/fs/inode/base_dir_test.go index 1642b1b091..4904c5e8e5 100644 --- a/internal/fs/inode/base_dir_test.go +++ b/internal/fs/inode/base_dir_test.go @@ -20,13 +20,13 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/jacobsa/fuse/fuseops" . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" diff --git a/internal/fs/inode/core.go b/internal/fs/inode/core.go index 96df808561..bbcb617e62 100644 --- a/internal/fs/inode/core.go +++ b/internal/fs/inode/core.go @@ -17,9 +17,9 @@ package inode import ( "fmt" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" ) // Core contains critical information about an inode before its creation. diff --git a/internal/fs/inode/core_test.go b/internal/fs/inode/core_test.go index 5ab0a3a63b..3f7218ad19 100644 --- a/internal/fs/inode/core_test.go +++ b/internal/fs/inode/core_test.go @@ -18,12 +18,12 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" "golang.org/x/net/context" diff --git a/internal/fs/inode/dir.go b/internal/fs/inode/dir.go index fff3ab3b09..df9993fd8d 100644 --- a/internal/fs/inode/dir.go +++ b/internal/fs/inode/dir.go @@ -21,12 +21,12 @@ import ( "strings" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/timeutil" diff --git a/internal/fs/inode/dir_test.go b/internal/fs/inode/dir_test.go index 014bb56d87..f0bf6dd1b7 100644 --- a/internal/fs/inode/dir_test.go +++ b/internal/fs/inode/dir_test.go @@ -23,17 +23,17 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "golang.org/x/net/context" "golang.org/x/sync/semaphore" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" . "github.com/jacobsa/oglematchers" diff --git a/internal/fs/inode/explicit_dir.go b/internal/fs/inode/explicit_dir.go index 4cfa9c9fb9..bafa873978 100644 --- a/internal/fs/inode/explicit_dir.go +++ b/internal/fs/inode/explicit_dir.go @@ -17,8 +17,8 @@ package inode import ( "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/timeutil" ) diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index e942004342..5b0c6c2255 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -23,15 +23,15 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/block" - "github.com/googlecloudplatform/gcsfuse/v2/internal/bufferedwrites" - "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/bufferedwrites" + "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/syncutil" "github.com/jacobsa/timeutil" diff --git a/internal/fs/inode/file_mock_bucket_test.go b/internal/fs/inode/file_mock_bucket_test.go index 49939af993..7f36ca4eec 100644 --- a/internal/fs/inode/file_mock_bucket_test.go +++ b/internal/fs/inode/file_mock_bucket_test.go @@ -20,12 +20,12 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - storagemock "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/mock" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + storagemock "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/mock" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/syncutil" "github.com/jacobsa/timeutil" diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index dd1ce1fa99..f087e9f4f0 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -21,16 +21,16 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/bufferedwrites" - "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/bufferedwrites" + "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/syncutil" "github.com/jacobsa/timeutil" diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 5d5ba4cfc4..6b42aa8f20 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -26,14 +26,14 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/syncutil" "github.com/jacobsa/timeutil" diff --git a/internal/fs/inode/hns_dir_test.go b/internal/fs/inode/hns_dir_test.go index b21eff945f..63341728ee 100644 --- a/internal/fs/inode/hns_dir_test.go +++ b/internal/fs/inode/hns_dir_test.go @@ -22,9 +22,9 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - storagemock "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/mock" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + storagemock "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/mock" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" "github.com/jacobsa/timeutil" @@ -34,7 +34,7 @@ import ( "golang.org/x/net/context" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" ) type hnsDirTest struct { diff --git a/internal/fs/inode/inode.go b/internal/fs/inode/inode.go index ba26c481ec..59c58eecb1 100644 --- a/internal/fs/inode/inode.go +++ b/internal/fs/inode/inode.go @@ -17,7 +17,7 @@ package inode import ( "sync" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/jacobsa/fuse/fuseops" "golang.org/x/net/context" ) diff --git a/internal/fs/inode/inode_test.go b/internal/fs/inode/inode_test.go index 5f28d28254..717d3d51c5 100644 --- a/internal/fs/inode/inode_test.go +++ b/internal/fs/inode/inode_test.go @@ -17,7 +17,7 @@ package inode_test import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" "github.com/stretchr/testify/assert" ) diff --git a/internal/fs/inode/name_test.go b/internal/fs/inode/name_test.go index d8f801ceec..7dbd92f935 100644 --- a/internal/fs/inode/name_test.go +++ b/internal/fs/inode/name_test.go @@ -17,7 +17,7 @@ package inode_test import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" . "github.com/jacobsa/ogletest" ) diff --git a/internal/fs/inode/symlink.go b/internal/fs/inode/symlink.go index 8c9d59fd29..0e7ca5e726 100644 --- a/internal/fs/inode/symlink.go +++ b/internal/fs/inode/symlink.go @@ -17,7 +17,7 @@ package inode import ( "sync" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/jacobsa/fuse/fuseops" "golang.org/x/net/context" ) diff --git a/internal/fs/inode/symlink_test.go b/internal/fs/inode/symlink_test.go index bf29cbb894..1c2a1c9e46 100644 --- a/internal/fs/inode/symlink_test.go +++ b/internal/fs/inode/symlink_test.go @@ -17,8 +17,8 @@ package inode_test import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" . "github.com/jacobsa/ogletest" ) diff --git a/internal/fs/kernel_list_cache_inifinite_ttl_test.go b/internal/fs/kernel_list_cache_inifinite_ttl_test.go index 007f16cf91..d8effb5ae7 100644 --- a/internal/fs/kernel_list_cache_inifinite_ttl_test.go +++ b/internal/fs/kernel_list_cache_inifinite_ttl_test.go @@ -23,8 +23,8 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" diff --git a/internal/fs/kernel_list_cache_test.go b/internal/fs/kernel_list_cache_test.go index 92724a229a..bc320d5c80 100644 --- a/internal/fs/kernel_list_cache_test.go +++ b/internal/fs/kernel_list_cache_test.go @@ -30,9 +30,9 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" diff --git a/internal/fs/kernel_list_cache_zero_ttl_test.go b/internal/fs/kernel_list_cache_zero_ttl_test.go index cd56a77a65..c40d6e3f98 100644 --- a/internal/fs/kernel_list_cache_zero_ttl_test.go +++ b/internal/fs/kernel_list_cache_zero_ttl_test.go @@ -21,8 +21,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" diff --git a/internal/fs/local_file_test.go b/internal/fs/local_file_test.go index ed03df2507..c3bc3d2f02 100644 --- a/internal/fs/local_file_test.go +++ b/internal/fs/local_file_test.go @@ -29,11 +29,11 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/jacobsa/fuse/fusetesting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/internal/fs/local_modifications_test.go b/internal/fs/local_modifications_test.go index a1a1fdf440..2566a1d685 100644 --- a/internal/fs/local_modifications_test.go +++ b/internal/fs/local_modifications_test.go @@ -33,9 +33,9 @@ import ( "unicode" "unicode/utf8" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" diff --git a/internal/fs/parallel_dirops_test.go b/internal/fs/parallel_dirops_test.go index d05192ac8d..f8d4416a48 100644 --- a/internal/fs/parallel_dirops_test.go +++ b/internal/fs/parallel_dirops_test.go @@ -23,8 +23,8 @@ import ( "sync" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" diff --git a/internal/fs/read_cache_test.go b/internal/fs/read_cache_test.go index c745f7c814..2bf75aa9a8 100644 --- a/internal/fs/read_cache_test.go +++ b/internal/fs/read_cache_test.go @@ -25,10 +25,10 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" . "github.com/jacobsa/ogletest" ) diff --git a/internal/fs/read_only_test.go b/internal/fs/read_only_test.go index c3b89246de..01b21a276b 100644 --- a/internal/fs/read_only_test.go +++ b/internal/fs/read_only_test.go @@ -18,7 +18,7 @@ import ( "os" "path" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" ) diff --git a/internal/fs/server.go b/internal/fs/server.go index 1aaafa11be..df8d41c694 100644 --- a/internal/fs/server.go +++ b/internal/fs/server.go @@ -17,8 +17,8 @@ package fs import ( "fmt" - newcfg "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/wrappers" + newcfg "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/wrappers" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fuseutil" "golang.org/x/net/context" diff --git a/internal/fs/stale_file_handle_common_test.go b/internal/fs/stale_file_handle_common_test.go index b5d218ef6e..24b47368ee 100644 --- a/internal/fs/stale_file_handle_common_test.go +++ b/internal/fs/stale_file_handle_common_test.go @@ -20,9 +20,9 @@ import ( "syscall" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/fs/stale_file_handle_local_file_test.go b/internal/fs/stale_file_handle_local_file_test.go index f660389837..128ce37233 100644 --- a/internal/fs/stale_file_handle_local_file_test.go +++ b/internal/fs/stale_file_handle_local_file_test.go @@ -17,7 +17,7 @@ package fs_test import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/suite" ) diff --git a/internal/fs/stale_file_handle_streaming_writes_common_test.go b/internal/fs/stale_file_handle_streaming_writes_common_test.go index c917595d6a..6cb0b70bb7 100644 --- a/internal/fs/stale_file_handle_streaming_writes_common_test.go +++ b/internal/fs/stale_file_handle_streaming_writes_common_test.go @@ -17,8 +17,8 @@ package fs_test import ( "math" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/fs/stale_file_handle_streaming_writes_local_file_test.go b/internal/fs/stale_file_handle_streaming_writes_local_file_test.go index 4dd0e27801..c3b82d3104 100644 --- a/internal/fs/stale_file_handle_streaming_writes_local_file_test.go +++ b/internal/fs/stale_file_handle_streaming_writes_local_file_test.go @@ -17,8 +17,8 @@ package fs_test import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go b/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go index 33878ef6ea..c8dc9bb662 100644 --- a/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go +++ b/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go @@ -19,8 +19,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/fs/stale_file_handle_synced_file_test.go b/internal/fs/stale_file_handle_synced_file_test.go index 8f997b50ec..b37f9cb555 100644 --- a/internal/fs/stale_file_handle_synced_file_test.go +++ b/internal/fs/stale_file_handle_synced_file_test.go @@ -19,8 +19,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/fs/streaming_writes_common_test.go b/internal/fs/streaming_writes_common_test.go index 7ba4f8300c..14196b7086 100644 --- a/internal/fs/streaming_writes_common_test.go +++ b/internal/fs/streaming_writes_common_test.go @@ -22,7 +22,7 @@ import ( "path" "strings" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/fs/streaming_writes_empty_gcs_object_test.go b/internal/fs/streaming_writes_empty_gcs_object_test.go index 95be49b140..eb6681b605 100644 --- a/internal/fs/streaming_writes_empty_gcs_object_test.go +++ b/internal/fs/streaming_writes_empty_gcs_object_test.go @@ -22,8 +22,8 @@ import ( "syscall" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/fs/streaming_writes_local_file_test.go b/internal/fs/streaming_writes_local_file_test.go index 3366eeed2c..1c3e45def4 100644 --- a/internal/fs/streaming_writes_local_file_test.go +++ b/internal/fs/streaming_writes_local_file_test.go @@ -22,8 +22,8 @@ import ( "syscall" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/fs/type_cache_test.go b/internal/fs/type_cache_test.go index 0770d99f29..1fafe2f50d 100644 --- a/internal/fs/type_cache_test.go +++ b/internal/fs/type_cache_test.go @@ -26,13 +26,13 @@ import ( "sync" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - gcsfusefs "github.com/googlecloudplatform/gcsfuse/v2/internal/fs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + gcsfusefs "github.com/googlecloudplatform/gcsfuse/v3/internal/fs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" diff --git a/internal/fs/wrappers/error_mapping.go b/internal/fs/wrappers/error_mapping.go index d5535cbb3b..b010fab6ff 100644 --- a/internal/fs/wrappers/error_mapping.go +++ b/internal/fs/wrappers/error_mapping.go @@ -22,8 +22,8 @@ import ( "syscall" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" "google.golang.org/api/googleapi" diff --git a/internal/fs/wrappers/error_mapping_test.go b/internal/fs/wrappers/error_mapping_test.go index fbddbe685b..206c0382cc 100644 --- a/internal/fs/wrappers/error_mapping_test.go +++ b/internal/fs/wrappers/error_mapping_test.go @@ -21,7 +21,7 @@ import ( "testing" "github.com/googleapis/gax-go/v2/apierror" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "google.golang.org/api/googleapi" diff --git a/internal/fs/wrappers/monitoring.go b/internal/fs/wrappers/monitoring.go index 113869a811..fc8da54d42 100644 --- a/internal/fs/wrappers/monitoring.go +++ b/internal/fs/wrappers/monitoring.go @@ -20,7 +20,7 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v2/common" + "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" ) diff --git a/internal/fs/zonal_bucket_test.go b/internal/fs/zonal_bucket_test.go index 302d35600a..59441a4c1d 100644 --- a/internal/fs/zonal_bucket_test.go +++ b/internal/fs/zonal_bucket_test.go @@ -17,8 +17,8 @@ package fs_test import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) diff --git a/internal/gcsx/bucket_manager.go b/internal/gcsx/bucket_manager.go index d9959803ff..e616f1e6d3 100644 --- a/internal/gcsx/bucket_manager.go +++ b/internal/gcsx/bucket_manager.go @@ -21,16 +21,16 @@ import ( "path" "time" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/canned" - "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor" - "github.com/googlecloudplatform/gcsfuse/v2/internal/ratelimit" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/caching" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/canned" + "github.com/googlecloudplatform/gcsfuse/v3/internal/monitor" + "github.com/googlecloudplatform/gcsfuse/v3/internal/ratelimit" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/caching" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/timeutil" ) diff --git a/internal/gcsx/bucket_manager_test.go b/internal/gcsx/bucket_manager_test.go index e45757f9c9..7675223f90 100644 --- a/internal/gcsx/bucket_manager_test.go +++ b/internal/gcsx/bucket_manager_test.go @@ -21,10 +21,10 @@ import ( "time" "cloud.google.com/go/storage/control/apiv2/controlpb" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" . "github.com/jacobsa/ogletest" "github.com/stretchr/testify/mock" ) diff --git a/internal/gcsx/client_readers/gcs_reader.go b/internal/gcsx/client_readers/gcs_reader.go index 11af1577d8..5aaf2b78ef 100644 --- a/internal/gcsx/client_readers/gcs_reader.go +++ b/internal/gcsx/client_readers/gcs_reader.go @@ -20,11 +20,11 @@ import ( "fmt" "io" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) // ReaderType represents different types of go-sdk gcs readers. diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go index b0913c64b5..b1fdfd509f 100644 --- a/internal/gcsx/client_readers/gcs_reader_test.go +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -21,14 +21,14 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - testUtil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/gcsx/client_readers/multi_range_reader.go b/internal/gcsx/client_readers/multi_range_reader.go index f9546ebad2..813e477468 100644 --- a/internal/gcsx/client_readers/multi_range_reader.go +++ b/internal/gcsx/client_readers/multi_range_reader.go @@ -20,10 +20,10 @@ import ( "io" "time" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" ) // TimeoutForMultiRangeRead is the timeout value for multi-range read operations. diff --git a/internal/gcsx/client_readers/multi_range_reader_test.go b/internal/gcsx/client_readers/multi_range_reader_test.go index c38075371d..7c89ded6c2 100644 --- a/internal/gcsx/client_readers/multi_range_reader_test.go +++ b/internal/gcsx/client_readers/multi_range_reader_test.go @@ -21,13 +21,13 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - testUtil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index e9bd80e625..76fd3ae1cf 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -21,13 +21,13 @@ import ( "io" "math" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) const ( diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go index 59d82ac2f7..569c0bc306 100644 --- a/internal/gcsx/client_readers/range_reader_test.go +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -24,13 +24,13 @@ import ( "testing/iotest" "time" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - testUtil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" diff --git a/internal/gcsx/compose_object_creator.go b/internal/gcsx/compose_object_creator.go index bbc6758f6c..4a3100696f 100644 --- a/internal/gcsx/compose_object_creator.go +++ b/internal/gcsx/compose_object_creator.go @@ -21,7 +21,7 @@ import ( "io" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/gcsx/compose_object_creator_test.go b/internal/gcsx/compose_object_creator_test.go index 1200c52bb6..0655502821 100644 --- a/internal/gcsx/compose_object_creator_test.go +++ b/internal/gcsx/compose_object_creator_test.go @@ -22,8 +22,8 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/oglemock" . "github.com/jacobsa/ogletest" diff --git a/internal/gcsx/content_type_bucket.go b/internal/gcsx/content_type_bucket.go index ac81f39748..dc88e41623 100644 --- a/internal/gcsx/content_type_bucket.go +++ b/internal/gcsx/content_type_bucket.go @@ -18,7 +18,7 @@ import ( "mime" "path" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/gcsx/content_type_bucket_test.go b/internal/gcsx/content_type_bucket_test.go index 3aff9319b2..d901eabdeb 100644 --- a/internal/gcsx/content_type_bucket_test.go +++ b/internal/gcsx/content_type_bucket_test.go @@ -18,9 +18,9 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/jacobsa/timeutil" "golang.org/x/net/context" ) diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index edaaae963e..5fee0e3992 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -23,13 +23,13 @@ import ( "time" "github.com/google/uuid" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - cacheUtil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + cacheUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/fuse/fuseops" ) diff --git a/internal/gcsx/file_cache_reader_test.go b/internal/gcsx/file_cache_reader_test.go index 80112ee6fe..368b8fc337 100644 --- a/internal/gcsx/file_cache_reader_test.go +++ b/internal/gcsx/file_cache_reader_test.go @@ -24,16 +24,16 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/gcsx/garbage_collect.go b/internal/gcsx/garbage_collect.go index 896bd18163..db842fa746 100644 --- a/internal/gcsx/garbage_collect.go +++ b/internal/gcsx/garbage_collect.go @@ -19,12 +19,12 @@ import ( "sync/atomic" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "golang.org/x/net/context" "golang.org/x/sync/errgroup" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" ) func garbageCollectOnce( diff --git a/internal/gcsx/inactive_timeout_reader.go b/internal/gcsx/inactive_timeout_reader.go index 7ac73b1133..f992da4ea6 100644 --- a/internal/gcsx/inactive_timeout_reader.go +++ b/internal/gcsx/inactive_timeout_reader.go @@ -20,10 +20,10 @@ import ( "time" storagev2 "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" - "github.com/googlecloudplatform/gcsfuse/v2/internal/locker" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/gcsx/inactive_timeout_reader_test.go b/internal/gcsx/inactive_timeout_reader_test.go index d74f4d5e4b..a028d10fa4 100644 --- a/internal/gcsx/inactive_timeout_reader_test.go +++ b/internal/gcsx/inactive_timeout_reader_test.go @@ -23,10 +23,10 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" diff --git a/internal/gcsx/integration_test.go b/internal/gcsx/integration_test.go index 9bc45521fe..dfdc2e5351 100644 --- a/internal/gcsx/integration_test.go +++ b/internal/gcsx/integration_test.go @@ -24,12 +24,12 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "golang.org/x/net/context" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go index 1568ed2532..278f384677 100644 --- a/internal/gcsx/multi_range_downloader_wrapper.go +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -22,11 +22,11 @@ import ( "time" "github.com/google/uuid" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/monitor" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/monitor" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/gcsx/multi_range_downloader_wrapper_test.go b/internal/gcsx/multi_range_downloader_wrapper_test.go index 0a1112940a..a81ef5c9b1 100644 --- a/internal/gcsx/multi_range_downloader_wrapper_test.go +++ b/internal/gcsx/multi_range_downloader_wrapper_test.go @@ -22,12 +22,12 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index 0c96be7296..095ca0358d 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -19,7 +19,7 @@ import ( "strings" "unicode/utf8" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/gcsx/prefix_bucket_test.go b/internal/gcsx/prefix_bucket_test.go index fc63c823d0..9dac305d61 100644 --- a/internal/gcsx/prefix_bucket_test.go +++ b/internal/gcsx/prefix_bucket_test.go @@ -21,14 +21,14 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/net/context" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/stretchr/testify/suite" "github.com/jacobsa/timeutil" diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 136c9ac2e8..042490986e 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -22,15 +22,15 @@ import ( "time" "github.com/google/uuid" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - cacheutil "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + cacheutil "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/fuse/fuseops" "golang.org/x/net/context" ) diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index eb53f8848f..10051013b6 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -25,17 +25,17 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/clock" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index 2c9c814a47..c1fa9a8486 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -26,17 +26,17 @@ import ( "testing/iotest" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - testutil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/fuse/fuseops" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/oglemock" diff --git a/internal/gcsx/read_manager/read_manager.go b/internal/gcsx/read_manager/read_manager.go index de88685f07..26f1fd7ee7 100644 --- a/internal/gcsx/read_manager/read_manager.go +++ b/internal/gcsx/read_manager/read_manager.go @@ -19,12 +19,12 @@ import ( "errors" "io" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - clientReaders "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx/client_readers" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + clientReaders "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx/client_readers" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" ) type ReadManager struct { diff --git a/internal/gcsx/read_manager/read_manager_test.go b/internal/gcsx/read_manager/read_manager_test.go index a6286a5e36..da05d10d7b 100644 --- a/internal/gcsx/read_manager/read_manager_test.go +++ b/internal/gcsx/read_manager/read_manager_test.go @@ -25,19 +25,19 @@ import ( "testing" "testing/iotest" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/file/downloader" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/gcsfuse_errors" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" - clientReaders "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx/client_readers" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - testUtil "github.com/googlecloudplatform/gcsfuse/v2/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + clientReaders "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx/client_readers" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" diff --git a/internal/gcsx/reader.go b/internal/gcsx/reader.go index cb67226f6b..52ed275680 100644 --- a/internal/gcsx/reader.go +++ b/internal/gcsx/reader.go @@ -18,7 +18,7 @@ import ( "context" "errors" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" ) // FallbackToAnotherReader is returned when data could not be retrieved diff --git a/internal/gcsx/syncer.go b/internal/gcsx/syncer.go index be00105acb..50d7c48f96 100644 --- a/internal/gcsx/syncer.go +++ b/internal/gcsx/syncer.go @@ -19,7 +19,7 @@ import ( "io" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/gcsx/syncer_bucket.go b/internal/gcsx/syncer_bucket.go index 9e5654a740..a8bfdabf28 100644 --- a/internal/gcsx/syncer_bucket.go +++ b/internal/gcsx/syncer_bucket.go @@ -15,7 +15,7 @@ package gcsx import ( - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" ) type SyncerBucket struct { diff --git a/internal/gcsx/syncer_test.go b/internal/gcsx/syncer_test.go index b7d8fe39c3..953820251a 100644 --- a/internal/gcsx/syncer_test.go +++ b/internal/gcsx/syncer_test.go @@ -21,9 +21,9 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/oglemock" . "github.com/jacobsa/ogletest" diff --git a/internal/gcsx/temp_file_test.go b/internal/gcsx/temp_file_test.go index 461e36f088..aa39bf21ef 100644 --- a/internal/gcsx/temp_file_test.go +++ b/internal/gcsx/temp_file_test.go @@ -21,7 +21,7 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" diff --git a/internal/locker/locker.go b/internal/locker/locker.go index f2f8c27cc7..b507a615b2 100644 --- a/internal/locker/locker.go +++ b/internal/locker/locker.go @@ -20,7 +20,7 @@ import ( "sync" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" ) var gEnableInvariantsCheck bool diff --git a/internal/locker/rw_locker.go b/internal/locker/rw_locker.go index a2a9dc0633..5b7ff4a8c2 100644 --- a/internal/locker/rw_locker.go +++ b/internal/locker/rw_locker.go @@ -20,7 +20,7 @@ import ( "sync" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" ) type RWLocker interface { diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 641bd3a6a6..07b58bd8d7 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -23,7 +23,7 @@ import ( "os" "runtime/debug" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" "gopkg.in/natefinch/lumberjack.v2" ) diff --git a/internal/logger/logger_test.go b/internal/logger/logger_test.go index 3d22d88683..4df543a9aa 100644 --- a/internal/logger/logger_test.go +++ b/internal/logger/logger_test.go @@ -21,7 +21,7 @@ import ( "regexp" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/logger/slog_helper.go b/internal/logger/slog_helper.go index 48bd98ae78..707f4b7d87 100644 --- a/internal/logger/slog_helper.go +++ b/internal/logger/slog_helper.go @@ -19,7 +19,7 @@ import ( "strings" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" ) const ( diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index b6fb3b9b6f..3f8733bb0e 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -20,8 +20,8 @@ import ( "time" storagev2 "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" ) // recordRequest records a request and its latency. diff --git a/internal/monitor/otelexporters.go b/internal/monitor/otelexporters.go index bf04364a90..6075d95412 100644 --- a/internal/monitor/otelexporters.go +++ b/internal/monitor/otelexporters.go @@ -22,9 +22,9 @@ import ( "time" cloudmetric "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/prometheus/client_golang/prometheus/promhttp" "go.opentelemetry.io/contrib/detectors/gcp" "go.opentelemetry.io/otel" diff --git a/internal/monitor/traceexporter.go b/internal/monitor/traceexporter.go index 84e4d89b7e..bcaae7a278 100644 --- a/internal/monitor/traceexporter.go +++ b/internal/monitor/traceexporter.go @@ -18,9 +18,9 @@ import ( "context" cloudtrace "github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/exporters/stdout/stdouttrace" "go.opentelemetry.io/otel/sdk/resource" diff --git a/internal/perf/cpu.go b/internal/perf/cpu.go index 2551458664..0eccc376ab 100644 --- a/internal/perf/cpu.go +++ b/internal/perf/cpu.go @@ -22,7 +22,7 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" ) func HandleCPUProfileSignals() { diff --git a/internal/perf/memory.go b/internal/perf/memory.go index bac85e1d65..5818129652 100644 --- a/internal/perf/memory.go +++ b/internal/perf/memory.go @@ -23,7 +23,7 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" ) const ( diff --git a/internal/perms/perms_test.go b/internal/perms/perms_test.go index 9cf3d8f837..9a85cd4ad5 100644 --- a/internal/perms/perms_test.go +++ b/internal/perms/perms_test.go @@ -18,7 +18,7 @@ package perms_test import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/perms" + "github.com/googlecloudplatform/gcsfuse/v3/internal/perms" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/profiler/cloud_profiler.go b/internal/profiler/cloud_profiler.go index 3aa0f026eb..9cda7c9dfc 100644 --- a/internal/profiler/cloud_profiler.go +++ b/internal/profiler/cloud_profiler.go @@ -16,8 +16,8 @@ package profiler import ( cloudprofiler "cloud.google.com/go/profiler" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "google.golang.org/api/option" ) diff --git a/internal/profiler/cloud_profiler_test.go b/internal/profiler/cloud_profiler_test.go index d3ee732e30..117ab6775f 100644 --- a/internal/profiler/cloud_profiler_test.go +++ b/internal/profiler/cloud_profiler_test.go @@ -19,7 +19,7 @@ import ( "testing" cloudprofiler "cloud.google.com/go/profiler" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/api/option" diff --git a/internal/ratelimit/throttle_test.go b/internal/ratelimit/throttle_test.go index 03fe8979df..e7680de59f 100644 --- a/internal/ratelimit/throttle_test.go +++ b/internal/ratelimit/throttle_test.go @@ -30,7 +30,7 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/ratelimit" + "github.com/googlecloudplatform/gcsfuse/v3/internal/ratelimit" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "golang.org/x/net/context" diff --git a/internal/ratelimit/throttled_bucket.go b/internal/ratelimit/throttled_bucket.go index fd79793128..00270a82a5 100644 --- a/internal/ratelimit/throttled_bucket.go +++ b/internal/ratelimit/throttled_bucket.go @@ -18,7 +18,7 @@ import ( "io" storagev2 "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 3ed5e92f57..03f10387b7 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -28,9 +28,9 @@ import ( "cloud.google.com/go/storage" "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googleapis/gax-go/v2" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "google.golang.org/api/iterator" ) diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 8c63326e56..61aae3a388 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -26,8 +26,8 @@ import ( "cloud.google.com/go/storage" control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index ee8a73fc69..705a0b0b97 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -20,10 +20,10 @@ import ( "sync" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "golang.org/x/net/context" "github.com/jacobsa/timeutil" diff --git a/internal/storage/caching/fast_stat_bucket_test.go b/internal/storage/caching/fast_stat_bucket_test.go index 41f40516fe..514e09eccd 100644 --- a/internal/storage/caching/fast_stat_bucket_test.go +++ b/internal/storage/caching/fast_stat_bucket_test.go @@ -21,10 +21,10 @@ import ( "time" gostorage "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/caching" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/caching/mock_gcscaching" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/caching" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/caching/mock_gcscaching" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/oglemock" . "github.com/jacobsa/ogletest" diff --git a/internal/storage/caching/integration_test.go b/internal/storage/caching/integration_test.go index 48b1685c86..9a464a7179 100644 --- a/internal/storage/caching/integration_test.go +++ b/internal/storage/caching/integration_test.go @@ -18,13 +18,13 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/lru" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/caching" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/caching" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" diff --git a/internal/storage/caching/mock_gcscaching/mock_stat_cache.go b/internal/storage/caching/mock_gcscaching/mock_stat_cache.go index b4691755d9..6eed67775a 100644 --- a/internal/storage/caching/mock_gcscaching/mock_stat_cache.go +++ b/internal/storage/caching/mock_gcscaching/mock_stat_cache.go @@ -12,8 +12,8 @@ import ( time "time" unsafe "unsafe" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" oglemock "github.com/jacobsa/oglemock" ) diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index 04ef1a20fe..7289cde2b4 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -21,8 +21,8 @@ import ( "time" storagev2 "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index bb0d2f9eae..364051d488 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -29,8 +29,8 @@ import ( "time" "unicode/utf8" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/jacobsa/syncutil" "github.com/jacobsa/timeutil" ) diff --git a/internal/storage/fake/bucket_test.go b/internal/storage/fake/bucket_test.go index 379159cdf4..898a2f8368 100644 --- a/internal/storage/fake/bucket_test.go +++ b/internal/storage/fake/bucket_test.go @@ -18,8 +18,8 @@ import ( "testing" "time" - gcstesting "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/fake/testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + gcstesting "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake/testing" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" "golang.org/x/net/context" diff --git a/internal/storage/fake/fake_multi_range_downloader.go b/internal/storage/fake/fake_multi_range_downloader.go index a3f35bae6b..96c7c86186 100644 --- a/internal/storage/fake/fake_multi_range_downloader.go +++ b/internal/storage/fake/fake_multi_range_downloader.go @@ -20,8 +20,8 @@ import ( "sync" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" ) // This struct is an implementation of the gcs.MultiRangeDownloader interface. diff --git a/internal/storage/fake/fake_object_writer.go b/internal/storage/fake/fake_object_writer.go index 1f81bfdf17..b7667669a0 100644 --- a/internal/storage/fake/fake_object_writer.go +++ b/internal/storage/fake/fake_object_writer.go @@ -24,8 +24,8 @@ import ( "io" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" ) // FakeObjectWriter is a mock implementation of storage.Writer used by FakeBucket. diff --git a/internal/storage/fake/testing/bucket_tests.go b/internal/storage/fake/testing/bucket_tests.go index a0c1837d69..6e7577681e 100644 --- a/internal/storage/fake/testing/bucket_tests.go +++ b/internal/storage/fake/testing/bucket_tests.go @@ -33,8 +33,8 @@ import ( "time" "unicode" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" diff --git a/internal/storage/fake/testing/register_bucket_tests.go b/internal/storage/fake/testing/register_bucket_tests.go index c901d01d67..9019cd2001 100644 --- a/internal/storage/fake/testing/register_bucket_tests.go +++ b/internal/storage/fake/testing/register_bucket_tests.go @@ -18,7 +18,7 @@ import ( "errors" "reflect" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" "golang.org/x/text/cases" "golang.org/x/text/language" diff --git a/internal/storage/fake_storage_util.go b/internal/storage/fake_storage_util.go index 9a5622d530..ec292935bb 100644 --- a/internal/storage/fake_storage_util.go +++ b/internal/storage/fake_storage_util.go @@ -16,9 +16,9 @@ package storage import ( "github.com/fsouza/fake-gcs-server/fakestorage" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" ) const TestBucketName string = "gcsfuse-default-bucket" diff --git a/internal/storage/full_read_closer.go b/internal/storage/full_read_closer.go index f0afefe8ae..c112ad9f54 100644 --- a/internal/storage/full_read_closer.go +++ b/internal/storage/full_read_closer.go @@ -18,7 +18,7 @@ import ( "io" storagev2 "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" ) // gcsFullReadCloser wraps a gcs.StorageReader and ensures that the Read call reads the entire response up to the buffer size even if the wrapped read returns data in smaller chunks. diff --git a/internal/storage/mock/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go index a1eaec2d09..8d05af81b7 100644 --- a/internal/storage/mock/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -17,7 +17,7 @@ package mock import ( "context" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/stretchr/testify/mock" ) diff --git a/internal/storage/mock_bucket.go b/internal/storage/mock_bucket.go index 97b89c4943..18033c510b 100644 --- a/internal/storage/mock_bucket.go +++ b/internal/storage/mock_bucket.go @@ -11,7 +11,7 @@ import ( runtime "runtime" unsafe "unsafe" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" oglemock "github.com/jacobsa/oglemock" context "golang.org/x/net/context" ) diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index 5c09be7cac..eaede87563 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -27,10 +27,10 @@ import ( "cloud.google.com/go/storage/control/apiv2/controlpb" "cloud.google.com/go/storage/experimental" "github.com/googleapis/gax-go/v2" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "golang.org/x/net/context" option "google.golang.org/api/option" "google.golang.org/grpc" diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index aa907fae09..ca0ebe5d2c 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -22,9 +22,9 @@ import ( "time" "cloud.google.com/go/storage/control/apiv2/controlpb" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" diff --git a/internal/storage/storageutil/client.go b/internal/storage/storageutil/client.go index 77016b5ece..a7c410e527 100644 --- a/internal/storage/storageutil/client.go +++ b/internal/storage/storageutil/client.go @@ -21,8 +21,8 @@ import ( "strings" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/auth" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/auth" "golang.org/x/net/context" "golang.org/x/oauth2" ) diff --git a/internal/storage/storageutil/control_client.go b/internal/storage/storageutil/control_client.go index b4eceb4d62..c3961d1f30 100644 --- a/internal/storage/storageutil/control_client.go +++ b/internal/storage/storageutil/control_client.go @@ -22,7 +22,7 @@ import ( control "cloud.google.com/go/storage/control/apiv2" "github.com/googleapis/gax-go/v2" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "google.golang.org/api/option" "google.golang.org/grpc/codes" ) diff --git a/internal/storage/storageutil/create_empty_objects.go b/internal/storage/storageutil/create_empty_objects.go index 4419b0c35b..9683af442a 100644 --- a/internal/storage/storageutil/create_empty_objects.go +++ b/internal/storage/storageutil/create_empty_objects.go @@ -15,7 +15,7 @@ package storageutil import ( - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/storage/storageutil/create_object.go b/internal/storage/storageutil/create_object.go index cf506799d8..d262c83db3 100644 --- a/internal/storage/storageutil/create_object.go +++ b/internal/storage/storageutil/create_object.go @@ -17,7 +17,7 @@ package storageutil import ( "bytes" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/storage/storageutil/create_objects.go b/internal/storage/storageutil/create_objects.go index 1402bef5d9..ff9e948e62 100644 --- a/internal/storage/storageutil/create_objects.go +++ b/internal/storage/storageutil/create_objects.go @@ -15,7 +15,7 @@ package storageutil import ( - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" "golang.org/x/sync/errgroup" ) diff --git a/internal/storage/storageutil/custom_retry.go b/internal/storage/storageutil/custom_retry.go index 996f360883..956159b088 100644 --- a/internal/storage/storageutil/custom_retry.go +++ b/internal/storage/storageutil/custom_retry.go @@ -16,7 +16,7 @@ package storageutil import ( "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "google.golang.org/api/googleapi" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" diff --git a/internal/storage/storageutil/delete_all_objects.go b/internal/storage/storageutil/delete_all_objects.go index 3ec8b77575..4c656fbe15 100644 --- a/internal/storage/storageutil/delete_all_objects.go +++ b/internal/storage/storageutil/delete_all_objects.go @@ -15,7 +15,7 @@ package storageutil import ( - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" "golang.org/x/sync/errgroup" ) diff --git a/internal/storage/storageutil/delete_object.go b/internal/storage/storageutil/delete_object.go index 31deb5953d..50db4d91fc 100644 --- a/internal/storage/storageutil/delete_object.go +++ b/internal/storage/storageutil/delete_object.go @@ -15,7 +15,7 @@ package storageutil import ( - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/storage/storageutil/list_all.go b/internal/storage/storageutil/list_all.go index 7b72dbf8e6..a2e19a7019 100644 --- a/internal/storage/storageutil/list_all.go +++ b/internal/storage/storageutil/list_all.go @@ -15,7 +15,7 @@ package storageutil import ( - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/storage/storageutil/list_prefix.go b/internal/storage/storageutil/list_prefix.go index 501fa0618a..d68292e105 100644 --- a/internal/storage/storageutil/list_prefix.go +++ b/internal/storage/storageutil/list_prefix.go @@ -17,7 +17,7 @@ package storageutil import ( "fmt" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/storage/storageutil/object_attrs.go b/internal/storage/storageutil/object_attrs.go index 3ddc1232a9..46d78619f6 100644 --- a/internal/storage/storageutil/object_attrs.go +++ b/internal/storage/storageutil/object_attrs.go @@ -19,7 +19,7 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" storagev1 "google.golang.org/api/storage/v1" ) diff --git a/internal/storage/storageutil/object_attrs_test.go b/internal/storage/storageutil/object_attrs_test.go index 23a9c4a70f..7aaebb7be9 100644 --- a/internal/storage/storageutil/object_attrs_test.go +++ b/internal/storage/storageutil/object_attrs_test.go @@ -21,7 +21,7 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" . "github.com/jacobsa/ogletest" storagev1 "google.golang.org/api/storage/v1" ) diff --git a/internal/storage/storageutil/read_object.go b/internal/storage/storageutil/read_object.go index 8b1b8f5fc7..2e9359058a 100644 --- a/internal/storage/storageutil/read_object.go +++ b/internal/storage/storageutil/read_object.go @@ -18,7 +18,7 @@ import ( "fmt" "io" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "golang.org/x/net/context" ) diff --git a/internal/storage/storageutil/test_util.go b/internal/storage/storageutil/test_util.go index c71efd6d0b..0c935b8a1f 100644 --- a/internal/storage/storageutil/test_util.go +++ b/internal/storage/storageutil/test_util.go @@ -17,7 +17,7 @@ package storageutil import ( "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" ) const CustomEndpoint = "https://localhost:9000" diff --git a/internal/storage/storageutil/unsupported_object_util_test.go b/internal/storage/storageutil/unsupported_object_util_test.go index f23344ae49..3946552329 100644 --- a/internal/storage/storageutil/unsupported_object_util_test.go +++ b/internal/storage/storageutil/unsupported_object_util_test.go @@ -18,7 +18,7 @@ import ( "fmt" "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + . "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/internal/storage/testify_mock_bucket.go b/internal/storage/testify_mock_bucket.go index 61c67cc2ca..9ba7fdd7b9 100644 --- a/internal/storage/testify_mock_bucket.go +++ b/internal/storage/testify_mock_bucket.go @@ -17,7 +17,7 @@ package storage import ( "context" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/stretchr/testify/mock" ) diff --git a/internal/util/sizeof.go b/internal/util/sizeof.go index 422d215be9..f4a5a74d45 100644 --- a/internal/util/sizeof.go +++ b/internal/util/sizeof.go @@ -17,7 +17,7 @@ package util import ( "reflect" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "google.golang.org/api/googleapi" storagev1 "google.golang.org/api/storage/v1" ) diff --git a/internal/util/sizeof_test.go b/internal/util/sizeof_test.go index 3bbd61675e..14dcc3bd97 100644 --- a/internal/util/sizeof_test.go +++ b/internal/util/sizeof_test.go @@ -20,7 +20,7 @@ import ( "time" "unsafe" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "google.golang.org/api/googleapi" diff --git a/main.go b/main.go index f8c72c54ac..0ed319a079 100644 --- a/main.go +++ b/main.go @@ -22,10 +22,10 @@ package main import ( "log" - "github.com/googlecloudplatform/gcsfuse/v2/cmd" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/internal/perf" + "github.com/googlecloudplatform/gcsfuse/v3/cmd" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/perf" ) func logPanic() { diff --git a/tools/build_gcsfuse/main.go b/tools/build_gcsfuse/main.go index a0e83440a1..5f0e9514d2 100644 --- a/tools/build_gcsfuse/main.go +++ b/tools/build_gcsfuse/main.go @@ -118,11 +118,11 @@ func buildBinaries(dstDir, srcDir, version string, buildArgs []string) (err erro outputPath string }{ { - "github.com/googlecloudplatform/gcsfuse/v2", + "github.com/googlecloudplatform/gcsfuse/v3", "bin/gcsfuse", }, { - "github.com/googlecloudplatform/gcsfuse/v2/tools/mount_gcsfuse", + "github.com/googlecloudplatform/gcsfuse/v3/tools/mount_gcsfuse", path.Join("sbin", mountHelperName), }, } @@ -143,7 +143,7 @@ func buildBinaries(dstDir, srcDir, version string, buildArgs []string) (err erro cmd.Args = append( cmd.Args, "-ldflags", - fmt.Sprintf("-X github.com/googlecloudplatform/gcsfuse/v2/common.gcsfuseVersion=%s", version), + fmt.Sprintf("-X github.com/googlecloudplatform/gcsfuse/v3/common.gcsfuseVersion=%s", version), ) cmd.Args = append(cmd.Args, buildArgs...) } diff --git a/tools/integration_tests/benchmarking/benchmark_delete_test.go b/tools/integration_tests/benchmarking/benchmark_delete_test.go index 61f10375cc..5ad1a2bdfd 100644 --- a/tools/integration_tests/benchmarking/benchmark_delete_test.go +++ b/tools/integration_tests/benchmarking/benchmark_delete_test.go @@ -22,8 +22,8 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/benchmark_setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/benchmark_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/benchmarking/benchmark_rename_test.go b/tools/integration_tests/benchmarking/benchmark_rename_test.go index 29af67c222..2a7764b2f8 100644 --- a/tools/integration_tests/benchmarking/benchmark_rename_test.go +++ b/tools/integration_tests/benchmarking/benchmark_rename_test.go @@ -22,8 +22,8 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/benchmark_setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/benchmark_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) type benchmarkRenameTest struct { diff --git a/tools/integration_tests/benchmarking/benchmark_stat_test.go b/tools/integration_tests/benchmarking/benchmark_stat_test.go index a5062cb224..c8341cc320 100644 --- a/tools/integration_tests/benchmarking/benchmark_stat_test.go +++ b/tools/integration_tests/benchmarking/benchmark_stat_test.go @@ -20,9 +20,9 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/benchmark_setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/benchmark_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/benchmarking/setup_test.go b/tools/integration_tests/benchmarking/setup_test.go index e97a496939..f194ca3916 100644 --- a/tools/integration_tests/benchmarking/setup_test.go +++ b/tools/integration_tests/benchmarking/setup_test.go @@ -27,10 +27,10 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/cloud_profiler/cloud_profiler_test.go b/tools/integration_tests/cloud_profiler/cloud_profiler_test.go index ab0e977af6..afcfed60f9 100644 --- a/tools/integration_tests/cloud_profiler/cloud_profiler_test.go +++ b/tools/integration_tests/cloud_profiler/cloud_profiler_test.go @@ -28,10 +28,10 @@ import ( "cloud.google.com/go/storage" "github.com/google/uuid" - "github.com/googlecloudplatform/gcsfuse/v2/internal/logger" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/concurrent_operations/concurrent_listing_test.go b/tools/integration_tests/concurrent_operations/concurrent_listing_test.go index a4db528bf0..98f6d1a590 100644 --- a/tools/integration_tests/concurrent_operations/concurrent_listing_test.go +++ b/tools/integration_tests/concurrent_operations/concurrent_listing_test.go @@ -22,9 +22,9 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/concurrent_operations/setup_test.go b/tools/integration_tests/concurrent_operations/setup_test.go index cdf50e2ef7..13d2dde58a 100644 --- a/tools/integration_tests/concurrent_operations/setup_test.go +++ b/tools/integration_tests/concurrent_operations/setup_test.go @@ -22,11 +22,11 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/only_dir_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/emulator_tests/proxy_server/main_test.go b/tools/integration_tests/emulator_tests/proxy_server/main_test.go index d5b71664c3..1c834bac4a 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/main_test.go +++ b/tools/integration_tests/emulator_tests/proxy_server/main_test.go @@ -20,7 +20,7 @@ import ( "net/http/httptest" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" ) diff --git a/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go b/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go index 86a83a58cb..773bdda397 100644 --- a/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go +++ b/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go @@ -24,9 +24,9 @@ import ( "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - emulator_tests "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/emulator_tests/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + emulator_tests "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/emulator_tests/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" ) diff --git a/tools/integration_tests/emulator_tests/read_stall/setup_test.go b/tools/integration_tests/emulator_tests/read_stall/setup_test.go index d1858e16bd..68345dfc70 100644 --- a/tools/integration_tests/emulator_tests/read_stall/setup_test.go +++ b/tools/integration_tests/emulator_tests/read_stall/setup_test.go @@ -19,8 +19,8 @@ import ( "os" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) var ( diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go index 1fe479a389..673bdeb1a0 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/common_failure_test.go @@ -20,10 +20,10 @@ import ( "os" "cloud.google.com/go/storage" - emulator_tests "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/emulator_tests/util" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + emulator_tests "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/emulator_tests/util" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go index 27d589f041..95c5085fde 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go @@ -18,8 +18,8 @@ import ( "path" "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/suite" ) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go index 34f65a0cc0..9850def934 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go @@ -17,7 +17,7 @@ package streaming_writes_failure import ( "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/stretchr/testify/suite" ) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go index e3294b9a95..1dabdb2f98 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/streaming_writes_failure_test.go @@ -19,8 +19,8 @@ import ( "os" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/emulator_tests/util/test_helper.go b/tools/integration_tests/emulator_tests/util/test_helper.go index ae1f468a90..352bd66f30 100644 --- a/tools/integration_tests/emulator_tests/util/test_helper.go +++ b/tools/integration_tests/emulator_tests/util/test_helper.go @@ -28,7 +28,7 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" ) const PortAndProxyProcessIdInfoRegex = `Listening Proxy Server On Port \[(\d+)\] with Process ID \[(\d+)\]` diff --git a/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go b/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go index 74dde554d2..dca243f97a 100644 --- a/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/write_stall_test.go @@ -19,8 +19,8 @@ import ( "os" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) var ( diff --git a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go index b8cb54736e..820f64616f 100644 --- a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go @@ -21,9 +21,9 @@ import ( "testing" "time" - emulator_tests "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/emulator_tests/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + emulator_tests "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/emulator_tests/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/explicit_dir/explicit_dir_test.go b/tools/integration_tests/explicit_dir/explicit_dir_test.go index 42e9ed94dc..2620873dfe 100644 --- a/tools/integration_tests/explicit_dir/explicit_dir_test.go +++ b/tools/integration_tests/explicit_dir/explicit_dir_test.go @@ -22,9 +22,9 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" ) const DirForExplicitDirTests = "dirForExplicitDirTests" diff --git a/tools/integration_tests/explicit_dir/list_test.go b/tools/integration_tests/explicit_dir/list_test.go index 605ebc15a6..b5f9434f43 100644 --- a/tools/integration_tests/explicit_dir/list_test.go +++ b/tools/integration_tests/explicit_dir/list_test.go @@ -22,8 +22,8 @@ import ( "path/filepath" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" ) func TestListOnlyExplicitObjectsFromBucket(t *testing.T) { diff --git a/tools/integration_tests/grpc_validation/grpc_validation_test.go b/tools/integration_tests/grpc_validation/grpc_validation_test.go index 7391b1e4dd..a40ce727c1 100644 --- a/tools/integration_tests/grpc_validation/grpc_validation_test.go +++ b/tools/integration_tests/grpc_validation/grpc_validation_test.go @@ -20,11 +20,11 @@ import ( "testing" "time" - client_util "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + client_util "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" diff --git a/tools/integration_tests/grpc_validation/setup_test.go b/tools/integration_tests/grpc_validation/setup_test.go index a0bf3127a1..4590ab371f 100644 --- a/tools/integration_tests/grpc_validation/setup_test.go +++ b/tools/integration_tests/grpc_validation/setup_test.go @@ -24,8 +24,8 @@ import ( "time" "cloud.google.com/go/storage" - client_util "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + client_util "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "go.opentelemetry.io/contrib/detectors/gcp" "go.opentelemetry.io/otel/sdk/resource" ) diff --git a/tools/integration_tests/gzip/gzip_test.go b/tools/integration_tests/gzip/gzip_test.go index 93e13e2ec1..389fe8b99e 100644 --- a/tools/integration_tests/gzip/gzip_test.go +++ b/tools/integration_tests/gzip/gzip_test.go @@ -25,10 +25,10 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/gzip/read_gzip_test.go b/tools/integration_tests/gzip/read_gzip_test.go index fee1a9cf71..f20389085a 100644 --- a/tools/integration_tests/gzip/read_gzip_test.go +++ b/tools/integration_tests/gzip/read_gzip_test.go @@ -24,9 +24,9 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) // Verify that the passed file exists on the GCS test-bucket and in the mounted bucket diff --git a/tools/integration_tests/gzip/write_gzip_test.go b/tools/integration_tests/gzip/write_gzip_test.go index 8c14ad7e5c..94468dae33 100644 --- a/tools/integration_tests/gzip/write_gzip_test.go +++ b/tools/integration_tests/gzip/write_gzip_test.go @@ -19,9 +19,9 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) // Size of the overwritten content created in bytes. diff --git a/tools/integration_tests/implicit_dir/delete_test.go b/tools/integration_tests/implicit_dir/delete_test.go index 7e81fa110a..43816a46b1 100644 --- a/tools/integration_tests/implicit_dir/delete_test.go +++ b/tools/integration_tests/implicit_dir/delete_test.go @@ -19,9 +19,9 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" ) // Directory Structure diff --git a/tools/integration_tests/implicit_dir/implicit_dir_test.go b/tools/integration_tests/implicit_dir/implicit_dir_test.go index d7410cec45..ff759a1066 100644 --- a/tools/integration_tests/implicit_dir/implicit_dir_test.go +++ b/tools/integration_tests/implicit_dir/implicit_dir_test.go @@ -23,9 +23,9 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" ) const ExplicitDirInImplicitDir = "explicitDirInImplicitDir" diff --git a/tools/integration_tests/implicit_dir/list_test.go b/tools/integration_tests/implicit_dir/list_test.go index aa018eb5b3..a2867ce393 100644 --- a/tools/integration_tests/implicit_dir/list_test.go +++ b/tools/integration_tests/implicit_dir/list_test.go @@ -23,8 +23,8 @@ import ( "path/filepath" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" ) func TestListImplicitObjectsFromBucket(t *testing.T) { diff --git a/tools/integration_tests/implicit_dir/local_file_test.go b/tools/integration_tests/implicit_dir/local_file_test.go index c94a64b03c..1056f59998 100644 --- a/tools/integration_tests/implicit_dir/local_file_test.go +++ b/tools/integration_tests/implicit_dir/local_file_test.go @@ -19,9 +19,9 @@ import ( "path/filepath" "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/inactive_stream_timeout/setup_test.go b/tools/integration_tests/inactive_stream_timeout/setup_test.go index 2ef759bcd1..7a85c4086b 100644 --- a/tools/integration_tests/inactive_stream_timeout/setup_test.go +++ b/tools/integration_tests/inactive_stream_timeout/setup_test.go @@ -26,12 +26,12 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/only_dir_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go b/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go index 87b9d352e7..614d42756f 100644 --- a/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go +++ b/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go @@ -22,9 +22,9 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go b/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go index 2a58671d54..72774fda60 100644 --- a/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go +++ b/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go @@ -22,9 +22,9 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/interrupt/git_clone_test.go b/tools/integration_tests/interrupt/git_clone_test.go index 8531f70107..5cf30ee21c 100644 --- a/tools/integration_tests/interrupt/git_clone_test.go +++ b/tools/integration_tests/interrupt/git_clone_test.go @@ -22,10 +22,10 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) const ( diff --git a/tools/integration_tests/interrupt/interrupt_test.go b/tools/integration_tests/interrupt/interrupt_test.go index 497d6a6745..65d51db26f 100644 --- a/tools/integration_tests/interrupt/interrupt_test.go +++ b/tools/integration_tests/interrupt/interrupt_test.go @@ -22,9 +22,9 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go index e05e7539c8..e2be0264d0 100644 --- a/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go @@ -20,10 +20,10 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go index e499017ab1..384785484f 100644 --- a/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go @@ -21,10 +21,10 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go index c6d88122e2..fcbbb63791 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go @@ -20,10 +20,10 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go index 2f6196a7c3..ef1e3aee08 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go @@ -21,10 +21,10 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/kernel_list_cache/setup_test.go b/tools/integration_tests/kernel_list_cache/setup_test.go index 10dc3e0c1f..f82c0c64a4 100644 --- a/tools/integration_tests/kernel_list_cache/setup_test.go +++ b/tools/integration_tests/kernel_list_cache/setup_test.go @@ -22,11 +22,11 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/only_dir_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go index 5eafe85aab..a16042bdbd 100644 --- a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go +++ b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go @@ -29,10 +29,10 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "golang.org/x/sync/errgroup" "github.com/stretchr/testify/assert" diff --git a/tools/integration_tests/list_large_dir/list_large_dir_test.go b/tools/integration_tests/list_large_dir/list_large_dir_test.go index 5958e855e5..53860cdda9 100644 --- a/tools/integration_tests/list_large_dir/list_large_dir_test.go +++ b/tools/integration_tests/list_large_dir/list_large_dir_test.go @@ -22,9 +22,9 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const prefixFileInDirectoryWithTwelveThousandFiles = "fileInDirectoryWithTwelveThousandFiles" diff --git a/tools/integration_tests/local_file/create_file.go b/tools/integration_tests/local_file/create_file.go index 828392df36..f4a46565bf 100644 --- a/tools/integration_tests/local_file/create_file.go +++ b/tools/integration_tests/local_file/create_file.go @@ -18,9 +18,9 @@ package local_file import ( "path" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/local_file/edit_file_test.go b/tools/integration_tests/local_file/edit_file_test.go index 4edc7ce017..525836decd 100644 --- a/tools/integration_tests/local_file/edit_file_test.go +++ b/tools/integration_tests/local_file/edit_file_test.go @@ -18,9 +18,9 @@ import ( "os" "path" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/local_file/local_file_helper.go b/tools/integration_tests/local_file/local_file_helper.go index 9bd784d020..fac5fb6510 100644 --- a/tools/integration_tests/local_file/local_file_helper.go +++ b/tools/integration_tests/local_file/local_file_helper.go @@ -20,8 +20,8 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" ) const ( diff --git a/tools/integration_tests/local_file/local_file_suite.go b/tools/integration_tests/local_file/local_file_suite.go index e946876055..38a0067b1f 100644 --- a/tools/integration_tests/local_file/local_file_suite.go +++ b/tools/integration_tests/local_file/local_file_suite.go @@ -15,7 +15,7 @@ package local_file import ( - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_suite" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" "github.com/stretchr/testify/suite" ) diff --git a/tools/integration_tests/local_file/read_dir.go b/tools/integration_tests/local_file/read_dir.go index 945a2bfc1c..1dc35e230d 100644 --- a/tools/integration_tests/local_file/read_dir.go +++ b/tools/integration_tests/local_file/read_dir.go @@ -25,9 +25,9 @@ import ( "testing" "time" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/local_file/read_file.go b/tools/integration_tests/local_file/read_file.go index 0741ece184..df8a1d0ce8 100644 --- a/tools/integration_tests/local_file/read_file.go +++ b/tools/integration_tests/local_file/read_file.go @@ -16,8 +16,8 @@ package local_file import ( - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/local_file/remove_dir.go b/tools/integration_tests/local_file/remove_dir.go index 8a04eda16a..6576cdaee4 100644 --- a/tools/integration_tests/local_file/remove_dir.go +++ b/tools/integration_tests/local_file/remove_dir.go @@ -18,9 +18,9 @@ package local_file import ( "path" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/local_file/rename.go b/tools/integration_tests/local_file/rename.go index 8f31976bc1..95d96071ec 100644 --- a/tools/integration_tests/local_file/rename.go +++ b/tools/integration_tests/local_file/rename.go @@ -21,9 +21,9 @@ import ( "strings" "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/local_file/setup_test.go b/tools/integration_tests/local_file/setup_test.go index 835c262de0..143b16672b 100644 --- a/tools/integration_tests/local_file/setup_test.go +++ b/tools/integration_tests/local_file/setup_test.go @@ -23,11 +23,11 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/only_dir_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/suite" ) diff --git a/tools/integration_tests/local_file/stat_file.go b/tools/integration_tests/local_file/stat_file.go index 14f4f59304..7626880ee3 100644 --- a/tools/integration_tests/local_file/stat_file.go +++ b/tools/integration_tests/local_file/stat_file.go @@ -18,10 +18,10 @@ package local_file import ( "os" - "github.com/googlecloudplatform/gcsfuse/v2/internal/fs/inode" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func (t *CommonLocalFileTestSuite) TestStatOnLocalFile() { diff --git a/tools/integration_tests/local_file/sym_link.go b/tools/integration_tests/local_file/sym_link.go index a74d692475..1b4e21c6f8 100644 --- a/tools/integration_tests/local_file/sym_link.go +++ b/tools/integration_tests/local_file/sym_link.go @@ -21,9 +21,9 @@ import ( "strings" "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func createAndVerifySymLink(t *testing.T) (filePath, symlink string, fh *os.File) { diff --git a/tools/integration_tests/local_file/unlinked_file.go b/tools/integration_tests/local_file/unlinked_file.go index 27e64fce57..38c64a2b91 100644 --- a/tools/integration_tests/local_file/unlinked_file.go +++ b/tools/integration_tests/local_file/unlinked_file.go @@ -18,9 +18,9 @@ package local_file import ( "path" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func (t *CommonLocalFileTestSuite) TestStatOnUnlinkedLocalFile() { diff --git a/tools/integration_tests/local_file/write_file.go b/tools/integration_tests/local_file/write_file.go index f8045ee879..55efed393a 100644 --- a/tools/integration_tests/local_file/write_file.go +++ b/tools/integration_tests/local_file/write_file.go @@ -16,9 +16,9 @@ package local_file import ( - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func (t *CommonLocalFileTestSuite) TestMultipleWritesToLocalFile() { diff --git a/tools/integration_tests/log_rotation/log_rotation_test.go b/tools/integration_tests/log_rotation/log_rotation_test.go index bbcdfbc10f..1fb04553b9 100644 --- a/tools/integration_tests/log_rotation/log_rotation_test.go +++ b/tools/integration_tests/log_rotation/log_rotation_test.go @@ -24,9 +24,9 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/log_rotation/logrotate_logfile_test.go b/tools/integration_tests/log_rotation/logrotate_logfile_test.go index 530ec9f5cd..fc7485b692 100644 --- a/tools/integration_tests/log_rotation/logrotate_logfile_test.go +++ b/tools/integration_tests/log_rotation/logrotate_logfile_test.go @@ -23,8 +23,8 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/managed_folders/admin_permissions_test.go b/tools/integration_tests/managed_folders/admin_permissions_test.go index f202494e4e..6361e362c5 100644 --- a/tools/integration_tests/managed_folders/admin_permissions_test.go +++ b/tools/integration_tests/managed_folders/admin_permissions_test.go @@ -26,11 +26,11 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/creds_tests" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/creds_tests" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) // ////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go b/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go index 714893e0c5..8724267036 100644 --- a/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go +++ b/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go @@ -24,11 +24,11 @@ import ( "path/filepath" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/managed_folders/managed_folders_test.go b/tools/integration_tests/managed_folders/managed_folders_test.go index b335daf581..2c4865a99e 100644 --- a/tools/integration_tests/managed_folders/managed_folders_test.go +++ b/tools/integration_tests/managed_folders/managed_folders_test.go @@ -24,12 +24,12 @@ import ( "cloud.google.com/go/storage" control "cloud.google.com/go/storage/control/apiv2" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/only_dir_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/managed_folders/test_helper.go b/tools/integration_tests/managed_folders/test_helper.go index de364ee1bf..a461692df1 100644 --- a/tools/integration_tests/managed_folders/test_helper.go +++ b/tools/integration_tests/managed_folders/test_helper.go @@ -28,9 +28,9 @@ import ( "cloud.google.com/go/storage" control "cloud.google.com/go/storage/control/apiv2" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/managed_folders/view_permissions_test.go b/tools/integration_tests/managed_folders/view_permissions_test.go index 88d68cb467..7d4a9feb52 100644 --- a/tools/integration_tests/managed_folders/view_permissions_test.go +++ b/tools/integration_tests/managed_folders/view_permissions_test.go @@ -25,10 +25,10 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/creds_tests" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/creds_tests" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) const ( diff --git a/tools/integration_tests/monitoring/prom_test.go b/tools/integration_tests/monitoring/prom_test.go index 2ce3fee8d3..e8669c7f26 100644 --- a/tools/integration_tests/monitoring/prom_test.go +++ b/tools/integration_tests/monitoring/prom_test.go @@ -24,10 +24,10 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/util" promclient "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" "github.com/stretchr/testify/assert" diff --git a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go index cff2461812..e9f0d238c7 100644 --- a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go +++ b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go @@ -22,10 +22,10 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/tools/integration_tests/mount_timeout/mount_timeout_test.go b/tools/integration_tests/mount_timeout/mount_timeout_test.go index 8b5dae6684..6f90279040 100644 --- a/tools/integration_tests/mount_timeout/mount_timeout_test.go +++ b/tools/integration_tests/mount_timeout/mount_timeout_test.go @@ -25,8 +25,8 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/util" "go.opentelemetry.io/contrib/detectors/gcp" "go.opentelemetry.io/otel/sdk/resource" ) diff --git a/tools/integration_tests/mounting/gcsfuse_darwin_test.go b/tools/integration_tests/mounting/gcsfuse_darwin_test.go index e237b4932f..a41925d39d 100644 --- a/tools/integration_tests/mounting/gcsfuse_darwin_test.go +++ b/tools/integration_tests/mounting/gcsfuse_darwin_test.go @@ -22,8 +22,8 @@ import ( "math" "syscall" - "github.com/googlecloudplatform/gcsfuse/v2/internal/canned" - "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/canned" + "github.com/googlecloudplatform/gcsfuse/v3/tools/util" . "github.com/jacobsa/ogletest" ) diff --git a/tools/integration_tests/mounting/gcsfuse_linux_test.go b/tools/integration_tests/mounting/gcsfuse_linux_test.go index e4b0254b04..3bf0c78bc9 100644 --- a/tools/integration_tests/mounting/gcsfuse_linux_test.go +++ b/tools/integration_tests/mounting/gcsfuse_linux_test.go @@ -22,8 +22,8 @@ import ( "math" "syscall" - "github.com/googlecloudplatform/gcsfuse/v2/internal/canned" - "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/canned" + "github.com/googlecloudplatform/gcsfuse/v3/tools/util" . "github.com/jacobsa/ogletest" ) diff --git a/tools/integration_tests/mounting/gcsfuse_test.go b/tools/integration_tests/mounting/gcsfuse_test.go index 6eb4f7d94c..539be7e74a 100644 --- a/tools/integration_tests/mounting/gcsfuse_test.go +++ b/tools/integration_tests/mounting/gcsfuse_test.go @@ -25,8 +25,8 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/canned" - "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/canned" + "github.com/googlecloudplatform/gcsfuse/v3/tools/util" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" diff --git a/tools/integration_tests/mounting/main_test.go b/tools/integration_tests/mounting/main_test.go index 7f0b746d40..92f5a64d77 100644 --- a/tools/integration_tests/mounting/main_test.go +++ b/tools/integration_tests/mounting/main_test.go @@ -21,8 +21,8 @@ import ( "runtime" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/util" ) // A directory containing outputs created by build_gcsfuse, set up and deleted diff --git a/tools/integration_tests/mounting/mount_helper_test.go b/tools/integration_tests/mounting/mount_helper_test.go index 68a4ad0e53..984e2a0379 100644 --- a/tools/integration_tests/mounting/mount_helper_test.go +++ b/tools/integration_tests/mounting/mount_helper_test.go @@ -22,8 +22,8 @@ import ( "runtime" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/canned" - "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/canned" + "github.com/googlecloudplatform/gcsfuse/v3/tools/util" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" ) diff --git a/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go index 7a4620314b..7d8e3956ba 100644 --- a/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go +++ b/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go @@ -20,10 +20,10 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" ) diff --git a/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go index d99871df26..056b2056bc 100644 --- a/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go +++ b/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go @@ -21,10 +21,10 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" ) diff --git a/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go index 2fb25cd053..40a7e4718d 100644 --- a/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go +++ b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go @@ -21,10 +21,10 @@ import ( "syscall" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/negative_stat_cache/setup_test.go b/tools/integration_tests/negative_stat_cache/setup_test.go index 15a46afdf6..2b104c4d1c 100644 --- a/tools/integration_tests/negative_stat_cache/setup_test.go +++ b/tools/integration_tests/negative_stat_cache/setup_test.go @@ -23,11 +23,11 @@ import ( "cloud.google.com/go/storage" control "cloud.google.com/go/storage/control/apiv2" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/only_dir_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/operations/copy_dir_test.go b/tools/integration_tests/operations/copy_dir_test.go index 80eb98437f..4ac4421d59 100644 --- a/tools/integration_tests/operations/copy_dir_test.go +++ b/tools/integration_tests/operations/copy_dir_test.go @@ -21,8 +21,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) // Create below directory structure. diff --git a/tools/integration_tests/operations/copy_file_test.go b/tools/integration_tests/operations/copy_file_test.go index 2d6ceb57c0..dad991947e 100644 --- a/tools/integration_tests/operations/copy_file_test.go +++ b/tools/integration_tests/operations/copy_file_test.go @@ -20,8 +20,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func TestCopyFile(t *testing.T) { diff --git a/tools/integration_tests/operations/create_three_level_dir_test.go b/tools/integration_tests/operations/create_three_level_dir_test.go index a264009d20..2d87115b82 100644 --- a/tools/integration_tests/operations/create_three_level_dir_test.go +++ b/tools/integration_tests/operations/create_three_level_dir_test.go @@ -24,8 +24,8 @@ import ( "path/filepath" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func TestCreateThreeLevelDirectories(t *testing.T) { diff --git a/tools/integration_tests/operations/delete_dir_test.go b/tools/integration_tests/operations/delete_dir_test.go index e89fd88cb5..8eab7fd244 100644 --- a/tools/integration_tests/operations/delete_dir_test.go +++ b/tools/integration_tests/operations/delete_dir_test.go @@ -20,8 +20,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func TestDeleteEmptyExplicitDir(t *testing.T) { diff --git a/tools/integration_tests/operations/delete_file_test.go b/tools/integration_tests/operations/delete_file_test.go index 716f1e6126..ac6fad0474 100644 --- a/tools/integration_tests/operations/delete_file_test.go +++ b/tools/integration_tests/operations/delete_file_test.go @@ -20,8 +20,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const DirNameInTestBucket = "A" // testBucket/A diff --git a/tools/integration_tests/operations/file_and_dir_attributes_test.go b/tools/integration_tests/operations/file_and_dir_attributes_test.go index 23704d8458..b6a1fdc492 100644 --- a/tools/integration_tests/operations/file_and_dir_attributes_test.go +++ b/tools/integration_tests/operations/file_and_dir_attributes_test.go @@ -21,8 +21,8 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const DirAttrTest = "dirAttrTest" diff --git a/tools/integration_tests/operations/list_dir_test.go b/tools/integration_tests/operations/list_dir_test.go index 7e02e90c37..5d5bd46324 100644 --- a/tools/integration_tests/operations/list_dir_test.go +++ b/tools/integration_tests/operations/list_dir_test.go @@ -24,8 +24,8 @@ import ( "path/filepath" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func createDirectoryStructureForTest(t *testing.T) { diff --git a/tools/integration_tests/operations/move_file_test.go b/tools/integration_tests/operations/move_file_test.go index 4e16fc3657..eb2eb4a81d 100644 --- a/tools/integration_tests/operations/move_file_test.go +++ b/tools/integration_tests/operations/move_file_test.go @@ -21,8 +21,8 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" ) diff --git a/tools/integration_tests/operations/operations_test.go b/tools/integration_tests/operations/operations_test.go index e92b44c198..44daaa290f 100644 --- a/tools/integration_tests/operations/operations_test.go +++ b/tools/integration_tests/operations/operations_test.go @@ -24,13 +24,13 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/creds_tests" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/persistent_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/creds_tests" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/only_dir_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/persistent_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const DirForOperationTests = "dirForOperationsTest" diff --git a/tools/integration_tests/operations/parallel_dirops_test.go b/tools/integration_tests/operations/parallel_dirops_test.go index 609a44b09e..c5f68a82ec 100644 --- a/tools/integration_tests/operations/parallel_dirops_test.go +++ b/tools/integration_tests/operations/parallel_dirops_test.go @@ -25,8 +25,8 @@ import ( "sync" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" ) diff --git a/tools/integration_tests/operations/read_test.go b/tools/integration_tests/operations/read_test.go index f31bb127aa..86d2af13bf 100644 --- a/tools/integration_tests/operations/read_test.go +++ b/tools/integration_tests/operations/read_test.go @@ -19,8 +19,8 @@ import ( "os" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func TestReadAfterWrite(t *testing.T) { diff --git a/tools/integration_tests/operations/rename_file_test.go b/tools/integration_tests/operations/rename_file_test.go index c105b8efa2..cc3bb470ef 100644 --- a/tools/integration_tests/operations/rename_file_test.go +++ b/tools/integration_tests/operations/rename_file_test.go @@ -20,8 +20,8 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" ) diff --git a/tools/integration_tests/operations/stat_file_test.go b/tools/integration_tests/operations/stat_file_test.go index 424812d518..a706f97416 100644 --- a/tools/integration_tests/operations/stat_file_test.go +++ b/tools/integration_tests/operations/stat_file_test.go @@ -19,7 +19,7 @@ import ( "syscall" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/operations/write_test.go b/tools/integration_tests/operations/write_test.go index c0dc0886b9..0aa793fcf3 100644 --- a/tools/integration_tests/operations/write_test.go +++ b/tools/integration_tests/operations/write_test.go @@ -25,11 +25,11 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const tempFileName = "tmpFile" diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go index 6f7f7b2525..ee664033ea 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go @@ -23,11 +23,11 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go index e65677fd6e..cff9e4dff0 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go @@ -22,11 +22,11 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/read_cache/disabled_cache_ttl_test.go b/tools/integration_tests/read_cache/disabled_cache_ttl_test.go index 40b312dced..26c9c25f31 100644 --- a/tools/integration_tests/read_cache/disabled_cache_ttl_test.go +++ b/tools/integration_tests/read_cache/disabled_cache_ttl_test.go @@ -19,13 +19,13 @@ import ( "log" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/read_cache/helpers_test.go b/tools/integration_tests/read_cache/helpers_test.go index 227a78af60..3e9fcdbe6b 100644 --- a/tools/integration_tests/read_cache/helpers_test.go +++ b/tools/integration_tests/read_cache/helpers_test.go @@ -27,10 +27,10 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/read_cache/job_chunk_test.go b/tools/integration_tests/read_cache/job_chunk_test.go index 39e515a360..d30c99a1d3 100644 --- a/tools/integration_tests/read_cache/job_chunk_test.go +++ b/tools/integration_tests/read_cache/job_chunk_test.go @@ -22,12 +22,12 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/read_cache/local_modification_test.go b/tools/integration_tests/read_cache/local_modification_test.go index 504c73fb2f..316f39ab70 100644 --- a/tools/integration_tests/read_cache/local_modification_test.go +++ b/tools/integration_tests/read_cache/local_modification_test.go @@ -21,11 +21,11 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) // ////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/read_cache/range_read_test.go b/tools/integration_tests/read_cache/range_read_test.go index b7d25ee724..cd97ae37a1 100644 --- a/tools/integration_tests/read_cache/range_read_test.go +++ b/tools/integration_tests/read_cache/range_read_test.go @@ -19,13 +19,13 @@ import ( "log" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/read_cache/read_only_test.go b/tools/integration_tests/read_cache/read_only_test.go index 4d07bfe14c..219d0ed17e 100644 --- a/tools/integration_tests/read_cache/read_only_test.go +++ b/tools/integration_tests/read_cache/read_only_test.go @@ -20,11 +20,11 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/read_cache/remount_test.go b/tools/integration_tests/read_cache/remount_test.go index f8a54de6c4..d1848f6676 100644 --- a/tools/integration_tests/read_cache/remount_test.go +++ b/tools/integration_tests/read_cache/remount_test.go @@ -21,14 +21,14 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/read_cache/setup_test.go b/tools/integration_tests/read_cache/setup_test.go index 4d35d78fe9..40ad9176b2 100644 --- a/tools/integration_tests/read_cache/setup_test.go +++ b/tools/integration_tests/read_cache/setup_test.go @@ -23,12 +23,12 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/dynamic_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/only_dir_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/read_cache/small_cache_ttl_test.go b/tools/integration_tests/read_cache/small_cache_ttl_test.go index 5aeee77c9a..928b069d51 100644 --- a/tools/integration_tests/read_cache/small_cache_ttl_test.go +++ b/tools/integration_tests/read_cache/small_cache_ttl_test.go @@ -23,11 +23,11 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go b/tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go index 72cb581cee..92595178c3 100644 --- a/tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go +++ b/tools/integration_tests/read_gcs_algo/concurrent_read_same_file_test.go @@ -21,8 +21,8 @@ import ( "os" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "golang.org/x/sync/errgroup" ) diff --git a/tools/integration_tests/read_gcs_algo/read_gcs_algo_test.go b/tools/integration_tests/read_gcs_algo/read_gcs_algo_test.go index 6276802c00..f10a67d4a5 100644 --- a/tools/integration_tests/read_gcs_algo/read_gcs_algo_test.go +++ b/tools/integration_tests/read_gcs_algo/read_gcs_algo_test.go @@ -18,8 +18,8 @@ import ( "os" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const OneMB = 1024 * 1024 diff --git a/tools/integration_tests/read_gcs_algo/seq_diff_block_size_test.go b/tools/integration_tests/read_gcs_algo/seq_diff_block_size_test.go index ba043e33df..4c5524a7f7 100644 --- a/tools/integration_tests/read_gcs_algo/seq_diff_block_size_test.go +++ b/tools/integration_tests/read_gcs_algo/seq_diff_block_size_test.go @@ -17,8 +17,8 @@ package read_gcs_algo import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) type testCase struct { diff --git a/tools/integration_tests/read_gcs_algo/seq_to_ran_to_seq_read_test.go b/tools/integration_tests/read_gcs_algo/seq_to_ran_to_seq_read_test.go index 473fee8f86..fffe59d81c 100644 --- a/tools/integration_tests/read_gcs_algo/seq_to_ran_to_seq_read_test.go +++ b/tools/integration_tests/read_gcs_algo/seq_to_ran_to_seq_read_test.go @@ -17,8 +17,8 @@ package read_gcs_algo import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func TestSeqReadThenRandomThenSeqRead(t *testing.T) { diff --git a/tools/integration_tests/read_large_files/concurrent_read_files_test.go b/tools/integration_tests/read_large_files/concurrent_read_files_test.go index 34a5f26d63..900a54bf29 100644 --- a/tools/integration_tests/read_large_files/concurrent_read_files_test.go +++ b/tools/integration_tests/read_large_files/concurrent_read_files_test.go @@ -19,8 +19,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "golang.org/x/sync/errgroup" ) diff --git a/tools/integration_tests/read_large_files/random_read_large_file_test.go b/tools/integration_tests/read_large_files/random_read_large_file_test.go index 90286928ab..043efd43bf 100644 --- a/tools/integration_tests/read_large_files/random_read_large_file_test.go +++ b/tools/integration_tests/read_large_files/random_read_large_file_test.go @@ -18,8 +18,8 @@ import ( "math/rand" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func TestReadLargeFileRandomly(t *testing.T) { diff --git a/tools/integration_tests/read_large_files/read_large_files_test.go b/tools/integration_tests/read_large_files/read_large_files_test.go index e6713953f8..1b9c3ff00a 100644 --- a/tools/integration_tests/read_large_files/read_large_files_test.go +++ b/tools/integration_tests/read_large_files/read_large_files_test.go @@ -24,9 +24,9 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const OneMB = 1024 * 1024 diff --git a/tools/integration_tests/read_large_files/seq_read_large_file_test.go b/tools/integration_tests/read_large_files/seq_read_large_file_test.go index aa48e73f55..4847761e83 100644 --- a/tools/integration_tests/read_large_files/seq_read_large_file_test.go +++ b/tools/integration_tests/read_large_files/seq_read_large_file_test.go @@ -18,8 +18,8 @@ import ( "bytes" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func TestReadLargeFileSequentially(t *testing.T) { diff --git a/tools/integration_tests/readonly/copy_object_test.go b/tools/integration_tests/readonly/copy_object_test.go index 2a22bcad7c..ad47134100 100644 --- a/tools/integration_tests/readonly/copy_object_test.go +++ b/tools/integration_tests/readonly/copy_object_test.go @@ -20,8 +20,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) // Copy srcFile in testBucket/testDirForReadOnlyTest/Test/b/b.txt destination. diff --git a/tools/integration_tests/readonly/create_object_test.go b/tools/integration_tests/readonly/create_object_test.go index e5d25aa2f9..0ddc03b7d1 100644 --- a/tools/integration_tests/readonly/create_object_test.go +++ b/tools/integration_tests/readonly/create_object_test.go @@ -21,9 +21,9 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func checkIfFileCreationFailed(filePath string, t *testing.T) { diff --git a/tools/integration_tests/readonly/delete_object_test.go b/tools/integration_tests/readonly/delete_object_test.go index d874e4eec9..ac40ed1cba 100644 --- a/tools/integration_tests/readonly/delete_object_test.go +++ b/tools/integration_tests/readonly/delete_object_test.go @@ -20,9 +20,9 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func checkIfObjDeletionFailed(objPath string, t *testing.T) { diff --git a/tools/integration_tests/readonly/list_objects_test.go b/tools/integration_tests/readonly/list_objects_test.go index f0033f01f5..b090d8fd1b 100644 --- a/tools/integration_tests/readonly/list_objects_test.go +++ b/tools/integration_tests/readonly/list_objects_test.go @@ -21,7 +21,7 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func TestListObjectsInBucket(t *testing.T) { diff --git a/tools/integration_tests/readonly/read_test.go b/tools/integration_tests/readonly/read_test.go index 578b79977b..96421cef1b 100644 --- a/tools/integration_tests/readonly/read_test.go +++ b/tools/integration_tests/readonly/read_test.go @@ -21,8 +21,8 @@ import ( "syscall" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func checkIfFileReadSucceeded(filePath string, expectedContent string, t *testing.T) { diff --git a/tools/integration_tests/readonly/readonly_test.go b/tools/integration_tests/readonly/readonly_test.go index 824e13acc7..2dcf2203c3 100644 --- a/tools/integration_tests/readonly/readonly_test.go +++ b/tools/integration_tests/readonly/readonly_test.go @@ -26,11 +26,11 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/creds_tests" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/persistent_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/creds_tests" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/persistent_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const TestDirForReadOnlyTest = "testDirForReadOnlyTest" diff --git a/tools/integration_tests/readonly/rename_object_test.go b/tools/integration_tests/readonly/rename_object_test.go index 16bb3aff07..42f1cb74ad 100644 --- a/tools/integration_tests/readonly/rename_object_test.go +++ b/tools/integration_tests/readonly/rename_object_test.go @@ -20,8 +20,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) // Rename oldFile to newFile diff --git a/tools/integration_tests/readonly/stat_object_test.go b/tools/integration_tests/readonly/stat_object_test.go index ed06cd93c8..1a6fc81af8 100644 --- a/tools/integration_tests/readonly/stat_object_test.go +++ b/tools/integration_tests/readonly/stat_object_test.go @@ -20,7 +20,7 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func statObject(objPath string, t *testing.T) (file os.FileInfo) { diff --git a/tools/integration_tests/readonly/write_test.go b/tools/integration_tests/readonly/write_test.go index 37707316a1..1003703eeb 100644 --- a/tools/integration_tests/readonly/write_test.go +++ b/tools/integration_tests/readonly/write_test.go @@ -19,8 +19,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const Content = "Testing" diff --git a/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go b/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go index 86fe005ccf..a1b3afd222 100644 --- a/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go +++ b/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go @@ -20,9 +20,9 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/readonly_creds/readonly_creds_test.go b/tools/integration_tests/readonly_creds/readonly_creds_test.go index 4c692fd851..32a5939680 100644 --- a/tools/integration_tests/readonly_creds/readonly_creds_test.go +++ b/tools/integration_tests/readonly_creds/readonly_creds_test.go @@ -22,9 +22,9 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/creds_tests" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/creds_tests" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/rename_dir_limit/move_dir_test.go b/tools/integration_tests/rename_dir_limit/move_dir_test.go index 8680849b35..756826cb20 100644 --- a/tools/integration_tests/rename_dir_limit/move_dir_test.go +++ b/tools/integration_tests/rename_dir_limit/move_dir_test.go @@ -23,8 +23,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const SrcMoveDirectory = "srcMoveDir" diff --git a/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go b/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go index c52cef26e6..933a6a3150 100644 --- a/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go +++ b/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go @@ -22,11 +22,11 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/only_dir_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/persistent_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/only_dir_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/persistent_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const DirForRenameDirLimitTests = "dirForRenameDirLimitTests" diff --git a/tools/integration_tests/rename_dir_limit/rename_dir_test.go b/tools/integration_tests/rename_dir_limit/rename_dir_test.go index 1cc567b274..c57d2e653d 100644 --- a/tools/integration_tests/rename_dir_limit/rename_dir_test.go +++ b/tools/integration_tests/rename_dir_limit/rename_dir_test.go @@ -22,8 +22,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" ) diff --git a/tools/integration_tests/stale_handle/setup_test.go b/tools/integration_tests/stale_handle/setup_test.go index 0c9137f10c..00f9d60744 100644 --- a/tools/integration_tests/stale_handle/setup_test.go +++ b/tools/integration_tests/stale_handle/setup_test.go @@ -21,9 +21,9 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/stale_handle/stale_file_handle_common_test.go b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go index 6e3da8c975..e212b4aece 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_common_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_common_test.go @@ -19,10 +19,10 @@ import ( "path" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go index 892f7032c0..5a2dec76de 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_local_file_test.go @@ -19,10 +19,10 @@ import ( "slices" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/suite" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) // ////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go index 52b6a41911..7b96f4f819 100644 --- a/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go +++ b/tools/integration_tests/stale_handle/stale_file_handle_synced_file_test.go @@ -20,9 +20,9 @@ import ( "testing" "cloud.google.com/go/storage" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) diff --git a/tools/integration_tests/streaming_writes/buffer_size_test.go b/tools/integration_tests/streaming_writes/buffer_size_test.go index f97bd1097a..0dae0e2635 100644 --- a/tools/integration_tests/streaming_writes/buffer_size_test.go +++ b/tools/integration_tests/streaming_writes/buffer_size_test.go @@ -18,9 +18,9 @@ import ( "path" "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func TestWritesWithDifferentConfig(t *testing.T) { diff --git a/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go b/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go index 16813c6539..16feb98e48 100644 --- a/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go +++ b/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go @@ -18,10 +18,10 @@ import ( "os" "slices" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_suite" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/streaming_writes/empty_gcs_file_test.go b/tools/integration_tests/streaming_writes/empty_gcs_file_test.go index f0e3864d76..149f1f3a51 100644 --- a/tools/integration_tests/streaming_writes/empty_gcs_file_test.go +++ b/tools/integration_tests/streaming_writes/empty_gcs_file_test.go @@ -18,9 +18,9 @@ import ( "path" "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/suite" ) diff --git a/tools/integration_tests/streaming_writes/local_file_test.go b/tools/integration_tests/streaming_writes/local_file_test.go index 7d6cb18860..4fcd1c5189 100644 --- a/tools/integration_tests/streaming_writes/local_file_test.go +++ b/tools/integration_tests/streaming_writes/local_file_test.go @@ -18,10 +18,10 @@ import ( "path" "testing" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/local_file" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/local_file" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/suite" ) diff --git a/tools/integration_tests/streaming_writes/read_file_test.go b/tools/integration_tests/streaming_writes/read_file_test.go index f32f6258aa..5ceb368e47 100644 --- a/tools/integration_tests/streaming_writes/read_file_test.go +++ b/tools/integration_tests/streaming_writes/read_file_test.go @@ -17,8 +17,8 @@ package streaming_writes import ( "path" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/streaming_writes/rename_file_test.go b/tools/integration_tests/streaming_writes/rename_file_test.go index fbb07fa4c8..11db331ab4 100644 --- a/tools/integration_tests/streaming_writes/rename_file_test.go +++ b/tools/integration_tests/streaming_writes/rename_file_test.go @@ -17,8 +17,8 @@ package streaming_writes import ( "path" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/streaming_writes/setup_test.go b/tools/integration_tests/streaming_writes/setup_test.go index 5f27f69aea..6ab5aa92de 100644 --- a/tools/integration_tests/streaming_writes/setup_test.go +++ b/tools/integration_tests/streaming_writes/setup_test.go @@ -21,9 +21,9 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/streaming_writes/symlink_file_test.go b/tools/integration_tests/streaming_writes/symlink_file_test.go index da01f203d8..614f84820c 100644 --- a/tools/integration_tests/streaming_writes/symlink_file_test.go +++ b/tools/integration_tests/streaming_writes/symlink_file_test.go @@ -18,9 +18,9 @@ import ( "os" "path" - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" ) diff --git a/tools/integration_tests/streaming_writes/truncate_file_test.go b/tools/integration_tests/streaming_writes/truncate_file_test.go index 7d69ed90aa..77cf63f447 100644 --- a/tools/integration_tests/streaming_writes/truncate_file_test.go +++ b/tools/integration_tests/streaming_writes/truncate_file_test.go @@ -15,8 +15,8 @@ package streaming_writes import ( - . "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/unfinalized_object/setup_test.go b/tools/integration_tests/unfinalized_object/setup_test.go index c5c3205c7a..4dd99492e3 100644 --- a/tools/integration_tests/unfinalized_object/setup_test.go +++ b/tools/integration_tests/unfinalized_object/setup_test.go @@ -19,8 +19,8 @@ import ( "os" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go index e0f603b7fe..9e9ecaf875 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go @@ -22,9 +22,9 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" diff --git a/tools/integration_tests/unfinalized_object/unfinalized_read_test.go b/tools/integration_tests/unfinalized_object/unfinalized_read_test.go index 873e3b2baf..288f8eea1c 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_read_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_read_test.go @@ -22,10 +22,10 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" diff --git a/tools/integration_tests/util/benchmark_setup/benchmark_setup_test.go b/tools/integration_tests/util/benchmark_setup/benchmark_setup_test.go index 0bdf7ef8fa..e15eacb0e0 100644 --- a/tools/integration_tests/util/benchmark_setup/benchmark_setup_test.go +++ b/tools/integration_tests/util/benchmark_setup/benchmark_setup_test.go @@ -18,7 +18,7 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/benchmark_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/benchmark_setup" "github.com/stretchr/testify/assert" ) diff --git a/tools/integration_tests/util/client/control_client.go b/tools/integration_tests/util/client/control_client.go index 5643ab13aa..50199edf1f 100644 --- a/tools/integration_tests/util/client/control_client.go +++ b/tools/integration_tests/util/client/control_client.go @@ -29,8 +29,8 @@ import ( control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googleapis/gax-go/v2" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "google.golang.org/grpc/codes" ) diff --git a/tools/integration_tests/util/client/gcs_helper.go b/tools/integration_tests/util/client/gcs_helper.go index c1d988c850..a1c0174a64 100644 --- a/tools/integration_tests/util/client/gcs_helper.go +++ b/tools/integration_tests/util/client/gcs_helper.go @@ -24,8 +24,8 @@ import ( "testing" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 1e34f752a9..613366850e 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -32,9 +32,9 @@ import ( "cloud.google.com/go/storage" "cloud.google.com/go/storage/experimental" "github.com/googleapis/gax-go/v2" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/iterator" diff --git a/tools/integration_tests/util/creds_tests/creds.go b/tools/integration_tests/util/creds_tests/creds.go index cdac923280..672774d86f 100644 --- a/tools/integration_tests/util/creds_tests/creds.go +++ b/tools/integration_tests/util/creds_tests/creds.go @@ -33,9 +33,9 @@ import ( secretmanager "cloud.google.com/go/secretmanager/apiv1" "cloud.google.com/go/secretmanager/apiv1/secretmanagerpb" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const NameOfServiceAccount = "creds-integration-tests" diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_job_log_parser.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_job_log_parser.go index 4cf22d1479..942a5343fe 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_job_log_parser.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_job_log_parser.go @@ -23,7 +23,7 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) /* diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_job_log_parser_test.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_job_log_parser_test.go index 9236376ec1..9ef82cd030 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_job_log_parser_test.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_job_log_parser_test.go @@ -19,7 +19,7 @@ import ( "io" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go index 940da88a94..e681c1e1fe 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser.go @@ -25,7 +25,7 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func filterAndParseLogLine(logLine string, diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go index 2d828c6c33..f3f34ebe74 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go @@ -22,8 +22,8 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/log_parser/json_parser/read_logs" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go index 3b32c0a52e..af1af82005 100644 --- a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go +++ b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go @@ -24,9 +24,9 @@ import ( "cloud.google.com/go/compute/metadata" "cloud.google.com/go/storage" - client_util "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + client_util "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const PrefixBucketForDynamicMountingTest = "gcsfuse-dynamic-mounting-test-" diff --git a/tools/integration_tests/util/mounting/mounting.go b/tools/integration_tests/util/mounting/mounting.go index b42690a489..467156d761 100644 --- a/tools/integration_tests/util/mounting/mounting.go +++ b/tools/integration_tests/util/mounting/mounting.go @@ -20,8 +20,8 @@ import ( "os" "os/exec" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func MountGcsfuse(binaryFile string, flags []string) error { diff --git a/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go b/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go index 65ec973f1a..9c8a461c06 100644 --- a/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go +++ b/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go @@ -19,9 +19,9 @@ import ( "log" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "golang.org/x/net/context" ) diff --git a/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go b/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go index e674eb5f78..b36689ebdb 100644 --- a/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go +++ b/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go @@ -20,8 +20,8 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) // Change e.g --log_severity=trace to log_severity=trace diff --git a/tools/integration_tests/util/mounting/static_mounting/static_mounting.go b/tools/integration_tests/util/mounting/static_mounting/static_mounting.go index e7e46eba5d..37b54931fa 100644 --- a/tools/integration_tests/util/mounting/static_mounting/static_mounting.go +++ b/tools/integration_tests/util/mounting/static_mounting/static_mounting.go @@ -19,8 +19,8 @@ import ( "log" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) func MountGcsfuseWithStaticMounting(flags []string) (err error) { diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index f92c0d0ddd..e9ca638256 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -32,7 +32,7 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/net/context" diff --git a/tools/integration_tests/util/operations/validation_helper.go b/tools/integration_tests/util/operations/validation_helper.go index 98e17984b6..db32e994fa 100644 --- a/tools/integration_tests/util/operations/validation_helper.go +++ b/tools/integration_tests/util/operations/validation_helper.go @@ -22,9 +22,9 @@ import ( "syscall" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/common" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v2/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go index cd4adefb0c..9730b2fca6 100644 --- a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go +++ b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go @@ -22,11 +22,11 @@ import ( "testing" storage "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/persistent_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/persistent_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index c5e2428e6c..68ae0a3d68 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -31,8 +31,8 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/util" "google.golang.org/api/iterator" ) diff --git a/tools/integration_tests/util/test_setup/test_setup_test.go b/tools/integration_tests/util/test_setup/test_setup_test.go index 0196ff61b7..aeafbf7dae 100644 --- a/tools/integration_tests/util/test_setup/test_setup_test.go +++ b/tools/integration_tests/util/test_setup/test_setup_test.go @@ -17,8 +17,8 @@ package test_setup_test import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/test_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" . "github.com/jacobsa/ogletest" ) diff --git a/tools/integration_tests/write_large_files/concurrent_write_files_test.go b/tools/integration_tests/write_large_files/concurrent_write_files_test.go index 6867b89a63..7c076817fd 100644 --- a/tools/integration_tests/write_large_files/concurrent_write_files_test.go +++ b/tools/integration_tests/write_large_files/concurrent_write_files_test.go @@ -18,8 +18,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" diff --git a/tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go b/tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go index 5c42b6917a..e0dff82963 100644 --- a/tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go +++ b/tools/integration_tests/write_large_files/concurrent_write_to_same_file_test.go @@ -20,8 +20,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" diff --git a/tools/integration_tests/write_large_files/random_write_large_file_test.go b/tools/integration_tests/write_large_files/random_write_large_file_test.go index 2f20b88ec2..5871e3f8ed 100644 --- a/tools/integration_tests/write_large_files/random_write_large_file_test.go +++ b/tools/integration_tests/write_large_files/random_write_large_file_test.go @@ -19,9 +19,9 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/write_large_files/seq_write_large_file_test.go b/tools/integration_tests/write_large_files/seq_write_large_file_test.go index 5163af5099..35ee1a551b 100644 --- a/tools/integration_tests/write_large_files/seq_write_large_file_test.go +++ b/tools/integration_tests/write_large_files/seq_write_large_file_test.go @@ -18,8 +18,8 @@ import ( "path" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/tools/integration_tests/write_large_files/write_large_files_test.go b/tools/integration_tests/write_large_files/write_large_files_test.go index c2f733e474..80359711c7 100644 --- a/tools/integration_tests/write_large_files/write_large_files_test.go +++ b/tools/integration_tests/write_large_files/write_large_files_test.go @@ -20,8 +20,8 @@ import ( "os" "testing" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/mounting/static_mounting" - "github.com/googlecloudplatform/gcsfuse/v2/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) const ( diff --git a/tools/mount_gcsfuse/main.go b/tools/mount_gcsfuse/main.go index 10ec598615..9848a1cb8d 100644 --- a/tools/mount_gcsfuse/main.go +++ b/tools/mount_gcsfuse/main.go @@ -63,8 +63,8 @@ import ( "slices" "strings" - "github.com/googlecloudplatform/gcsfuse/v2/cfg" - "github.com/googlecloudplatform/gcsfuse/v2/internal/mount" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/mount" "github.com/spf13/pflag" ) diff --git a/tools/prefetch_cache_gcsfuse/prefetch.go b/tools/prefetch_cache_gcsfuse/prefetch.go index d2d6ae9227..74eaae6f50 100644 --- a/tools/prefetch_cache_gcsfuse/prefetch.go +++ b/tools/prefetch_cache_gcsfuse/prefetch.go @@ -26,7 +26,7 @@ import ( "time" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v2/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" "google.golang.org/api/iterator" ) diff --git a/tools/util/build_gcsfuse.go b/tools/util/build_gcsfuse.go index 0ba19bb3f4..703cc55863 100644 --- a/tools/util/build_gcsfuse.go +++ b/tools/util/build_gcsfuse.go @@ -53,7 +53,7 @@ func BuildGcsfuse(dstDir string) (err error) { { var pkg *build.Package pkg, err = build.Import( - "github.com/googlecloudplatform/gcsfuse/v2", + "github.com/googlecloudplatform/gcsfuse/v3", "", build.FindOnly) @@ -94,7 +94,7 @@ func buildBuildGcsfuse(dst string) (err error) { { var pkg *build.Package pkg, err = build.Import( - "github.com/googlecloudplatform/gcsfuse/v2/tools/build_gcsfuse", + "github.com/googlecloudplatform/gcsfuse/v3/tools/build_gcsfuse", "", build.FindOnly) From 82d23100a94de258c3f0a9fbd8731d38a8d74760 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Wed, 28 May 2025 14:31:55 +0530 Subject: [PATCH 0470/1298] Add markers for benchmark tables (#3364) * add markers for benchmark tables * add markers in form of comment --- docs/benchmarks.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/benchmarks.md b/docs/benchmarks.md index 72f988a558..f1d50ac76f 100644 --- a/docs/benchmarks.md +++ b/docs/benchmarks.md @@ -84,6 +84,8 @@ numbers will be different for edits/appends to existing files. all writes are first staged to a local temporary directory before being written to GCS on close/fsync. + + ## GCSFuse Benchmarking on c4 machine-type * VM Type: c4-standard-96 * VM location: us-south1 @@ -169,7 +171,7 @@ to GCS on close/fsync. | 100M |1M | 10 | 3519 | 3518 | 11.83 | | 1G | 1M | 2 | 1892 | 1891 | 45.40 | - + ## Steps to benchmark GCSFuse performance From ec8022e4f9a8c031ad8594c479a3d87ebc989f3b Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 28 May 2025 15:03:54 +0530 Subject: [PATCH 0471/1298] Fix broken support for requester-pays buckets (#3256) Pass billing-project in every GCS call, except LROs (Long Running Operations) from storage control-client (through "x-goog-user-project") to fix mounting and operations of requester-pays buckets . This functionality was broken since v2.9.0 . This fixes the mount of requester-pays buckets, as well as the following operations which go through storage-control-client. - GetFolder, - CreateFolder, - RenameFolder # might fail even after this change until the bug fix for GCS bug (b/414790551) completely rolls out. - DeleteFolder In terms of code, this also required - Add a new implementation of the interface StorageControlClient for the case when a billing-project has been specified by the gcsfuse user. Note: After this PR merged, E2e tests need to be added for requester-pays buckets. That task is captured in b/415244758 Link to the issue in case of a bug fix. b/406090515, b/405913611 * pass x-goog-user-project to GetStorageLayout call * dont pass empty billingProject to GetStorageLayout * Revert "dont pass empty billingProject to GetStorageLayout" This reverts commit 62ddbce573f27e152ca5888056012367bc191bba. * Revert "pass x-goog-user-project to GetStorageLayout call" This reverts commit 74fc8917f1e139a9a60b386f76dfbd7a7044cec3. * [unstable] Use biling-project in control-client: part 1 * Use biling-project in control-client: part 2 * fix header * fix lint errors * empty commit * reverted all prev commits at once * Add cleaner, shorter code * add unit test * empty commit * Fix failure in emulator_tests * dont pass billing-project in RenameFolder calll LRO --- cmd/legacy_main.go | 2 +- internal/storage/control_client_wrapper.go | 45 +++++++++++++++++ internal/storage/storage_handle.go | 10 ++-- internal/storage/storage_handle_test.go | 57 +++++++++++++++------- internal/storage/storageutil/client.go | 2 +- 5 files changed, 91 insertions(+), 25 deletions(-) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 94db652b48..e91cc2be4a 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -138,7 +138,7 @@ func createStorageHandle(newConfig *cfg.Config, userAgent string) (storageHandle ReadStallRetryConfig: newConfig.GcsRetries.ReadStall, } logger.Infof("UserAgent = %s\n", storageClientConfig.UserAgent) - storageHandle, err = storage.NewStorageHandle(context.Background(), storageClientConfig) + storageHandle, err = storage.NewStorageHandle(context.Background(), storageClientConfig, newConfig.GcsConnection.BillingProject) return } diff --git a/internal/storage/control_client_wrapper.go b/internal/storage/control_client_wrapper.go index 4fc87da24a..a655189875 100644 --- a/internal/storage/control_client_wrapper.go +++ b/internal/storage/control_client_wrapper.go @@ -20,6 +20,7 @@ import ( control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googleapis/gax-go/v2" + "google.golang.org/grpc/metadata" ) type StorageControlClient interface { @@ -37,3 +38,47 @@ type StorageControlClient interface { CreateFolder(ctx context.Context, req *controlpb.CreateFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) } + +// storageControlClientWithBillingProject is a wrapper for an existing +// StorageControlClient object in that it passes +// the billing project in every call made through the base StorageControlClient. +type storageControlClientWithBillingProject struct { + raw StorageControlClient + billingProject string +} + +func (sccwbp *storageControlClientWithBillingProject) contextWithBillingProject(ctx context.Context) context.Context { + return metadata.AppendToOutgoingContext(ctx, "x-goog-user-project", sccwbp.billingProject) +} + +func (sccwbp *storageControlClientWithBillingProject) GetStorageLayout(ctx context.Context, + req *controlpb.GetStorageLayoutRequest, + opts ...gax.CallOption) (*controlpb.StorageLayout, error) { + return sccwbp.raw.GetStorageLayout(sccwbp.contextWithBillingProject(ctx), req, opts...) +} + +func (sccwbp *storageControlClientWithBillingProject) DeleteFolder(ctx context.Context, + req *controlpb.DeleteFolderRequest, + opts ...gax.CallOption) error { + return sccwbp.raw.DeleteFolder(sccwbp.contextWithBillingProject(ctx), req, opts...) +} + +func (sccwbp *storageControlClientWithBillingProject) GetFolder(ctx context.Context, req *controlpb.GetFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) { + return sccwbp.raw.GetFolder(sccwbp.contextWithBillingProject(ctx), req, opts...) +} + +func (sccwbp *storageControlClientWithBillingProject) RenameFolder(ctx context.Context, req *controlpb.RenameFolderRequest, opts ...gax.CallOption) (*control.RenameFolderOperation, error) { + // Don't pass billing-project for LROs as it's not supported. + return sccwbp.raw.RenameFolder(ctx, req, opts...) +} + +func (sccwbp *storageControlClientWithBillingProject) CreateFolder(ctx context.Context, req *controlpb.CreateFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) { + return sccwbp.raw.CreateFolder(sccwbp.contextWithBillingProject(ctx), req, opts...) +} + +func withBillingProject(controlClient StorageControlClient, billingProject string) StorageControlClient { + if billingProject != "" { + controlClient = &storageControlClientWithBillingProject{raw: controlClient, billingProject: billingProject} + } + return controlClient +} diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index eaede87563..20b80e8645 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -23,7 +23,6 @@ import ( "time" "cloud.google.com/go/storage" - control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" "cloud.google.com/go/storage/experimental" "github.com/googleapis/gax-go/v2" @@ -228,8 +227,7 @@ func createHTTPClientHandle(ctx context.Context, clientConfig *storageutil.Stora } func (sh *storageClient) lookupBucketType(bucketName string) (*gcs.BucketType, error) { - var nilControlClient *control.StorageControlClient = nil - if sh.storageControlClient == nilControlClient { + if sh.storageControlClient == nil { return &gcs.BucketType{}, nil // Assume defaults } @@ -263,10 +261,10 @@ func (sh *storageClient) getStorageLayout(bucketName string) (*controlpb.Storage // NewStorageHandle creates control client and stores client config to allow dynamic // creation of http or grpc client. -func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClientConfig) (sh StorageHandle, err error) { +func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClientConfig, billingProject string) (sh StorageHandle, err error) { // The default protocol for the Go Storage control client's folders API is gRPC. // gcsfuse will initially mirror this behavior due to the client's lack of HTTP support. - var controlClient *control.StorageControlClient + var controlClient StorageControlClient var clientOpts []option.ClientOption // Control-client is needed for folder APIs and for getting storage-layout of the bucket. @@ -280,6 +278,8 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien if err != nil { return nil, fmt.Errorf("could not create StorageControl Client: %w", err) } + // special handling for requester-pays buckets and for mounts created with custom billing projects. + controlClient = withBillingProject(controlClient, billingProject) } else { logger.Infof("Skipping storage control client creation because custom-endpoint %q was passed, which is assumed to be a storage testbench server because of 'localhost' in it.", clientConfig.CustomEndpoint) } diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index ca0ebe5d2c..91d089e470 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -125,7 +125,7 @@ func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketDoesNotExistWithNo func (testSuite *StorageHandleTest) TestNewStorageHandleHttp2Disabled() { sc := storageutil.GetDefaultStorageClientConfig() // by default http1 enabled - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -136,7 +136,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleHttp2EnabledAndAuthEnabl sc.ClientProtocol = cfg.HTTP2 sc.AnonymousAccess = false - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.NoError(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -146,7 +146,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithZeroMaxConnsPerHost( sc := storageutil.GetDefaultStorageClientConfig() sc.MaxConnsPerHost = 0 - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -156,7 +156,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenUserAgentIsSet() { sc := storageutil.GetDefaultStorageClientConfig() sc.UserAgent = "gcsfuse/unknown (Go version go1.20-pre3 cl/474093167 +a813be86df) appName (GPN:Gcsfuse-DLC)" - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -169,7 +169,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithCustomEndpointAndAut sc.CustomEndpoint = url.String() sc.AnonymousAccess = false - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.NoError(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -181,7 +181,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenCustomEndpointIsNilA sc.CustomEndpoint = "" sc.AnonymousAccess = false - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.NoError(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -191,7 +191,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenKeyFileIsEmpty() { sc := storageutil.GetDefaultStorageClientConfig() sc.KeyFile = "" - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -201,7 +201,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenReuseTokenUrlFalse() sc := storageutil.GetDefaultStorageClientConfig() sc.ReuseTokenFromUrl = false - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -211,7 +211,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenTokenUrlIsSet() { sc := storageutil.GetDefaultStorageClientConfig() sc.TokenUrl = storageutil.CustomTokenUrl - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -221,12 +221,33 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenJsonReadEnabled() { sc := storageutil.GetDefaultStorageClientConfig() sc.ExperimentalEnableJsonRead = true - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) } +func (testSuite *StorageHandleTest) TestNewStorageHandleWithBillingProject() { + sc := storageutil.GetDefaultStorageClientConfig() + sc.EnableHNS = true + + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, projectID) + + assert.Nil(testSuite.T(), err) + assert.NotNil(testSuite.T(), handleCreated) + storageClient, ok := handleCreated.(*storageClient) + assert.NotNil(testSuite.T(), storageClient) + assert.True(testSuite.T(), ok) + // Confirm that the returned storage-handle's control-client is of type storageControlClientWithBillingProject + // and its billing-project is same as the one passed while + // creating the storage-handle. + storageControlClient, ok := storageClient.storageControlClient.(*storageControlClientWithBillingProject) + assert.NotNil(testSuite.T(), storageControlClient) + assert.True(testSuite.T(), ok) + assert.Equal(testSuite.T(), storageControlClient.billingProject, projectID) + assert.NotNil(testSuite.T(), storageControlClient.raw) +} + func (testSuite *StorageHandleTest) TestNewStorageHandleWithInvalidClientProtocol() { fakeStorage := NewFakeStorageWithMockClient(testSuite.mockClient, "test-protocol") testSuite.mockStorageLayout(gcs.BucketType{}) @@ -264,7 +285,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleDirectPathDetector() { sc.ExperimentalEnableJsonRead = true sc.ClientProtocol = tc.clientProtocol - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -309,7 +330,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientProtocol() sc := storageutil.GetDefaultStorageClientConfig() sc.ClientProtocol = cfg.GRPC - storageClient, err := NewStorageHandle(testSuite.ctx, sc) + storageClient, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), storageClient) @@ -499,7 +520,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustom sc.AnonymousAccess = false sc.ClientProtocol = cfg.GRPC - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.NoError(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -515,7 +536,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustom sc.TokenUrl = storageutil.CustomTokenUrl sc.KeyFile = "" - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -530,7 +551,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustom sc.TokenUrl = storageutil.CustomTokenUrl sc.KeyFile = "" - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) @@ -540,7 +561,7 @@ func (testSuite *StorageHandleTest) TestCreateStorageHandleWithEnableHNSTrue() { sc := storageutil.GetDefaultStorageClientConfig() sc.EnableHNS = true - sh, err := NewStorageHandle(testSuite.ctx, sc) + sh, err := NewStorageHandle(testSuite.ctx, sc, "") assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), sh) @@ -553,7 +574,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithCustomEndpointAndEna sc.CustomEndpoint = url.String() sc.EnableHNS = true - sh, err := NewStorageHandle(testSuite.ctx, sc) + sh, err := NewStorageHandle(testSuite.ctx, sc, "") assert.NoError(testSuite.T(), err) assert.NotNil(testSuite.T(), sh) @@ -572,7 +593,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithMaxRetryAttemptsNotZ sc := storageutil.GetDefaultStorageClientConfig() sc.MaxRetryAttempts = 100 - handleCreated, err := NewStorageHandle(testSuite.ctx, sc) + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") if assert.NoError(testSuite.T(), err) { assert.NotNil(testSuite.T(), handleCreated) diff --git a/internal/storage/storageutil/client.go b/internal/storage/storageutil/client.go index a7c410e527..16917579eb 100644 --- a/internal/storage/storageutil/client.go +++ b/internal/storage/storageutil/client.go @@ -1,4 +1,4 @@ -// Copyright 2023 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From 839b1ba5439d6fc2a4a0e992d2c9652b7048a814 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 29 May 2025 14:53:52 +0530 Subject: [PATCH 0472/1298] refactor go installation to separate script (#3373) --- perfmetrics/scripts/install_go.sh | 64 ++++++++++++++++++++++++ tools/integration_tests/run_e2e_tests.sh | 9 ++-- 2 files changed, 67 insertions(+), 6 deletions(-) create mode 100755 perfmetrics/scripts/install_go.sh diff --git a/perfmetrics/scripts/install_go.sh b/perfmetrics/scripts/install_go.sh new file mode 100755 index 0000000000..bb40d40ef7 --- /dev/null +++ b/perfmetrics/scripts/install_go.sh @@ -0,0 +1,64 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Script to install a specific go version to /usr/local +# Usage: install_go.sh + +# Exit on error, treat unset variables as errors, and propagate pipeline errors. +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "This script requires exactly one argument." + echo "Usage: $0 " + echo "Example: $0 1.24.0" + exit 1 +fi + +GO_VERSION="$1" +INSTALL_DIR="/usr/local" # Installation directory + +# Function to download, extract, and install go +install_go() { + local temp_dir architecture + temp_dir=$(mktemp -d /tmp/go_install_src.XXXXXX) + pushd "$temp_dir" + + architecture=$(dpkg --print-architecture) + wget -O go_tar.tar.gz "https://go.dev/dl/go${GO_VERSION}.linux-${architecture}.tar.gz" -q + sudo rm -rf "${INSTALL_DIR}/go" # Remove previous installation. + sudo tar -C "$INSTALL_DIR" -xzf go_tar.tar.gz + + popd + sudo rm -rf "$temp_dir" +} + +echo "Installing Go version ${GO_VERSION} to ${INSTALL_DIR}" +INSTALLATION_LOG=$(mktemp /tmp/go_install_log.XXXXXX) +if ! install_go > "$INSTALLATION_LOG" 2>&1; then + echo "Go version ${GO_VERSION} installation failed." + cat "$INSTALLATION_LOG" + rm -f "$INSTALLATION_LOG" + exit 1 +else + echo "Go version ${GO_VERSION} installed successfully." + # If this script is run in background or different shell then + # export PATH needs to be called from the shell or use absolute go path + # or permanently add this to path variable in bashrc. + export PATH="${INSTALL_DIR}/go/bin:$PATH" + echo "Go version is: " + go version + echo "Go is present at: $( (which go) )" + rm -f "$INSTALLATION_LOG" +fi diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 12e4694fb1..958321027c 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -251,12 +251,9 @@ function upgrade_gcloud_version() { } function install_packages() { - # e.g. architecture=arm64 or amd64 - architecture=$(dpkg --print-architecture) - echo "Installing go-lang 1.24.0..." - wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-${architecture}.tar.gz -q - sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local - export PATH=$PATH:/usr/local/go/bin + # Install required go version. + ./perfmetrics/scripts/install_go.sh "1.24.0" + export PATH="/usr/local/go/bin:$PATH" sudo apt-get install -y python3 # install python3-setuptools tools. sudo apt-get install -y gcc python3-dev python3-setuptools From 32f47674d33909ea1d1036f33f9e8592024e70e2 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Thu, 29 May 2025 17:19:57 +0530 Subject: [PATCH 0473/1298] routing writes to inode via fileHandle (#3328) * invoking write on inode via the file handle * updated comment * adding unit tests * resolving merge conflicts due to v2->v3 upgrade --- internal/fs/fs.go | 19 ++- internal/fs/handle/file.go | 12 ++ internal/fs/handle/file_test.go | 132 ++++++++++++++++++ internal/fs/inode/file.go | 4 +- .../fs/inode/file_streaming_writes_test.go | 43 +++--- internal/fs/inode/file_test.go | 39 +++--- 6 files changed, 205 insertions(+), 44 deletions(-) create mode 100644 internal/fs/handle/file_test.go diff --git a/internal/fs/fs.go b/internal/fs/fs.go index b0ad87b611..1297607e41 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1838,8 +1838,9 @@ func (fs *fileSystem) CreateFile( handleID := fs.nextHandleID fs.nextHandleID++ - // Creating new file is always a write operation, hence passing readOnly as false. - fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, util.Write, &fs.newConfig.Read) + // CreateFile() invoked to create new files, can be safely considered as filehandle + // opened in append mode. + fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, util.Append, &fs.newConfig.Read) op.Handle = handleID fs.mu.Unlock() @@ -2646,6 +2647,18 @@ func (fs *fileSystem) WriteFile( ctx, cancel = util.IsolateContextFromParentContext(ctx) defer cancel() } + + if fs.newConfig.Write.ExperimentalEnableRapidAppends { + fs.mu.Lock() + fh := fs.handles[op.Handle].(*handle.FileHandle) + fs.mu.Unlock() + + //TODO: Initialize BWH before invoking write() + if err := fh.Write(ctx, op.Data, op.Offset); err != nil { + return err + } + return + } // Find the inode. fs.mu.Lock() in := fs.fileInodeOrDie(op.Inode) @@ -2660,7 +2673,7 @@ func (fs *fileSystem) WriteFile( } // Serve the request. - err = in.Write(ctx, op.Data, op.Offset) + err = in.Write(ctx, op.Data, op.Offset, util.Write) return } diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 8d978a8b49..a46852e97d 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -156,6 +156,18 @@ func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequen return } +// Adding the Write() method to fileHandle to be able to pass the fileOpenMode +// which is used for determining write path. For e.g. in case of append mode for +// unfinalized objects in zonal buckets, streaming writes is used. +// Note that the writes are still done at the inode level. +// LOCKS_EXCLUDED(fh.inode) +func (fh *FileHandle) Write(ctx context.Context, data []byte, offset int64) error { + fh.inode.Lock() + defer fh.inode.Unlock() + + return fh.inode.Write(ctx, data, offset, fh.openMode) +} + //////////////////////////////////////////////////////////////////////// // Helpers //////////////////////////////////////////////////////////////////////// diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go new file mode 100644 index 0000000000..22de44d271 --- /dev/null +++ b/internal/fs/handle/file_test.go @@ -0,0 +1,132 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package handle + +import ( + "bytes" + "context" + "io" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/jacobsa/fuse/fuseops" + "github.com/jacobsa/timeutil" + "github.com/stretchr/testify/assert" + "golang.org/x/sync/semaphore" +) + +// createDirInode helps create the parent directory inode for the file inode +// which will be used for testing methods defined on the fileHandle. +func createDirInode( + bucket *gcsx.SyncerBucket, + clock *timeutil.SimulatedClock, + dirName string) inode.DirInode { + return inode.NewDirInode( + 1, + inode.NewDirName(inode.NewRootName(""), dirName), + fuseops.InodeAttributes{ + Uid: 0, + Gid: 0, + Mode: 0712, + }, + false, + false, + true, + 0, + bucket, + clock, + clock, + 4, + false, + ) +} + +// createFileInode is a helper to create a FileInode for testing. +func createFileInode( + t *testing.T, + bucket *gcsx.SyncerBucket, + clock *timeutil.SimulatedClock, + config *cfg.Config, + parent inode.DirInode, + objectName string, + content []byte) *inode.FileInode { + + obj := &gcs.MinObject{ + Name: objectName, + Size: uint64(len(content)), + Generation: 1, + MetaGeneration: 1, + Updated: clock.Now(), + } + + // Create object in the fake bucket to simulate existing GCS object + _, err := bucket.CreateObject(context.Background(), &gcs.CreateObjectRequest{ + Name: objectName, + Contents: io.NopCloser(bytes.NewReader(content)), + }) + if err != nil { + t.Fatalf("Failed to create object in fake bucket: %v", err) + } + + return inode.NewFileInode( + fuseops.InodeID(2), + inode.NewFileName(parent.Name(), obj.Name), + obj, + fuseops.InodeAttributes{}, + bucket, + false, + contentcache.New("", clock), + clock, + false, + config, + semaphore.NewWeighted(100), + ) +} + +// TODO: Add unit test for fh.Read() + +func TestFileHandleWrite(t *testing.T) { + var clock timeutil.SimulatedClock + clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local)) + bucket := gcsx.NewSyncerBucket( + 1, 10, ".gcsfuse_tmp/", fake.NewFakeBucket(&clock, "some_bucket", gcs.BucketType{})) + parent := createDirInode(&bucket, &clock, "parentRoot") + config := &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: false}} + in := createFileInode(t, &bucket, &clock, config, parent, "test_obj", nil) + fh := NewFileHandle(in, nil, false, nil, util.Write, &cfg.ReadConfig{}) + ctx := context.Background() + data := []byte("hello") + + err := fh.Write(ctx, data, 0) + + assert.Nil(t, err) + // Validate that write is successful at inode. + buf := make([]byte, len(data)) + n, err := in.Read(ctx, buf, 0) + buf = buf[:n] + // Ignore EOF. + if err == io.EOF { + err = nil + } + assert.Nil(t, err) + assert.Equal(t, data, buf) +} diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 5b0c6c2255..650883ac5a 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -32,6 +32,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/syncutil" "github.com/jacobsa/timeutil" @@ -583,7 +584,8 @@ func (f *FileInode) Read( func (f *FileInode) Write( ctx context.Context, data []byte, - offset int64) error { + offset int64, + openMode util.OpenMode) error { if f.bwh != nil { return f.writeUsingBufferedWrites(ctx, data, offset) } diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index f087e9f4f0..3496c5926b 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -29,6 +29,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/jacobsa/fuse/fuseops" @@ -188,7 +189,7 @@ func (t *FileStreamingWritesCommon) TestflushUsingBufferedWriteHandlerOnZeroSize func (t *FileStreamingWritesCommon) TestflushUsingBufferedWriteHandlerOnNonZeroSizeDoesNotRecreatesBwhOnInitAgain() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0)) + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0, util.Write)) err := t.in.flushUsingBufferedWriteHandler() require.NoError(t.T(), err) assert.Nil(t.T(), t.in.bwh) @@ -214,14 +215,14 @@ func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritative func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWriteForZonalBuckets() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0, util.Write)) assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) } func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBucketsPromotesInodeToNonLocal() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("pizza"), 0)) + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("pizza"), 0, util.Write)) gcsSynced, err := t.in.SyncPendingBufferedWrites() @@ -235,7 +236,7 @@ func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZon func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBucketsUpdatesSrcSize() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0)) + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0, util.Write)) assert.Equal(t.T(), uint64(0), t.in.src.Size) gcsSynced, err := t.in.SyncPendingBufferedWrites() @@ -259,14 +260,14 @@ func (t *FileStreamingWritesTest) TestSourceGenerationIsAuthoritativeReturnsTrue func (t *FileStreamingWritesTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWriteForNonZonalBuckets() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0, util.Write)) assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) } func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucketsDoesNotPromoteInodeToNonLocal() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0, util.Write)) gcsSynced, err := t.in.SyncPendingBufferedWrites() @@ -278,7 +279,7 @@ func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucket func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucketsDoesNotUpdateSrcSize() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0)) + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0, util.Write)) assert.Equal(t.T(), uint64(0), t.in.src.Size) gcsSynced, err := t.in.SyncPendingBufferedWrites() @@ -318,7 +319,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF createTime := t.clock.Now() t.clock.AdvanceTime(15 * time.Minute) // Sequential Write at offset 0 - err := t.in.Write(t.ctx, []byte("taco"), 0) + err := t.in.Write(t.ctx, []byte("taco"), 0, util.Write) require.Nil(t.T(), err) require.NotNil(t.T(), t.in.bwh) // validate attributes. @@ -329,7 +330,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF // Out of order write. mtime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("hello"), tc.offset) + err = t.in.Write(t.ctx, []byte("hello"), tc.offset, util.Write) require.Nil(t.T(), err) // Ensure bwh cleared and temp file created. @@ -357,7 +358,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { assert.True(t.T(), t.in.IsLocal()) createTime := t.in.mtimeClock.Now() // Out of order write. - err := t.in.Write(t.ctx, []byte("taco"), 6) + err := t.in.Write(t.ctx, []byte("taco"), 6, util.Write) require.Nil(t.T(), err) // Ensure bwh cleared and temp file created. assert.Nil(t.T(), t.in.bwh) @@ -370,7 +371,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { // Ordered write. mtime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("hello"), 0) + err = t.in.Write(t.ctx, []byte("hello"), 0, util.Write) require.Nil(t.T(), err) // Ensure bwh not re-created. @@ -392,7 +393,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { func (t *FileStreamingWritesTest) TestOutOfOrderWritesOnClobberedFileThrowsError() { t.createBufferedWriteHandler() - err := t.in.Write(t.ctx, []byte("hi"), 0) + err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) require.Nil(t.T(), err) require.NotNil(t.T(), t.in.bwh) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) @@ -400,7 +401,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesOnClobberedFileThrowsError objWritten, err := storageutil.CreateObject(t.ctx, t.bucket, fileName, []byte("taco")) require.Nil(t.T(), err) - err = t.in.Write(t.ctx, []byte("hello"), 10) + err = t.in.Write(t.ctx, []byte("hello"), 10, util.Write) require.Error(t.T(), err) var fileClobberedError *gcsfuse_errors.FileClobberedError @@ -427,7 +428,7 @@ func (t *FileStreamingWritesTest) TestUnlinkLocalFileAfterWrite() { assert.True(t.T(), t.in.IsLocal()) t.createBufferedWriteHandler() // Write some content. - err := t.in.Write(t.ctx, []byte("tacos"), 0) + err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) @@ -444,7 +445,7 @@ func (t *FileStreamingWritesTest) TestUnlinkEmptySyncedFile() { assert.False(t.T(), t.in.IsLocal()) t.createBufferedWriteHandler() // Write some content to temp file. - err := t.in.Write(t.ctx, []byte("tacos"), 0) + err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) @@ -479,7 +480,7 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndFlush() { } t.createBufferedWriteHandler() // Write some content to temp file. - err := t.in.Write(t.ctx, []byte("tacos"), 0) + err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) t.clock.AdvanceTime(10 * time.Second) @@ -639,7 +640,7 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndSync() { } t.createBufferedWriteHandler() // Write some content to temp file. - err := t.in.Write(t.ctx, []byte("tacos"), 0) + err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) t.clock.AdvanceTime(10 * time.Second) @@ -669,7 +670,7 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndSync() { func (t *FileStreamingWritesTest) TestSourceGenerationSizeForLocalFileIsReflected() { t.createBufferedWriteHandler() assert.True(t.T(), t.in.IsLocal()) - err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0) + err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0, util.Write) require.NoError(t.T(), err) sg := t.in.SourceGeneration() @@ -683,7 +684,7 @@ func (t *FileStreamingWritesTest) TestSourceGenerationSizeForSyncedFileIsReflect t.createInode(fileName, emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) t.createBufferedWriteHandler() - err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0) + err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0, util.Write) require.NoError(t.T(), err) sg := t.in.SourceGeneration() @@ -696,7 +697,7 @@ func (t *FileStreamingWritesTest) TestTruncateOnFileUsingTempFileDoesNotRecreate t.createBufferedWriteHandler() assert.True(t.T(), t.in.IsLocal()) // Out of order write. - err := t.in.Write(t.ctx, []byte("taco"), 2) + err := t.in.Write(t.ctx, []byte("taco"), 2, util.Write) require.Nil(t.T(), err) // Ensure bwh cleared and temp file created. assert.Nil(t.T(), t.in.bwh) @@ -812,7 +813,7 @@ func (t *FileStreamingWritesTest) TestWriteUsingBufferedWritesFails() { }, } - err := t.in.Write(context.Background(), []byte("hello"), 0) + err := t.in.Write(context.Background(), []byte("hello"), 0, util.Write) require.Error(t.T(), err) assert.Regexp(t.T(), writeErr.Error(), err.Error()) diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 6b42aa8f20..5b8f3093d3 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -33,6 +33,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/syncutil" @@ -185,7 +186,7 @@ func (t *FileTest) TestInitialSourceGeneration() { } func (t *FileTest) TestSourceGenerationSizeAfterWriteDoesNotChange() { - err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0) + err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0, util.Write) require.NoError(t.T(), err) sg := t.in.SourceGeneration() @@ -199,7 +200,7 @@ func (t *FileTest) TestSourceGenerationIsAuthoritativeReturnsTrue() { } func (t *FileTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWrite() { - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0)) + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0, util.Write)) assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) } @@ -209,7 +210,7 @@ func (t *FileTest) TestSyncPendingBufferedWritesReturnsNilAndNoOpForNonStreaming require.NoError(t.T(), err) assert.Equal(t.T(), t.initialContents, string(contents)) - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("bar"), 0)) + assert.NoError(t.T(), t.in.Write(t.ctx, []byte("bar"), 0, util.Write)) gcsSynced, err := t.in.SyncPendingBufferedWrites() require.NoError(t.T(), err) @@ -345,14 +346,14 @@ func (t *FileTest) TestWrite() { assert.Equal(t.T(), "taco", t.initialContents) // Overwite a byte. - err = t.in.Write(t.ctx, []byte("p"), 0) + err = t.in.Write(t.ctx, []byte("p"), 0, util.Write) assert.Nil(t.T(), err) // Add some data at the end. t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("burrito"), 4) + err = t.in.Write(t.ctx, []byte("burrito"), 4, util.Write) assert.Nil(t.T(), err) t.clock.AdvanceTime(time.Second) @@ -436,7 +437,7 @@ func (t *FileTest) TestWriteThenSync() { t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("p"), 0) + err = t.in.Write(t.ctx, []byte("p"), 0, util.Write) assert.Nil(t.T(), err) t.clock.AdvanceTime(time.Second) @@ -515,7 +516,7 @@ func (t *FileTest) TestWriteToLocalFileThenSync() { // Write some content to temp file. t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("tacos"), 0) + err = t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) assert.Nil(t.T(), err) t.clock.AdvanceTime(time.Second) @@ -652,7 +653,7 @@ func (t *FileTest) TestAppendThenSync() { t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("burrito"), int64(len("taco"))) + err = t.in.Write(t.ctx, []byte("burrito"), int64(len("taco")), util.Append) assert.Nil(t.T(), err) t.clock.AdvanceTime(time.Second) @@ -880,7 +881,7 @@ func (t *FileTest) TestTruncateDownwardForLocalFileShouldUpdateLocalFileAttribut err = t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) // Write some data to the local file. - err = t.in.Write(t.ctx, []byte("burrito"), 0) + err = t.in.Write(t.ctx, []byte("burrito"), 0, util.Write) assert.Nil(t.T(), err) // Validate the new data is written correctly. attrs, err = t.in.Attributes(t.ctx) @@ -927,7 +928,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() if tc.performWrite { t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hi"), 0) + err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) // Fetch the attributes and check if the file size reflects the write. @@ -979,7 +980,7 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable if tc.performWrite { t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hi"), 0) + err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) // Fetch the attributes and check if the file size reflects the write. @@ -1053,7 +1054,7 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { if tc.performWrite { t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hihello"), 0) + err := t.in.Write(t.ctx, []byte("hihello"), 0, util.Write) assert.Nil(t.T(), err) assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) // Fetch the attributes and check if the file size reflects the write. @@ -1220,7 +1221,7 @@ func (t *FileTest) TestSetMtime_ContentDirty() { var attrs fuseops.InodeAttributes // Dirty the content. - err = t.in.Write(t.ctx, []byte("a"), 0) + err = t.in.Write(t.ctx, []byte("a"), 0, util.Write) assert.Nil(t.T(), err) // Set mtime. @@ -1535,7 +1536,7 @@ func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { if tc.performWrite { t.createBufferedWriteHandler(tc.fileType != LocalFile) - err := t.in.Write(t.ctx, []byte("hi"), 0) + err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) } @@ -1581,7 +1582,7 @@ func (t *FileTest) TestWriteToLocalFileWhenStreamingWritesAreEnabled() { t.in.config = &cfg.Config{Write: *getWriteConfig()} t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hi"), 0) + err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) @@ -1596,12 +1597,12 @@ func (t *FileTest) TestMultipleWritesToLocalFileWhenStreamingWritesAreEnabled() t.in.config = &cfg.Config{Write: *getWriteConfig()} t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hi"), 0) + err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) - err = t.in.Write(t.ctx, []byte("hello"), 2) + err = t.in.Write(t.ctx, []byte("hello"), 2, util.Write) assert.Nil(t.T(), err) assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) // The inode should agree about the new mtime. @@ -1617,7 +1618,7 @@ func (t *FileTest) TestWriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { createTime := t.in.mtimeClock.Now() t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hi"), 0) + err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) @@ -1647,7 +1648,7 @@ func (t *FileTest) TestSetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEn t.in.config = &cfg.Config{Write: *getWriteConfig()} t.createBufferedWriteHandler(true) // Initiate write call. - err := t.in.Write(t.ctx, []byte("hi"), 0) + err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) writeFileInfo := t.in.bwh.WriteFileInfo() From f11452672546998511c72a1568365c8c0e17061d Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Fri, 30 May 2025 07:20:57 +0530 Subject: [PATCH 0474/1298] refactor gcloud installation to separate script (#3375) --- .../gcp_ubuntu/e2e_tests/tpc_build.sh | 11 +--- .../run_rename_benchmark.sh | 5 +- perfmetrics/scripts/install_latest_gcloud.sh | 61 +++++++++++++++++++ perfmetrics/scripts/upgrade_gcloud.sh | 28 --------- tools/integration_tests/run_e2e_tests.sh | 17 ++---- 5 files changed, 72 insertions(+), 50 deletions(-) create mode 100755 perfmetrics/scripts/install_latest_gcloud.sh delete mode 100755 perfmetrics/scripts/upgrade_gcloud.sh diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh index 10555f63e8..940fee54f2 100755 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/e2e_tests/tpc_build.sh @@ -32,14 +32,9 @@ readonly RUN_TESTS_WITH_ZONAL_BUCKET=false cd "${KOKORO_ARTIFACTS_DIR}/github/gcsfuse" -# Upgrade gcloud version -gcloud version -wget -O gcloud.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz -q -sudo tar xzf gcloud.tar.gz && sudo cp -r google-cloud-sdk /usr/local && sudo rm -r google-cloud-sdk -sudo /usr/local/google-cloud-sdk/install.sh -export PATH=/usr/local/google-cloud-sdk/bin:$PATH -echo 'export PATH=/usr/local/google-cloud-sdk/bin:$PATH' >> ~/.bashrc -gcloud version && rm gcloud.tar.gz +# Install latest gcloud. +./perfmetrics/scripts/install_latest_gcloud.sh +export PATH="/usr/local/google-cloud-sdk/bin:$PATH" # Copy the key file for the TPC service account to use for authentication. gcloud storage cp gs://gcsfuse-tpc-tests/creds.json /tmp/sa.key.json diff --git a/perfmetrics/scripts/hns_rename_folders_metrics/run_rename_benchmark.sh b/perfmetrics/scripts/hns_rename_folders_metrics/run_rename_benchmark.sh index fd1f102ed2..17b92d60c5 100755 --- a/perfmetrics/scripts/hns_rename_folders_metrics/run_rename_benchmark.sh +++ b/perfmetrics/scripts/hns_rename_folders_metrics/run_rename_benchmark.sh @@ -32,8 +32,9 @@ sudo bash add-google-cloud-ops-agent-repo.sh --also-install UPLOAD_FLAGS=$1 gsutil cp gs://periodic-perf-tests/creds.json ../gsheet/ -echo "Upgrading gcloud version" -../upgrade_gcloud.sh +# Install latest gcloud. +../install_latest_gcloud.sh +export PATH="/usr/local/google-cloud-sdk/bin:$PATH" #echo "Running renaming benchmark on flat bucket" #python3 renaming_benchmark.py config-flat.json flat "$UPLOAD_FLAGS" diff --git a/perfmetrics/scripts/install_latest_gcloud.sh b/perfmetrics/scripts/install_latest_gcloud.sh new file mode 100755 index 0000000000..9b344c72ac --- /dev/null +++ b/perfmetrics/scripts/install_latest_gcloud.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Script to install latest version of gcloud along with alpha components + +# Exit on error, treat unset variables as errors, and propagate pipeline errors. +set -euo pipefail + +if [[ $# -ne 0 ]]; then + echo "This script requires no argument." + echo "Usage: $0" + exit 1 +fi + +INSTALL_DIR="/usr/local" # Installation directory + +install_latest_gcloud() { + local temp_dir + temp_dir=$(mktemp -d /tmp/gcloud_install_src.XXXXXX) + pushd "$temp_dir" + + wget -O gcloud.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz -q + sudo rm -rf "${INSTALL_DIR}/google-cloud-sdk" # Remove existing gcloud installation + sudo tar -C "$INSTALL_DIR" -xzf gcloud.tar.gz + sudo "${INSTALL_DIR}/google-cloud-sdk/install.sh" -q + sudo "${INSTALL_DIR}/google-cloud-sdk/bin/gcloud" components update -q + sudo "${INSTALL_DIR}/google-cloud-sdk/bin/gcloud" components install alpha -q + popd + sudo rm -rf "$temp_dir" +} + +echo "Installing latest gcloud version to ${INSTALL_DIR}" +INSTALLATION_LOG=$(mktemp /tmp/gcloud_install_log.XXXXXX) +if ! install_latest_gcloud >"$INSTALLATION_LOG" 2>&1; then + echo "latest gcloud installation failed." + cat "$INSTALLATION_LOG" + rm -f "$INSTALLATION_LOG" + exit 1 +else + echo "latest gcloud installed successfully." + # If this script is run in background or different shell then + # export PATH needs to be called from the shell or use absolute gcloud path + # or permanently add this path to path variable in bashrc. + export PATH="${INSTALL_DIR}/google-cloud-sdk/bin:$PATH" + echo "gcloud Version is:" + gcloud version + echo "Gcloud is present at: $( (which gcloud) )" + rm -f "$INSTALLATION_LOG" +fi diff --git a/perfmetrics/scripts/upgrade_gcloud.sh b/perfmetrics/scripts/upgrade_gcloud.sh deleted file mode 100755 index 9cc72b833b..0000000000 --- a/perfmetrics/scripts/upgrade_gcloud.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# Copyright 2024 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -set -e -sudo apt-get update -# Upgrade gcloud version. -# Kokoro machine's outdated gcloud version prevents the use of the "gcloud storage" feature. -gcloud version -wget -O gcloud.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz -q -sudo tar xzf gcloud.tar.gz && sudo cp -r google-cloud-sdk /usr/local && sudo rm -r google-cloud-sdk -sudo /usr/local/google-cloud-sdk/install.sh -export PATH=/usr/local/google-cloud-sdk/bin:$PATH -echo 'export PATH=/usr/local/google-cloud-sdk/bin:$PATH' >> ~/.bashrc -gcloud version && rm gcloud.tar.gz -sudo /usr/local/google-cloud-sdk/bin/gcloud components update -sudo /usr/local/google-cloud-sdk/bin/gcloud components install alpha diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 958321027c..cd495d01b4 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -236,24 +236,17 @@ function delete_buckets_listed_in_file() { } function upgrade_gcloud_version() { - sudo apt-get update - # Upgrade gcloud version. - # Kokoro machine's outdated gcloud version prevents the use of the "managed-folders" feature. - gcloud version - wget -O gcloud.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz -q - sudo tar xzf gcloud.tar.gz && sudo cp -r google-cloud-sdk /usr/local && sudo rm -r google-cloud-sdk - sudo /usr/local/google-cloud-sdk/install.sh - export PATH=/usr/local/google-cloud-sdk/bin:$PATH - echo 'export PATH=/usr/local/google-cloud-sdk/bin:$PATH' >> ~/.bashrc - gcloud version && rm gcloud.tar.gz - sudo /usr/local/google-cloud-sdk/bin/gcloud components update - sudo /usr/local/google-cloud-sdk/bin/gcloud components install alpha + # Install latest gcloud. + ./perfmetrics/scripts/install_latest_gcloud.sh + export PATH="/usr/local/google-cloud-sdk/bin:$PATH" } function install_packages() { # Install required go version. ./perfmetrics/scripts/install_go.sh "1.24.0" export PATH="/usr/local/go/bin:$PATH" + + sudo apt-get update sudo apt-get install -y python3 # install python3-setuptools tools. sudo apt-get install -y gcc python3-dev python3-setuptools From 75e79d6bdf79e907f1a049f20880a704b527bbdf Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Fri, 30 May 2025 13:12:57 +0530 Subject: [PATCH 0475/1298] add bash installation script (#3376) --- perfmetrics/scripts/install_bash.sh | 62 +++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100755 perfmetrics/scripts/install_bash.sh diff --git a/perfmetrics/scripts/install_bash.sh b/perfmetrics/scripts/install_bash.sh new file mode 100755 index 0000000000..38fff18edb --- /dev/null +++ b/perfmetrics/scripts/install_bash.sh @@ -0,0 +1,62 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Script to install a specific version of GNU Bash to /usr/local/bin/bash +# Usage: install_bash.go + +# Exit on error, treat unset variables as errors, and propagate pipeline errors. +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "This script requires exactly one argument." + echo "Usage: $0 " + echo "Example: $0 5.1" + exit 1 +fi + +BASH_VERSION="$1" +INSTALL_DIR="/usr/local/" # Installation directory + +# Function to download, compile, and install Bash +install_bash() { + local temp_dir + temp_dir=$(mktemp -d /tmp/bash_install_src.XXXXXX) + pushd "$temp_dir" + + wget -q "https://ftp.gnu.org/gnu/bash/bash-${BASH_VERSION}.tar.gz" + tar -xzf "bash-${BASH_VERSION}.tar.gz" + cd "bash-${BASH_VERSION}" + ./configure --prefix="$INSTALL_DIR" --enable-readline + make -s -j"$(nproc 2>/dev/null || echo 1)" + sudo make install + + popd + rm -rf "$temp_dir" +} + +echo "Installing bash version ${BASH_VERSION} to ${INSTALL_DIR}bin/bash" +INSTALLATION_LOG=$(mktemp /tmp/bash_install_log.XXXXXX) + +if ! install_bash >"$INSTALLATION_LOG" 2>&1; then + echo "Bash version ${BASH_VERSION} installation failed." + cat "$INSTALLATION_LOG" + rm -f "$INSTALLATION_LOG" + exit 1 +else + echo "Bash ${BASH_VERSION} installed successfully." + echo "Checking bash version at ${INSTALL_DIR}bin/bash:" + "${INSTALL_DIR}bin/bash" --version + rm -f "$INSTALLATION_LOG" +fi From 283be45ecf4964579ff1ceee53bb3a7db56a0d1a Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 30 May 2025 16:15:59 +0530 Subject: [PATCH 0476/1298] Fix dynamic-mounting test in "operations" package for ZB (#3187) This addresses a TODO left behind from couple of comments (comment1 and comment2) in PR #3172 . The issue was that the bucket created for the dynamic bucket test was creating a non-ZB earlier which returned storage-class as "STANDARD" which mismatched with our test which was checking for it to be "RAPID". I replaced this code to create a ZB instead of non-ZB in case of ZB test runs. Link to the issue in case of a bug fix. b/409734728 Testing detail * create zonal bucket for dynamic mounting * fix zonal bucket creation * terminate test if file open fails * comment out added sleeps between creates and opens * change bucket name for ZB * add an important TODO for future * disallow false-pass for ZB in dynamic mount test * Revert "change bucket name for ZB" This reverts commit a8aded9809b4ea1f8a179609e374a001778f1b5a. * remove some commented out debug code * Revert "add an important TODO for future" This reverts commit 33f9d2b949099d9af20524da28e46d67db4313ac. * removal commented out line --- .../integration_tests/operations/write_test.go | 12 ++---------- .../dynamic_mounting/dynamic_mounting.go | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/tools/integration_tests/operations/write_test.go b/tools/integration_tests/operations/write_test.go index 0aa793fcf3..41515cabaf 100644 --- a/tools/integration_tests/operations/write_test.go +++ b/tools/integration_tests/operations/write_test.go @@ -108,15 +108,7 @@ func validateObjectAttributes(attr1, attr2 *storage.ObjectAttrs, t *testing.T) { } } if attr1.StorageClass != storageClass || attr2.StorageClass != storageClass { - if setup.IsZonalBucketRun() { - if attr1.StorageClass != attr2.StorageClass { - t.Errorf("Expected storage classes to be same (%q) for both, but found attr1.StorageClass = %q (bucketName = %q) != attr2.StorageClass = %q (bucketName = %q)", storageClass, attr1.StorageClass, attr1.Bucket, attr2.StorageClass, attr2.Bucket) - } else { - t.Logf("Expected storage class to be %q for both, but found StorageClass = %q for both (buckets = %q, %q).", storageClass, attr1.StorageClass, attr1.Bucket, attr2.Bucket) - } - } else { - t.Errorf("Expected storage class to be %q, but found attr1.StorageClass = %q (bucketName = %q), attr2.StorageClass = %q (bucketName = %q)", storageClass, attr1.StorageClass, attr1.Bucket, attr2.StorageClass, attr2.Bucket) - } + t.Errorf("Expected storage class to be %q, but found attr1.StorageClass = %q (bucketName = %q), attr2.StorageClass = %q (bucketName = %q)", storageClass, attr1.StorageClass, attr1.Bucket, attr2.StorageClass, attr2.Bucket) } attr1MTime, _ := time.Parse(time.RFC3339Nano, attr1.Metadata[gcs.MtimeMetadataKey]) attr2MTime, _ := time.Parse(time.RFC3339Nano, attr2.Metadata[gcs.MtimeMetadataKey]) @@ -228,7 +220,7 @@ func TestWriteAtFileOperationsDoesNotChangeObjectAttributes(t *testing.T) { // Over-write the file. fh, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_TRUNC|syscall.O_DIRECT, operations.FilePermission_0600) if err != nil { - t.Errorf("Could not open file %s after creation.", fileName) + t.Fatalf("Could not open file %s after creation.", fileName) } operations.WriteAt(tempFileContent+appendContent, 0, fh, t) operations.CloseFileShouldNotThrowError(t, fh) diff --git a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go index af1af82005..4c452fd4d9 100644 --- a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go +++ b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go @@ -108,6 +108,23 @@ func CreateTestBucketForDynamicMounting(ctx context.Context, client *storage.Cli Location: "us-west1", } + if setup.IsZonalBucketRun() { + storageClassAndLocation.StorageClass = "RAPID" + if setup.IsPresubmitRun() { + storageClassAndLocation.Location = "us-west4" + storageClassAndLocation.CustomPlacementConfig = &storage.CustomPlacementConfig{DataLocations: []string{"us-west4-a"}} + } else { + storageClassAndLocation.Location = "us-central1" + storageClassAndLocation.CustomPlacementConfig = &storage.CustomPlacementConfig{DataLocations: []string{"us-central1-a"}} + } + storageClassAndLocation.HierarchicalNamespace = &storage.HierarchicalNamespace{ + Enabled: true, + } + storageClassAndLocation.UniformBucketLevelAccess = storage.UniformBucketLevelAccess{ + Enabled: true, + } + } + bucketName = PrefixBucketForDynamicMountingTest + setup.GenerateRandomString(5) bucket := client.Bucket(bucketName) if err := bucket.Create(ctx, projectID, storageClassAndLocation); err != nil { From 7cb6a2952efa9db359e48b6121a0c5358dd4aa13 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Fri, 30 May 2025 11:25:59 +0000 Subject: [PATCH 0477/1298] fix CVE-2025-27144 and upgrade direct dependencies (#3372) * fix CVE-2025-27144 and upgrade direct dependencies * upgrade go/storage to v1.55.0 --- go.mod | 48 ++++++++++++++++++++++++------------------------ go.sum | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 2f2cd26f18..bdda772884 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ require ( cloud.google.com/go/iam v1.5.2 cloud.google.com/go/profiler v0.4.2 cloud.google.com/go/secretmanager v1.14.7 - cloud.google.com/go/storage v1.54.0 - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 + cloud.google.com/go/storage v1.55.0 + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0 + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.28.0 github.com/fsouza/fake-gcs-server v1.52.2 github.com/go-viper/mapstructure/v2 v2.2.1 github.com/google/uuid v1.6.0 @@ -31,7 +31,7 @@ require ( github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 go.opencensus.io v0.24.0 - go.opentelemetry.io/contrib/detectors/gcp v1.35.0 + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 go.opentelemetry.io/otel v1.36.0 go.opentelemetry.io/otel/exporters/prometheus v0.58.0 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 @@ -45,37 +45,37 @@ require ( golang.org/x/sys v0.33.0 golang.org/x/text v0.25.0 golang.org/x/time v0.11.0 - google.golang.org/api v0.234.0 - google.golang.org/grpc v1.72.1 + google.golang.org/api v0.235.0 + google.golang.org/grpc v1.72.2 google.golang.org/protobuf v1.36.6 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 ) require ( - cel.dev/expr v0.22.0 // indirect - cloud.google.com/go v0.121.0 // indirect + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.121.2 // indirect cloud.google.com/go/auth v0.16.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/longrunning v0.6.7 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect cloud.google.com/go/pubsub v1.49.0 // indirect cloud.google.com/go/trace v1.11.6 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.28.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/go-jose/go-jose/v4 v4.0.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect + github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect @@ -83,25 +83,25 @@ require ( github.com/gorilla/mux v1.8.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/xattr v0.4.10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/procfs v0.16.1 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/sagikazarmark/locafero v0.9.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.12.0 // indirect - github.com/spf13/cast v1.7.1 // indirect + github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/cast v1.8.0 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.38.0 // indirect - google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect + google.golang.org/genproto v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect ) diff --git a/go.sum b/go.sum index 9e164368ae..17b2ce23d9 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,25 @@ cel.dev/expr v0.22.0 h1:+hFFhLPmquBImfs1BiN2PZmkr5ASse2ZOuaxIs9e4R8= cel.dev/expr v0.22.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.121.0 h1:pgfwva8nGw7vivjZiRfrmglGWiCJBP+0OmDpenG/Fwg= cloud.google.com/go v0.121.0/go.mod h1:rS7Kytwheu/y9buoDmu5EIpMMCI4Mb8ND4aeN4Vwj7Q= +cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg= +cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= +cloud.google.com/go/compute v1.38.0 h1:MilCLYQW2m7Dku8hRIIKo4r0oKastlD74sSu16riYKs= +cloud.google.com/go/compute v1.38.0/go.mod h1:oAFNIuXOmXbK/ssXm3z4nZB8ckPdjltJ7xhHCdbWFZM= cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= cloud.google.com/go/kms v1.21.2 h1:c/PRUSMNQ8zXrc1sdAUnsenWWaNXN+PzTXfXOcSFdoE= cloud.google.com/go/kms v1.21.2/go.mod h1:8wkMtHV/9Z8mLXEXr1GK7xPSBdi6knuLXIhqjuWcI6w= +cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= @@ -27,19 +34,30 @@ cloud.google.com/go/secretmanager v1.14.7 h1:VkscIRzj7GcmZyO4z9y1EH7Xf81PcoiAo7M cloud.google.com/go/secretmanager v1.14.7/go.mod h1:uRuB4F6NTFbg0vLQ6HsT7PSsfbY7FqHbtJP1J94qxGc= cloud.google.com/go/storage v1.54.0 h1:Du3XEyliAiftfyW0bwfdppm2MMLdpVAfiIg4T2nAI+0= cloud.google.com/go/storage v1.54.0/go.mod h1:hIi9Boe8cHxTyaeqh7KMMwKg088VblFK46C2x/BWaZE= +cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0= +cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.28.0 h1:VaFXBL0NJpiFBtw4aVJpKHeKULVTcHpD+/G0ibZkcBw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.28.0/go.mod h1:JXkPazkEc/dZTHzOlzv2vT1DlpWSTbSLmu/1KY6Ly0I= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0 h1:QFgWzcdmJlgEAwJz/zePYVJQxfoJGRtgIqZfIUFg5oQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0/go.mod h1:ayYHuYU7iNcNtEs1K9k6D/Bju7u1VEHMQm5qQ1n3GtM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 h1:Jtr816GUk6+I2ox9L/v+VcOwN6IyGOEDTSNHfD6m9sY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0/go.mod h1:E05RN++yLx9W4fXPtX978OLo9P0+fBacauUdET1BckA= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.28.0 h1:RC78FvsZ8rLRLgVQuw1jMJ8d6t38QgOv3hDoUVGD50U= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.28.0/go.mod h1:03+QlJ+6zSrBaVaZ9K87fzUyKBDcAh0X1n1Vxq3XAjc= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.52.0 h1:0l8ynskVvq1dvIn5vJbFMf/a/3TqFpRmCMrruFbzlvk= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0 h1:wbMd4eG/fOhsCa6+IP8uEDvWF5vl7rNoUWmP5f72Tbs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0/go.mod h1:gdIm9TxRk5soClCwuB0FtdXsbqtw0aqPwBEurK9tPkw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -49,6 +67,8 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -74,15 +94,23 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsouza/fake-gcs-server v1.52.2 h1:j6ne83nqHrlX5EEor7WWVIKdBsztGtwJ1J2mL+k+iio= github.com/fsouza/fake-gcs-server v1.52.2/go.mod h1:47HKyIkz6oLTes1R8vEaHLwXfzYsGfmDUk1ViHHAUsA= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= +github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= +github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= @@ -119,6 +147,8 @@ github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9 github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4= +github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= @@ -174,6 +204,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA= github.com/pkg/xattr v0.4.10/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= @@ -197,12 +229,18 @@ github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= +github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= +github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= @@ -233,16 +271,23 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA= go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel/exporters/prometheus v0.58.0 h1:CJAxWKFIqdBennqxJyOgnt5LqkeFRT+Mz3Yjz3hL+h8= go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY= go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= @@ -302,6 +347,8 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.234.0 h1:d3sAmYq3E9gdr2mpmiWGbm9pHsA/KJmyiLkwKfHBqU4= google.golang.org/api v0.234.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= +google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo= +google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -309,10 +356,16 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= +google.golang.org/genproto v0.0.0-20250528174236-200df99c418a h1:KXuwdBmgjb4T3l4ZzXhP6HxxFKXD9FcK5/8qfJI4WwU= +google.golang.org/genproto v0.0.0-20250528174236-200df99c418a/go.mod h1:Nlk93rrS2X7rV8hiC2gh2A/AJspZhElz9Oh2KGsjLEY= google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0= google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw= +google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg= google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -320,6 +373,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= +google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From b006a6e29509bc5db9a45d202411636bfa22fb4f Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 3 Jun 2025 16:21:02 +0530 Subject: [PATCH 0478/1298] Add new e2e test script with multiple improvements and refactoring. (#3332) * create improved_run_e2e_tests.sh * fix review comments * fix review comments --- .../improved_run_e2e_tests.sh | 559 ++++++++++++++++++ 1 file changed, 559 insertions(+) create mode 100755 tools/integration_tests/improved_run_e2e_tests.sh diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh new file mode 100755 index 0000000000..fd0bed51f5 --- /dev/null +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -0,0 +1,559 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Script Usage Documentation +usage() { + echo "Usage: $0 [RUN_TEST_ON_TPC_ENDPOINT] [RUN_TESTS_WITH_PRESUBMIT_FLAG] [RUN_TESTS_WITH_ZONAL_BUCKET] [BUILD_BINARY_IN_SCRIPT]" + echo " TEST_INSTALLED_PACKAGE: 'true' or 'false' to test installed gcsfuse package." + echo " SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE: 'true' or 'false' to skip few non-essential tests inside packages." + echo " BUCKET_LOCATION: The Google Cloud Storage bucket location (e.g., 'us-central1')." + echo " RUN_TEST_ON_TPC_ENDPOINT (optional): 'true' or 'false' to run tests on TPC endpoint (Default: 'false')." + echo " RUN_TESTS_WITH_PRESUBMIT_FLAG (optional): 'true' or 'false' to run tests with presubmit flag (Default: 'false')." + echo " RUN_TESTS_WITH_ZONAL_BUCKET (optional): 'true' or 'false' to run tests with zonal bucket (Default: 'false')." + echo " BUILD_BINARY_IN_SCRIPT (optional): 'true' or 'false' to build binary in script (Default: 'true')." + exit 1 +} + +# Logging Helpers +log_info() { + echo "[INFO] $(date +"%Y-%m-%d %H:%M:%S"): $1" +} + +log_error() { + echo "[ERROR] $(date +"%Y-%m-%d %H:%M:%S"): $1" +} + +# Confirm bash version before continuing script. +REQUIRED_BASH_MAJOR=5 +REQUIRED_BASH_MINOR=1 +if (( BASH_VERSINFO[0] < REQUIRED_BASH_MAJOR || ( BASH_VERSINFO[0] == REQUIRED_BASH_MAJOR && BASH_VERSINFO[1] < REQUIRED_BASH_MINOR ) )); then + log_error "This script requires Bash version: ${REQUIRED_BASH_MAJOR}.${REQUIRED_BASH_MINOR} or higher." + log_error "You are currently using Bash version: ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}" + exit 1 +fi +log_info "Bash version: ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}" + +# Constants +readonly GO_VERSION="1.24.0" +readonly DEFAULT_PROJECT_ID="gcs-fuse-test-ml" +readonly TPCZERO_PROJECT_ID="tpczero-system:gcsfuse-test-project" +readonly TPC_BUCKET_LOCATION="u-us-prp1" +readonly BUCKET_PREFIX="gcsfuse-e2e" +readonly INTEGRATION_TEST_PACKAGE_DIR="./tools/integration_tests" +readonly INTEGRATION_TEST_PACKAGE_TIMEOUT_IN_MINS=60 +readonly TMP_PREFIX="gcsfuse_e2e" +readonly ZONAL_BUCKET_SUPPORTED_LOCATIONS=("us-central1" "us-west4") +readonly PACKAGE_LEVEL_PARALLELISM=10 # Controls how many test packages are run in parallel for hns, flat or zonal buckets. +readonly DELETE_BUCKET_PARALLELISM=10 # Controls how many buckets are deleted in parallel. +# 6 second delay between creating buckets as both hns and flat runs create buckets in parallel. +# Ref: https://cloud.google.com/storage/quotas#buckets +readonly DELAY_BETWEEN_BUCKET_CREATION=6 +readonly ZONAL="zonal" +readonly FLAT="flat" +readonly HNS="hns" + +# Set default project id for tests. +PROJECT_ID="${DEFAULT_PROJECT_ID}" +# This variable will store the path if the script builds GCSFuse binaries (gcsfuse, mount.gcsfuse) +BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR="" + +LOG_LOCK_FILE=$(mktemp "/tmp/${TMP_PREFIX}_logging_lock.XXXXXX") || { log_error "Unable to create lock file"; exit 1; } +BUCKET_NAMES=$(mktemp "/tmp/${TMP_PREFIX}_bucket_names.XXXXXX") || { log_error "Unable to create bucket names file"; exit 1; } + +# Argument Parsing and Assignments +if [ "$#" -lt 3 ]; then + log_error "Missing required arguments." + usage +fi +TEST_INSTALLED_PACKAGE="$1" +SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE="$2" +BUCKET_LOCATION="$3" +# Assign optional arguments with provided or default values +RUN_TEST_ON_TPC_ENDPOINT="${4:-false}" +RUN_TESTS_WITH_PRESUBMIT_FLAG="${5:-false}" +RUN_TESTS_WITH_ZONAL_BUCKET="${6:-false}" +BUILD_BINARY_IN_SCRIPT="${7:-true}" +if [ "$#" -gt 7 ]; then + log_error "Too many arguments." + usage +fi + +# Zonal Bucket location validation. +if [[ "${RUN_TESTS_WITH_ZONAL_BUCKET}" == "true" ]]; then + supported_bucket=false + for location in "${ZONAL_BUCKET_SUPPORTED_LOCATIONS[@]}"; do + if [[ "$BUCKET_LOCATION" == "$location" ]]; then + supported_bucket=true + break + fi + done + if [[ "${supported_bucket}" == false ]]; then + log_error "Unsupported Bucket Location ${BUCKET_LOCATION} for Zonal Run. Supported Locations are: ${ZONAL_BUCKET_SUPPORTED_LOCATIONS[*]}" + exit 1 + fi +fi + +# Test packages which can be run for both Zonal and Regional buckets. +# Sorted list descending run times. (Longest Processing Time first strategy) +TEST_PACKAGES_COMMON=( + "managed_folders" + "operations" + "read_large_files" + "concurrent_operations" + "read_cache" + "list_large_dir" + "mount_timeout" + "write_large_files" + "implicit_dir" + "interrupt" + "local_file" + "readonly" + "readonly_creds" + "rename_dir_limit" + "kernel_list_cache" + "streaming_writes" + "benchmarking" + "explicit_dir" + "gzip" + "log_rotation" + "monitoring" + "mounting" + # "grpc_validation" + "negative_stat_cache" + "stale_handle" +) + +# Test packages for regional buckets. +TEST_PACKAGES_FOR_RB=("${TEST_PACKAGES_COMMON[@]}" "inactive_stream_timeout" "cloud_profiler") +# Test packages for zonal buckets. +TEST_PACKAGES_FOR_ZB=("${TEST_PACKAGES_COMMON[@]}" "unfinalized_object") +# Test packages for TPC buckets. +TEST_PACKAGES_FOR_TPC=("operations") + +# acquire_lock: Acquires exclusive lock or exits script on failure. +# Args: $1 = path to lock file. +acquire_lock() { + if [[ -z "$1" ]]; then + log_error "acquire_lock: Lock file path is required." + exit 1 + fi + local lock_file="$1" + local timeout_seconds=600 # 10 minutes + exec 200>"$lock_file" || { + log_error "Could not open lock file $lock_file." + exit 1 + } + # Attempt to acquire the lock with a timeout + if ! flock -x -w "$timeout_seconds" 200; then + log_error "Failed to acquire lock on $lock_file within $timeout_seconds seconds." + # Close the file descriptor if the lock was not acquired + exec 200>&- + exit 1 + fi + return 0 +} + +# release_lock: Releases lock or exits script on failure. +# Args: $1 = path to lock file +release_lock() { + if [[ -z "$1" ]]; then + log_error "release_lock: Lock file path is required." + exit 1 + fi + local lock_file="$1" + [[ -e "/proc/self/fd/200" || -L "/proc/self/fd/200" ]] && exec 200>&- || { + log_error "Lock file descriptor (FD 200) not open for $lock_file. Possible previous error or double release." + exit 1 + } # FD not open or close failed + return 0 +} + +# logs info to stdout exclusively. used in background commands to ensure logs aren't interleaved. +log_info_locked() { + acquire_lock "$LOG_LOCK_FILE" + log_info "$1" + release_lock "$LOG_LOCK_FILE" +} + +# logs error to stdout exclusively. Used in background commands to ensure logs aren't interleaved. +log_error_locked() { + acquire_lock "$LOG_LOCK_FILE" + log_error "$1" + release_lock "$LOG_LOCK_FILE" +} + +# Helper method to create "flat", "hns" or "zonal" bucket. +create_bucket() { + if [[ $# -ne 2 ]]; then + log_error "create_bucket() called with incorrect number of arguments." + return 1 + fi + local package="$1" + local bucket_type="$2" + local bucket_name="${BUCKET_PREFIX}-${package}-${bucket_type}-$(date +%s%N)" + local bucket_cmd_parts=("gcloud" "alpha" "storage" "buckets" "create" "gs://${bucket_name}" "--project=${PROJECT_ID}" "--location=${BUCKET_LOCATION}" "--uniform-bucket-level-access") + if [[ "$bucket_type" == "$HNS" ]]; then + bucket_cmd_parts+=("--enable-hierarchical-namespace") + elif [[ "$bucket_type" == "$ZONAL" ]]; then + bucket_cmd_parts+=("--enable-hierarchical-namespace" "--placement=${BUCKET_LOCATION}-a" "--default-storage-class=RAPID") + elif [[ "$bucket_type" != "$FLAT" ]]; then + log_error "Invalid bucket type: $bucket_type." + return 1 + fi + local bucket_cmd bucket_cmd_log attempt=5 + bucket_cmd=$(printf "%q " "${bucket_cmd_parts[@]}") + bucket_cmd_log=$(mktemp "/tmp/${TMP_PREFIX}_bucket_cmd_log.XXXXXX") + while : ; do + attempt=$((attempt - 1)) + if [ $attempt -lt 0 ]; then + log_error "Unable to create bucket [${bucket_name}] after 5 attempts." + cat "$bucket_cmd_log" + return 1 + fi + eval "$bucket_cmd" > "$bucket_cmd_log" 2>&1 + if [ $? -eq 0 ]; then + sleep "$DELAY_BETWEEN_BUCKET_CREATION" # have 6 seconds gap between creating buckets. + break + fi + done + echo "$bucket_name" >> "$BUCKET_NAMES" # Add bucket names to file. + echo "$bucket_name" + return 0 +} + +# Helper method to create buckets for each of the package. +setup_package_buckets () { + if [[ "$#" -ne 3 ]]; then + log_error "setup_buckets() called with incorrect number of arguments." + exit 1 + fi + local -n package_array="$1" + local -n package_bucket_array="$2" + local bucket_type="$3" + local exit_code=0 + for package in "${package_array[@]}"; do + local bucket_name + bucket_name=$(create_bucket "$package" "$bucket_type") + if [ $? -eq 0 ]; then + package_bucket_array+=("${package} ${bucket_name} ${bucket_type}") + else + exit_code=1 + fi + done + return $exit_code +} + +# Helper method to delete the bucket. +delete_bucket() { + if [[ $# -ne 1 ]]; then + log_error_locked "delete_bucket() called with incorrect number of arguments." + return 1 + fi + local bucket="$1" + if ! gcloud -q storage rm -r "gs://${bucket}"; then + return 1 + fi + return 0 +} + +# Cleanup ensures each of the buckets created is destroyed and the temp files are cleaned up. +clean_up() { + if [ -n "${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" ] && [ -d "${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" ]; then + log_info "Cleaning up GCSFuse build directory created by script: ${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" + rm -rf "${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" + fi + local buckets=() + # Read each line from BUCKET_NAMES into buckets array + mapfile -t buckets < "$BUCKET_NAMES" + # Clean up buckets if any. + if [[ "${#buckets[@]}" -gt 0 ]]; then + local clean_up_log=$(mktemp "/tmp/${TMP_PREFIX}_clean_up.XXXXXX") + if ! run_parallel "$DELETE_BUCKET_PARALLELISM" "delete_bucket @" "${buckets[@]}" > "$clean_up_log" 2>&1; then + log_error "Failed to delete all buckets" + cat "$clean_up_log" + else + log_info "Successfully deleted all buckets." + fi + fi + if ! rm -rf /tmp/"${TMP_PREFIX}_"*; then + log_error "Failed to delete temporary files" + else + log_info "Successfully cleaned up temporary files" + fi +} + +# Helper method to process any of the background process and +# returns exit status of waited pid. +process_any_pid() { + local -n cmds_by_pid_ref="$1" + local waited_pid + local pid_status # To store the exit status of the waited pid + + wait -n -p waited_pid # waited_pid gets the PID, $? gets the status + pid_status=$? + + local cmd_and_output_file="${cmds_by_pid_ref[$waited_pid]}" + local parallel_cmd_executed="${cmd_and_output_file%%;*}" + local output_file="${cmd_and_output_file#*;}" + unset "cmds_by_pid_ref[$waited_pid]" + if [[ "$pid_status" -ne 0 ]]; then + acquire_lock "$LOG_LOCK_FILE" + log_error "Parallel Command failed: $parallel_cmd_executed" + cat "$output_file" + release_lock "$LOG_LOCK_FILE" + return 1 + fi + log_info_locked "Parallel Command succeeded: $parallel_cmd_executed" + return 0 +} + +# run_parallel: Executes commands in parallel based on a template and substitutes. +# Prints output (stdout/stderr) if the command errors out. +# Prints success message if command succeeds. +# The function returns a non-zero exit status if any of the parallel commands fail. +# +# Usage: run_parallel "parallelism" "command_template_with_@" "substitute1" "substitute2" ... +# The '@' in the command_template will be replaced by each substitute argument. +# This first argument is exten of parallelism for this command. +# +# Example: +# run_parallel 2 "echo 'Processing @' && sleep 1" "itemA" "itemB" "itemC" +# This command will run at max 2 commands in parallel. +run_parallel() { + if [[ $# -lt 2 ]]; then + log_error_locked "run_parallel() called with incorrect number of arguments." + return 1 + fi + local parallelism="$1" + shift + local cmd_template="$1" + shift + local -A cmds_by_pid=() + local overall_exit_code=0 parallel_cmd parallel_cmd_output pid + # Launch parallel commands in the background based on parallelism. + for arg in "$@"; do + parallel_cmd="${cmd_template//@/$arg}" + parallel_cmd_output=$(mktemp "/tmp/${TMP_PREFIX}_{$parallel_cmd}_output.XXXXXX") + log_info_locked "Executing Parallel Command: $parallel_cmd" + eval "$parallel_cmd" > "$parallel_cmd_output" 2>&1 & + pid=$! + cmds_by_pid["$pid"]="$parallel_cmd;$parallel_cmd_output" + if [[ ${#cmds_by_pid[@]} -eq $parallelism ]]; then + process_any_pid "cmds_by_pid" + overall_exit_code=$((overall_exit_code || $? )) + fi + done + # Process any remaining PIDs + while [[ ${#cmds_by_pid[@]} -gt 0 ]]; do + process_any_pid "cmds_by_pid" + overall_exit_code=$((overall_exit_code || $? )) + done + return $overall_exit_code +} + +# Helper method to executes e2e test package. +test_package() { + if [[ $# -ne 3 ]]; then + log_error_locked "test_package() called with incorrect number of arguments." + return 1 + fi + local package_name="$1" + local bucket_name="$2" + local bucket_type="$3" + + # Build go package test command. + local go_test_cmd_parts=("GODEBUG=asyncpreemptoff=1" "go" "test" "-v" "-timeout=${INTEGRATION_TEST_PACKAGE_TIMEOUT_IN_MINS}m" "${INTEGRATION_TEST_PACKAGE_DIR}/${package_name}") + if [[ "$SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE" == "true" ]]; then + go_test_cmd_parts+=("-short") + fi + if [[ "$package_name" == "benchmarking" ]]; then + go_test_cmd_parts+=("-bench=." "-benchtime=100x") + fi + # Test Binary flags after this. + go_test_cmd_parts+=("-args" "--integrationTest" "--testbucket=${bucket_name}") + if [[ "$TEST_INSTALLED_PACKAGE" == "true" ]]; then + go_test_cmd_parts+=("--testInstalledPackage") + fi + if [[ "$RUN_TESTS_WITH_PRESUBMIT_FLAG" == "true" ]]; then + go_test_cmd_parts+=("--presubmit") + fi + if [[ "$bucket_type" == "$ZONAL" ]]; then + go_test_cmd_parts+=("--zonal") + fi + if [[ "$RUN_TEST_ON_TPC_ENDPOINT" == "true" ]]; then + go_test_cmd_parts+=("--testOnTPCEndPoint") + fi + if [[ -n "$BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR" ]]; then + go_test_cmd_parts+=("--gcsfuse_prebuilt_dir=${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}") + fi + # Use printf %q to quote each argument safely for eval + # This ensures spaces and special characters within arguments are handled correctly. + local go_test_cmd=$(printf "%q " "${go_test_cmd_parts[@]}") + + # Run the package test command + eval "$go_test_cmd" + if [[ $? -ne 0 ]]; then + return 1 + fi + return 0 +} + +build_gcsfuse_once() { + local build_output_dir # For the final gcsfuse binaries + build_output_dir=$(mktemp -d -t gcsfuse_e2e_run_build_XXXXXX) + log_info "GCSFuse binaries will be built in ${build_output_dir}/" + + local gcsfuse_src_dir + # Determine GCSFuse source directory + # If this script is in tools/integration_tests, project root is ../../ + SCRIPT_DIR_REALPATH=$(realpath "$(dirname "${BASH_SOURCE[0]}")") + gcsfuse_src_dir=$(realpath "${SCRIPT_DIR_REALPATH}/../../") + + if [[ ! -f "${gcsfuse_src_dir}/go.mod" ]]; then + log_error "Could not reliably determine GCSFuse project root from ${SCRIPT_DIR_REALPATH}. Expected go.mod at ${gcsfuse_src_dir}" >&2 + rm -rf "${build_output_dir}" + exit 1 + fi + log_info "Using GCSFuse source directory: ${gcsfuse_src_dir}" + + log_info "Building GCSFuse using 'go run ./tools/build_gcsfuse/main.go'..." + (cd "${gcsfuse_src_dir}" && go run ./tools/build_gcsfuse/main.go . "${build_output_dir}" "e2e-$(date +%s)") + if [ $? -ne 0 ]; then + log_error "Building GCSFuse binaries using 'go run ./tools/build_gcsfuse/main.go' failed." + rm -rf "${build_output_dir}" # Clean up created temp dir + return 1 + fi + + # Set the directory path for use by the script (to form the go test flag) + BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR="${build_output_dir}" + log_info "GCSFuse binaries built by script in: ${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" + log_info "GCSFuse executable: ${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}/bin/gcsfuse" + return 0 +} + +install_packages() { + sudo apt-get install -y python3 + # install python3-setuptools tools. + sudo apt-get install -y gcc python3-dev python3-setuptools + # Downloading composite object requires integrity checking with CRC32c in gsutil. + # it requires to install crcmod. + sudo apt install -y python3-crcmod + # Install required go version. + bash ./perfmetrics/scripts/install_go.sh "$GO_VERSION" + export PATH="/usr/local/go/bin:$PATH" + # Install latest gcloud version. + bash ./perfmetrics/scripts/install_latest_gcloud.sh + export PATH="/usr/local/google-cloud-sdk/bin:$PATH" +} + +# Generic function to run a group of E2E tests for a given bucket type. +# Args: +# $1: Descriptive group name (e.g., "REGIONAL", "ZONAL", "TPC") +# $2: Name of the array holding test packages (e.g., "TEST_PACKAGES_FOR_RB", "TEST_PACKAGES_FOR_ZB") +# $3: Bucket type ("flat", "hns", "zonal") +run_test_group() { + local group_name="$1" + local test_packages_var_name="$2" + local bucket_type="$3" + local packages_for_run=() + local group_exit_code=0 + log_info_locked "Started running e2e tests for ${group_name} group (bucket type: ${bucket_type})." + + setup_package_buckets "${test_packages_var_name}" "packages_for_run" "${bucket_type}" + group_exit_code=$? + + run_parallel "$PACKAGE_LEVEL_PARALLELISM" "test_package @" "${packages_for_run[@]}" + group_exit_code=$((group_exit_code || $?)) + + if [ "$group_exit_code" -ne 0 ]; then + log_error_locked "The e2e tests for ${group_name} group (bucket type: ${bucket_type}) FAILED." + return 1 + fi + log_info_locked "The e2e tests for ${group_name} group (bucket type: ${bucket_type}) successful." + return 0 +} + +run_e2e_tests_for_emulator() { + log_info_locked "Started running e2e tests for emulator." + local emulator_test_log=$(mktemp "/tmp/${TMP_PREFIX}_emulator_test_log.XXXXXX") + if ! ./tools/integration_tests/emulator_tests/emulator_tests.sh "$TEST_INSTALLED_PACKAGE" > "$emulator_test_log" 2>&1; then + acquire_lock "$LOG_LOCK_FILE" + log_error "" + log_error "--- Emulator Tests Failed ---" + cat "$emulator_test_log" + release_lock "$LOG_LOCK_FILE" + return 1 + fi + log_info_locked "Emulator tests successful." + return 0 +} + +main() { + # Clean up everything on exit. + trap clean_up EXIT + log_info "" + log_info "------ Upgrading gcloud and installing packages ------" + log_info "" + set -e + install_packages + set +e + log_info "------ Upgrading gcloud and installing packages took $SECONDS seconds ------" + + log_info "" + log_info "------ Started running E2E test packages ------" + log_info "" + + # Decide whether to build GCSFuse based on RUN_E2E_TESTS_ON_PACKAGE + if [ "$TEST_INSTALLED_PACKAGE" != "true" ] && [ "$BUILD_BINARY_IN_SCRIPT" == "true" ]; then + log_info "TEST_INSTALLED_PACKAGE is not 'true' (value: '${TEST_INSTALLED_PACKAGE}') and BUILD_BINARY_IN_SCRIPT is 'true'." + log_info "Building GCSFuse inside script..." + if ! build_gcsfuse_once; then + log_error "build_gcsfuse_once failed. Exiting." + # The trap will handle cleanup + exit 1 + fi + log_info "Script built GCSFuse at: ${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" + fi + + # Reset SECONDS to 0 + SECONDS=0 + local pids=() + local overall_exit_code=0 + if [[ "${RUN_TESTS_WITH_ZONAL_BUCKET}" == "true" ]]; then + run_test_group "ZONAL" "TEST_PACKAGES_FOR_ZB" "$ZONAL" & pids+=($!) + elif [[ "${RUN_TEST_ON_TPC_ENDPOINT}" == "true" ]]; then + # Override PROJECT_ID and BUCKET_LOCATION for TPC tests + PROJECT_ID="$TPCZERO_PROJECT_ID" + BUCKET_LOCATION="$TPC_BUCKET_LOCATION" + run_test_group "TPC" "TEST_PACKAGES_FOR_TPC" "$HNS" & pids+=($!) + run_test_group "TPC" "TEST_PACKAGES_FOR_TPC" "$FLAT" & pids+=($!) + else + run_test_group "REGIONAL" "TEST_PACKAGES_FOR_RB" "$HNS" & pids+=($!) + run_test_group "REGIONAL" "TEST_PACKAGES_FOR_RB" "$FLAT" & pids+=($!) + run_e2e_tests_for_emulator & pids+=($!) # Emulator tests are a separate group + fi + # Wait for all background processes to complete and aggregate their exit codes + for pid in "${pids[@]}"; do + wait "$pid" + overall_exit_code=$((overall_exit_code || $?)) + done + elapsed_min=$(((SECONDS + 60) / 60)) + log_info "------ E2E test packages complete run took ${elapsed_min} minutes ------" + log_info "" + exit $overall_exit_code +} + +#Main method to run script +main From c38d8a7f657cb9e387bbcce325edda4924a1205c Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 3 Jun 2025 17:03:37 +0530 Subject: [PATCH 0479/1298] Add support for truncate to lower for streaming writes and fix promotion to generation backed Inode in Write and Truncate Flow. (#3371) * add truncate support to lower * fix flow to be more intutive and add table driven tests * fix review comment * add changes for promotion of inode to generation backed indoes in truncate and write case * fix review comments --- .../bufferedwrites/buffered_write_handler.go | 2 +- .../buffered_write_handler_test.go | 1 + internal/fs/fs.go | 16 +- internal/fs/handle/file.go | 2 +- internal/fs/handle/file_test.go | 2 +- internal/fs/inode/file.go | 78 ++++++---- .../fs/inode/file_streaming_writes_test.go | 99 ++++++++---- internal/fs/inode/file_test.go | 147 +++++++++++------- internal/fs/streaming_writes_common_test.go | 76 +++++++++ .../streaming_writes_empty_gcs_object_test.go | 12 +- .../integration_tests/local_file/stat_file.go | 11 +- .../streaming_writes/truncate_file_test.go | 127 ++++++++++----- .../streaming_writes/write_file_test.go | 53 +++++++ 13 files changed, 461 insertions(+), 165 deletions(-) create mode 100644 tools/integration_tests/streaming_writes/write_file_test.go diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 3817a3fc58..41605ac880 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -260,7 +260,7 @@ func (wh *bufferedWriteHandlerImpl) SetMtime(mtime time.Time) { func (wh *bufferedWriteHandlerImpl) Truncate(size int64) error { if size < wh.totalSize { - return fmt.Errorf("cannot truncate to lesser size when upload is in progress") + return ErrOutOfOrderWrite } wh.truncatedSize = size diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index de98f012c2..4ffc56d127 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -410,6 +410,7 @@ func (testSuite *BufferedWriteTest) TestTruncateWithLesserSize() { err := testSuite.bwh.Truncate(2) assert.Error(testSuite.T(), err) + assert.Equal(testSuite.T(), ErrOutOfOrderWrite, err) } func (testSuite *BufferedWriteTest) TestTruncateWithSizeGreaterThanCurrentObjectSize() { diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 1297607e41..7b6e0a917f 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1572,7 +1572,12 @@ func (fs *fileSystem) SetInodeAttributes( if err != nil { return } - err = file.Truncate(ctx, int64(*op.Size)) + gcsSynced, err := file.Truncate(ctx, int64(*op.Size)) + // Sync the inode if finalize during truncate is successful + // even if the truncate operation later resulted error. + if gcsSynced { + fs.promoteToGenerationBacked(file) + } if err != nil { err = fmt.Errorf("truncate: %w", err) return err @@ -2654,7 +2659,7 @@ func (fs *fileSystem) WriteFile( fs.mu.Unlock() //TODO: Initialize BWH before invoking write() - if err := fh.Write(ctx, op.Data, op.Offset); err != nil { + if _, err := fh.Write(ctx, op.Data, op.Offset); err != nil { return err } return @@ -2673,7 +2678,12 @@ func (fs *fileSystem) WriteFile( } // Serve the request. - err = in.Write(ctx, op.Data, op.Offset, util.Write) + gcsSynced, err := in.Write(ctx, op.Data, op.Offset, util.Write) + // Sync the inode if finalize during write is successful + // even if the write operation later resulted in error. + if gcsSynced { + fs.promoteToGenerationBacked(in) + } return } diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index a46852e97d..62a730f9a5 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -161,7 +161,7 @@ func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequen // unfinalized objects in zonal buckets, streaming writes is used. // Note that the writes are still done at the inode level. // LOCKS_EXCLUDED(fh.inode) -func (fh *FileHandle) Write(ctx context.Context, data []byte, offset int64) error { +func (fh *FileHandle) Write(ctx context.Context, data []byte, offset int64) (bool, error) { fh.inode.Lock() defer fh.inode.Unlock() diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 22de44d271..23ce89bb3f 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -116,7 +116,7 @@ func TestFileHandleWrite(t *testing.T) { ctx := context.Background() data := []byte("hello") - err := fh.Write(ctx, data, 0) + _, err := fh.Write(ctx, data, 0) assert.Nil(t, err) // Validate that write is successful at inode. diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 650883ac5a..9fd4651ad5 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -579,18 +579,19 @@ func (f *FileInode) Read( } // Serve a write for this file with semantics matching fuseops.WriteFileOp. +// It returns true if the file is successfully synced during the write operation. // // LOCKS_REQUIRED(f.mu) func (f *FileInode) Write( ctx context.Context, data []byte, offset int64, - openMode util.OpenMode) error { + openMode util.OpenMode) (bool, error) { if f.bwh != nil { return f.writeUsingBufferedWrites(ctx, data, offset) } - return f.writeUsingTempFile(ctx, data, offset) + return false, f.writeUsingTempFile(ctx, data, offset) } // Helper function to serve write for file using temp file. @@ -614,30 +615,28 @@ func (f *FileInode) writeUsingTempFile(ctx context.Context, data []byte, offset // Helper function to serve write for file using buffered writes handler. // // LOCKS_REQUIRED(f.mu) -func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, offset int64) error { +func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, offset int64) (bool, error) { err := f.bwh.Write(data, offset) var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { - return &gcsfuse_errors.FileClobberedError{ + return false, &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("f.bwh.Write(): %w", err), } } - if err != nil && !errors.Is(err, bufferedwrites.ErrOutOfOrderWrite) { - return fmt.Errorf("write to buffered write handler failed: %w", err) - } - // Fall back to temp file for Out-Of-Order Writes. if errors.Is(err, bufferedwrites.ErrOutOfOrderWrite) { logger.Infof("Falling back to staged writes on disk for file %s (inode %d) due to err: %v.", f.Name(), f.ID(), err.Error()) // Finalize the object. err = f.flushUsingBufferedWriteHandler() if err != nil { - return fmt.Errorf("could not finalize what has been written so far: %w", err) + return false, fmt.Errorf("could not finalize what has been written so far: %w", err) } - return f.writeUsingTempFile(ctx, data, offset) + return true, f.writeUsingTempFile(ctx, data, offset) } - - return err + if err != nil { + return false, fmt.Errorf("write to buffered write handler failed: %w", err) + } + return false, nil } // Helper function to flush buffered writes handler and update inode state with @@ -926,28 +925,55 @@ func (f *FileInode) updateMRDWrapper() { } } -// Truncate the file to the specified size. +// truncateUsingBufferedWriteHandler attempts to truncate the file using the buffered +// write handler. If the requested size is smaller than the file's current size, +// it finalizes the existing writes, falls back to using a temporary file for +// the truncation, and returns true to indicate the file has been synced. // // LOCKS_REQUIRED(f.mu) -func (f *FileInode) Truncate( - ctx context.Context, - size int64) (err error) { - - if f.bwh != nil { - return f.bwh.Truncate(size) +func (f *FileInode) truncateUsingBufferedWriteHandler(ctx context.Context, size int64) (bool, error) { + err := f.bwh.Truncate(size) + // If truncate size is less than the total file size resulting in OutOfOrder write, finalize and fall back to temp file. + if errors.Is(err, bufferedwrites.ErrOutOfOrderWrite) { + logger.Warnf("Falling back to staged writes on disk for file %s (inode %d) due to err: %v.", f.Name(), f.ID(), err.Error()) + // Finalize the object. + err = f.flushUsingBufferedWriteHandler() + if err != nil { + return false, fmt.Errorf("could not finalize what has been written so far: %w", err) + } + return true, f.truncateUsingTempFile(ctx, size) } + if err != nil { + return false, fmt.Errorf("got unexpected error from bwh.Truncate(): %w", err) + } + return false, nil +} +// truncateUsingTempFile truncates the file by first ensuring a temporary file +// is available and then truncating that temporary file to the specified size. +// +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) truncateUsingTempFile(ctx context.Context, size int64) error { // Make sure f.content != nil. - err = f.ensureContent(ctx) + err := f.ensureContent(ctx) if err != nil { - err = fmt.Errorf("ensureContent: %w", err) - return + return fmt.Errorf("ensureContent: %w", err) } + // Truncate temp file. + return f.content.Truncate(size) +} - // Call through. - err = f.content.Truncate(size) - - return +// Truncate the file to the specified size. +// It returns true if the file has been successfully synced to GCS. +// +// LOCKS_REQUIRED(f.mu) +func (f *FileInode) Truncate( + ctx context.Context, + size int64) (bool, error) { + if f.IsUsingBWH() { + return f.truncateUsingBufferedWriteHandler(ctx, size) + } + return false, f.truncateUsingTempFile(ctx, size) } // Ensures cache content on read if content cache enabled diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 3496c5926b..5cc62d8e82 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -189,8 +189,10 @@ func (t *FileStreamingWritesCommon) TestflushUsingBufferedWriteHandlerOnZeroSize func (t *FileStreamingWritesCommon) TestflushUsingBufferedWriteHandlerOnNonZeroSizeDoesNotRecreatesBwhOnInitAgain() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0, util.Write)) - err := t.in.flushUsingBufferedWriteHandler() + gcsSynced, err := t.in.Write(t.ctx, []byte("foobar"), 0, util.Write) + assert.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) + err = t.in.flushUsingBufferedWriteHandler() require.NoError(t.T(), err) assert.Nil(t.T(), t.in.bwh) @@ -201,6 +203,14 @@ func (t *FileStreamingWritesCommon) TestflushUsingBufferedWriteHandlerOnNonZeroS assert.Nil(t.T(), t.in.bwh) } +func (t *FileStreamingWritesCommon) TestTruncateNegative() { + // Truncate neagtive. + gcsSynced, err := t.in.Truncate(t.ctx, -1) + + require.Error(t.T(), err) + assert.False(t.T(), gcsSynced) +} + //////////////////////////////////////////////////////////////////////// // Tests (Zonal Bucket) //////////////////////////////////////////////////////////////////////// @@ -215,16 +225,20 @@ func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritative func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWriteForZonalBuckets() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0, util.Write)) + gcsSynced, err := t.in.Write(t.ctx, []byte("taco"), 0, util.Write) + assert.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) } func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBucketsPromotesInodeToNonLocal() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("pizza"), 0, util.Write)) + gcsSynced, err := t.in.Write(t.ctx, []byte("pizza"), 0, util.Write) + assert.NoError(t.T(), err) + require.False(t.T(), gcsSynced) - gcsSynced, err := t.in.SyncPendingBufferedWrites() + gcsSynced, err = t.in.SyncPendingBufferedWrites() require.NoError(t.T(), err) assert.True(t.T(), gcsSynced) @@ -236,10 +250,12 @@ func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZon func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBucketsUpdatesSrcSize() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0, util.Write)) + gcsSynced, err := t.in.Write(t.ctx, []byte("foobar"), 0, util.Write) + assert.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) assert.Equal(t.T(), uint64(0), t.in.src.Size) - gcsSynced, err := t.in.SyncPendingBufferedWrites() + gcsSynced, err = t.in.SyncPendingBufferedWrites() require.NoError(t.T(), err) assert.True(t.T(), gcsSynced) @@ -260,16 +276,20 @@ func (t *FileStreamingWritesTest) TestSourceGenerationIsAuthoritativeReturnsTrue func (t *FileStreamingWritesTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWriteForNonZonalBuckets() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0, util.Write)) + gcsSynced, err := t.in.Write(t.ctx, []byte("taco"), 0, util.Write) + assert.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) } func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucketsDoesNotPromoteInodeToNonLocal() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0, util.Write)) + gcsSynced, err := t.in.Write(t.ctx, []byte("taco"), 0, util.Write) + assert.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) - gcsSynced, err := t.in.SyncPendingBufferedWrites() + gcsSynced, err = t.in.SyncPendingBufferedWrites() require.NoError(t.T(), err) assert.False(t.T(), gcsSynced) @@ -279,10 +299,12 @@ func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucket func (t *FileStreamingWritesTest) TestSyncPendingBufferedWritesForNonZonalBucketsDoesNotUpdateSrcSize() { t.createBufferedWriteHandler() - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("foobar"), 0, util.Write)) + gcsSynced, err := t.in.Write(t.ctx, []byte("foobar"), 0, util.Write) + assert.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) assert.Equal(t.T(), uint64(0), t.in.src.Size) - gcsSynced, err := t.in.SyncPendingBufferedWrites() + gcsSynced, err = t.in.SyncPendingBufferedWrites() require.NoError(t.T(), err) assert.False(t.T(), gcsSynced) @@ -319,8 +341,9 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF createTime := t.clock.Now() t.clock.AdvanceTime(15 * time.Minute) // Sequential Write at offset 0 - err := t.in.Write(t.ctx, []byte("taco"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("taco"), 0, util.Write) require.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) require.NotNil(t.T(), t.in.bwh) // validate attributes. attrs, err := t.in.Attributes(t.ctx) @@ -330,8 +353,9 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF // Out of order write. mtime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("hello"), tc.offset, util.Write) + gcsSynced, err = t.in.Write(t.ctx, []byte("hello"), tc.offset, util.Write) require.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) // Ensure bwh cleared and temp file created. assert.Nil(t.T(), t.in.bwh) @@ -342,7 +366,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF assert.Equal(t.T(), uint64(len(tc.expectedContent)), attrs.Size) assert.WithinDuration(t.T(), attrs.Mtime, mtime, 0) // sync file and validate content - gcsSynced, err := t.in.Sync(t.ctx) + gcsSynced, err = t.in.Sync(t.ctx) require.Nil(t.T(), err) assert.True(t.T(), gcsSynced) // Read the object's contents. @@ -358,8 +382,9 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { assert.True(t.T(), t.in.IsLocal()) createTime := t.in.mtimeClock.Now() // Out of order write. - err := t.in.Write(t.ctx, []byte("taco"), 6, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("taco"), 6, util.Write) require.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) // Ensure bwh cleared and temp file created. assert.Nil(t.T(), t.in.bwh) assert.NotNil(t.T(), t.in.content) @@ -371,8 +396,9 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { // Ordered write. mtime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("hello"), 0, util.Write) + gcsSynced, err = t.in.Write(t.ctx, []byte("hello"), 0, util.Write) require.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) // Ensure bwh not re-created. assert.Nil(t.T(), t.in.bwh) @@ -382,7 +408,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { assert.Equal(t.T(), uint64(len("hello\x00taco")), attrs.Size) assert.WithinDuration(t.T(), attrs.Mtime, mtime, 0) // sync file and validate content - gcsSynced, err := t.in.Sync(t.ctx) + gcsSynced, err = t.in.Sync(t.ctx) require.Nil(t.T(), err) assert.True(t.T(), gcsSynced) // Read the object's contents. @@ -393,17 +419,19 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { func (t *FileStreamingWritesTest) TestOutOfOrderWritesOnClobberedFileThrowsError() { t.createBufferedWriteHandler() - err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) require.Nil(t.T(), err) require.NotNil(t.T(), t.in.bwh) + assert.False(t.T(), gcsSynced) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) // Clobber the file. objWritten, err := storageutil.CreateObject(t.ctx, t.bucket, fileName, []byte("taco")) require.Nil(t.T(), err) - err = t.in.Write(t.ctx, []byte("hello"), 10, util.Write) + gcsSynced, err = t.in.Write(t.ctx, []byte("hello"), 10, util.Write) require.Error(t.T(), err) + assert.False(t.T(), gcsSynced) var fileClobberedError *gcsfuse_errors.FileClobberedError assert.ErrorAs(t.T(), err, &fileClobberedError) // Validate Object on GCS not updated. @@ -428,9 +456,10 @@ func (t *FileStreamingWritesTest) TestUnlinkLocalFileAfterWrite() { assert.True(t.T(), t.in.IsLocal()) t.createBufferedWriteHandler() // Write some content. - err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) + assert.False(t.T(), gcsSynced) // Unlink. t.in.Unlink() @@ -445,9 +474,10 @@ func (t *FileStreamingWritesTest) TestUnlinkEmptySyncedFile() { assert.False(t.T(), t.in.IsLocal()) t.createBufferedWriteHandler() // Write some content to temp file. - err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) + assert.False(t.T(), gcsSynced) // Unlink. t.in.Unlink() @@ -480,9 +510,10 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndFlush() { } t.createBufferedWriteHandler() // Write some content to temp file. - err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) + assert.False(t.T(), gcsSynced) t.clock.AdvanceTime(10 * time.Second) err = t.in.Flush(t.ctx) @@ -640,12 +671,13 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndSync() { } t.createBufferedWriteHandler() // Write some content to temp file. - err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) assert.Nil(t.T(), err) assert.NotNil(t.T(), t.in.bwh) + assert.False(t.T(), gcsSynced) t.clock.AdvanceTime(10 * time.Second) - gcsSynced, err := t.in.Sync(t.ctx) + gcsSynced, err = t.in.Sync(t.ctx) require.Nil(t.T(), err) assert.False(t.T(), gcsSynced) @@ -670,8 +702,9 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndSync() { func (t *FileStreamingWritesTest) TestSourceGenerationSizeForLocalFileIsReflected() { t.createBufferedWriteHandler() assert.True(t.T(), t.in.IsLocal()) - err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0, util.Write) + gcsSynced, err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0, util.Write) require.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) sg := t.in.SourceGeneration() assert.Nil(t.T(), t.backingObj) @@ -684,8 +717,9 @@ func (t *FileStreamingWritesTest) TestSourceGenerationSizeForSyncedFileIsReflect t.createInode(fileName, emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) t.createBufferedWriteHandler() - err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0, util.Write) + gcsSynced, err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0, util.Write) require.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) sg := t.in.SourceGeneration() assert.EqualValues(t.T(), t.backingObj.Generation, sg.Object) @@ -697,14 +731,16 @@ func (t *FileStreamingWritesTest) TestTruncateOnFileUsingTempFileDoesNotRecreate t.createBufferedWriteHandler() assert.True(t.T(), t.in.IsLocal()) // Out of order write. - err := t.in.Write(t.ctx, []byte("taco"), 2, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("taco"), 2, util.Write) require.Nil(t.T(), err) + assert.True(t.T(), gcsSynced) // Ensure bwh cleared and temp file created. assert.Nil(t.T(), t.in.bwh) assert.NotNil(t.T(), t.in.content) - err = t.in.Truncate(t.ctx, 10) + gcsSynced, err = t.in.Truncate(t.ctx, 10) require.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) // Ensure bwh not re-created. assert.Nil(t.T(), t.in.bwh) @@ -713,7 +749,7 @@ func (t *FileStreamingWritesTest) TestTruncateOnFileUsingTempFileDoesNotRecreate require.Nil(t.T(), err) assert.Equal(t.T(), uint64(10), attrs.Size) // sync file and validate content - gcsSynced, err := t.in.Sync(t.ctx) + gcsSynced, err = t.in.Sync(t.ctx) require.Nil(t.T(), err) assert.True(t.T(), gcsSynced) // Read the object's contents. @@ -813,8 +849,9 @@ func (t *FileStreamingWritesTest) TestWriteUsingBufferedWritesFails() { }, } - err := t.in.Write(context.Background(), []byte("hello"), 0, util.Write) + gcsSynced, err := t.in.Write(context.Background(), []byte("hello"), 0, util.Write) require.Error(t.T(), err) + assert.False(t.T(), gcsSynced) assert.Regexp(t.T(), writeErr.Error(), err.Error()) } diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 5b8f3093d3..bc7e68f9cb 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -163,7 +163,9 @@ func (t *FileTest) createBufferedWriteHandler(shouldInitialize bool) { initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) require.NoError(t.T(), err) assert.Equal(t.T(), shouldInitialize, initialized) - assert.NotNil(t.T(), t.in.bwh) + if shouldInitialize { + assert.NotNil(t.T(), t.in.bwh) + } } //////////////////////////////////////////////////////////////////////// @@ -186,10 +188,12 @@ func (t *FileTest) TestInitialSourceGeneration() { } func (t *FileTest) TestSourceGenerationSizeAfterWriteDoesNotChange() { - err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0, util.Write) + gcsSynced, err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0, util.Write) require.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) sg := t.in.SourceGeneration() + assert.Equal(t.T(), t.backingObj.Generation, sg.Object) assert.Equal(t.T(), t.backingObj.MetaGeneration, sg.Metadata) assert.Equal(t.T(), t.backingObj.Size, sg.Size) @@ -200,7 +204,9 @@ func (t *FileTest) TestSourceGenerationIsAuthoritativeReturnsTrue() { } func (t *FileTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWrite() { - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("taco"), 0, util.Write)) + gcsSynced, err := t.in.Write(t.ctx, []byte("taco"), 0, util.Write) + assert.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) } @@ -209,9 +215,11 @@ func (t *FileTest) TestSyncPendingBufferedWritesReturnsNilAndNoOpForNonStreaming contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) require.NoError(t.T(), err) assert.Equal(t.T(), t.initialContents, string(contents)) + gcsSynced, err := t.in.Write(t.ctx, []byte("bar"), 0, util.Write) + assert.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) - assert.NoError(t.T(), t.in.Write(t.ctx, []byte("bar"), 0, util.Write)) - gcsSynced, err := t.in.SyncPendingBufferedWrites() + gcsSynced, err = t.in.SyncPendingBufferedWrites() require.NoError(t.T(), err) assert.False(t.T(), gcsSynced) @@ -346,15 +354,17 @@ func (t *FileTest) TestWrite() { assert.Equal(t.T(), "taco", t.initialContents) // Overwite a byte. - err = t.in.Write(t.ctx, []byte("p"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("p"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) // Add some data at the end. t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("burrito"), 4, util.Write) + gcsSynced, err = t.in.Write(t.ctx, []byte("burrito"), 4, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) t.clock.AdvanceTime(time.Second) @@ -387,8 +397,9 @@ func (t *FileTest) TestTruncate() { t.clock.AdvanceTime(time.Second) truncateTime := t.clock.Now() - err = t.in.Truncate(t.ctx, 2) + gcsSynced, err := t.in.Truncate(t.ctx, 2) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) t.clock.AdvanceTime(time.Second) @@ -411,6 +422,16 @@ func (t *FileTest) TestTruncate() { assert.Equal(t.T(), attrs.Mtime, truncateTime) } +func (t *FileTest) TestTruncateNegative() { + assert.Equal(t.T(), "taco", t.initialContents) + + // Truncate neagtive. + gcsSynced, err := t.in.Truncate(t.ctx, -1) + + require.Error(t.T(), err) + assert.False(t.T(), gcsSynced) +} + func (t *FileTest) TestWriteThenSync() { testcases := []struct { name string @@ -437,8 +458,9 @@ func (t *FileTest) TestWriteThenSync() { t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("p"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("p"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) t.clock.AdvanceTime(time.Second) @@ -516,8 +538,9 @@ func (t *FileTest) TestWriteToLocalFileThenSync() { // Write some content to temp file. t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("tacos"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) t.clock.AdvanceTime(time.Second) if tc.callSync { @@ -653,8 +676,9 @@ func (t *FileTest) TestAppendThenSync() { t.clock.AdvanceTime(time.Second) writeTime := t.clock.Now() - err = t.in.Write(t.ctx, []byte("burrito"), int64(len("taco")), util.Append) + gcsSynced, err := t.in.Write(t.ctx, []byte("burrito"), int64(len("taco")), util.Append) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) t.clock.AdvanceTime(time.Second) @@ -727,8 +751,9 @@ func (t *FileTest) TestTruncateDownwardThenSync() { t.clock.AdvanceTime(time.Second) truncateTime := t.clock.Now() - err = t.in.Truncate(t.ctx, 2) + gcsSynced, err := t.in.Truncate(t.ctx, 2) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) t.clock.AdvanceTime(time.Second) @@ -799,8 +824,9 @@ func (t *FileTest) TestTruncateUpwardThenFlush() { t.clock.AdvanceTime(time.Second) truncateTime := t.clock.Now() - err = t.in.Truncate(t.ctx, 6) + gcsSynced, err := t.in.Truncate(t.ctx, 6) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) t.clock.AdvanceTime(time.Second) @@ -858,9 +884,10 @@ func (t *FileTest) TestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) - err = t.in.Truncate(t.ctx, 6) + gcsSynced, err := t.in.Truncate(t.ctx, 6) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) // The inode should return the new size. attrs, err = t.in.Attributes(t.ctx) require.NoError(t.T(), err) @@ -881,16 +908,18 @@ func (t *FileTest) TestTruncateDownwardForLocalFileShouldUpdateLocalFileAttribut err = t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) // Write some data to the local file. - err = t.in.Write(t.ctx, []byte("burrito"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("burrito"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) // Validate the new data is written correctly. attrs, err = t.in.Attributes(t.ctx) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(7), attrs.Size) - err = t.in.Truncate(t.ctx, 2) + gcsSynced, err = t.in.Truncate(t.ctx, 2) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) // The inode should return the new size. attrs, err = t.in.Attributes(t.ctx) require.NoError(t.T(), err) @@ -928,8 +957,9 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() if tc.performWrite { t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) // Fetch the attributes and check if the file size reflects the write. attrs, err := t.in.Attributes(t.ctx) @@ -938,9 +968,10 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() } t.createBufferedWriteHandler(!tc.performWrite) - err = t.in.Truncate(t.ctx, 10) + gcsSynced, err := t.in.Truncate(t.ctx, 10) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) // The inode should return the new size. attrs, err = t.in.Attributes(t.ctx) require.NoError(t.T(), err) @@ -980,8 +1011,9 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable if tc.performWrite { t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) // Fetch the attributes and check if the file size reflects the write. attrs, err := t.in.Attributes(t.ctx) @@ -990,9 +1022,10 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable } t.createBufferedWriteHandler(!tc.performWrite) - err = t.in.Truncate(t.ctx, 10) + gcsSynced, err := t.in.Truncate(t.ctx, 10) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) // The inode should return the new size. attrs, err = t.in.Attributes(t.ctx) require.NoError(t.T(), err) @@ -1010,32 +1043,27 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { tbl := []struct { name string fileType string - performWrite bool truncateSize int64 }{ { - name: "LocalFileWithWrite", + name: "LocalFileTruncateToNonZero", fileType: LocalFile, - performWrite: true, truncateSize: 2, }, { - name: "LocalFileWithOutWrite", + name: "LocalFileTruncateToZero", fileType: LocalFile, - performWrite: false, - truncateSize: -1, + truncateSize: 0, }, { - name: "EmptyGCSFileWithWrite", + name: "EmptyGCSFileTruncateToNonZero", fileType: EmptyGCSFile, - performWrite: true, truncateSize: 2, }, { - name: "EmptyGCSFileWithOutWrite", + name: "EmptyGCSFileTruncateToZero", fileType: EmptyGCSFile, - performWrite: false, - truncateSize: -1, + truncateSize: 0, }, } for _, tc := range tbl { @@ -1052,22 +1080,20 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) - if tc.performWrite { - t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hihello"), 0, util.Write) - assert.Nil(t.T(), err) - assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) - // Fetch the attributes and check if the file size reflects the write. - attrs, err := t.in.Attributes(t.ctx) - require.NoError(t.T(), err) - assert.Equal(t.T(), uint64(7), attrs.Size) - } - t.createBufferedWriteHandler(!tc.performWrite) - - err = t.in.Truncate(t.ctx, tc.truncateSize) + t.createBufferedWriteHandler(true) + gcsSynced, err := t.in.Write(t.ctx, []byte("hihello"), 0, util.Write) + assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) + assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) + // Fetch the attributes and check if the file size reflects the write. + attrs, err = t.in.Attributes(t.ctx) + require.NoError(t.T(), err) + assert.Equal(t.T(), uint64(7), attrs.Size) + gcsSynced, err = t.in.Truncate(t.ctx, tc.truncateSize) - require.Error(t.T(), err) - assert.ErrorContains(t.T(), err, "cannot truncate") + require.NoError(t.T(), err) + assert.True(t.T(), gcsSynced) + t.createBufferedWriteHandler(false) }) } } @@ -1092,8 +1118,9 @@ func (t *FileTest) TestSyncFlush_Clobbered() { var err error // Truncate downward. - err = t.in.Truncate(t.ctx, 2) + gcsSynced, err := t.in.Truncate(t.ctx, 2) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) // Clobber the backing object. newObj, err := storageutil.CreateObject( @@ -1141,8 +1168,9 @@ func (t *FileTest) TestSyncFlush_Clobbered() { func (t *FileTest) TestOpenReader_ThrowsFileClobberedError() { // Modify the file locally. - err := t.in.Truncate(t.ctx, 2) + gcsSynced, err := t.in.Truncate(t.ctx, 2) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) // Clobber the backing object. _, err = storageutil.CreateObject( t.ctx, @@ -1221,8 +1249,9 @@ func (t *FileTest) TestSetMtime_ContentDirty() { var attrs fuseops.InodeAttributes // Dirty the content. - err = t.in.Write(t.ctx, []byte("a"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("a"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) // Set mtime. mtime := time.Now().UTC().Add(123 * time.Second) @@ -1237,7 +1266,7 @@ func (t *FileTest) TestSetMtime_ContentDirty() { assert.Equal(t.T(), attrs.Mtime, mtime) // Sync. - gcsSynced, err := t.in.Sync(t.ctx) + gcsSynced, err = t.in.Sync(t.ctx) require.NoError(t.T(), err) assert.True(t.T(), gcsSynced) @@ -1536,8 +1565,9 @@ func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { if tc.performWrite { t.createBufferedWriteHandler(tc.fileType != LocalFile) - err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) } @@ -1582,9 +1612,10 @@ func (t *FileTest) TestWriteToLocalFileWhenStreamingWritesAreEnabled() { t.in.config = &cfg.Config{Write: *getWriteConfig()} t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) assert.NotNil(t.T(), t.in.bwh) writeFileInfo := t.in.bwh.WriteFileInfo() assert.Equal(t.T(), int64(2), writeFileInfo.TotalSize) @@ -1597,13 +1628,15 @@ func (t *FileTest) TestMultipleWritesToLocalFileWhenStreamingWritesAreEnabled() t.in.config = &cfg.Config{Write: *getWriteConfig()} t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) assert.NotNil(t.T(), t.in.bwh) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) - err = t.in.Write(t.ctx, []byte("hello"), 2, util.Write) + gcsSynced, err = t.in.Write(t.ctx, []byte("hello"), 2, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) // The inode should agree about the new mtime. attrs, err := t.in.Attributes(t.ctx) @@ -1618,9 +1651,10 @@ func (t *FileTest) TestWriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { createTime := t.in.mtimeClock.Now() t.createBufferedWriteHandler(true) - err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) assert.NotNil(t.T(), t.in.bwh) writeFileInfo := t.in.bwh.WriteFileInfo() assert.Equal(t.T(), int64(2), writeFileInfo.TotalSize) @@ -1648,8 +1682,9 @@ func (t *FileTest) TestSetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEn t.in.config = &cfg.Config{Write: *getWriteConfig()} t.createBufferedWriteHandler(true) // Initiate write call. - err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) + gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) assert.NotNil(t.T(), t.in.bwh) writeFileInfo := t.in.bwh.WriteFileInfo() assert.Equal(t.T(), int64(2), writeFileInfo.TotalSize) diff --git a/internal/fs/streaming_writes_common_test.go b/internal/fs/streaming_writes_common_test.go index 14196b7086..1279806a28 100644 --- a/internal/fs/streaming_writes_common_test.go +++ b/internal/fs/streaming_writes_common_test.go @@ -18,12 +18,16 @@ package fs_test import ( + "errors" "os" "path" "strings" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -77,3 +81,75 @@ func (t *StreamingWritesCommonTest) TestRenameFileWithPendingWrites() { assert.NoError(t.T(), err) assert.Equal(t.T(), "tacos", string(content)) } + +func (t *StreamingWritesCommonTest) TestTruncateToLowerSizeSyncsFileToGcs() { + _, err := t.f1.Write([]byte("foobar")) + assert.NoError(t.T(), err) + + err = t.f1.Truncate(3) + + assert.NoError(t.T(), err) + content, err := storageutil.ReadObject(ctx, bucket, fileName) + require.NoError(t.T(), err) + assert.Equal(t.T(), "foobar", string(content)) + err = t.f1.Close() + assert.NoError(t.T(), err) + content, err = storageutil.ReadObject(ctx, bucket, fileName) + require.NoError(t.T(), err) + assert.Equal(t.T(), "foo", string(content)) + t.f1 = nil +} + +func (t *StreamingWritesCommonTest) TestTruncateToLowerSizeSyncsFileToGcsAndDeletingFileDeletesFromGcs() { + _, err := t.f1.Write([]byte("foobar")) + assert.NoError(t.T(), err) + err = t.f1.Truncate(3) + assert.NoError(t.T(), err) + content, err := storageutil.ReadObject(ctx, bucket, fileName) + require.NoError(t.T(), err) + assert.Equal(t.T(), "foobar", string(content)) + + err = os.Remove(t.f1.Name()) + + assert.NoError(t.T(), err) + _, err = storageutil.ReadObject(ctx, bucket, fileName) + require.Error(t.T(), err) + var notFoundErr *gcs.NotFoundError + assert.True(t.T(), errors.As(err, ¬FoundErr)) +} + +func (t *StreamingWritesCommonTest) TestOutOfOrderWriteSyncsFileToGcs() { + _, err := t.f1.Write([]byte("foobar")) + assert.NoError(t.T(), err) + + _, err = t.f1.WriteAt([]byte("foo"), 3) + + assert.NoError(t.T(), err) + content, err := storageutil.ReadObject(ctx, bucket, fileName) + require.NoError(t.T(), err) + assert.Equal(t.T(), "foobar", string(content)) + err = t.f1.Close() + assert.NoError(t.T(), err) + content, err = storageutil.ReadObject(ctx, bucket, fileName) + require.NoError(t.T(), err) + assert.Equal(t.T(), "foofoo", string(content)) + t.f1 = nil +} + +func (t *StreamingWritesCommonTest) TestOutOfOrderWriteSyncsFileToGcsAndDeletingFileDeletesFromGcs() { + _, err := t.f1.Write([]byte("foobar")) + assert.NoError(t.T(), err) + _, err = t.f1.WriteAt([]byte("foo"), 3) + assert.NoError(t.T(), err) + content, err := storageutil.ReadObject(ctx, bucket, fileName) + require.NoError(t.T(), err) + assert.Equal(t.T(), "foobar", string(content)) + + err = os.Remove(t.f1.Name()) + + assert.NoError(t.T(), err) + _, err = storageutil.ReadObject(ctx, bucket, fileName) + require.Error(t.T(), err) + var notFoundErr *gcs.NotFoundError + assert.True(t.T(), errors.As(err, ¬FoundErr)) +} diff --git a/internal/fs/streaming_writes_empty_gcs_object_test.go b/internal/fs/streaming_writes_empty_gcs_object_test.go index eb6681b605..6568ffddda 100644 --- a/internal/fs/streaming_writes_empty_gcs_object_test.go +++ b/internal/fs/streaming_writes_empty_gcs_object_test.go @@ -25,6 +25,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -50,20 +51,21 @@ func (t *StreamingWritesEmptyGCSObjectTest) TearDownSuite() { t.fsTest.TearDownTestSuite() } func (t *StreamingWritesEmptyGCSObjectTest) SetupTest() { - // Create an object on bucket. + // Create an empty object on bucket. _, err := storageutil.CreateObject( ctx, bucket, fileName, - []byte("bar")) + []byte("")) assert.Equal(t.T(), nil, err) // Open file handle to read or write. t.f1, err = os.OpenFile(path.Join(mntDir, fileName), os.O_RDWR|syscall.O_DIRECT, filePerms) assert.Equal(t.T(), nil, err) - // Validate that file exists on GCS. - _, err = storageutil.ReadObject(ctx, bucket, fileName) - assert.NoError(t.T(), err) + // Validate that empty file exists on GCS. + content, err := storageutil.ReadObject(ctx, bucket, fileName) + require.NoError(t.T(), err) + assert.Equal(t.T(), "", string(content)) } func (t *StreamingWritesEmptyGCSObjectTest) TearDownTest() { diff --git a/tools/integration_tests/local_file/stat_file.go b/tools/integration_tests/local_file/stat_file.go index 7626880ee3..16e370f610 100644 --- a/tools/integration_tests/local_file/stat_file.go +++ b/tools/integration_tests/local_file/stat_file.go @@ -56,12 +56,13 @@ func (t *CommonLocalFileTestSuite) TestStatOnLocalFileWithConflictingFileNameSuf FileName1, "", t.T()) } -func (t *localFileTestSuite) TestTruncateLocalFileToSmallerSize() { +func (t *CommonLocalFileTestSuite) TestTruncateLocalFileToSmallerSize() { testDirPath = setup.SetupTestDirectory(testDirName) // Create a local file. - filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, FileName1, t.T()) + fileName := FileName1 + setup.GenerateRandomString(5) + filePath, fh := CreateLocalFileInTestDir(ctx, storageClient, testDirPath, fileName, t.T()) // Writing contents to local file . - WritingToLocalFileShouldNotWriteToGCS(ctx, storageClient, fh, testDirName, FileName1, t.T()) + operations.WriteWithoutClose(fh, FileContents, t.T()) // Stat the file to validate if new contents are written. operations.VerifyStatFile(filePath, SizeOfFileContents, FilePerms, t.T()) @@ -72,12 +73,10 @@ func (t *localFileTestSuite) TestTruncateLocalFileToSmallerSize() { t.T().Fatalf("os.Truncate err: %v", err) } - ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, FileName1, t.T()) - // Stat the file to validate if file is truncated correctly. operations.VerifyStatFile(filePath, SmallerSizeTruncate, FilePerms, t.T()) // Close the file and validate that the file is created on GCS. CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, FileContents[:SmallerSizeTruncate], t.T()) + fileName, FileContents[:SmallerSizeTruncate], t.T()) } diff --git a/tools/integration_tests/streaming_writes/truncate_file_test.go b/tools/integration_tests/streaming_writes/truncate_file_test.go index 77cf63f447..45c2c615fc 100644 --- a/tools/integration_tests/streaming_writes/truncate_file_test.go +++ b/tools/integration_tests/streaming_writes/truncate_file_test.go @@ -15,6 +15,8 @@ package streaming_writes import ( + "os" + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" @@ -34,6 +36,12 @@ func (t *StreamingWritesSuite) TestTruncate() { CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, string(data[:]), t.T()) } +func (t *StreamingWritesSuite) TestTruncateNegative() { + err := t.f1.Truncate(-1) + + assert.Error(t.T(), err) +} + func (t *StreamingWritesSuite) TestWriteAfterTruncate() { truncateSize := 10 @@ -84,51 +92,100 @@ func (t *StreamingWritesSuite) TestWriteAfterTruncate() { } func (t *StreamingWritesSuite) TestWriteAndTruncate() { - var truncateSize int64 = 20 - operations.WriteWithoutClose(t.f1, FileContents, t.T()) - operations.VerifyStatFile(t.filePath, int64(len(FileContents)), FilePerms, t.T()) + testCases := []struct { + name string + intitialContent string + truncateSize int64 + finalContent string + }{ + { + name: "WriteTruncateToUpper", + intitialContent: "foobar", + truncateSize: 9, + finalContent: "foobar\x00\x00\x00", + }, + { + name: "WriteTruncateToLower", + intitialContent: "foobar", + truncateSize: 3, + finalContent: "foo", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func() { + // Write + operations.WriteWithoutClose(t.f1, tc.intitialContent, t.T()) + operations.VerifyStatFile(t.filePath, int64(len(tc.intitialContent)), FilePerms, t.T()) - err := t.f1.Truncate(truncateSize) + // Perform truncate + err := t.f1.Truncate(tc.truncateSize) - require.NoError(t.T(), err) - data := make([]byte, 10) - // Verify that GCSFuse is returning correct file size before the file is uploaded. - operations.VerifyStatFile(t.filePath, truncateSize, FilePerms, t.T()) - // Close the file and validate that the file is created on GCS. - CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents+string(data[:]), t.T()) + require.NoError(t.T(), err) + operations.VerifyStatFile(t.filePath, tc.truncateSize, FilePerms, t.T()) + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, tc.finalContent, t.T()) + }) + } } func (t *StreamingWritesSuite) TestWriteTruncateWrite() { - truncateSize := 30 + testCases := []struct { + name string + truncateSize int64 + initialContent string + writeContent string + finalContent string + }{ + { + name: "WriteTruncateToUpperWrite", + truncateSize: 12, + initialContent: "foobar", + writeContent: "foo", + finalContent: "foobarfoo\x00\x00\x00", + }, + { + name: "WriteTruncateToLowerWrite", + truncateSize: 3, + initialContent: "foobar", + writeContent: "foo", + finalContent: "foo\x00\x00\x00foo", + }, + } + for _, tc := range testCases { + t.Run(tc.name, func() { + // Write + operations.WriteWithoutClose(t.f1, tc.initialContent, t.T()) + operations.VerifyStatFile(t.filePath, int64(len(tc.initialContent)), FilePerms, t.T()) + // Perform truncate + // Note: truncate operation does not change the position of the file pointer of file handle. + err := t.f1.Truncate(tc.truncateSize) + require.NoError(t.T(), err) + operations.VerifyStatFile(t.filePath, tc.truncateSize, FilePerms, t.T()) - // Write - operations.WriteWithoutClose(t.f1, FileContents, t.T()) - operations.VerifyStatFile(t.filePath, int64(len(FileContents)), FilePerms, t.T()) - // Perform truncate - err := t.f1.Truncate(int64(truncateSize)) - require.NoError(t.T(), err) - operations.VerifyStatFile(t.filePath, int64(truncateSize), FilePerms, t.T()) - // Write - operations.WriteWithoutClose(t.f1, FileContents, t.T()) - operations.VerifyStatFile(t.filePath, int64(truncateSize), FilePerms, t.T()) + // Write again + operations.WriteWithoutClose(t.f1, tc.writeContent, t.T()) - data := make([]byte, 10) - // Close the file and validate that the file is created on GCS. - CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, FileContents+FileContents+string(data[:]), t.T()) + operations.VerifyStatFile(t.filePath, int64(len(tc.finalContent)), FilePerms, t.T()) + // Close the file and validate that the file is created on GCS. + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, tc.finalContent, t.T()) + }) + } } -func (t *StreamingWritesSuite) TestTruncateToLowerSizeAfterWrite() { +func (t *StreamingWritesSuite) TestTruncateDownAndDeleteFile() { // Write - operations.WriteWithoutClose(t.f1, FileContents+FileContents, t.T()) - operations.VerifyStatFile(t.filePath, int64(2*len(FileContents)), FilePerms, t.T()) + operations.WriteWithoutClose(t.f1, "foobar", t.T()) + operations.VerifyStatFile(t.filePath, int64(len("foobar")), FilePerms, t.T()) // Perform truncate - err := t.f1.Truncate(int64(5)) - - if t.fallbackToDiskCase { - require.NoError(t.T(), err) - } else { - // Truncating to lower size after writes are not allowed if buffered writes are used. - require.Error(t.T(), err) - operations.VerifyStatFile(t.filePath, int64(2*len(FileContents)), FilePerms, t.T()) + err := t.f1.Truncate(3) + require.NoError(t.T(), err) + operations.VerifyStatFile(t.filePath, 3, FilePerms, t.T()) + if !t.fallbackToDiskCase { + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, "foobar", t.T()) } + + err = os.Remove(t.filePath) + + require.NoError(t.T(), err) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) } diff --git a/tools/integration_tests/streaming_writes/write_file_test.go b/tools/integration_tests/streaming_writes/write_file_test.go new file mode 100644 index 0000000000..94b77fd9bd --- /dev/null +++ b/tools/integration_tests/streaming_writes/write_file_test.go @@ -0,0 +1,53 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package streaming_writes + +import ( + "os" + + . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/stretchr/testify/require" +) + +func (t *StreamingWritesSuite) TestOutOfOrderWriteSyncsFileToGcs() { + // Write + operations.WriteWithoutClose(t.f1, "foobar", t.T()) + operations.VerifyStatFile(t.filePath, int64(len("foobar")), FilePerms, t.T()) + + // Perform out of order write. + operations.WriteAt("foo", 3, t.f1, t.T()) + + if !t.fallbackToDiskCase { + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, "foobar", t.T()) + } + CloseFileAndValidateContentFromGCS(ctx, storageClient, t.f1, testDirName, t.fileName, "foofoo", t.T()) +} + +func (t *StreamingWritesSuite) TestOutOfOrderWriteSyncsFileToGcsAndDeletingFileDeletsFileFromGcs() { + // Write + operations.WriteWithoutClose(t.f1, "foobar", t.T()) + operations.VerifyStatFile(t.filePath, int64(len("foobar")), FilePerms, t.T()) + // Perform out of order write. + operations.WriteAt("foo", 3, t.f1, t.T()) + if !t.fallbackToDiskCase { + ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, t.fileName, "foobar", t.T()) + } + + err := os.Remove(t.filePath) + + require.NoError(t.T(), err) + ValidateObjectNotFoundErrOnGCS(ctx, storageClient, testDirName, t.fileName, t.T()) +} From 6148abe4e717c3d0cd182e81063ec9dea9394310 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 4 Jun 2025 10:28:35 +0530 Subject: [PATCH 0480/1298] Turn on the flag for Move object (#3355) * turn move object on * review comment --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/root_test.go | 9 +++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index cd56f3b866..1d0f641563 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -353,7 +353,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.BoolP("enable-atomic-rename-object", "", false, "Enables support for atomic rename object operation on HNS bucket.") + flagSet.BoolP("enable-atomic-rename-object", "", true, "Enables support for atomic rename object operation on HNS bucket.") if err := flagSet.MarkHidden("enable-atomic-rename-object"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 892cae639d..9ab83b1077 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -82,7 +82,7 @@ flag-name: "enable-atomic-rename-object" type: "bool" usage: "Enables support for atomic rename object operation on HNS bucket." - default: false + default: true hide-flag: true - config-path: "enable-hns" diff --git a/cmd/root_test.go b/cmd/root_test.go index d0c22e5726..0b22182266 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -996,10 +996,15 @@ func TestArgsParsing_EnableAtomicRenameObjectFlag(t *testing.T) { expectedEnableAtomicRenameObject bool }{ { - name: "normal", - args: []string{"gcsfuse", "--enable-atomic-rename-object=true", "abc", "pqr"}, + name: "default", + args: []string{"gcsfuse", "abc", "pqr"}, expectedEnableAtomicRenameObject: true, }, + { + name: "normal", + args: []string{"gcsfuse", "--enable-atomic-rename-object=false", "abc", "pqr"}, + expectedEnableAtomicRenameObject: false, + }, } for _, tc := range tests { From ca46a4a04d0db9b9498cdf4f42f6d18ca9fba8da Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Wed, 4 Jun 2025 12:30:09 +0530 Subject: [PATCH 0481/1298] removing redundant conversions while inserting obj into stat cache (#3378) --- internal/storage/caching/fast_stat_bucket.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index 705a0b0b97..8f6c7ddb72 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -442,8 +442,7 @@ func (b *fastStatBucket) StatObjectFromGcs(ctx context.Context, } // Put the object in cache. - o := storageutil.ConvertMinObjectToObject(m) - b.insert(o) + b.insertMinObject(m) return } From 8aa24b018f7ac95cf659fb491a51df071f5316ab Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 4 Jun 2025 15:02:43 +0530 Subject: [PATCH 0482/1298] [Random Reader Refactoring] Add seek improvement in new reader (#3377) * changes * small fix * remove one case * adding assertion in unit tests * review comments * review comments --- internal/gcsx/client_readers/gcs_reader.go | 20 ++++++- .../gcsx/client_readers/gcs_reader_test.go | 52 +++++++++++++++++++ internal/gcsx/client_readers/range_reader.go | 16 +----- .../gcsx/client_readers/range_reader_test.go | 15 +----- 4 files changed, 73 insertions(+), 30 deletions(-) diff --git a/internal/gcsx/client_readers/gcs_reader.go b/internal/gcsx/client_readers/gcs_reader.go index 5aaf2b78ef..5cb4a00248 100644 --- a/internal/gcsx/client_readers/gcs_reader.go +++ b/internal/gcsx/client_readers/gcs_reader.go @@ -65,6 +65,10 @@ type GCSReader struct { sequentialReadSizeMb int32 + // Specifies the next expected offset for the reads. Used to distinguish between + // sequential and random reads. + expectedOffset int64 + // seeks represents the number of random reads performed by the reader. seeks uint64 @@ -97,7 +101,11 @@ func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.R return readerResponse, io.EOF } - if gr.rangeReader.invalidateReaderIfMisalignedOrTooSmall(offset, p) { + gr.rangeReader.invalidateReaderIfMisalignedOrTooSmall(offset, p) + + // If the data can't be served from the existing reader, then we need to update the seeks. + // If current offset is not same as expected offset, it's a random read. + if gr.expectedOffset != 0 && gr.expectedOffset != offset { gr.seeks++ } @@ -106,8 +114,12 @@ func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.R Offset: offset, EndOffset: -1, } + defer func() { + gr.updateExpectedOffset(offset + int64(readerResponse.Size)) + }() - readerResponse, err := gr.rangeReader.readFromExistingReader(ctx, readReq) + var err error + readerResponse, err = gr.rangeReader.readFromExistingReader(ctx, readReq) if err == nil { return readerResponse, nil } @@ -195,6 +207,10 @@ func (gr *GCSReader) limitEnd(start, currentEnd int64) int64 { return currentEnd } +func (gr *GCSReader) updateExpectedOffset(offset int64) { + gr.expectedOffset = offset +} + func (gr *GCSReader) Destroy() { gr.rangeReader.destroy() gr.mrr.destroy() diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go index b1fdfd509f..ca6e73a46b 100644 --- a/internal/gcsx/client_readers/gcs_reader_test.go +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -172,6 +172,7 @@ func (t *gcsReaderTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedDataSi assert.Equal(t.T(), requestSize, readerResponse.Size) assert.Equal(t.T(), content, string(readerResponse.DataBuf[:readerResponse.Size])) assert.Equal(t.T(), uint64(requestSize), t.gcsReader.totalReadBytes) + assert.Equal(t.T(), int64(2+requestSize), t.gcsReader.expectedOffset) assert.Equal(t.T(), expectedHandleInRequest, t.gcsReader.rangeReader.readHandle) } @@ -205,6 +206,7 @@ func (t *gcsReaderTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedObject assert.Nil(t.T(), t.gcsReader.rangeReader.reader) assert.Equal(t.T(), int(t.object.Size), readerResponse.Size) assert.Equal(t.T(), content, string(readerResponse.DataBuf[:readerResponse.Size])) + assert.Equal(t.T(), int64(t.object.Size), t.gcsReader.expectedOffset) assert.Equal(t.T(), []byte(nil), t.gcsReader.rangeReader.readHandle) } @@ -260,6 +262,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { bucketType gcs.BucketType readRanges [][]int expectedReadTypes []string + expectedSeeks []int }{ { name: "SequentialReadFlat", @@ -267,6 +270,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Sequential, testUtil.Sequential}, + expectedSeeks: []int{0, 0, 0, 0, 0}, }, { name: "SequentialReadZonal", @@ -274,6 +278,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Sequential, testUtil.Sequential}, + expectedSeeks: []int{0, 0, 0, 0, 0}, }, { name: "RandomReadFlat", @@ -281,6 +286,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Random, testUtil.Random, testUtil.Random}, + expectedSeeks: []int{0, 1, 2, 2, 2}, }, { name: "RandomReadZonal", @@ -288,6 +294,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Random, testUtil.Random, testUtil.Random}, + expectedSeeks: []int{0, 1, 2, 2, 2}, }, } @@ -298,6 +305,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { t.gcsReader.mrr.isMRDInUse = false t.gcsReader.seeks = 0 t.gcsReader.rangeReader.readType = testUtil.Sequential + t.gcsReader.expectedOffset = 0 t.object.Size = uint64(tc.dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) @@ -313,6 +321,8 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { assert.NoError(t.T(), err) assert.Equal(t.T(), tc.expectedReadTypes[i], t.gcsReader.readType) + assert.Equal(t.T(), int64(readRange[1]), t.gcsReader.expectedOffset) + assert.Equal(t.T(), uint64(tc.expectedSeeks[i]), t.gcsReader.seeks) } }) } @@ -571,3 +581,45 @@ func (t *gcsReaderTest) Test_ReadAt_WithAndWithoutReadConfig() { }) } } + +// This test validates the bug fix where seeks are not updated correctly in case of zonal bucket random reads (b/410904634). +func (t *gcsReaderTest) Test_ReadAt_ValidateZonalRandomReads() { + t.gcsReader.rangeReader.reader = nil + t.gcsReader.mrr.isMRDInUse = false + t.gcsReader.seeks = 0 + t.gcsReader.rangeReader.readType = testUtil.Sequential + t.gcsReader.expectedOffset = 0 + t.gcsReader.totalReadBytes = 0 + t.object.Size = 20 * MiB + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}) + testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + assert.Nil(t.T(), err, "Error in creating MRDWrapper") + t.gcsReader.mrr.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(&fake.FakeReader{ReadCloser: getReadCloser(testContent)}, nil).Twice() + buf := make([]byte, 3*MiB) + + // Sequential read #1 + _, err = t.gcsReader.ReadAt(t.ctx, buf, 13*MiB) + assert.NoError(t.T(), err) + // Random read #1 + seeks := 1 + _, err = t.gcsReader.ReadAt(t.ctx, buf, 12*MiB) + assert.NoError(t.T(), err) + assert.Equal(t.T(), uint64(seeks), t.gcsReader.seeks) + + readRanges := [][]int{{11 * MiB, 15 * MiB}, {12 * MiB, 14 * MiB}, {10 * MiB, 12 * MiB}, {9 * MiB, 11 * MiB}, {8 * MiB, 10 * MiB}} + // Series of random reads to check if seeks are updated correctly and MRD is invoked always + for _, readRange := range readRanges { + seeks++ + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)) + buf := make([]byte, readRange[1]-readRange[0]) + + _, err := t.gcsReader.ReadAt(t.ctx, buf, int64(readRange[0])) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), uint64(seeks), t.gcsReader.seeks) + assert.Equal(t.T(), testUtil.Random, t.gcsReader.readType) + assert.Equal(t.T(), int64(readRange[1]), t.gcsReader.expectedOffset) + } +} diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index 76fd3ae1cf..1f6dab8663 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -311,17 +311,12 @@ func (rr *RangeReader) skipBytes(offset int64) { // offset) or cannot serve the full request within its limit, it is closed and discarded. // // It attempts to skip forward to the requested offset if possible to avoid creating -// a new reader unnecessarily. If the reader is discarded due to misalignment, the method -// returns true to signal that a seek should be recorded. +// a new reader unnecessarily. // // Parameters: // - offset: the starting byte position of the requested read. // - p: the buffer representing the size of the requested read. -// -// Returns: -// - true if the reader was discarded due to being misaligned (seek should be counted). -// - false otherwise. -func (rr *RangeReader) invalidateReaderIfMisalignedOrTooSmall(offset int64, p []byte) bool { +func (rr *RangeReader) invalidateReaderIfMisalignedOrTooSmall(offset int64, p []byte) { rr.skipBytes(offset) // If we have an existing reader, but it's positioned at the wrong place, @@ -332,14 +327,7 @@ func (rr *RangeReader) invalidateReaderIfMisalignedOrTooSmall(offset int64, p [] rr.closeReader() rr.reader = nil rr.cancel = nil - if rr.start != offset { - // Return true to increment the seek count when discarding a reader due to incorrect positioning. - // Discarding readers that can't fulfill the entire request without this check would prevent - // the reader size from growing appropriately in random read scenarios. - return true - } } - return false } // readFromExistingReader attempts to read data from an existing reader if one is available. diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go index 569c0bc306..46b61f7521 100644 --- a/internal/gcsx/client_readers/range_reader_test.go +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -337,18 +337,6 @@ func (t *rangeReaderTest) Test_invalidateReaderIfMisalignedOrTooSmall() { expectIncreaseSeek bool expectReaderNil bool }{ - { - name: "InvalidateReaderDueToWrongOffset", - readerSetup: func() { - t.rangeReader.reader = &fake.FakeReader{ReadCloser: getReader(100)} - t.rangeReader.start = 50 // misaligned - t.rangeReader.limit = 1000 - }, - offset: 200, - bufferSize: 100, - expectIncreaseSeek: true, - expectReaderNil: true, - }, { name: "InvalidateReaderDueToTooSmall", readerSetup: func() { @@ -380,9 +368,8 @@ func (t *rangeReaderTest) Test_invalidateReaderIfMisalignedOrTooSmall() { t.Run(tt.name, func() { tt.readerSetup() - result := t.rangeReader.invalidateReaderIfMisalignedOrTooSmall(tt.offset, make([]byte, tt.bufferSize)) + t.rangeReader.invalidateReaderIfMisalignedOrTooSmall(tt.offset, make([]byte, tt.bufferSize)) - assert.Equal(t.T(), tt.expectIncreaseSeek, result, "invalidateReaderIfMisalignedOrTooSmall() result") if tt.expectReaderNil { assert.Nil(t.T(), t.rangeReader.reader, "rangeReader.reader should be nil") } else { From 86691469d0622fb2bea2d93f3087abd68821e655 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 5 Jun 2025 09:00:59 +0000 Subject: [PATCH 0483/1298] fix install test for patch release (#3188) * fix install test for patch release * review comments --- tools/cd_scripts/install_test.sh | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/tools/cd_scripts/install_test.sh b/tools/cd_scripts/install_test.sh index 9309286edb..2e86fe0cf0 100755 --- a/tools/cd_scripts/install_test.sh +++ b/tools/cd_scripts/install_test.sh @@ -20,6 +20,8 @@ set -x gsutil cp gs://gcsfuse-release-packages/version-detail/details.txt . # Writing VM instance name to details.txt (Format: release-test-) vm_instance_name=$(curl http://metadata.google.internal/computeMetadata/v1/instance/name -H "Metadata-Flavor: Google") +# first line of details.txt contains the release version in the format MAJOR.MINOR.PATCH +to_release_version=$(sed '1q' details.txt | tr -d '\n') echo $vm_instance_name >> details.txt touch ~/logs.txt @@ -51,8 +53,8 @@ then fi sudo apt-get update - # Install latest released gcsfuse version - sudo apt-get install -y gcsfuse + # Install to be released gcsfuse version + sudo apt-get install -y gcsfuse="$to_release_version" >> ~/logs.txt else # For rhel and centos sudo yum install fuse @@ -72,20 +74,20 @@ repo_gpgcheck=0 gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg EOF -sudo yum install -y gcsfuse +sudo yum install -y gcsfuse-"$to_release_version" >> ~/logs.txt fi # Verify gcsfuse version (successful installation) gcsfuse --version |& tee version.txt installed_version=$(echo $(sed -n 1p version.txt) | cut -d' ' -f3) if grep -q $installed_version details.txt; then - echo "GCSFuse latest version installed correctly." &>> ~/logs.txt + echo "GCSFuse to be released version installed correctly." &>> ~/logs.txt else - echo "Failure detected in latest gcsfuse version installation." &>> ~/logs.txt + echo "Failure detected in to be released gcsfuse version installation." &>> ~/logs.txt fi -# Uninstall gcsfuse latest version and install old version +# Uninstall gcsfuse and install old version. if grep -q ubuntu details.txt || grep -q debian details.txt; then sudo apt-get remove -y gcsfuse |& tee -a ~/logs.txt @@ -104,7 +106,7 @@ else echo "Failure detected in GCSFuse old version installation." &>> ~/logs.txt fi -# Upgrade gcsfuse to latest version +# Upgrade gcsfuse to latest version. if grep -q ubuntu details.txt || grep -q debian details.txt; then sudo apt-get install --only-upgrade gcsfuse |& tee -a ~/logs.txt @@ -112,12 +114,19 @@ else sudo yum -y upgrade gcsfuse |& tee -a ~/logs.txt fi +# Verify that gcsfuse has been upgraded to the to_be_released version using version comparison. +# This is to ensure that the correct version is installed after the upgrade. gcsfuse --version |& tee version.txt installed_version=$(echo $(sed -n 1p version.txt) | cut -d' ' -f3) -if grep -q $installed_version details.txt; then - echo "GCSFuse successfully upgraded to latest version $installed_version." &>> ~/logs.txt +# The following command compares the two versions: +# 1. `printf` outputs to_release_version and installed_version on a new line. +# 2. `sort -V` sorts them naturally (version sort). +# 3. `tail -n 1` gets the last line, which is the highest version. +# The condition is true if installed_version is greater than or equal to to_release_version. +if [[ "$(printf '%s\n%s\n' "$to_release_version" "$installed_version" | sort -V | tail -n 1)" == "$installed_version" ]]; then + echo "GCSFuse successfully upgraded to latest version: installed_version ($installed_version), to_release_version: ($to_release_version)" &>> ~/logs.txt else - echo "Failure detected in upgrading to latest gcsfuse version." &>> ~/logs.txt + echo "Failure detected in upgrading to latest gcsfuse version: installed_version ($installed_version), to_release_version: ($to_release_version)" &>> ~/logs.txt fi if grep -q Failure ~/logs.txt; then @@ -128,4 +137,3 @@ else fi gsutil cp ~/logs.txt gs://gcsfuse-release-packages/v$(sed -n 1p details.txt)/installation-test/$(sed -n 3p details.txt)/ - From a651d01424df33dbcfb0b19dc7cd6827336183e6 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Fri, 6 Jun 2025 18:55:00 +0530 Subject: [PATCH 0484/1298] Use new e2e script for presubmit runs (#3385) * start using new e2e script for presubmit runs * failing on purpose to see logs for package runs * revert test changes --- .../presubmit_test/pr_perf_test/build.sh | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index 54df25fe75..7ddd407103 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -24,6 +24,8 @@ readonly RUN_E2E_TESTS_ON_INSTALLED_PACKAGE=false readonly SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE=false readonly BUCKET_LOCATION=us-west4 readonly RUN_TEST_ON_TPC_ENDPOINT=false +readonly GO_VERSION="1.24.0" +readonly REQUIRED_BASH_VERSION_FOR_E2E_SCRIPT="5.1" # This flag, if set true, will indicate to underlying script to customize for a presubmit run. readonly RUN_TESTS_WITH_PRESUBMIT_FLAG=true @@ -50,12 +52,12 @@ set -e sudo apt-get update echo Installing git sudo apt-get install git -echo Installing go-lang 1.24.0 -wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-amd64.tar.gz -q -sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local -export PATH=$PATH:/usr/local/go/bin -export CGO_ENABLED=0 cd "${KOKORO_ARTIFACTS_DIR}/github/gcsfuse" +# Install required go version. +./perfmetrics/scripts/install_go.sh "$GO_VERSION" +export CGO_ENABLED=0 +export PATH="/usr/local/go/bin:$PATH" + # Fetch PR branch echo '[remote "origin"] fetch = +refs/pull/*/head:refs/remotes/origin/pr/*' >> .git/config @@ -112,6 +114,9 @@ then python3 ./perfmetrics/scripts/presubmit/print_results.py fi +# Install required bash version for e2e script as kokoro has outdated bash versions. +./perfmetrics/scripts/install_bash.sh "$REQUIRED_BASH_VERSION_FOR_E2E_SCRIPT" + # Execute integration tests on zonal bucket(s). if test -n "${integrationTestsOnZBStr}" ; then @@ -120,7 +125,7 @@ then echo "Running e2e tests on zonal bucket(s) ..." # $1 argument is refering to value of testInstalledPackage. - ./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG true + /usr/local/bin/bash ./tools/integration_tests/improved_run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG true fi # Execute integration tests on non-zonal bucket(s). @@ -131,7 +136,7 @@ then echo "Running e2e tests on non-zonal bucket(s) ..." # $1 argument is refering to value of testInstalledPackage. - ./tools/integration_tests/run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG false + /usr/local/bin/bash ./tools/integration_tests/improved_run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG false fi # Execute package build tests. From 6728ec5d1d151223e0290d5f5f0cebe3e38d163b Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Mon, 9 Jun 2025 22:44:03 +0530 Subject: [PATCH 0485/1298] adding Finalized obj attribute to gcs.MinObject (#3365) * adding finalized obj attr to gcs.MinObject * increasing average positive stat cache entry size to accomodate new attr in minObj * updating default value of stat cache size * do not fetch finalized attribute for regional buckets * do not assert on finalized attr for local mod tests * correcting mem usage relation with added attr size * nit --- cfg/config.go | 4 ++-- cfg/constants.go | 7 ++++++- cfg/params.yaml | 4 ++-- cmd/config_rationalization_test.go | 6 +++--- cmd/config_validation_test.go | 2 +- cmd/root_test.go | 4 ++-- internal/fs/fs_test.go | 2 +- internal/fs/hns_bucket_test.go | 2 +- internal/fs/local_modifications_test.go | 4 ++++ internal/storage/bucket_handle.go | 9 ++++++++- internal/storage/fake/bucket.go | 1 + internal/storage/fake/testing/bucket_tests.go | 14 ++++++++++++++ internal/storage/gcs/object.go | 2 ++ internal/storage/storageutil/object_attrs.go | 5 +++++ internal/storage/storageutil/object_attrs_test.go | 8 ++++++++ 15 files changed, 60 insertions(+), 14 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 1d0f641563..e285f397ac 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -619,13 +619,13 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.IntP("stat-cache-capacity", "", 20460, "How many entries can the stat-cache hold (impacts memory consumption). This flag has been deprecated (starting v2.0) and in favor of stat-cache-max-size-mb. For now, the value of stat-cache-capacity will be translated to the next higher corresponding value of stat-cache-max-size-mb (assuming stat-cache entry-size ~= 1640 bytes, including 1400 for positive entry and 240 for corresponding negative entry), if stat-cache-max-size-mb is not set.\"") + flagSet.IntP("stat-cache-capacity", "", 20460, "How many entries can the stat-cache hold (impacts memory consumption). This flag has been deprecated (starting v2.0) and in favor of stat-cache-max-size-mb. For now, the value of stat-cache-capacity will be translated to the next higher corresponding value of stat-cache-max-size-mb (assuming stat-cache entry-size ~= 1688 bytes, including 1448 for positive entry and 240 for corresponding negative entry), if stat-cache-max-size-mb is not set.\"") if err := flagSet.MarkDeprecated("stat-cache-capacity", "Please use --stat-cache-max-size-mb instead."); err != nil { return err } - flagSet.IntP("stat-cache-max-size-mb", "", 32, "The maximum size of stat-cache in MiBs. It can also be set to -1 for no-size-limit, 0 for no cache. Values below -1 are not supported.") + flagSet.IntP("stat-cache-max-size-mb", "", 33, "The maximum size of stat-cache in MiBs. It can also be set to -1 for no-size-limit, 0 for no cache. Values below -1 are not supported.") flagSet.DurationP("stat-cache-ttl", "", 60000000000*time.Nanosecond, "How long to cache StatObject results and inode attributes. This flag has been deprecated (starting v2.0) in favor of metadata-cache-ttl-secs. For now, the minimum of stat-cache-ttl and type-cache-ttl values, rounded up to the next higher multiple of a second is used as ttl for both stat-cache and type-cache, when metadata-cache-ttl-secs is not set.") diff --git a/cfg/constants.go b/cfg/constants.go index 2807261703..00cec6da0d 100644 --- a/cfg/constants.go +++ b/cfg/constants.go @@ -67,7 +67,12 @@ const ( // meant for two purposes. // 1. for conversion from stat-cache-capacity to stat-cache-max-size-mb. // 2. internal testing. - AverageSizeOfPositiveStatCacheEntry uint64 = 1400 + // Note: When adding 'X' bytes to the heap,it is expected the Resident Set Size + // which is closer to the actual memory usage increases by roughly '2X' + // due to overheads like fragmentation and alignment. + // Thus, incase an attribute of size x has been added to stat cache entry, update + // the average size here by 2x. + AverageSizeOfPositiveStatCacheEntry uint64 = 1448 // AverageSizeOfNegativeStatCacheEntry is the assumed size of each negative stat-cache-entry, // meant for two purposes. // 1. for conversion from stat-cache-capacity to stat-cache-max-size-mb. diff --git a/cfg/params.yaml b/cfg/params.yaml index 9ab83b1077..50156bd4a8 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -512,7 +512,7 @@ flag has been deprecated (starting v2.0) and in favor of stat-cache-max-size-mb. For now, the value of stat-cache-capacity will be translated to the next higher corresponding value of stat-cache-max-size-mb - (assuming stat-cache entry-size ~= 1640 bytes, including 1400 for positive + (assuming stat-cache entry-size ~= 1688 bytes, including 1448 for positive entry and 240 for corresponding negative entry), if stat-cache-max-size-mb is not set." deprecated: true @@ -588,7 +588,7 @@ usage: >- The maximum size of stat-cache in MiBs. It can also be set to -1 for no-size-limit, 0 for no cache. Values below -1 are not supported. - default: "32" + default: "33" - config-path: "metadata-cache.ttl-secs" flag-name: "metadata-cache-ttl-secs" diff --git a/cmd/config_rationalization_test.go b/cmd/config_rationalization_test.go index 7b96351a2b..17da040c2f 100644 --- a/cmd/config_rationalization_test.go +++ b/cmd/config_rationalization_test.go @@ -33,13 +33,13 @@ func TestRationalizeMetadataCache(t *testing.T) { name: "new_ttl_flag_set", args: []string{"--metadata-cache-ttl-secs=30"}, expectedTTLSecs: 30, - expectedStatCacheSize: 32, // default. + expectedStatCacheSize: 33, // default. }, { name: "old_ttl_flags_set", args: []string{"--stat-cache-ttl=10s", "--type-cache-ttl=5s"}, expectedTTLSecs: 5, - expectedStatCacheSize: 32, // default. + expectedStatCacheSize: 33, // default. }, { name: "new_stat-cache-size-mb_flag_set", @@ -57,7 +57,7 @@ func TestRationalizeMetadataCache(t *testing.T) { name: "no_relevant_flags_set", args: []string{""}, expectedTTLSecs: 60, // default. - expectedStatCacheSize: 32, //default. + expectedStatCacheSize: 33, //default. }, { name: "both_new_and_old_flags_set", diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index b9d90569ad..0c856c430b 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -657,7 +657,7 @@ func TestValidateConfigFile_MetadataCacheConfigSuccessful(t *testing.T) { DeprecatedTypeCacheTtl: 60 * time.Second, EnableNonexistentTypeCache: false, ExperimentalMetadataPrefetchOnMount: "disabled", - StatCacheMaxSizeMb: 32, + StatCacheMaxSizeMb: 33, TtlSecs: 60, NegativeTtlSecs: 5, TypeCacheMaxSizeMb: 4, diff --git a/cmd/root_test.go b/cmd/root_test.go index 0b22182266..98953a74ad 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1182,7 +1182,7 @@ func TestArgsParsing_MetadataCacheFlags(t *testing.T) { DeprecatedTypeCacheTtl: 60 * time.Second, EnableNonexistentTypeCache: false, ExperimentalMetadataPrefetchOnMount: "disabled", - StatCacheMaxSizeMb: 32, + StatCacheMaxSizeMb: 33, TtlSecs: 60, NegativeTtlSecs: 5, TypeCacheMaxSizeMb: 4, @@ -1199,7 +1199,7 @@ func TestArgsParsing_MetadataCacheFlags(t *testing.T) { DeprecatedTypeCacheTtl: 60 * time.Second, EnableNonexistentTypeCache: false, ExperimentalMetadataPrefetchOnMount: "disabled", - StatCacheMaxSizeMb: 32, + StatCacheMaxSizeMb: 33, TtlSecs: 60, NegativeTtlSecs: 5, TypeCacheMaxSizeMb: 4, diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index a1fce02c7d..c88cd499bd 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -159,7 +159,7 @@ func (t *fsTest) SetUpTestSuite() { FileCache: defaultFileCacheConfig(), MetadataCache: cfg.MetadataCacheConfig{ // Setting default values. - StatCacheMaxSizeMb: 32, + StatCacheMaxSizeMb: 33, TtlSecs: 60, TypeCacheMaxSizeMb: 4, }, diff --git a/internal/fs/hns_bucket_test.go b/internal/fs/hns_bucket_test.go index c84378f50e..b5cfffd680 100644 --- a/internal/fs/hns_bucket_test.go +++ b/internal/fs/hns_bucket_test.go @@ -428,7 +428,7 @@ func (t *HNSCachedBucketMountTest) SetupSuite() { FileCache: defaultFileCacheConfig(), MetadataCache: cfg.MetadataCacheConfig{ // Setting default values. - StatCacheMaxSizeMb: 32, + StatCacheMaxSizeMb: 33, TtlSecs: 60, TypeCacheMaxSizeMb: 4, }, diff --git a/internal/fs/local_modifications_test.go b/internal/fs/local_modifications_test.go index 2566a1d685..c4e44dea3e 100644 --- a/internal/fs/local_modifications_test.go +++ b/internal/fs/local_modifications_test.go @@ -1583,6 +1583,8 @@ func (t *FileTest) AppendFileOperation_ShouldNotChangeObjectAttributes() { minObject2, extendedAttr2, err := bucket.StatObject(ctx, &gcs.StatObjectRequest{Name: fileName, ForceFetchFromGcs: true, ReturnExtendedObjectAttributes: true}) AssertEq(nil, err) // Validate object attributes are as expected. + // TODO: Validate on Finalized attribute once the default behavior on GCSFuse + // side is to never finalize object. validateObjectAttributes(extendedAttr1, extendedAttr2, minObject1, minObject2) } @@ -1606,6 +1608,8 @@ func (t *FileTest) WriteAtFileOperation_ShouldNotChangeObjectAttributes() { AssertEq(nil, err) // Validate object attributes are as expected. + // TODO: Validate on Finalized attribute once the default behavior on GCSFuse + // side is to never finalize object. validateObjectAttributes(extendedAttr1, extendedAttr2, minObject1, minObject2) } diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 03f10387b7..0ea21fc47d 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -367,7 +367,14 @@ func (bh *bucketHandle) ListObjects(ctx context.Context, req *gcs.ListObjectsReq IncludeFoldersAsPrefixes: req.IncludeFoldersAsPrefixes, //MaxResults: , (Field not present in storage.Query of Go Storage Library but present in ListObjectsQuery in Jacobsa code.) } - err = query.SetAttrSelection([]string{"Name", "Size", "Generation", "Metageneration", "Updated", "Metadata", "ContentEncoding", "CRC32C"}) + minObjAttrs := []string{"Name", "Size", "Generation", "Metageneration", "Updated", "Metadata", "ContentEncoding", "CRC32C"} + if bh.BucketType().Zonal { + // For regional buckets, partial response API fails to populate the Finalized field.(b/398916957) + // For objects in regional buckets, this field will be *unset*. + minObjAttrs = append(minObjAttrs, "Finalized") + } + err = query.SetAttrSelection(minObjAttrs) + if err != nil { err = fmt.Errorf("error while setting attribute selection for List Object query :%w", err) return diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index 364051d488..da6b5d9dbf 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -524,6 +524,7 @@ func copyMinObject(o *gcs.Object) *gcs.MinObject { copy.Generation = o.Generation copy.MetaGeneration = o.MetaGeneration copy.Updated = o.Updated + copy.Finalized = o.Finalized copy.Metadata = copyMetadata(o.Metadata) copy.ContentEncoding = o.ContentEncoding copy.CRC32C = o.CRC32C diff --git a/internal/storage/fake/testing/bucket_tests.go b/internal/storage/fake/testing/bucket_tests.go index 6e7577681e..2fa1f7280e 100644 --- a/internal/storage/fake/testing/bucket_tests.go +++ b/internal/storage/fake/testing/bucket_tests.go @@ -503,6 +503,7 @@ func (t *bucketTest) assertOnObjectAttributes(expectedMinObj *gcs.MinObject, exp ExpectThat(expectedMinObj.Generation, Equals(o.Generation)) ExpectThat(expectedMinObj.MetaGeneration, Equals(o.MetaGeneration)) ExpectThat(expectedMinObj.Updated, DeepEquals(o.Updated)) + ExpectThat(expectedMinObj.Finalized, DeepEquals(o.Finalized)) ExpectThat(expectedMinObj.Metadata, DeepEquals(o.Metadata)) ExpectThat(expectedMinObj.ContentEncoding, Equals(o.ContentEncoding)) ExpectThat(expectedMinObj.CRC32C, Equals(o.CRC32C)) @@ -638,6 +639,7 @@ func (t *createTest) ObjectAttributes_Default() { ExpectEq("STANDARD", o.StorageClass) ExpectThat(o.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(o.Updated, t.matchesStartTime(createTime)) + ExpectThat(o.Finalized, timeutil.TimeEq(time.Time{})) // Make sure it matches when we stat object. minObj, extendedAttr, err := t.bucket.StatObject( @@ -692,6 +694,7 @@ func (t *createTest) ObjectAttributes_Explicit() { ExpectThat(o.Deleted, DeepEquals(time.Time{})) ExpectThat(o.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(o.Updated, t.matchesStartTime(createTime)) + ExpectThat(o.Finalized, timeutil.TimeEq(time.Time{})) // Make sure it matches when we stat object. minObj, extendedAttr, err := t.bucket.StatObject( @@ -1350,6 +1353,7 @@ func (t *copyTest) DestinationDoesntExist() { ExpectThat(dst.Deleted, DeepEquals(time.Time{})) ExpectThat(dst.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(dst.Updated, t.matchesStartTime(createTime)) + ExpectThat(dst.Finalized, timeutil.TimeEq(time.Time{})) // The object should be readable. contents, err := storageutil.ReadObject(t.ctx, t.bucket, "bar") @@ -1440,6 +1444,7 @@ func (t *copyTest) DestinationExists() { ExpectThat(dst.Deleted, DeepEquals(time.Time{})) ExpectThat(dst.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(dst.Updated, t.matchesStartTime(createTime)) + ExpectThat(dst.Finalized, timeutil.TimeEq(time.Time{})) // The object should be readable. contents, err := storageutil.ReadObject(t.ctx, t.bucket, "bar") @@ -1513,6 +1518,7 @@ func (t *copyTest) DestinationIsSameName() { ExpectThat(dst.Deleted, DeepEquals(time.Time{})) ExpectThat(dst.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(dst.Updated, t.matchesStartTime(createTime)) + ExpectThat(dst.Finalized, timeutil.TimeEq(time.Time{})) // The object should be readable. contents, err := storageutil.ReadObject(t.ctx, t.bucket, "foo") @@ -1835,6 +1841,7 @@ func (t *composeTest) OneSimpleSource() { ExpectEq("STANDARD", o.StorageClass) ExpectThat(o.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(o.Updated, t.matchesStartTime(composeTime)) + ExpectThat(o.Finalized, timeutil.TimeEq(time.Time{})) // Check contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, "foo") @@ -1894,6 +1901,7 @@ func (t *composeTest) TwoSimpleSources() { ExpectEq("STANDARD", o.StorageClass) ExpectThat(o.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(o.Updated, t.matchesStartTime(composeTime)) + ExpectThat(o.Finalized, timeutil.TimeEq(time.Time{})) // Check contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, "foo") @@ -1950,6 +1958,7 @@ func (t *composeTest) ManySimpleSources() { ExpectEq("STANDARD", o.StorageClass) ExpectThat(o.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(o.Updated, t.matchesStartTime(composeTime)) + ExpectThat(o.Finalized, timeutil.TimeEq(time.Time{})) for _, src := range sources { ExpectLt(src.Generation, o.Generation) @@ -2021,6 +2030,7 @@ func (t *composeTest) RepeatedSources() { ExpectEq("STANDARD", o.StorageClass) ExpectThat(o.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(o.Updated, t.matchesStartTime(composeTime)) + ExpectThat(o.Finalized, timeutil.TimeEq(time.Time{})) // Check contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, "foo") @@ -2103,6 +2113,7 @@ func (t *composeTest) CompositeSources() { ExpectEq("STANDARD", o.StorageClass) ExpectThat(o.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(o.Updated, t.matchesStartTime(composeTime)) + ExpectThat(o.Finalized, timeutil.TimeEq(time.Time{})) // Check contents. contents, err := storageutil.ReadObject(t.ctx, t.bucket, "foo") @@ -3476,6 +3487,7 @@ func (t *statTest) StatAfterCreating() { ExpectEq(len("taco"), m.Size) ExpectThat(e.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(m.Updated, timeutil.TimeEq(orig.Updated)) + ExpectThat(m.Finalized, timeutil.TimeEq(time.Time{})) } func (t *statTest) StatAfterOverwriting() { @@ -3512,6 +3524,7 @@ func (t *statTest) StatAfterOverwriting() { ExpectEq(len("burrito"), m.Size) ExpectThat(e.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(m.Updated, timeutil.TimeEq(o2.Updated)) + ExpectThat(m.Finalized, timeutil.TimeEq(time.Time{})) } func (t *statTest) StatAfterUpdating() { @@ -3565,6 +3578,7 @@ func (t *statTest) StatAfterUpdating() { ExpectEq(len("taco"), m.Size) ExpectThat(e.Deleted, timeutil.TimeEq(time.Time{})) ExpectThat(m.Updated, timeutil.TimeEq(o2.Updated)) + ExpectThat(m.Finalized, timeutil.TimeEq(time.Time{})) } //////////////////////////////////////////////////////////////////////// diff --git a/internal/storage/gcs/object.go b/internal/storage/gcs/object.go index baa6220617..5bddb2387e 100644 --- a/internal/storage/gcs/object.go +++ b/internal/storage/gcs/object.go @@ -46,6 +46,7 @@ type Object struct { StorageClass string Deleted time.Time Updated time.Time + Finalized time.Time // As of 2015-06-03, the official GCS documentation for this property // (https://tinyurl.com/2zjza2cu) says this: @@ -86,6 +87,7 @@ type MinObject struct { Generation int64 MetaGeneration int64 Updated time.Time + Finalized time.Time Metadata map[string]string ContentEncoding string CRC32C *uint32 // Missing for CMEK buckets diff --git a/internal/storage/storageutil/object_attrs.go b/internal/storage/storageutil/object_attrs.go index 46d78619f6..66f7189655 100644 --- a/internal/storage/storageutil/object_attrs.go +++ b/internal/storage/storageutil/object_attrs.go @@ -96,6 +96,7 @@ func ObjectAttrsToBucketObject(attrs *storage.ObjectAttrs) *gcs.Object { StorageClass: attrs.StorageClass, Deleted: attrs.Deleted, Updated: attrs.Updated, + Finalized: attrs.Finalized, ComponentCount: attrs.ComponentCount, ContentDisposition: attrs.ContentDisposition, CustomTime: string(attrs.CustomTime.Format(time.RFC3339)), @@ -122,6 +123,7 @@ func ObjectAttrsToMinObject(attrs *storage.ObjectAttrs) *gcs.MinObject { Generation: attrs.Generation, MetaGeneration: attrs.Metageneration, Updated: attrs.Updated, + Finalized: attrs.Finalized, } } @@ -172,6 +174,7 @@ func ConvertObjToMinObject(o *gcs.Object) *gcs.MinObject { Metadata: o.Metadata, ContentEncoding: o.ContentEncoding, CRC32C: o.CRC32C, + Finalized: o.Finalized, } } @@ -209,6 +212,7 @@ func ConvertMinObjectAndExtendedObjectAttributesToObject(m *gcs.MinObject, Generation: m.Generation, MetaGeneration: m.MetaGeneration, Updated: m.Updated, + Finalized: m.Finalized, Metadata: m.Metadata, ContentEncoding: m.ContentEncoding, ContentType: e.ContentType, @@ -242,5 +246,6 @@ func ConvertMinObjectToObject(m *gcs.MinObject) *gcs.Object { Metadata: m.Metadata, ContentEncoding: m.ContentEncoding, CRC32C: m.CRC32C, + Finalized: m.Finalized, } } diff --git a/internal/storage/storageutil/object_attrs_test.go b/internal/storage/storageutil/object_attrs_test.go index 7aaebb7be9..769bf0649d 100644 --- a/internal/storage/storageutil/object_attrs_test.go +++ b/internal/storage/storageutil/object_attrs_test.go @@ -98,6 +98,7 @@ func (t objectAttrsTest) TestObjectAttrsToBucketObjectMethod() { Created: timeAttr, Deleted: timeAttr, Updated: timeAttr, + Finalized: timeAttr, CustomerKeySHA256: "CustomerKeySHA256", KMSKeyName: "KMSKeyName", Prefix: "Prefix", @@ -133,6 +134,7 @@ func (t objectAttrsTest) TestObjectAttrsToBucketObjectMethod() { ExpectEq(object.MetaGeneration, attrs.Metageneration) ExpectEq(object.StorageClass, attrs.StorageClass) ExpectEq(object.Updated.String(), attrs.Updated.String()) + ExpectEq(object.Finalized.String(), attrs.Finalized.String()) ExpectEq(object.Deleted.String(), attrs.Deleted.String()) ExpectEq(object.ContentDisposition, attrs.ContentDisposition) ExpectEq(object.CustomTime, customeTimeExpected) @@ -241,6 +243,7 @@ func (t objectAttrsTest) Test_ConvertObjToMinObject_WithValidObject() { Generation: generation, MetaGeneration: metaGeneration, Updated: currentTime, + Finalized: currentTime, Metadata: metadata, ContentEncoding: contentEncode, CRC32C: &crc32C, @@ -254,6 +257,7 @@ func (t objectAttrsTest) Test_ConvertObjToMinObject_WithValidObject() { ExpectEq(generation, gcsMinObject.Generation) ExpectEq(metaGeneration, gcsMinObject.MetaGeneration) ExpectTrue(currentTime.Equal(gcsMinObject.Updated)) + ExpectTrue(currentTime.Equal(gcsMinObject.Finalized)) ExpectEq(contentEncode, gcsMinObject.ContentEncoding) ExpectEq(metadata, gcsMinObject.Metadata) ExpectEq(crc32C, *gcsMinObject.CRC32C) @@ -345,6 +349,7 @@ func (t objectAttrsTest) Test_ConvertObjToExtendedObjectAttributes_WithNonNilMin Generation: int64(444), MetaGeneration: int64(555), Updated: timeAttr, + Finalized: timeAttr, Metadata: map[string]string{"test_key": "test_value"}, ContentEncoding: "test_encoding", } @@ -372,6 +377,7 @@ func (t objectAttrsTest) Test_ConvertObjToExtendedObjectAttributes_WithNonNilMin ExpectEq(gcsObject.Generation, minObject.Generation) ExpectEq(gcsObject.MetaGeneration, minObject.MetaGeneration) ExpectEq(0, gcsObject.Updated.Compare(minObject.Updated)) + ExpectEq(0, gcsObject.Finalized.Compare(minObject.Finalized)) ExpectEq(gcsObject.Metadata, minObject.Metadata) ExpectEq(gcsObject.ContentEncoding, minObject.ContentEncoding) ExpectEq(gcsObject.ContentType, extendedObjAttr.ContentType) @@ -407,6 +413,7 @@ func (t objectAttrsTest) Test_ConvertMinObjectToObject_WithNonNilMinObject() { Generation: int64(444), MetaGeneration: int64(555), Updated: timeAttr, + Finalized: timeAttr, Metadata: map[string]string{"test_key": "test_value"}, ContentEncoding: "test_encoding", CRC32C: &crc32C, @@ -420,6 +427,7 @@ func (t objectAttrsTest) Test_ConvertMinObjectToObject_WithNonNilMinObject() { ExpectEq(gcsObject.Generation, minObject.Generation) ExpectEq(gcsObject.MetaGeneration, minObject.MetaGeneration) ExpectEq(0, gcsObject.Updated.Compare(minObject.Updated)) + ExpectEq(0, gcsObject.Finalized.Compare(minObject.Finalized)) ExpectEq(gcsObject.Metadata, minObject.Metadata) ExpectEq(gcsObject.ContentEncoding, minObject.ContentEncoding) ExpectEq(gcsObject.ContentType, "") From d946fd5a637756afc87324fc75b918a66e955312 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 10 Jun 2025 13:43:47 +0530 Subject: [PATCH 0486/1298] [Random reader refactoring] Integrate a read manager instance with a file handle to support the new read flow, controlled by a hidden flag (#3381) * enable read manager behind the flag * add unit tests * small fix * adding more unit tests * small improvments * add more unit tests * make default value of flag off * merge unit tests * remove unnecessary lines * small fix * optimze test more * review comment * check e2e tests * undo test check * review comments * updating comment --- internal/fs/fs.go | 7 +- internal/fs/handle/file.go | 115 +++++++++- internal/fs/handle/file_test.go | 215 ++++++++++++++++-- internal/gcsx/file_cache_reader.go | 12 +- internal/gcsx/mock_random_reader.go | 45 ++++ .../gcsx/read_manager/mock_read_manager.go | 46 ++++ 6 files changed, 418 insertions(+), 22 deletions(-) create mode 100644 internal/gcsx/mock_random_reader.go create mode 100644 internal/gcsx/read_manager/mock_read_manager.go diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 7b6e0a917f..7b266d35c9 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2613,7 +2613,12 @@ func (fs *fileSystem) ReadFile( } } // Serve the read. - op.Dst, op.BytesRead, err = fh.Read(ctx, op.Dst, op.Offset, fs.sequentialReadSizeMb) + + if fs.newConfig.EnableNewReader { + op.Dst, op.BytesRead, err = fh.ReadWithReadManager(ctx, op.Dst, op.Offset, fs.sequentialReadSizeMb) + } else { + op.Dst, op.BytesRead, err = fh.Read(ctx, op.Dst, op.Offset, fs.sequentialReadSizeMb) + } // As required by fuse, we don't treat EOF as an error. if err == io.EOF { diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 62a730f9a5..be774e9a82 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -24,6 +24,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx/read_manager" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/syncutil" @@ -43,6 +44,14 @@ type FileHandle struct { // GUARDED_BY(mu) reader gcsx.RandomReader + // A readManager configured to some (potentially previous) generation of + // the object backing the inode, or nil. + // + // INVARIANT: If readManager != nil, readManager.CheckInvariants() doesn't panic. + // + // GUARDED_BY(mu) + readManager gcsx.ReadManager + // fileCacheHandler is used to get file cache handle and read happens using that. // This will be nil if the file cache is disabled. fileCacheHandler *file.CacheHandler @@ -103,6 +112,54 @@ func (fh *FileHandle) Unlock() { fh.mu.Unlock() } +// ReadWithReadManager reads data at the given offset using the read manager if available, +// falling back to inode.Read otherwise. It may be more efficient than directly calling inode.Read. +// +// LOCKS_REQUIRED(fh.mu) +// LOCKS_REQUIRED(fh.inode.mu) +// UNLOCK_FUNCTION(fh.inode.mu) +func (fh *FileHandle) ReadWithReadManager(ctx context.Context, dst []byte, offset int64, sequentialReadSizeMb int32) ([]byte, int, error) { + // fh.inode.mu is already locked to ensure that we have a readManager for its current + // state, or clear fh.readManager if it's not possible to create one (probably + // because the inode is dirty). + err := fh.tryEnsureReadManager(ctx, sequentialReadSizeMb) + if err != nil { + fh.inode.Unlock() + return nil, 0, fmt.Errorf("tryEnsureReadManager: %w", err) + } + + // If we have an appropriate readManager, unlock the inode and use that. This + // allows reads to proceed concurrently with other operations; in particular, + // multiple reads can run concurrently. It's safe because the user can't tell + // if a concurrent write started during or after a read. + if fh.readManager != nil { + fh.inode.Unlock() + + var readerResponse gcsx.ReaderResponse + readerResponse, err = fh.readManager.ReadAt(ctx, dst, offset) + switch { + case errors.Is(err, io.EOF): + if err != io.EOF { + logger.Warnf("Unexpected EOF error encountered while reading, err: %v type: %T ", err, err) + } + return nil, 0, io.EOF + + case err != nil: + return nil, 0, fmt.Errorf("fh.readManager.ReadAt: %w", err) + } + + return readerResponse.DataBuf, readerResponse.Size, nil + } + + // If read manager is not available, fall back to reading via inode + defer fh.inode.Unlock() + + n, err := fh.inode.Read(ctx, dst, offset) + + // Return the original dst buffer and number of bytes read + return dst, n, err +} + // Equivalent to locking fh.Inode() and calling fh.Inode().Read, but may be // more efficient. // @@ -181,7 +238,7 @@ func (fh *FileHandle) checkInvariants() { } // If possible, ensure that fh.reader is set to an appropriate random reader -// for the current state of the inode. Otherwise set it to nil. +// for the current state of the inode otherwise set it to nil. // // LOCKS_REQUIRED(fh) // LOCKS_REQUIRED(fh.inode) @@ -204,7 +261,7 @@ func (fh *FileHandle) tryEnsureReader(ctx context.Context, sequentialReadSizeMb } // If we already have a reader, and it's at the appropriate generation, we - // can use it. Otherwise we must throw it away. + // can use it otherwise we must throw it away. if fh.reader != nil { if fh.reader.Object().Generation == fh.inode.SourceGeneration().Object { // Update reader object size to source object size. @@ -221,3 +278,57 @@ func (fh *FileHandle) tryEnsureReader(ctx context.Context, sequentialReadSizeMb fh.reader = rr return } + +// If possible, ensure that fh.readManager is set to an appropriate read manager +// for the current state of the inode otherwise set it to nil. +// +// LOCKS_REQUIRED(fh) +// LOCKS_REQUIRED(fh.inode) +func (fh *FileHandle) tryEnsureReadManager(ctx context.Context, sequentialReadSizeMb int32) error { + // If content cache enabled, CacheEnsureContent forces the file handler to fall through to the inode + // and fh.inode.SourceGenerationIsAuthoritative() will return false + if err := fh.inode.CacheEnsureContent(ctx); err != nil { + return fmt.Errorf("failed to ensure inode content: %w", err) + } + + // If the inode is dirty, there's nothing we can do. Throw away our readManager if + // we have one. + if !fh.inode.SourceGenerationIsAuthoritative() { + fh.destroyReadManager() + return nil + } + + // If we already have a readManager, and it's at the appropriate generation, we + // can use it otherwise we must throw it away. + if fh.readManager != nil && fh.readManager.Object().Generation == fh.inode.SourceGeneration().Object { + // Update reader object size to source object size. + fh.readManager.Object().Size = fh.inode.SourceGeneration().Size + return nil + } + + // If we reached here, either no readManager exists, or the existing one is outdated. + // Destroy any old read manager before creating a new one. + fh.destroyReadManager() + + // Create a new read manager for the current inode state. + fh.readManager = read_manager.NewReadManager(fh.inode.Source(), fh.inode.Bucket(), &read_manager.ReadManagerConfig{ + SequentialReadSizeMB: sequentialReadSizeMb, + FileCacheHandler: fh.fileCacheHandler, + CacheFileForRangeRead: fh.cacheFileForRangeRead, + MetricHandle: fh.metricHandle, + MrdWrapper: &fh.inode.MRDWrapper, + ReadConfig: fh.readConfig, + }) + + return nil +} + +// destroyReadManager is a helper function to safely destroy and nil the readManager. +// This assumes the necessary locks (fh.mu, fh.inode.mu) are already held by the caller. +func (fh *FileHandle) destroyReadManager() { + if fh.readManager == nil { + return + } + fh.readManager.Destroy() + fh.readManager = nil +} diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 23ce89bb3f..96ad3850c3 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -17,23 +17,56 @@ package handle import ( "bytes" "context" + "errors" + "fmt" "io" "testing" "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx/read_manager" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/timeutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "golang.org/x/sync/semaphore" ) +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type fileTest struct { + suite.Suite + ctx context.Context + clock timeutil.SimulatedClock + bucket gcsx.SyncerBucket +} + +func TestFileTestSuite(t *testing.T) { + suite.Run(t, new(fileTest)) +} + +func (t *fileTest) SetupTest() { + t.ctx = context.TODO() + t.clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local)) + t.bucket = gcsx.NewSyncerBucket(1, 10, ".gcsfuse_tmp/", fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{})) +} + +func (t *fileTest) TearDownTest() { +} + +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + // createDirInode helps create the parent directory inode for the file inode // which will be used for testing methods defined on the fileHandle. func createDirInode( @@ -68,7 +101,8 @@ func createFileInode( config *cfg.Config, parent inode.DirInode, objectName string, - content []byte) *inode.FileInode { + content []byte, + localFileCache bool) *inode.FileInode { obj := &gcs.MinObject{ Name: objectName, @@ -93,7 +127,7 @@ func createFileInode( obj, fuseops.InodeAttributes{}, bucket, - false, + localFileCache, contentcache.New("", clock), clock, false, @@ -102,31 +136,180 @@ func createFileInode( ) } -// TODO: Add unit test for fh.Read() +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// -func TestFileHandleWrite(t *testing.T) { - var clock timeutil.SimulatedClock - clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local)) - bucket := gcsx.NewSyncerBucket( - 1, 10, ".gcsfuse_tmp/", fake.NewFakeBucket(&clock, "some_bucket", gcs.BucketType{})) - parent := createDirInode(&bucket, &clock, "parentRoot") +func (t *fileTest) TestFileHandleWrite() { + parent := createDirInode(&t.bucket, &t.clock, "parentRoot") config := &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: false}} - in := createFileInode(t, &bucket, &clock, config, parent, "test_obj", nil) + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj", nil, false) fh := NewFileHandle(in, nil, false, nil, util.Write, &cfg.ReadConfig{}) - ctx := context.Background() data := []byte("hello") - _, err := fh.Write(ctx, data, 0) + _, err := fh.Write(t.ctx, data, 0) - assert.Nil(t, err) + assert.Nil(t.T(), err) // Validate that write is successful at inode. buf := make([]byte, len(data)) - n, err := in.Read(ctx, buf, 0) + n, err := in.Read(t.ctx, buf, 0) buf = buf[:n] // Ignore EOF. if err == io.EOF { err = nil } - assert.Nil(t, err) - assert.Equal(t, data, buf) + assert.Nil(t.T(), err) + assert.Equal(t.T(), data, buf) +} + +// Test_Read_Success validates successful read behavior using the random reader. +func (t *fileTest) Test_Read_Success() { + expectedData := []byte("hello from reader") + parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, "test_obj_reader", expectedData, false) + fh := NewFileHandle(in, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + buf := make([]byte, len(expectedData)) + fh.inode.Lock() + + output, n, err := fh.Read(t.ctx, buf, 0, 200) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), len(expectedData), n) + assert.Equal(t.T(), expectedData, output) +} + +// Test_ReadWithReadManager_Success validates successful read behavior using the readManager. +func (t *fileTest) Test_ReadWithReadManager_Success() { + expectedData := []byte("hello from readManager") + parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, "test_obj_readManager", expectedData, false) + fh := NewFileHandle(in, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + buf := make([]byte, len(expectedData)) + fh.inode.Lock() + + output, n, err := fh.ReadWithReadManager(t.ctx, buf, 0, 200) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), len(expectedData), n) + assert.Equal(t.T(), expectedData, output) +} + +// Test_ReadWithReadManager_ErrorScenarios verifies error handling in ReadWithReadManager. +func (t *fileTest) Test_ReadWithReadManager_ErrorScenarios() { + type testCase struct { + name string + returnErr error + } + + object := gcs.MinObject{Name: "test_obj", Generation: 1} + mockErr := fmt.Errorf("mock error") + dst := make([]byte, 100) + + testCases := []testCase{ + {name: "EOF via readManager", returnErr: io.EOF}, + {name: "mock error via readManager", returnErr: mockErr}, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.SetupTest() + parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + testInode := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, []byte("data"), false) + fh := NewFileHandle(testInode, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh.inode.Lock() + mockRM := new(read_manager.MockReadManager) + mockRM.On("ReadAt", t.ctx, dst, int64(0)).Return(gcsx.ReaderResponse{}, tc.returnErr) + mockRM.On("Object").Return(&object) + fh.readManager = mockRM + + output, n, err := fh.ReadWithReadManager(t.ctx, dst, 0, 200) + + assert.Zero(t.T(), n, "expected 0 bytes read") + assert.Nil(t.T(), output, "expected output to be nil") + assert.True(t.T(), errors.Is(err, tc.returnErr), "expected error to match") + mockRM.AssertExpectations(t.T()) + }) + } +} + +// Test_Read_ErrorScenarios verifies error handling in Read (random reader). +func (t *fileTest) Test_Read_ErrorScenarios() { + type testCase struct { + name string + returnErr error + } + + object := gcs.MinObject{Name: "test_obj", Generation: 1} + mockErr := fmt.Errorf("mock error") + dst := make([]byte, 100) + + testCases := []testCase{ + {name: "EOF via random reader", returnErr: io.EOF}, + {name: "mock error via random reader", returnErr: mockErr}, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.SetupTest() + parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + testInode := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, []byte("data"), false) + fh := NewFileHandle(testInode, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh.inode.Lock() + mockReader := new(gcsx.MockRandomReader) + mockReader.On("ReadAt", t.ctx, dst, int64(0)).Return(gcsx.ObjectData{}, tc.returnErr) + mockReader.On("Object").Return(&object) + fh.reader = mockReader + + output, n, err := fh.Read(t.ctx, dst, 0, 200) + + assert.Zero(t.T(), n, "expected 0 bytes read") + assert.Nil(t.T(), output, "expected output to be nil") + assert.True(t.T(), errors.Is(err, tc.returnErr), "expected error to match") + mockReader.AssertExpectations(t.T()) + }) + } +} + +// Test_ReadWithReadManager_FallbackToInode verifies that ReadWithReadManager +// falls back to inode object data when readManager is not valid. +func (t *fileTest) Test_ReadWithReadManager_FallbackToInode() { + dst := make([]byte, 100) + objectData := []byte("fallback data") + object := gcs.MinObject{Name: "test_obj", Generation: 0} + parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, objectData, true) + fh := NewFileHandle(in, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh.inode.Lock() + mockRM := new(read_manager.MockReadManager) + mockRM.On("Destroy").Return() + fh.readManager = mockRM + + output, n, err := fh.ReadWithReadManager(t.ctx, dst, 0, 200) + + assert.Equal(t.T(), io.EOF, err) + assert.Equal(t.T(), len(objectData), n) + assert.Equal(t.T(), objectData, output[:n]) + mockRM.AssertExpectations(t.T()) +} + +// Test_Read_FallbackToInode verifies that Read falls back to inode object data +// when reader is not valid. +func (t *fileTest) Test_Read_FallbackToInode() { + dst := make([]byte, 100) + objectData := []byte("fallback data") + object := gcs.MinObject{Name: "test_obj", Generation: 0} + parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, objectData, true) + fh := NewFileHandle(in, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh.inode.Lock() + mockR := new(gcsx.MockRandomReader) + mockR.On("Destroy").Return() + fh.reader = mockR + + output, n, err := fh.Read(t.ctx, dst, 0, 200) + + assert.Equal(t.T(), io.EOF, err) + assert.Equal(t.T(), len(objectData), n) + assert.Equal(t.T(), objectData, output[:n]) + mockR.AssertExpectations(t.T()) } diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index 5fee0e3992..4167496056 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -98,7 +98,7 @@ func (fc *FileCacheReader) tryReadingFromFileCache(ctx context.Context, p []byte handleID = uint64(readOp.Handle) } requestID := uuid.New() - logger.Tracef("%.13v <- FileCache(%s:/%s, offset: %d, size: %d, handle: %d)", requestID, fc.bucket.Name(), fc.object.Name, offset, len(p), handleID) + logger.Tracef("%.13v <- FileCache(%s:/%s, offset: %d, size: %d handle: %d)", requestID, fc.bucket.Name(), fc.object.Name, offset, len(p), handleID) startTime := time.Now() var bytesRead int @@ -130,17 +130,22 @@ func (fc *FileCacheReader) tryReadingFromFileCache(ctx context.Context, p []byte if fc.fileCacheHandle == nil { fc.fileCacheHandle, err = fc.fileCacheHandler.GetCacheHandle(fc.object, fc.bucket, fc.cacheFileForRangeRead, offset) if err != nil { + cacheHit = false + bytesRead = 0 switch { case errors.Is(err, lru.ErrInvalidEntrySize): logger.Warnf("tryReadingFromFileCache: while creating CacheHandle: %v", err) + err = nil return 0, false, nil case errors.Is(err, cacheUtil.ErrCacheHandleNotRequiredForRandomRead): // Fall back to GCS if it is a random read, cacheFileForRangeRead is // false and there doesn't already exist file in cache. isSequential = false + err = nil return 0, false, nil default: - return 0, false, fmt.Errorf("tryReadingFromFileCache: GetCacheHandle failed: %w", err) + err = fmt.Errorf("tryReadingFromFileCache: GetCacheHandle failed: %w", err) + return 0, false, err } } } @@ -161,7 +166,8 @@ func (fc *FileCacheReader) tryReadingFromFileCache(ctx context.Context, p []byte } fc.fileCacheHandle = nil } else if !errors.Is(err, cacheUtil.ErrFallbackToGCS) { - return 0, false, fmt.Errorf("tryReadingFromFileCache: while reading via cache: %w", err) + err = fmt.Errorf("tryReadingFromFileCache: while reading via cache: %w", err) + return 0, false, err } err = nil diff --git a/internal/gcsx/mock_random_reader.go b/internal/gcsx/mock_random_reader.go new file mode 100644 index 0000000000..8812790e8e --- /dev/null +++ b/internal/gcsx/mock_random_reader.go @@ -0,0 +1,45 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gcsx + +import ( + "context" + + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/stretchr/testify/mock" +) + +type MockRandomReader struct { + RandomReader + mock.Mock +} + +func (m *MockRandomReader) ReadAt(ctx context.Context, dst []byte, offset int64) (ObjectData, error) { + args := m.Called(ctx, dst, offset) + return args.Get(0).(ObjectData), args.Error(1) +} + +func (m *MockRandomReader) Object() *gcs.MinObject { + args := m.Called() + return args.Get(0).(*gcs.MinObject) +} + +func (m *MockRandomReader) Destroy() { + m.Called() +} + +func (m *MockRandomReader) CheckInvariants() { + m.Called() +} diff --git a/internal/gcsx/read_manager/mock_read_manager.go b/internal/gcsx/read_manager/mock_read_manager.go new file mode 100644 index 0000000000..8c3bf5a782 --- /dev/null +++ b/internal/gcsx/read_manager/mock_read_manager.go @@ -0,0 +1,46 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_manager + +import ( + "context" + + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/stretchr/testify/mock" +) + +type MockReadManager struct { + gcsx.ReadManager + mock.Mock +} + +func (m *MockReadManager) ReadAt(ctx context.Context, dst []byte, offset int64) (gcsx.ReaderResponse, error) { + args := m.Called(ctx, dst, offset) + return args.Get(0).(gcsx.ReaderResponse), args.Error(1) +} + +func (m *MockReadManager) Object() *gcs.MinObject { + args := m.Called() + return args.Get(0).(*gcs.MinObject) +} + +func (m *MockReadManager) Destroy() { + m.Called() +} + +func (m *MockReadManager) CheckInvariants() { + m.Called() +} From 6a3dd66d5e737f09c55f0098d2e5a7eb15518827 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 10 Jun 2025 17:00:26 +0530 Subject: [PATCH 0487/1298] create package runtime table stats after e2e test runs (#3398) * create package runtime table after e2e test runs * fix review comments: remove spaces --- .../create_package_runtime_table.sh | 80 +++++++++++++++++++ .../improved_run_e2e_tests.sh | 13 ++- 2 files changed, 89 insertions(+), 4 deletions(-) create mode 100755 tools/integration_tests/create_package_runtime_table.sh diff --git a/tools/integration_tests/create_package_runtime_table.sh b/tools/integration_tests/create_package_runtime_table.sh new file mode 100755 index 0000000000..9878e06099 --- /dev/null +++ b/tools/integration_tests/create_package_runtime_table.sh @@ -0,0 +1,80 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Exit on error, treat unset variables as errors, and propagate pipeline errors. +set -euo pipefail + +# This script is used to create runtime tables for integration tests given +# package stats entries in file in the below format: +# `package_name bucket_type exit_code start_time end_time` +usage() { + echo "Usage: $0 " + exit 1 +} + +if [ "$#" -ne 1 ]; then + log_error "Missing required arguments." + usage +fi + +PACKAGE_RUNTIME_STATS=$1 + +if [ ! -f "$PACKAGE_RUNTIME_STATS" ]; then + echo "Error: File '$PACKAGE_RUNTIME_STATS' not found." + exit 1 +fi + +# sort pakages in ascending order. +sort -o "$PACKAGE_RUNTIME_STATS" "$PACKAGE_RUNTIME_STATS" + +# Print single package stats +print_package_stats() { + local package_name="$1" + local bucket_type="$2" + local exit_code="$3" + local start_sec="$4" + local end_sec="$5" + local wait_min run_min status + if [ "$exit_code" -eq 0 ]; then + status="PASSED" + else + status="FAILED" + fi + wait_min=$((start_sec / 60)) + run_min=$(((end_sec - start_sec + 60) / 60)) + package_stats=$(printf "| %-25s | %-15s | %-8s | %-10s |%-60s|\n" \ + "$package_name" \ + "$bucket_type" \ + "$status" \ + "${run_min}m" \ + "$(printf '%0.s_' $(seq 1 "$wait_min"))$(printf '%0.s>' $(seq 1 "$run_min"))") + echo "$package_stats" +} + +# Display legends +echo "" +echo "Timings for the e2e test packages run are listed below." +echo "_ is 1 min wait" +echo "> is 1 min run" +# Add Table headers +echo "+---------------------------+-----------------+----------+------------+------------------------------------------------------------+" +echo "| Package Name | Bucket Type | Status | Total Time |0 min runtime 60 min|" +# Read the file line by line and print stats. +while IFS= read -r line || [[ -n "$line" ]]; do # Process even if last line has no newline + echo "+---------------------------+-----------------+----------+------------+------------------------------------------------------------+" + print_package_stats $line +done <"$PACKAGE_RUNTIME_STATS" +echo "+---------------------------+-----------------+----------+------------+------------------------------------------------------------+" +echo "" diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh index fd0bed51f5..367a05938e 100755 --- a/tools/integration_tests/improved_run_e2e_tests.sh +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -71,6 +71,7 @@ BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR="" LOG_LOCK_FILE=$(mktemp "/tmp/${TMP_PREFIX}_logging_lock.XXXXXX") || { log_error "Unable to create lock file"; exit 1; } BUCKET_NAMES=$(mktemp "/tmp/${TMP_PREFIX}_bucket_names.XXXXXX") || { log_error "Unable to create bucket names file"; exit 1; } +PACKAGE_RUNTIME_STATS=$(mktemp "/tmp/${TMP_PREFIX}_package_stats_runtime.XXXXXX") || { log_error "Unable to create package stats runtime file"; exit 1; } # Argument Parsing and Assignments if [ "$#" -lt 3 ]; then @@ -403,11 +404,14 @@ test_package() { local go_test_cmd=$(printf "%q " "${go_test_cmd_parts[@]}") # Run the package test command - eval "$go_test_cmd" - if [[ $? -ne 0 ]]; then - return 1 + local start=$SECONDS exit_code=0 + if ! eval "$go_test_cmd"; then + exit_code=1 fi - return 0 + local end=$SECONDS + # Add the package stats to the file. + echo "${package_name} ${bucket_type} ${exit_code} ${start} ${end}" >> "$PACKAGE_RUNTIME_STATS" + return "$exit_code" } build_gcsfuse_once() { @@ -552,6 +556,7 @@ main() { elapsed_min=$(((SECONDS + 60) / 60)) log_info "------ E2E test packages complete run took ${elapsed_min} minutes ------" log_info "" + ./tools/integration_tests/create_package_runtime_table.sh "$PACKAGE_RUNTIME_STATS" exit $overall_exit_code } From 9ae325899a2306da015800b9993da362431fc916 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Wed, 11 Jun 2025 22:16:21 +0530 Subject: [PATCH 0488/1298] setting generation for object handle before creating takeover writer (#3388) * method to set obj handle gen * improved err handling * updating CreateObjectChunkWriterRequest to include generation field * readability improvements --- internal/storage/bucket_handle.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 0ea21fc47d..203899352d 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -246,6 +246,8 @@ func (bh *bucketHandle) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cr func (bh *bucketHandle) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (gcs.Writer, error) { obj := bh.getObjectHandleWithPreconditionsSet(&req.CreateObjectRequest) + // To create the takeover writer, the objectHandle.Generation must be set. + obj = obj.Generation(*req.CreateObjectRequest.GenerationPrecondition) callBack := func(bytesUploadedSoFar int64) { logger.Tracef("gcs: Req %#16x: -- UploadBlock(%q): %20v bytes uploaded so far", ctx.Value(gcs.ReqIdField), req.Name, bytesUploadedSoFar) } @@ -259,7 +261,7 @@ func (bh *bucketHandle) CreateAppendableObjectWriter(ctx context.Context, tw, off, err := obj.NewWriterFromAppendableObject(ctx, &opts) // Takeover writer tw created from offset off. if err != nil { - err = fmt.Errorf("Error while creating appendable object writer : %w", err) + err = fmt.Errorf("error while creating appendable object writer : %w", err) return nil, err } From d93e8e87d1fb334687ed20118f49138390522756 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 13 Jun 2025 09:10:20 +0530 Subject: [PATCH 0489/1298] Fix the location of the dynamically created ZBs for E2E tests (#3406) - Implement utilities GetGCEZone, GetGCERegion in e2e tests. - Use utilities GetGCEZone, GetGCERegion to determine where to create the zonal bucket for dynamic-mounting tests for ZB e2e. - Link to the issue in case of a bug fix: [b/424334320](http://b/424334320) * empty commit * add a GetGCEZone utility function in tools/integration_tests * add regex-match for gce zone * add GetGCERegion * use actual zone/region of VM in e2e test dynamically mounted buckets * address self-review * print PR_URL in shadow-reviewers add workflow --- .github/workflows/shadow.yml | 2 +- .../dynamic_mounting/dynamic_mounting.go | 15 ++++--- tools/integration_tests/util/setup/setup.go | 40 +++++++++++++++++++ 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/.github/workflows/shadow.yml b/.github/workflows/shadow.yml index cccbb6d3e5..8bc3f4c43b 100644 --- a/.github/workflows/shadow.yml +++ b/.github/workflows/shadow.yml @@ -16,7 +16,7 @@ jobs: timeout-minutes: 15 steps: - name: Add shadow reviewer - run: gh pr edit --add-reviewer @GoogleCloudPlatform/gcsfuse-shadow-reviewers "$PR_URL" + run: echo "PR_URL=\"${PR_URL}\"" && gh pr edit --add-reviewer @GoogleCloudPlatform/gcsfuse-shadow-reviewers "$PR_URL" env: GH_TOKEN: ${{ secrets.SHADOW_REVIEWER_CLASSIC }} PR_URL: ${{github.event.pull_request.html_url}} diff --git a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go index 4c452fd4d9..fa90a54016 100644 --- a/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go +++ b/tools/integration_tests/util/mounting/dynamic_mounting/dynamic_mounting.go @@ -110,13 +110,16 @@ func CreateTestBucketForDynamicMounting(ctx context.Context, client *storage.Cli if setup.IsZonalBucketRun() { storageClassAndLocation.StorageClass = "RAPID" - if setup.IsPresubmitRun() { - storageClassAndLocation.Location = "us-west4" - storageClassAndLocation.CustomPlacementConfig = &storage.CustomPlacementConfig{DataLocations: []string{"us-west4-a"}} - } else { - storageClassAndLocation.Location = "us-central1" - storageClassAndLocation.CustomPlacementConfig = &storage.CustomPlacementConfig{DataLocations: []string{"us-central1-a"}} + gceZone, err := setup.GetGCEZone(ctx) + if err != nil { + return "", fmt.Errorf("failed to find the GCE zone of the VM: %w", err) } + gceRegion, err := setup.GetGCERegion(gceZone) + if err != nil { + return "", fmt.Errorf("failed to find the GCE region of the VM: %w", err) + } + storageClassAndLocation.Location = gceRegion + storageClassAndLocation.CustomPlacementConfig = &storage.CustomPlacementConfig{DataLocations: []string{gceZone}} storageClassAndLocation.HierarchicalNamespace = &storage.HierarchicalNamespace{ Enabled: true, } diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 68ae0a3d68..842189efa4 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -24,6 +24,7 @@ import ( "os/exec" "path" "path/filepath" + "regexp" "runtime/debug" "strconv" "strings" @@ -33,6 +34,8 @@ import ( "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/util" + "go.opentelemetry.io/contrib/detectors/gcp" + "go.opentelemetry.io/otel/sdk/resource" "google.golang.org/api/iterator" ) @@ -53,6 +56,8 @@ const ( PathEnvVariable = "PATH" GCSFuseLogFilePrefix = "gcsfuse-failed-integration-test-logs-" ProxyServerLogFilePrefix = "proxy-server-failed-integration-test-logs-" + zoneMatcherRegex = "^[a-z]+-[a-z0-9]+-[a-z]$" + regionMatcherRegex = "^[a-z]+-[a-z0-9]+$" ) var ( @@ -642,3 +647,38 @@ func CreateProxyServerLogFile(t *testing.T) string { func AppendProxyEndpointToFlagSet(flagSet *[]string, port int) { *flagSet = append(*flagSet, "--custom-endpoint="+fmt.Sprintf("http://localhost:%d/storage/v1/", port)) } + +// GetGCEZone returns the GCE zone of the current machine from +// the GCP resource detector. +func GetGCEZone(ctx context.Context) (string, error) { + detectedAttrs, err := resource.New(ctx, resource.WithDetectors(gcp.NewDetector())) + if err != nil { + return "", fmt.Errorf("failed to fetch GCP resource detector: %w", err) + } + attrs := detectedAttrs.Set() + if zoneValue, exists := attrs.Value("cloud.availability_zone"); exists { + zone := zoneValue.AsString() + // Confirm that the zone string is in right format e.g. us-central1-a. + if match, err := regexp.MatchString(zoneMatcherRegex, zone); !match || err != nil { + return zone, fmt.Errorf("zone %q returned by GCP resource detector is not a valid zone-string: %w", zone, err) + } + return zone, nil + } + return "", fmt.Errorf("cloud.availability_zone not found in GCP resource detector") +} + +// GetGCERegion return the GCE region for a given GCE zone. +// E.g. from us-central1-a, it returns us-central1. +func GetGCERegion(gceZone string) (string, error) { + indexOfLastHyphen := strings.LastIndex(gceZone, "-") + if indexOfLastHyphen < 0 { + return "", fmt.Errorf("input gceZone %q is not proper. It is expected to be of the form -- e.g. us-central1-a.", gceZone) + } + region := gceZone[:indexOfLastHyphen] + + // Confirm that the region string is in right format e.g. us-central1. + if match, err := regexp.MatchString(regionMatcherRegex, region); !match || err != nil { + return region, fmt.Errorf("zone %q returned by GCE metadata server is not a valid zone-string: %w", region, err) + } + return region, nil +} From 2b20c4e840d1161db2bb1991037e38ce706dea26 Mon Sep 17 00:00:00 2001 From: codechanges Date: Fri, 13 Jun 2025 11:55:40 +0530 Subject: [PATCH 0490/1298] Fix shadow reviewer workflow action (#3411) * dummy changes to test shadow workflow * revert dummy changes --- .github/workflows/shadow.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/shadow.yml b/.github/workflows/shadow.yml index 8bc3f4c43b..1b114eebe8 100644 --- a/.github/workflows/shadow.yml +++ b/.github/workflows/shadow.yml @@ -14,7 +14,12 @@ jobs: runs-on: ubuntu-latest if: github.event.pull_request.draft == false timeout-minutes: 15 + permissions: + pull-requests: write steps: + - name: Checkout Repository + uses: actions/checkout@v4 + - name: Add shadow reviewer run: echo "PR_URL=\"${PR_URL}\"" && gh pr edit --add-reviewer @GoogleCloudPlatform/gcsfuse-shadow-reviewers "$PR_URL" env: From bad087b9a7b94d240356643211c5576641935dec Mon Sep 17 00:00:00 2001 From: codechanges Date: Fri, 13 Jun 2025 13:05:20 +0530 Subject: [PATCH 0491/1298] Fix flaky Download_WhenAsyncFails test (#3409) --- internal/cache/file/downloader/job.go | 5 +++-- internal/cache/file/downloader/job_test.go | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index f1877faedd..62b5e57650 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -367,10 +367,10 @@ func (job *Job) downloadObjectToFile(cacheFile *os.File) (err error) { // // Acquires and releases LOCK(job.mu) func (job *Job) cleanUpDownloadAsyncJob() { - // Close the job.doneCh, clear the cancelFunc & cancelCtx and call the + // Clear the cancelFunc & cancelCtx and call the // remove job callback function. + // Finally, close the job.doneCh. job.cancelFunc() - close(job.doneCh) job.mu.Lock() if job.removeJobCallback != nil { @@ -379,6 +379,7 @@ func (job *Job) cleanUpDownloadAsyncJob() { } job.cancelCtx, job.cancelFunc = nil, nil job.mu.Unlock() + close(job.doneCh) } // createCacheFile is a helper function which creates file in cache using diff --git a/internal/cache/file/downloader/job_test.go b/internal/cache/file/downloader/job_test.go index 7484b93b34..36512657cf 100644 --- a/internal/cache/file/downloader/job_test.go +++ b/internal/cache/file/downloader/job_test.go @@ -582,6 +582,8 @@ func (dt *downloaderTest) Test_Download_WhenAsyncFails() { AssertEq(Failed, jobStatus.Name) AssertGe(jobStatus.Offset, 0) AssertTrue(errors.Is(jobStatus.Err, lru.ErrInvalidUpdateEntrySize)) + // Wait for the async goroutine to complete its cleanup. + <-dt.job.doneCh // Verify callback is executed AssertTrue(callbackExecuted.Load()) } From 4fac620d6a60426af4bb7e83a73501a3cdb91d5a Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Fri, 13 Jun 2025 14:24:21 +0530 Subject: [PATCH 0492/1298] Enable Inactive Timeout Reader by Default with 10s Timeout (#3410) * Enable Inactive Timeout Reader by Default with 10s Timeout * fixing linux test --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/root_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index e285f397ac..a35662a5b3 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -569,7 +569,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.IntP("prometheus-port", "", 0, "Expose Prometheus metrics endpoint on this port and a path of /metrics.") - flagSet.DurationP("read-inactive-stream-timeout", "", 0*time.Nanosecond, "Duration of inactivity after which an open GCS read stream is automatically closed. This helps conserve resources when a file handle remains open without active Read calls. A value of '0s' disables this timeout.") + flagSet.DurationP("read-inactive-stream-timeout", "", 10000000000*time.Nanosecond, "Duration of inactivity after which an open GCS read stream is automatically closed. This helps conserve resources when a file handle remains open without active Read calls. A value of '0s' disables this timeout.") if err := flagSet.MarkHidden("read-inactive-stream-timeout"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 50156bd4a8..627805bcd8 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -712,7 +712,7 @@ Duration of inactivity after which an open GCS read stream is automatically closed. This helps conserve resources when a file handle remains open without active Read calls. A value of '0s' disables this timeout. - default: "0s" + default: "10s" hide-flag: true - config-path: "write.block-size-mb" diff --git a/cmd/root_test.go b/cmd/root_test.go index 98953a74ad..ab3e2f526b 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1425,7 +1425,7 @@ func TestArgsParsing_ReadInactiveTimeoutConfig(t *testing.T) { { name: "default", cfgFile: "empty.yaml", - expectedTimeout: 0, + expectedTimeout: 10 * time.Second, }, { name: "override_default", From 9a7bdf773ade6c7ee21ec660e2e11705ca48cc69 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Fri, 13 Jun 2025 14:44:01 +0530 Subject: [PATCH 0493/1298] zero byte read to fetch size for unfinalized obj (#3387) * pass enableRapidAppends in BucketHandle creator function * dont store enableRapidAppends in storageClient * update size in list and stat req * fix in listobj * Update internal/storage/bucket_handle.go Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> * Update internal/storage/bucket_handle.go Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> * Update internal/storage/bucket_handle.go Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> * nits * Update internal/storage/bucket_handle.go Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> * UT for bucket handle creation with rapid appends enabled --------- Co-authored-by: Nitin Garg Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> --- cmd/mount.go | 1 + internal/cache/file/cache_handle_test.go | 2 +- internal/cache/file/cache_handler_test.go | 2 +- .../cache/file/downloader/downloader_test.go | 2 +- .../downloader/jm_parallel_downloads_test.go | 4 +- internal/gcsx/bucket_manager.go | 9 ++-- internal/gcsx/bucket_manager_test.go | 2 +- internal/storage/bucket_handle.go | 44 +++++++++++++++++-- internal/storage/bucket_handle_test.go | 16 ++++++- internal/storage/storage_handle.go | 13 +++--- internal/storage/storage_handle_test.go | 10 ++--- 11 files changed, 78 insertions(+), 27 deletions(-) diff --git a/cmd/mount.go b/cmd/mount.go index 588a2e46fb..77a13f2b56 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -98,6 +98,7 @@ be interacting with the file system.`) AppendThreshold: 1 << 21, // 2 MiB, a total guess. ChunkTransferTimeoutSecs: newConfig.GcsRetries.ChunkTransferTimeoutSecs, TmpObjectPrefix: ".gcsfuse_tmp/", + ExperimentalEnableRapidAppends: newConfig.Write.ExperimentalEnableRapidAppends, } bm := gcsx.NewBucketManager(bucketCfg, storageHandle) diff --git a/internal/cache/file/cache_handle_test.go b/internal/cache/file/cache_handle_test.go index f53b694af5..24cde3f75a 100644 --- a/internal/cache/file/cache_handle_test.go +++ b/internal/cache/file/cache_handle_test.go @@ -117,7 +117,7 @@ func (cht *cacheHandleTest) SetupTest() { storageHandle := cht.fakeStorage.CreateStorageHandle() mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(&controlpb.StorageLayout{}, nil) - cht.bucket, err = storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + cht.bucket, err = storageHandle.BucketHandle(ctx, storage.TestBucketName, "", false) assert.Nil(cht.T(), err) // Create test object in the bucket. diff --git a/internal/cache/file/cache_handler_test.go b/internal/cache/file/cache_handler_test.go index 4149b7998e..42107f4190 100644 --- a/internal/cache/file/cache_handler_test.go +++ b/internal/cache/file/cache_handler_test.go @@ -71,7 +71,7 @@ func initializeCacheHandlerTestArgs(t *testing.T, fileCacheConfig *cfg.FileCache mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(&controlpb.StorageLayout{}, nil) ctx := context.Background() - bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "", false) require.NoError(t, err) // Create test object in the bucket. diff --git a/internal/cache/file/downloader/downloader_test.go b/internal/cache/file/downloader/downloader_test.go index 38c9bbe2b3..8f519170f7 100644 --- a/internal/cache/file/downloader/downloader_test.go +++ b/internal/cache/file/downloader/downloader_test.go @@ -66,7 +66,7 @@ func (dt *downloaderTest) setupHelper() { mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(&controlpb.StorageLayout{}, nil) ctx := context.Background() - dt.bucket, err = storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + dt.bucket, err = storageHandle.BucketHandle(ctx, storage.TestBucketName, "", false) ExpectEq(nil, err) dt.initJobTest(DefaultObjectName, []byte("taco"), DefaultSequentialReadSizeMb, CacheMaxSize, func() {}) diff --git a/internal/cache/file/downloader/jm_parallel_downloads_test.go b/internal/cache/file/downloader/jm_parallel_downloads_test.go index 8c63fd1720..55cdd28de0 100644 --- a/internal/cache/file/downloader/jm_parallel_downloads_test.go +++ b/internal/cache/file/downloader/jm_parallel_downloads_test.go @@ -145,7 +145,7 @@ func TestParallelDownloads(t *testing.T) { cache, cacheDir := configureCache(t, 2*tc.objectSize) storageHandle := configureFakeStorage(t) ctx := context.Background() - bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "", false) assert.Nil(t, err) minObj, content := createObjectInStoreAndInitCache(t, cache, bucket, "path/in/gcs/foo.txt", tc.objectSize) fileCacheConfig := &cfg.FileCacheConfig{ @@ -187,7 +187,7 @@ func TestMultipleConcurrentDownloads(t *testing.T) { storageHandle := configureFakeStorage(t) cache, cacheDir := configureCache(t, 30*util.MiB) ctx := context.Background() - bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") + bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "", false) assert.Nil(t, err) minObj1, content1 := createObjectInStoreAndInitCache(t, cache, bucket, "path/in/gcs/foo.txt", 10*util.MiB) minObj2, content2 := createObjectInStoreAndInitCache(t, cache, bucket, "path/in/gcs/bar.txt", 5*util.MiB) diff --git a/internal/gcsx/bucket_manager.go b/internal/gcsx/bucket_manager.go index e616f1e6d3..e271bc2d66 100644 --- a/internal/gcsx/bucket_manager.go +++ b/internal/gcsx/bucket_manager.go @@ -62,9 +62,10 @@ type BucketConfig struct { // Note that if the process fails or is interrupted the temporary object will // not be cleaned up, so the user must ensure that TmpObjectPrefix is // periodically garbage collected. - AppendThreshold int64 - ChunkTransferTimeoutSecs int64 - TmpObjectPrefix string + AppendThreshold int64 + ChunkTransferTimeoutSecs int64 + TmpObjectPrefix string + ExperimentalEnableRapidAppends bool } // BucketManager manages the lifecycle of buckets. @@ -167,7 +168,7 @@ func (bm *bucketManager) SetUpBucket( if name == canned.FakeBucketName { b = canned.MakeFakeBucket(ctx) } else { - b, err = bm.storageHandle.BucketHandle(ctx, name, bm.config.BillingProject) + b, err = bm.storageHandle.BucketHandle(ctx, name, bm.config.BillingProject, bm.config.ExperimentalEnableRapidAppends) if err != nil { err = fmt.Errorf("BucketHandle: %w", err) return diff --git a/internal/gcsx/bucket_manager_test.go b/internal/gcsx/bucket_manager_test.go index 7675223f90..934bd518f4 100644 --- a/internal/gcsx/bucket_manager_test.go +++ b/internal/gcsx/bucket_manager_test.go @@ -61,7 +61,7 @@ func (t *BucketManagerTest) SetUp(_ *TestInfo) { LocationType: "zone", }, nil) ctx := context.Background() - t.bucket, err = t.storageHandle.BucketHandle(ctx, TestBucketName, "") + t.bucket, err = t.storageHandle.BucketHandle(ctx, TestBucketName, "", false) AssertNe(nil, t.bucket) AssertEq(nil, err) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 203899352d..a4ecc9cce9 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -39,10 +39,11 @@ const FullBucketPathHNS = "projects/_/buckets/%s" type bucketHandle struct { gcs.Bucket - bucket *storage.BucketHandle - bucketName string - bucketType *gcs.BucketType - controlClient StorageControlClient + bucket *storage.BucketHandle + bucketName string + bucketType *gcs.BucketType + controlClient StorageControlClient + enableRapidAppends bool } func (bh *bucketHandle) Name() string { @@ -141,6 +142,12 @@ func (bh *bucketHandle) StatObject(ctx context.Context, err = fmt.Errorf("error in fetching object attributes: %w", err) return } + if attrs.Finalized.IsZero() { + if err = bh.fetchLatestSizeOfUnfinalizedObject(ctx, attrs); err != nil { + err = fmt.Errorf("failed to fetch the latest size of unfinalized object %q: %w", attrs.Name, err) + return + } + } // Converting attrs to type *Object o := storageutil.ObjectAttrsToBucketObject(attrs) @@ -152,6 +159,29 @@ func (bh *bucketHandle) StatObject(ctx context.Context, return } +// Note: This is not production ready code and will be removed once StatObject +// requests return correct attr values for appendable objects. +func (bh *bucketHandle) fetchLatestSizeOfUnfinalizedObject(ctx context.Context, attrs *storage.ObjectAttrs) error { + if bh.BucketType().Zonal && bh.enableRapidAppends { + // Get object handle + obj := bh.bucket.Object(attrs.Name) + // Create a new reader + reader, err := obj.NewRangeReader(ctx, 0, 0) + if err != nil { + return fmt.Errorf("failed to create zero-byte reader for object %q: %v", attrs.Name, err) + } + err = reader.Close() + if err != nil { + logger.Warnf("failed to close zero-byte reader for object %q: %v", attrs.Name, err) + } + + // Set the size + attrs.Size = reader.Attrs.Size + return nil + } + return nil +} + func (bh *bucketHandle) getObjectHandleWithPreconditionsSet(req *gcs.CreateObjectRequest) *storage.ObjectHandle { obj := bh.bucket.Object(req.Name) @@ -401,6 +431,12 @@ func (bh *bucketHandle) ListObjects(ctx context.Context, req *gcs.ListObjectsReq err = fmt.Errorf("error in iterating through objects: %w", err) return } + if attrs.Finalized.IsZero() { + if err = bh.fetchLatestSizeOfUnfinalizedObject(ctx, attrs); err != nil { + err = fmt.Errorf("failed to fetch the latest size of unfinalized object %q: %w", attrs.Name, err) + return + } + } // Prefix attribute will be set for the objects returned as part of Prefix[] array in list response. // https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/vendor/cloud.google.com/go/storage/storage.go#L1304 diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 61aae3a388..802219a5ef 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -66,7 +66,7 @@ func createBucketHandle(testSuite *BucketHandleTest, resp *controlpb.StorageLayo var err error testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(resp, err1) - testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "") + testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "", false) testSuite.bucketHandle.controlClient = testSuite.mockClient assert.NotNil(testSuite.T(), testSuite.bucketHandle) @@ -1496,12 +1496,24 @@ func (testSuite *BucketHandleTest) TestBucketHandleWithError() { var err error // Test when the client returns an error. testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything).Return(x, errors.New("mocked error")) - testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "") + testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "", false) assert.Nil(testSuite.T(), testSuite.bucketHandle) assert.Contains(testSuite.T(), err.Error(), "mocked error") } +func (testSuite *BucketHandleTest) TestBucketHandleWithRapidAppendsEnabled() { + var err error + testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything).Return(&controlpb.StorageLayout{}, nil) + testSuite.mockClient.On("getClient", mock.Anything, mock.Anything).Return(&storage.Client{}, nil) + + testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "", true) + + assert.NotNil(testSuite.T(), testSuite.bucketHandle) + assert.True(testSuite.T(), testSuite.bucketHandle.enableRapidAppends) + assert.Nil(testSuite.T(), err) +} + func (testSuite *BucketHandleTest) TestBucketTypeWithHierarchicalNamespaceIsNil() { createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index 20b80e8645..0f08456b67 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -55,7 +55,7 @@ type StorageHandle interface { // to that project rather than to the bucket's owning project. // // A user-project is required for all operations on Requester Pays buckets. - BucketHandle(ctx context.Context, bucketName string, billingProject string) (bh *bucketHandle, err error) + BucketHandle(ctx context.Context, bucketName string, billingProject string, enableRapidAppends bool) (bh *bucketHandle, err error) } type storageClient struct { @@ -318,7 +318,7 @@ func (sh *storageClient) getClient(ctx context.Context, isbucketZonal bool) (*st return nil, fmt.Errorf("invalid client-protocol requested: %s", sh.clientConfig.ClientProtocol) } -func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, billingProject string) (bh *bucketHandle, err error) { +func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, billingProject string, enableRapidAppends bool) (bh *bucketHandle, err error) { var client *storage.Client bucketType, err := sh.lookupBucketType(bucketName) if err != nil { @@ -347,10 +347,11 @@ func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, bi } bh = &bucketHandle{ - bucket: storageBucketHandle, - bucketName: bucketName, - controlClient: sh.storageControlClient, - bucketType: bucketType, + bucket: storageBucketHandle, + bucketName: bucketName, + controlClient: sh.storageControlClient, + bucketType: bucketType, + enableRapidAppends: enableRapidAppends, } return diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index 91d089e470..b7bca500af 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -77,7 +77,7 @@ func (testSuite *StorageHandleTest) mockStorageLayout(bucketType gcs.BucketType) func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketExistsWithEmptyBillingProject() { storageHandle := testSuite.fakeStorage.CreateStorageHandle() testSuite.mockStorageLayout(gcs.BucketType{}) - bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, "") + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, "", false) assert.NotNil(testSuite.T(), bucketHandle) assert.Nil(testSuite.T(), err) @@ -90,7 +90,7 @@ func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketDoesNotExistWithEm storageHandle := testSuite.fakeStorage.CreateStorageHandle() testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(nil, fmt.Errorf("bucket does not exist")) - bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, "") + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, "", false) assert.NotNil(testSuite.T(), err) assert.Nil(testSuite.T(), bucketHandle) @@ -100,7 +100,7 @@ func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketExistsWithNonEmpty storageHandle := testSuite.fakeStorage.CreateStorageHandle() testSuite.mockStorageLayout(gcs.BucketType{Hierarchical: true}) - bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, projectID) + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, projectID, false) assert.NotNil(testSuite.T(), bucketHandle) assert.Nil(testSuite.T(), err) @@ -116,7 +116,7 @@ func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketDoesNotExistWithNo storageHandle := testSuite.fakeStorage.CreateStorageHandle() testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(nil, fmt.Errorf("bucket does not exist")) - bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, projectID) + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, projectID, false) assert.Nil(testSuite.T(), bucketHandle) assert.NotNil(testSuite.T(), err) @@ -253,7 +253,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithInvalidClientProtoco testSuite.mockStorageLayout(gcs.BucketType{}) sh := fakeStorage.CreateStorageHandle() assert.NotNil(testSuite.T(), sh) - bh, err := sh.BucketHandle(testSuite.ctx, TestBucketName, projectID) + bh, err := sh.BucketHandle(testSuite.ctx, TestBucketName, projectID, false) assert.Nil(testSuite.T(), bh) assert.NotNil(testSuite.T(), err) From 829cb6c94ce7aea5d257189c3840c9cbcde36351 Mon Sep 17 00:00:00 2001 From: codechanges Date: Mon, 16 Jun 2025 15:06:04 +0530 Subject: [PATCH 0494/1298] Print GCSFuse version to stdout stream only in case of error (#3413) * Print version only in case of error * remove commented code --- cmd/root.go | 4 ++-- main.go | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 7e6b8ef686..43ee03830e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -155,10 +155,10 @@ func convertToPosixArgs(args []string, c *cobra.Command) []string { var ExecuteMountCmd = func() { rootCmd, err := newRootCmd(Mount) if err != nil { - log.Fatalf("Error occurred while creating the root command: %v", err) + log.Fatalf("Error occurred while creating the root command on gcsfuse/%s: %v", common.GetVersion(), err) } rootCmd.SetArgs(convertToPosixArgs(os.Args, rootCmd)) if err := rootCmd.Execute(); err != nil { - log.Fatalf("Error occurred during command execution: %v", err) + log.Fatalf("Error occurred during command execution on gcsfuse/%s: %v", common.GetVersion(), err) } } diff --git a/main.go b/main.go index 0ed319a079..77fdf68603 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,6 @@ import ( "log" "github.com/googlecloudplatform/gcsfuse/v3/cmd" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/perf" ) @@ -42,7 +41,6 @@ func logPanic() { // //go:generate go run -C tools/config-gen . --paramsFile=../../cfg/params.yaml --outDir=../../cfg --templateDir=templates func main() { - logger.Infof("Running gcsfuse/%s", common.GetVersion()) // Common configuration for all commands defer logPanic() // Make logging output better. From 051d180d67df1880a9c632910ffb28728b3d9506 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 17 Jun 2025 10:06:36 +0530 Subject: [PATCH 0495/1298] Revert "Revert "Enable Read Stall Retry by default (#3126)" (#3252)" (#3417) This reverts commit c23a94aa0526e35b036fb9afbfdb407fdf1bc021. --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/config_validation_test.go | 4 ++-- cmd/root_test.go | 2 +- cmd/testdata/valid_config.yaml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index a35662a5b3..37effda085 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -385,7 +385,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-nonexistent-type-cache", "", false, "Once set, if an inode is not found in GCS, a type cache entry with type NonexistentType will be created. This also means new file/dir created might not be seen. For example, if this flag is set, and metadata-cache-ttl-secs is set, then if we create the same file/node in the meantime using the same mount, since we are not refreshing the cache, it will still return nil.") - flagSet.BoolP("enable-read-stall-retry", "", false, "To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past.") + flagSet.BoolP("enable-read-stall-retry", "", true, "To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past.") if err := flagSet.MarkHidden("enable-read-stall-retry"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 627805bcd8..6afe0e33df 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -399,7 +399,7 @@ usage: >- To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past. - default: false + default: true hide-flag: true - config-path: "gcs-retries.read-stall.initial-req-timeout" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 0c856c430b..78e86aa539 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -711,7 +711,7 @@ func TestValidateConfigFile_GCSRetries(t *testing.T) { MaxRetrySleep: 30 * time.Second, Multiplier: 2, ReadStall: cfg.ReadStallGcsRetriesConfig{ - Enable: false, + Enable: true, MinReqTimeout: 1500 * time.Millisecond, MaxReqTimeout: 1200 * time.Second, InitialReqTimeout: 20 * time.Second, @@ -731,7 +731,7 @@ func TestValidateConfigFile_GCSRetries(t *testing.T) { MaxRetrySleep: 30 * time.Second, Multiplier: 2, ReadStall: cfg.ReadStallGcsRetriesConfig{ - Enable: true, + Enable: false, MinReqTimeout: 10 * time.Second, MaxReqTimeout: 200 * time.Second, InitialReqTimeout: 20 * time.Second, diff --git a/cmd/root_test.go b/cmd/root_test.go index ab3e2f526b..21e30f263d 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1294,7 +1294,7 @@ func TestArgParsing_GCSRetries(t *testing.T) { MaxRetrySleep: 30 * time.Second, Multiplier: 2, ReadStall: cfg.ReadStallGcsRetriesConfig{ - Enable: false, + Enable: true, InitialReqTimeout: 20 * time.Second, MinReqTimeout: 1500 * time.Millisecond, MaxReqTimeout: 1200 * time.Second, diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 0d00ffa574..6dd371da62 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -37,7 +37,7 @@ gcs-connection: gcs-retries: chunk-transfer-timeout-secs: 20 read-stall: - enable: true + enable: false min-req-timeout: 10s max-req-timeout: 200s initial-req-timeout: 20s From 76c6f18def418b74c9729dac25bcfbd0cf8baf66 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 17 Jun 2025 12:13:13 +0530 Subject: [PATCH 0496/1298] Make the --enable-read-stall-retry flag public (#3419) --- cfg/config.go | 4 ---- cfg/params.yaml | 1 - 2 files changed, 5 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 37effda085..246b8ce398 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -387,10 +387,6 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-read-stall-retry", "", true, "To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past.") - if err := flagSet.MarkHidden("enable-read-stall-retry"); err != nil { - return err - } - flagSet.BoolP("enable-streaming-writes", "", true, "Enables streaming uploads during write file operation.") flagSet.BoolP("experimental-enable-json-read", "", false, "By default, GCSFuse uses the GCS XML API to get and read objects. When this flag is specified, GCSFuse uses the GCS JSON API instead.\"") diff --git a/cfg/params.yaml b/cfg/params.yaml index 6afe0e33df..248af0369c 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -400,7 +400,6 @@ To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past. default: true - hide-flag: true - config-path: "gcs-retries.read-stall.initial-req-timeout" flag-name: "read-stall-initial-req-timeout" From 9c5c2d5120bc90f4f26f90f8a8629e6be77a85a0 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:49:30 +0530 Subject: [PATCH 0497/1298] Adding e2e tests for release version (#3421) * add release version tests * add package name * add package name * remove mounting unmounting part * review comment * adding package name in new script --- tools/cd_scripts/e2e_test.sh | 1 + .../improved_run_e2e_tests.sh | 3 +- .../release_version/release_version_test.go | 50 +++++++++++++++++++ .../release_version/setup_test.go | 38 ++++++++++++++ tools/integration_tests/run_e2e_tests.sh | 2 + tools/util/build_gcsfuse.go | 2 +- 6 files changed, 94 insertions(+), 2 deletions(-) create mode 100644 tools/integration_tests/release_version/release_version_test.go create mode 100644 tools/integration_tests/release_version/setup_test.go diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index e82822bfae..f2d06bc4de 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -193,6 +193,7 @@ TEST_DIR_PARALLEL=( "stale_handle" "negative_stat_cache" "streaming_writes" + "release_version" ) # These tests never become parallel as they are changing bucket permissions. diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh index 367a05938e..7c0b18aa5c 100755 --- a/tools/integration_tests/improved_run_e2e_tests.sh +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -134,6 +134,7 @@ TEST_PACKAGES_COMMON=( # "grpc_validation" "negative_stat_cache" "stale_handle" + "release_version" ) # Test packages for regional buckets. @@ -433,7 +434,7 @@ build_gcsfuse_once() { log_info "Using GCSFuse source directory: ${gcsfuse_src_dir}" log_info "Building GCSFuse using 'go run ./tools/build_gcsfuse/main.go'..." - (cd "${gcsfuse_src_dir}" && go run ./tools/build_gcsfuse/main.go . "${build_output_dir}" "e2e-$(date +%s)") + (cd "${gcsfuse_src_dir}" && go run ./tools/build_gcsfuse/main.go . "${build_output_dir}" "0.0.0") if [ $? -ne 0 ]; then log_error "Building GCSFuse binaries using 'go run ./tools/build_gcsfuse/main.go' failed." rm -rf "${build_output_dir}" # Clean up created temp dir diff --git a/tools/integration_tests/release_version/release_version_test.go b/tools/integration_tests/release_version/release_version_test.go new file mode 100644 index 0000000000..c20d1a0420 --- /dev/null +++ b/tools/integration_tests/release_version/release_version_test.go @@ -0,0 +1,50 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package release_version + +import ( + "os/exec" + "regexp" + "strings" + "testing" +) + +func TestReleaseVersion(t *testing.T) { + cmd := exec.Command("gcsfuse", "--version") + + outputBytes, err := cmd.CombinedOutput() + + if err != nil { + t.Fatalf("Failed to execute 'gcsfuse --version': %v\nOutput: %s", err, string(outputBytes)) + } + output := strings.TrimSpace(string(outputBytes)) + t.Logf("gcsfuse --version output:\n%s", output) // Log the output for debugging + expectedPattern := `^gcsfuse version (\d+\.\d+\.\d+) \(Go version (go.+)\)$` + r := regexp.MustCompile(expectedPattern) + // Match the output against the pattern + matches := r.FindStringSubmatch(output) + if len(matches) != 3 { // Expect 3 elements: full match, version, go version + t.Errorf("Output did not match expected pattern.\nExpected pattern: %q\nActual output: %q\nMatches: %v", expectedPattern, output, matches) + } else { + version := matches[1] + goVersion := matches[2] + if version == "" { + t.Errorf("Extracted gcsfuse version is empty") + } + if goVersion == "" { + t.Errorf("Extracted Go version is empty") + } + } +} diff --git a/tools/integration_tests/release_version/setup_test.go b/tools/integration_tests/release_version/setup_test.go new file mode 100644 index 0000000000..ffdbf3e4e9 --- /dev/null +++ b/tools/integration_tests/release_version/setup_test.go @@ -0,0 +1,38 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package release_version + +import ( + "log" + "os" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" +) + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + if setup.MountedDirectory() != "" { + log.Print("These tests will not run for mountedDirectory flag.") + os.Exit(1) + } + + setup.SetUpTestDirForTestBucketFlag() + + successCode := m.Run() + + os.Exit(successCode) +} diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index cd495d01b4..f6234e752d 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -118,6 +118,7 @@ TEST_DIR_PARALLEL=( "streaming_writes" "inactive_stream_timeout" "cloud_profiler" + "release_version" ) # These tests never become parallel as it is changing bucket permissions. @@ -151,6 +152,7 @@ TEST_DIR_PARALLEL_FOR_ZB=( "streaming_writes" "write_large_files" "unfinalized_object" + "release_version" ) # Subset of TEST_DIR_NON_PARALLEL, diff --git a/tools/util/build_gcsfuse.go b/tools/util/build_gcsfuse.go index 703cc55863..bf02cfda3c 100644 --- a/tools/util/build_gcsfuse.go +++ b/tools/util/build_gcsfuse.go @@ -73,7 +73,7 @@ func BuildGcsfuse(dstDir string) (err error) { toolPath, srcDir, dstDir, - "fake_version", + "0.0.0", ) var output []byte From 5497d1021d416afb90edf79c7dc8ae1efe75b802 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 19 Jun 2025 11:14:14 +0530 Subject: [PATCH 0498/1298] add resource usage script and add it to e2e test runs to print cpu/mem/disk usage after runs. (#3414) * add resource usage script to e2e test runs * add review comments * fix review comments --- .../improved_run_e2e_tests.sh | 37 +++- tools/integration_tests/resource_usage.sh | 174 ++++++++++++++++++ 2 files changed, 210 insertions(+), 1 deletion(-) create mode 100755 tools/integration_tests/resource_usage.sh diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh index 7c0b18aa5c..ee85c53be8 100755 --- a/tools/integration_tests/improved_run_e2e_tests.sh +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -72,6 +72,7 @@ BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR="" LOG_LOCK_FILE=$(mktemp "/tmp/${TMP_PREFIX}_logging_lock.XXXXXX") || { log_error "Unable to create lock file"; exit 1; } BUCKET_NAMES=$(mktemp "/tmp/${TMP_PREFIX}_bucket_names.XXXXXX") || { log_error "Unable to create bucket names file"; exit 1; } PACKAGE_RUNTIME_STATS=$(mktemp "/tmp/${TMP_PREFIX}_package_stats_runtime.XXXXXX") || { log_error "Unable to create package stats runtime file"; exit 1; } +RESOURCE_USAGE_FILE=$(mktemp "/tmp/${TMP_PREFIX}_system_resource_usage.XXXXXX") || { log_error "Unable to create system resource usage file"; exit 1; } # Argument Parsing and Assignments if [ "$#" -lt 3 ]; then @@ -270,8 +271,24 @@ delete_bucket() { return 0 } +# Get command of the PID and check if it contains the string. Kill if it does. +safe_kill() { + local pid=$1 + local str=$2 + local cmd + + if [[ -n "$pid" && -n "$str" ]] && cmd=$(ps -p "$pid" -o cmd=) && [[ "$cmd" == *"$str"* ]]; then + kill "$pid" + else + return 1 + fi +} + # Cleanup ensures each of the buckets created is destroyed and the temp files are cleaned up. clean_up() { + if ! safe_kill "$RESOURCE_USAGE_PID" "resource_usage.sh"; then + log_error "Failed to stop resource usage collection process (or it's already stopped): $RESOURCE_USAGE_PID" + fi if [ -n "${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" ] && [ -d "${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" ]; then log_info "Cleaning up GCSFuse build directory created by script: ${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" rm -rf "${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" @@ -531,9 +548,16 @@ main() { fi log_info "Script built GCSFuse at: ${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" fi - + # Reset SECONDS to 0 SECONDS=0 + + # Start collecting system resource usage in background. + log_info "Starting resource usage collection process." + ./tools/integration_tests/resource_usage.sh "COLLECT" "$RESOURCE_USAGE_FILE" & + RESOURCE_USAGE_PID=$! + log_info "Resource usage collection process started at PID: $RESOURCE_USAGE_PID" + local pids=() local overall_exit_code=0 if [[ "${RUN_TESTS_WITH_ZONAL_BUCKET}" == "true" ]]; then @@ -557,7 +581,18 @@ main() { elapsed_min=$(((SECONDS + 60) / 60)) log_info "------ E2E test packages complete run took ${elapsed_min} minutes ------" log_info "" + + # Print package runtime stats table. ./tools/integration_tests/create_package_runtime_table.sh "$PACKAGE_RUNTIME_STATS" + + # Kill resource usage background PID and print resource usage. + log_info "Stopping resource usage collection process: $RESOURCE_USAGE_PID" + if safe_kill "$RESOURCE_USAGE_PID" "resource_usage.sh"; then + log_info "Resource usage collection process stopped." + ./tools/integration_tests/resource_usage.sh "PRINT" "$RESOURCE_USAGE_FILE" + else + log_error "Failed to stop resource usage collection process (or it's already stopped)" + fi exit $overall_exit_code } diff --git a/tools/integration_tests/resource_usage.sh b/tools/integration_tests/resource_usage.sh new file mode 100755 index 0000000000..d826bf385e --- /dev/null +++ b/tools/integration_tests/resource_usage.sh @@ -0,0 +1,174 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Exit on error, treat unset variables as errors, and propagate pipeline errors. +set -euo pipefail + +# This script is used to collect the resource(mem,cpu,disk) usage of the machine in the file at given interval. +# This script can also be in use to print the resource usage visualisation in graph format. +usage() { + echo "A script to collect and visualize system resource usage." + echo "" + echo "========================================================================" + echo "" + echo "COMMAND: COLLECT" + echo "" + echo " Gathers resource usage (CPU, memory, disk) at predefined intervals." + echo "" + echo " USAGE:" + echo " $0 COLLECT " + echo "" + echo " ARGUMENTS:" + echo "" + echo " The path to the output file where resource data will be" + echo " saved. The script will create or overwrite this file." + echo "" + echo "========================================================================" + echo "" + echo "COMMAND: PRINT" + echo "" + echo " Visualizes resource data from a file in a graph format." + echo "" + echo " USAGE:" + echo " $0 PRINT " + echo "" + echo " ARGUMENTS:" + echo " The path to the input data file (generated by COLLECT)" + echo " that contains the statistics to be visualized." + echo "" + echo "========================================================================" + exit 1 +} + +readonly USAGE_COLLECTION_INTERVAL_SECONDS=10 # Collect usage every 10s. +readonly USAGE_COLLECTION_DURATION_SECONDS=$((3 * 60 * 60)) # Auto collect usage for upto 3 hrs only (VM timeout is 3 hrs in kokoro). +readonly COLLECT="COLLECT" +readonly PRINT="PRINT" + +# Validate number of arguments. +if [[ $# -ne 2 ]]; then + echo "Error: Invalid number of arguments." + usage +fi + +readonly COMMAND="$1" +readonly FILE_PATH="$2" + +# Calculates CPU usage as integer percentage using top command. +get_cpu_usage() { + # 'top -b -n 1 | awk '/^%Cpu/'' returns + # %Cpu(s): 1.1 us, 0.0 sy, 0.0 ni, 98.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st + # 8th entry is idle cpu % + top -b -n 1 | awk '/^%Cpu/ { usage = 100 - $8; print int(usage) }' || echo 0 +} + +# Calculates memory usage as integer percentage using free. +get_mem_usage() { + # 'free' returns + # total used free shared buff/cache available + # Mem: 371458720 6879376 285303364 9772 81770240 364579344 + # Swap: 0 0 0 + free | awk '/Mem:/ {p=($2-$7)*100/$2; print int(p) }' || echo 0 +} + +# Calculates disk usage as integer percentage using df. +get_disk_usage() { + # 'df -P /' returns + # Filesystem 1024-blocks Used Available Capacity Mounted on + # /dev/nvme0n1p1 515794480 82928236 411780644 17% / + df -P / | tail -n1 | awk '{sub(/%/, ""); printf "%.0f\n", $5}' || echo 0 +} + +# Helper function to collect the usage and write to file. +# Output: Appends new line to file with %cpu %mem %disk at given interval. +collect_all_metric() { + echo "CPU MEM DISK" >"$FILE_PATH" + while ((${SECONDS:-0} < USAGE_COLLECTION_DURATION_SECONDS)); do + { + cpu=$(get_cpu_usage) + mem=$(get_mem_usage) + disk=$(get_disk_usage) + echo "$cpu $mem $disk" + } >>"$FILE_PATH" + sleep "$USAGE_COLLECTION_INTERVAL_SECONDS" + done + echo "Exiting due to monitoring time limit." +} + +# Helper function to print usage of all metrics. +print_each_metric() { + # Read headers from file. + read -r -a headers <"$FILE_PATH" + declare -A data_columns + local line_num=0 + while read -r -a values; do + # Skip the header line we already read + ((line_num++ == 0)) && continue + + for i in "${!headers[@]}"; do + local header="${headers[$i]}" + local value="${values[$i]}" + # Append the value to the correct string in the associative array + data_columns["$header"]+="$value " + done + done <"$FILE_PATH" + + # Iterate through each resource and print its graph + for resource in "${headers[@]}"; do + local -a usage_values=(${data_columns[$resource]}) + local max_value=0 + for val in "${usage_values[@]}"; do + ((val > max_value)) && max_value=$val + done + + # Add some buffer to the graph ceiling, capping at 100 + local graph_ceiling=$((max_value + 5)) + ((graph_ceiling > 100)) && graph_ceiling=100 + + printf "\n%s usage in percentage every %s seconds:\n\n" "$resource" "$USAGE_COLLECTION_INTERVAL_SECONDS" + + # Print the graph from top to bottom + for ((y = graph_ceiling; y >= 0; y--)); do + printf "%s@ %3d%%|" "$resource" "$y" + for val in "${usage_values[@]}"; do + if ((val >= y)); then + printf "*" + else + printf " " + fi + done + printf "\n" + done + + # Print separator line + local total_width=$((${#usage_values[@]} + 15)) + for ((i = 0; i < total_width; i++)); do + printf "=" + done + printf "\n\n" + done +} + +# Exit the script if SIGTERM or SIGINT is received. +trap 'exit 0' SIGINT SIGTERM + +if [[ "$COMMAND" == "$COLLECT" ]]; then + collect_all_metric +elif [[ "$COMMAND" == "$PRINT" ]]; then + print_each_metric +else + echo "Error: Invalid command." + usage +fi From 0cb3f5745a38b41157bd51cc3f7ce72f214ec68e Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 19 Jun 2025 12:54:18 +0530 Subject: [PATCH 0499/1298] [Migrate auth] Add new auth functions (#3420) * add new auth functions * rename file * small fix in test * formating * add comment * review comments --- internal/auth/credentials.go | 62 ++++++++++++++++++++ internal/auth/credentials_test.go | 96 +++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 internal/auth/credentials.go create mode 100644 internal/auth/credentials_test.go diff --git a/internal/auth/credentials.go b/internal/auth/credentials.go new file mode 100644 index 0000000000..504bde6a33 --- /dev/null +++ b/internal/auth/credentials.go @@ -0,0 +1,62 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +import ( + "fmt" + + "cloud.google.com/go/auth" + "cloud.google.com/go/auth/credentials" + "cloud.google.com/go/storage" +) + +const scope = storage.ScopeFullControl + +// getCredentials is a private helper that takes a custom DetectDefault function. +func getCredentials(keyFile string, detectCredential func(*credentials.DetectOptions) (*auth.Credentials, error)) (*auth.Credentials, error) { + opts := &credentials.DetectOptions{ + CredentialsFile: keyFile, + Scopes: []string{scope}, + } + + creds, err := detectCredential(opts) + if err != nil { + return nil, fmt.Errorf("failed to detect credentials: %w", err) + } + + return creds, nil +} + +// GetCredentials detects default Google Cloud credentials. +// +// It prioritizes a service account key file if `keyFile` is provided. If `keyFile` is +// empty, it attempts to detect Application Default Credentials (ADC) and checks +// the metadata server for credentials. +// +// The function requests storage.ScopeFullControl to ensure the most comprehensive +// permissions for GCS. This allows subsequent operations using these credentials to have full read, +// write, and administrative control over GCS resources. +// +// Args: +// +// keyFile: Path to a service account key file. Pass an empty string to use ADC. +// +// Returns: +// +// *auth.Credentials: Discovered authentication credentials. +// error: An error if credential detection fails. +func GetCredentials(keyFile string) (*auth.Credentials, error) { + return getCredentials(keyFile, credentials.DetectDefault) +} diff --git a/internal/auth/credentials_test.go b/internal/auth/credentials_test.go new file mode 100644 index 0000000000..d60ff514a7 --- /dev/null +++ b/internal/auth/credentials_test.go @@ -0,0 +1,96 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +import ( + "fmt" + "testing" + + "cloud.google.com/go/auth" + "cloud.google.com/go/auth/credentials" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +//////////////////////////////////////////////////////////////////////// +// Mock +//////////////////////////////////////////////////////////////////////// + +type MockDetectCredentials struct { + mock.Mock +} + +func (m *MockDetectCredentials) DetectDefault(opts *credentials.DetectOptions) (*auth.Credentials, error) { + args := m.Called(opts) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*auth.Credentials), args.Error(1) +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func Test_getCredentials_Success(t *testing.T) { + tests := []struct { + name string + keyFile string + }{ + { + name: "valid key file", + keyFile: "/path/to/key.json", + }, + { + name: "empty key file", + keyFile: "", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mockDetector := new(MockDetectCredentials) + opts := &credentials.DetectOptions{ + CredentialsFile: tc.keyFile, + Scopes: []string{scope}, + } + mockDetector.On("DetectDefault", opts).Return(&auth.Credentials{}, nil).Once() + + creds, err := getCredentials(tc.keyFile, mockDetector.DetectDefault) + + assert.NoError(t, err) + assert.NotNil(t, creds) + mockDetector.AssertExpectations(t) + }) + } +} + +func Test_getCredentials_Error(t *testing.T) { + mockDetector := new(MockDetectCredentials) + keyFile := "/path/to/key.json" + expectedErr := fmt.Errorf("simulated detection error") + opts := &credentials.DetectOptions{ + CredentialsFile: keyFile, + Scopes: []string{scope}, + } + mockDetector.On("DetectDefault", opts).Return(nil, expectedErr).Once() + + creds, err := getCredentials(keyFile, mockDetector.DetectDefault) + + assert.Error(t, err) + assert.Nil(t, creds) + assert.ErrorIs(t, err, expectedErr) + mockDetector.AssertExpectations(t) +} From 65e5a44d8c2e5d1e0d4d7d3d4e9f1c154fcc2be5 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 19 Jun 2025 21:56:44 +0530 Subject: [PATCH 0500/1298] inc totalBytes for existing reader (#3428) --- internal/gcsx/client_readers/gcs_reader.go | 3 +-- .../gcsx/client_readers/gcs_reader_test.go | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/internal/gcsx/client_readers/gcs_reader.go b/internal/gcsx/client_readers/gcs_reader.go index 5cb4a00248..411f10d806 100644 --- a/internal/gcsx/client_readers/gcs_reader.go +++ b/internal/gcsx/client_readers/gcs_reader.go @@ -116,6 +116,7 @@ func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.R } defer func() { gr.updateExpectedOffset(offset + int64(readerResponse.Size)) + gr.totalReadBytes += uint64(readerResponse.Size) }() var err error @@ -138,12 +139,10 @@ func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.R readerType := gr.readerType(offset, end, gr.bucket.BucketType()) if readerType == RangeReaderType { readerResponse, err = gr.rangeReader.ReadAt(ctx, readReq) - gr.totalReadBytes += uint64(readerResponse.Size) return readerResponse, err } readerResponse, err = gr.mrr.ReadAt(ctx, readReq) - gr.totalReadBytes += uint64(readerResponse.Size) return readerResponse, err } diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go index ca6e73a46b..3dedd469cd 100644 --- a/internal/gcsx/client_readers/gcs_reader_test.go +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -210,6 +210,27 @@ func (t *gcsReaderTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedObject assert.Equal(t.T(), []byte(nil), t.gcsReader.rangeReader.readHandle) } +func (t *gcsReaderTest) Test_ReadAt_ExistingReaderIsFine() { + t.object.Size = 6 + content := "xxx" + // Simulate an existing reader + t.gcsReader.rangeReader.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte(content)), Handle: []byte("fake")} + t.gcsReader.rangeReader.cancel = func() {} + t.gcsReader.rangeReader.start = 2 + t.gcsReader.totalReadBytes = 2 + t.gcsReader.rangeReader.limit = 5 + requestSize := 3 + + readerResponse, err := t.readAt(2, int64(requestSize)) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), 3, readerResponse.Size) + assert.Equal(t.T(), content, string(readerResponse.DataBuf[:readerResponse.Size])) + assert.Equal(t.T(), uint64(5), t.gcsReader.totalReadBytes) + assert.Equal(t.T(), int64(5), t.gcsReader.expectedOffset) + assert.Equal(t.T(), []byte("fake"), t.gcsReader.rangeReader.readHandle) +} + func (t *gcsReaderTest) Test_ExistingReader_WrongOffset() { testCases := []struct { name string From e4da8973ae43a39fbb27a41b1b55de0741fedb1b Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Fri, 20 Jun 2025 09:52:06 +0530 Subject: [PATCH 0501/1298] Changing Block Interface to Follow io.Writer Interface (#3429) * Changing Block Interface to Follow io.Writer Interface * Deleting wrong file * Trigger integration test * Review comments --- internal/block/block.go | 10 ++--- internal/block/block_pool_test.go | 7 ++-- internal/block/block_test.go | 38 +++++++++++-------- .../bufferedwrites/buffered_write_handler.go | 2 +- .../bufferedwrites/upload_handler_test.go | 5 ++- 5 files changed, 36 insertions(+), 26 deletions(-) diff --git a/internal/block/block.go b/internal/block/block.go index 72a14f0ac5..f3e81fa7f8 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -31,7 +31,7 @@ type Block interface { Size() int64 // Write writes the given data to block. - Write(bytes []byte) error + Write(bytes []byte) (n int, err error) // Reader interface helps in copying the data directly to storage.writer // while uploading to GCS. @@ -62,18 +62,18 @@ func (m *memoryBlock) Reuse() { func (m *memoryBlock) Size() int64 { return m.offset.end - m.offset.start } -func (m *memoryBlock) Write(bytes []byte) error { +func (m *memoryBlock) Write(bytes []byte) (int, error) { if m.Size()+int64(len(bytes)) > int64(cap(m.buffer)) { - return fmt.Errorf("received data more than capacity of the block") + return 0, fmt.Errorf("received data more than capacity of the block") } n := copy(m.buffer[m.offset.end:], bytes) if n != len(bytes) { - return fmt.Errorf("error in copying the data to block. Expected %d, got %d", len(bytes), n) + return 0, fmt.Errorf("error in copying the data to block. Expected %d, got %d", len(bytes), n) } m.offset.end += int64(len(bytes)) - return nil + return n, nil } func (m *memoryBlock) Reader() io.Reader { diff --git a/internal/block/block_pool_test.go b/internal/block/block_pool_test.go index c346fe7581..75bd22ff06 100644 --- a/internal/block/block_pool_test.go +++ b/internal/block/block_pool_test.go @@ -82,12 +82,13 @@ func (t *BlockPoolTest) TestGetWhenBlockIsAvailableForReuse() { b, err := createBlock(2) require.Nil(t.T(), err) content := []byte("hi") - err = b.Write(content) + n, err := b.Write(content) + require.Equal(t.T(), 2, n) require.Nil(t.T(), err) // Validating the content of the block output, err := io.ReadAll(b.Reader()) - assert.Nil(t.T(), err) - assert.Equal(t.T(), content, output) + require.Nil(t.T(), err) + require.Equal(t.T(), content, output) bp.freeBlocksCh <- b // Setting totalBlocks same as maxBlocks to ensure no new blocks are created. bp.totalBlocks = 10 diff --git a/internal/block/block_test.go b/internal/block/block_test.go index 4ac5ea5d28..3b7d1673ec 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -37,9 +37,10 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockWrite() { mb, err := createBlock(12) require.Nil(testSuite.T(), err) content := []byte("hi") - err = mb.Write(content) + n, err := mb.Write(content) assert.Nil(testSuite.T(), err) + assert.Equal(testSuite.T(), len(content), n) output, err := io.ReadAll(mb.Reader()) assert.Nil(testSuite.T(), err) assert.Equal(testSuite.T(), content, output) @@ -50,19 +51,22 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockWriteWithDataGreaterThanCapacit mb, err := createBlock(1) require.Nil(testSuite.T(), err) content := []byte("hi") - err = mb.Write(content) + n, err := mb.Write(content) assert.NotNil(testSuite.T(), err) + assert.Equal(testSuite.T(), 0, n) assert.EqualError(testSuite.T(), err, outOfCapacityError) } func (testSuite *MemoryBlockTest) TestMemoryBlockWriteWithMultipleWrites() { mb, err := createBlock(12) require.Nil(testSuite.T(), err) - err = mb.Write([]byte("hi")) - assert.Nil(testSuite.T(), err) - err = mb.Write([]byte("hello")) - assert.Nil(testSuite.T(), err) + n, err := mb.Write([]byte("hi")) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), 2, n) + n, err = mb.Write([]byte("hello")) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), 5, n) output, err := io.ReadAll(mb.Reader()) assert.Nil(testSuite.T(), err) @@ -74,11 +78,13 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockWriteWith2ndWriteBeyondCapacity mb, err := createBlock(2) require.Nil(testSuite.T(), err) content := []byte("hi") - err = mb.Write(content) - assert.Nil(testSuite.T(), err) - err = mb.Write(content) + n, err := mb.Write(content) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), 2, n) + n, err = mb.Write(content) assert.NotNil(testSuite.T(), err) + assert.Equal(testSuite.T(), 0, n) assert.EqualError(testSuite.T(), err, outOfCapacityError) } @@ -86,12 +92,13 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockReuse() { mb, err := createBlock(12) require.Nil(testSuite.T(), err) content := []byte("hi") - err = mb.Write(content) - assert.Nil(testSuite.T(), err) + n, err := mb.Write(content) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), 2, n) output, err := io.ReadAll(mb.Reader()) - assert.Nil(testSuite.T(), err) - assert.Equal(testSuite.T(), content, output) - assert.Equal(testSuite.T(), int64(2), mb.Size()) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), content, output) + require.Equal(testSuite.T(), int64(2), mb.Size()) mb.Reuse() @@ -124,8 +131,9 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockDeAllocate() { mb, err := createBlock(12) require.Nil(testSuite.T(), err) content := []byte("hi") - err = mb.Write(content) + n, err := mb.Write(content) require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), 2, n) output, err := io.ReadAll(mb.Reader()) require.Nil(testSuite.T(), err) require.Equal(testSuite.T(), content, output) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 41605ac880..e423f41468 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -161,7 +161,7 @@ func (wh *bufferedWriteHandlerImpl) appendBuffer(data []byte) (err error) { remainingBlockSize := float64(wh.blockPool.BlockSize()) - float64(wh.current.Size()) pendingDataForWrite := float64(len(data)) - float64(dataWritten) bytesToCopy := int(math.Min(remainingBlockSize, pendingDataForWrite)) - err := wh.current.Write(data[dataWritten : dataWritten+bytesToCopy]) + _, err := wh.current.Write(data[dataWritten : dataWritten+bytesToCopy]) if err != nil { return err } diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 4408ae6a56..9c805efe4c 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -219,7 +219,7 @@ func (t *UploadHandlerTest) TestUploadSingleBlockThrowsErrorInCopy() { // Create a block with test data. b, err := t.blockPool.Get() require.NoError(t.T(), err) - err = b.Write([]byte("test data")) + _, err = b.Write([]byte("test data")) require.NoError(t.T(), err) // CreateObjectChunkWriter -- should be called once. writer := &storagemock.Writer{} @@ -241,7 +241,8 @@ func (t *UploadHandlerTest) TestUploadMultipleBlocksThrowsErrorInCopy() { // Create some blocks. blocks := t.createBlocks(4) for i := 0; i < 4; i++ { - err := blocks[i].Write([]byte("testdata" + strconv.Itoa(i) + " ")) + n, err := blocks[i].Write([]byte("testdata" + strconv.Itoa(i) + " ")) + require.Equal(t.T(), 10, n) require.NoError(t.T(), err) } // CreateObjectChunkWriter -- should be called once. From ec69c810b51c9825bdfdb8120f28a34dd8dd25fd Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 20 Jun 2025 14:32:28 +0530 Subject: [PATCH 0502/1298] [Migrate auth] Add new hidden flag for kill switch (#3425) * add new hidden flag for kill switch * review comment --- cfg/config.go | 12 ++++++++++++ cfg/params.yaml | 7 +++++++ cmd/root_test.go | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/cfg/config.go b/cfg/config.go index 246b8ce398..1bcb41c9b9 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -34,6 +34,8 @@ type Config struct { EnableAtomicRenameObject bool `yaml:"enable-atomic-rename-object"` + EnableGoogleLibAuth bool `yaml:"enable-google-lib-auth"` + EnableHns bool `yaml:"enable-hns"` EnableNewReader bool `yaml:"enable-new-reader"` @@ -371,6 +373,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } + flagSet.BoolP("enable-google-lib-auth", "", false, "Enable google library authentication method to fetch the credentials") + + if err := flagSet.MarkHidden("enable-google-lib-auth"); err != nil { + return err + } + flagSet.BoolP("enable-hns", "", true, "Enables support for HNS buckets") if err := flagSet.MarkHidden("enable-hns"); err != nil { @@ -744,6 +752,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("enable-google-lib-auth", flagSet.Lookup("enable-google-lib-auth")); err != nil { + return err + } + if err := v.BindPFlag("enable-hns", flagSet.Lookup("enable-hns")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 248af0369c..02f890021e 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -85,6 +85,13 @@ default: true hide-flag: true +- flag-name: "enable-google-lib-auth" + config-path: "enable-google-lib-auth" + type: "bool" + usage: "Enable google library authentication method to fetch the credentials" + default: false + hide-flag: true + - config-path: "enable-hns" flag-name: "enable-hns" type: "bool" diff --git a/cmd/root_test.go b/cmd/root_test.go index 21e30f263d..4a596d2b06 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -989,6 +989,43 @@ func TestArgsParsing_EnableHNSFlags(t *testing.T) { } } +func TestArgsParsing_EnableGoogleLibAuthFlag(t *testing.T) { + tests := []struct { + name string + args []string + expectedEnableGoogleLibAuth bool + }{ + { + name: "default", + args: []string{"gcsfuse", "abc", "pqr"}, + expectedEnableGoogleLibAuth: false, + }, + { + name: "normal", + args: []string{"gcsfuse", "--enable-google-lib-auth=true", "abc", "pqr"}, + expectedEnableGoogleLibAuth: true, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var gotEnableGoogleLibAuth bool + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { + gotEnableGoogleLibAuth = cfg.EnableGoogleLibAuth + return nil + }) + require.Nil(t, err) + cmd.SetArgs(convertToPosixArgs(tc.args, cmd)) + + err = cmd.Execute() + + if assert.NoError(t, err) { + assert.Equal(t, tc.expectedEnableGoogleLibAuth, gotEnableGoogleLibAuth) + } + }) + } +} + func TestArgsParsing_EnableAtomicRenameObjectFlag(t *testing.T) { tests := []struct { name string From 1cae0ae320b259d1c7194942f3e5181d13c5f985 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 20 Jun 2025 02:05:24 -0700 Subject: [PATCH 0503/1298] Reduce metric CPU usage (#3407) Reduce metric CPU usage from ~10% for single threaded reads to ~4%. --- common/noop_metrics.go | 24 +-- common/otel_metrics.go | 160 +++++++++++------- common/telemetry.go | 44 +++-- common/telemetry_test.go | 27 +-- internal/fs/wrappers/monitoring.go | 12 +- internal/gcsx/file_cache_reader.go | 10 +- internal/monitor/bucket.go | 6 +- internal/util/util.go | 15 +- .../integration_tests/monitoring/prom_test.go | 3 +- 9 files changed, 186 insertions(+), 115 deletions(-) diff --git a/common/noop_metrics.go b/common/noop_metrics.go index 4b1aa6df4a..b574248811 100644 --- a/common/noop_metrics.go +++ b/common/noop_metrics.go @@ -26,17 +26,17 @@ func NewNoopMetrics() MetricHandle { type noopMetrics struct{} -func (*noopMetrics) GCSReadBytesCount(_ context.Context, _ int64) {} -func (*noopMetrics) GCSReaderCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) GCSRequestCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) GCSRequestLatency(_ context.Context, _ time.Duration, _ []MetricAttr) {} -func (*noopMetrics) GCSReadCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) GCSDownloadBytesCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) GCSReadBytesCount(_ context.Context, _ int64) {} +func (*noopMetrics) GCSReaderCount(_ context.Context, _ int64, _ string) {} +func (*noopMetrics) GCSRequestCount(_ context.Context, _ int64, _ string) {} +func (*noopMetrics) GCSRequestLatency(_ context.Context, _ time.Duration, _ string) {} +func (*noopMetrics) GCSReadCount(_ context.Context, _ int64, _ string) {} +func (*noopMetrics) GCSDownloadBytesCount(_ context.Context, _ int64, _ string) {} -func (*noopMetrics) OpsCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) OpsLatency(_ context.Context, _ time.Duration, _ []MetricAttr) {} -func (*noopMetrics) OpsErrorCount(_ context.Context, _ int64, _ []MetricAttr) {} +func (*noopMetrics) OpsCount(_ context.Context, _ int64, _ string) {} +func (*noopMetrics) OpsLatency(_ context.Context, _ time.Duration, _ string) {} +func (*noopMetrics) OpsErrorCount(_ context.Context, _ int64, _ FSOpsErrorCategory) {} -func (*noopMetrics) FileCacheReadCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) FileCacheReadBytesCount(_ context.Context, _ int64, _ []MetricAttr) {} -func (*noopMetrics) FileCacheReadLatency(_ context.Context, _ time.Duration, _ []MetricAttr) {} +func (*noopMetrics) FileCacheReadCount(_ context.Context, _ int64, attrs CacheHitReadType) {} +func (*noopMetrics) FileCacheReadBytesCount(_ context.Context, _ int64, _ string) {} +func (*noopMetrics) FileCacheReadLatency(_ context.Context, _ time.Duration, _ string) {} diff --git a/common/otel_metrics.go b/common/otel_metrics.go index f3823141fc..4c5b6e018f 100644 --- a/common/otel_metrics.go +++ b/common/otel_metrics.go @@ -17,6 +17,7 @@ package common import ( "context" "errors" + "sync" "sync/atomic" "time" @@ -25,31 +26,90 @@ import ( "go.opentelemetry.io/otel/metric" ) -const ( - // IOMethod annotates the event that opens or closes a connection or file. - IOMethod = "io_method" +var ( + fsOpsMeter = otel.Meter("fs_op") + gcsMeter = otel.Meter("gcs") + fileCacheMeter = otel.Meter("file_cache") - // GCSMethod annotates the method called in the GCS client library. - GCSMethod = "gcs_method" + // Attribute Keys + // ioMethodKey specifies the I/O method attribute (e.g., opened, closed). + ioMethodKey = attribute.Key("io_method") + // gcsMethodKey specifies the name of the GCS method + gcsMethodKey = attribute.Key("gcs_method") + // fsOpKey specifies the FS operation like LookupInode, ReadFile etc. + fsOpKey = attribute.Key("fs_op") + // fsErrCategoryKey specifies the error category. The intention is to reduce the cardinality of FSError by grouping errors together. + fsErrCategoryKey = attribute.Key("fs_error_category") + // readTypeKey specifies the read operation like whether it's Sequential or Random + readTypeKey = attribute.Key("read_type") + // cacheHitKey specifies whether the read operation from file cache resulted in a cache-hit or miss. + cacheHitKey = attribute.Key("cache_hit") + + fsOpsOptionCache, + readTypeOptionCache, + ioMethodOptionCache, + gcsMethodOptionCache, + cacheHitOptionCache, + cacheHitReadTypeOptionCache, + fsOpsErrorCategoryOptionCache sync.Map +) - // FSOp annotates the file system op processed. - FSOp = "fs_op" +func loadOrStoreAttrOption[K comparable](mp *sync.Map, key K, attrSetGenFunc func() attribute.Set) metric.MeasurementOption { + attrSet, ok := mp.Load(key) + if ok { + return attrSet.(metric.MeasurementOption) + } + v, _ := mp.LoadOrStore(key, metric.WithAttributeSet(attrSetGenFunc())) + return v.(metric.MeasurementOption) +} - // FSErrCategory reduces the cardinality of FSError by grouping errors together. - FSErrCategory = "fs_error_category" +func ioMethodAttrOption(ioMethod string) metric.MeasurementOption { + return loadOrStoreAttrOption(&ioMethodOptionCache, ioMethod, + func() attribute.Set { + return attribute.NewSet(ioMethodKey.String(ioMethod)) + }) +} - // ReadType annotates the read operation with the type - Sequential/Random - ReadType = "read_type" +func readTypeAttrOption(readType string) metric.MeasurementOption { + return loadOrStoreAttrOption(&readTypeOptionCache, readType, + func() attribute.Set { + return attribute.NewSet(readTypeKey.String(readType)) + }) +} - // CacheHit annotates the read operation from file cache with true or false. - CacheHit = "cache_hit" -) +func fsOpsAttrOption(fsOps string) metric.MeasurementOption { + return loadOrStoreAttrOption(&fsOpsOptionCache, fsOps, + func() attribute.Set { + return attribute.NewSet(fsOpKey.String(fsOps)) + }) +} -var ( - fsOpsMeter = otel.Meter("fs_op") - gcsMeter = otel.Meter("gcs") - fileCacheMeter = otel.Meter("file_cache") -) +func getFsOpsErrorCategoryAttributeOption(attr FSOpsErrorCategory) metric.MeasurementOption { + return loadOrStoreAttrOption(&fsOpsErrorCategoryOptionCache, attr, + func() attribute.Set { + return attribute.NewSet(fsOpKey.String(attr.FSOps), fsErrCategoryKey.String(attr.ErrorCategory)) + }) +} + +func cacheHitAttrOption(cacheHit string) metric.MeasurementOption { + return loadOrStoreAttrOption(&cacheHitOptionCache, cacheHit, + func() attribute.Set { + return attribute.NewSet(cacheHitKey.String(cacheHit)) + }) +} + +func gcsMethodAttrOption(gcsMethod string) metric.MeasurementOption { + return loadOrStoreAttrOption(&gcsMethodOptionCache, gcsMethod, + func() attribute.Set { + return attribute.NewSet(gcsMethodKey.String(gcsMethod)) + }) +} + +func cacheHitReadTypeAttrOption(attr CacheHitReadType) metric.MeasurementOption { + return loadOrStoreAttrOption(&cacheHitReadTypeOptionCache, attr, func() attribute.Set { + return attribute.NewSet(cacheHitKey.String(attr.CacheHit), readTypeKey.String(attr.ReadType)) + }) +} // otelMetrics maintains the list of all metrics computed in GCSFuse. type otelMetrics struct { @@ -69,68 +129,52 @@ type otelMetrics struct { fileCacheReadLatency metric.Float64Histogram } -func attrsToRecordOption(attrs []MetricAttr) []metric.RecordOption { - otelOptions := make([]metric.RecordOption, 0, len(attrs)) - for _, attr := range attrs { - otelOptions = append(otelOptions, metric.WithAttributes(attribute.String(attr.Key, attr.Value))) - } - return otelOptions -} - -func attrsToAddOption(attrs []MetricAttr) []metric.AddOption { - otelOptions := make([]metric.AddOption, 0, len(attrs)) - for _, attr := range attrs { - otelOptions = append(otelOptions, metric.WithAttributes(attribute.String(attr.Key, attr.Value))) - } - return otelOptions -} - func (o *otelMetrics) GCSReadBytesCount(_ context.Context, inc int64) { o.gcsReadBytesCountAtomic.Add(inc) } -func (o *otelMetrics) GCSReaderCount(ctx context.Context, inc int64, attrs []MetricAttr) { - o.gcsReaderCount.Add(ctx, inc, attrsToAddOption(attrs)...) +func (o *otelMetrics) GCSReaderCount(ctx context.Context, inc int64, ioMethod string) { + o.gcsReaderCount.Add(ctx, inc, ioMethodAttrOption(ioMethod)) } -func (o *otelMetrics) GCSRequestCount(ctx context.Context, inc int64, attrs []MetricAttr) { - o.gcsRequestCount.Add(ctx, inc, attrsToAddOption(attrs)...) +func (o *otelMetrics) GCSRequestCount(ctx context.Context, inc int64, gcsMethod string) { + o.gcsRequestCount.Add(ctx, inc, gcsMethodAttrOption(gcsMethod)) } -func (o *otelMetrics) GCSRequestLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) { - o.gcsRequestLatency.Record(ctx, float64(latency.Milliseconds()), attrsToRecordOption(attrs)...) +func (o *otelMetrics) GCSRequestLatency(ctx context.Context, latency time.Duration, gcsMethod string) { + o.gcsRequestLatency.Record(ctx, float64(latency.Milliseconds()), gcsMethodAttrOption(gcsMethod)) } -func (o *otelMetrics) GCSReadCount(ctx context.Context, inc int64, attrs []MetricAttr) { - o.gcsReadCount.Add(ctx, inc, attrsToAddOption(attrs)...) +func (o *otelMetrics) GCSReadCount(ctx context.Context, inc int64, readType string) { + o.gcsReadCount.Add(ctx, inc, readTypeAttrOption(readType)) } -func (o *otelMetrics) GCSDownloadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { - o.gcsDownloadBytesCount.Add(ctx, inc, attrsToAddOption(attrs)...) +func (o *otelMetrics) GCSDownloadBytesCount(ctx context.Context, inc int64, readType string) { + o.gcsDownloadBytesCount.Add(ctx, inc, readTypeAttrOption(readType)) } -func (o *otelMetrics) OpsCount(ctx context.Context, inc int64, attrs []MetricAttr) { - o.fsOpsCount.Add(ctx, inc, attrsToAddOption(attrs)...) +func (o *otelMetrics) OpsCount(ctx context.Context, inc int64, fsOp string) { + o.fsOpsCount.Add(ctx, inc, fsOpsAttrOption(fsOp)) } -func (o *otelMetrics) OpsLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) { - o.fsOpsLatency.Record(ctx, float64(latency.Microseconds()), attrsToRecordOption(attrs)...) +func (o *otelMetrics) OpsLatency(ctx context.Context, latency time.Duration, fsOp string) { + o.fsOpsLatency.Record(ctx, float64(latency.Microseconds()), fsOpsAttrOption(fsOp)) } -func (o *otelMetrics) OpsErrorCount(ctx context.Context, inc int64, attrs []MetricAttr) { - o.fsOpsErrorCount.Add(ctx, inc, attrsToAddOption(attrs)...) +func (o *otelMetrics) OpsErrorCount(ctx context.Context, inc int64, attrs FSOpsErrorCategory) { + o.fsOpsErrorCount.Add(ctx, inc, getFsOpsErrorCategoryAttributeOption(attrs)) } -func (o *otelMetrics) FileCacheReadCount(ctx context.Context, inc int64, attrs []MetricAttr) { - o.fileCacheReadCount.Add(ctx, inc, attrsToAddOption(attrs)...) +func (o *otelMetrics) FileCacheReadCount(ctx context.Context, inc int64, attrs CacheHitReadType) { + o.fileCacheReadCount.Add(ctx, inc, cacheHitReadTypeAttrOption(attrs)) } -func (o *otelMetrics) FileCacheReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) { - o.fileCacheReadBytesCount.Add(ctx, inc, attrsToAddOption(attrs)...) +func (o *otelMetrics) FileCacheReadBytesCount(ctx context.Context, inc int64, readType string) { + o.fileCacheReadBytesCount.Add(ctx, inc, readTypeAttrOption(readType)) } -func (o *otelMetrics) FileCacheReadLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) { - o.fileCacheReadLatency.Record(ctx, float64(latency.Microseconds()), attrsToRecordOption(attrs)...) +func (o *otelMetrics) FileCacheReadLatency(ctx context.Context, latency time.Duration, cacheHit string) { + o.fileCacheReadLatency.Record(ctx, float64(latency.Microseconds()), cacheHitAttrOption(cacheHit)) } func NewOTelMetrics() (MetricHandle, error) { @@ -154,7 +198,7 @@ func NewOTelMetrics() (MetricHandle, error) { return nil })) gcsReaderCount, err7 := gcsMeter.Int64Counter("gcs/reader_count", metric.WithDescription("The cumulative number of GCS object readers opened or closed.")) - gcsRequestCount, err8 := gcsMeter.Int64Counter("gcs/request_count", metric.WithDescription("The cumulative number of GCS requests processed.")) + gcsRequestCount, err8 := gcsMeter.Int64Counter("gcs/request_count", metric.WithDescription("The cumulative number of GCS requests processed along with the GCS method.")) gcsRequestLatency, err9 := gcsMeter.Float64Histogram("gcs/request_latencies", metric.WithDescription("The cumulative distribution of the GCS request latencies."), metric.WithUnit("ms")) fileCacheReadCount, err10 := fileCacheMeter.Int64Counter("file_cache/read_count", diff --git a/common/telemetry.go b/common/telemetry.go index 4acf538a4b..2a59025b71 100644 --- a/common/telemetry.go +++ b/common/telemetry.go @@ -29,6 +29,24 @@ type ShutdownFn func(ctx context.Context) error // The unit can however change for different units i.e. for one metric the unit could be microseconds and for another it could be milliseconds. var defaultLatencyDistribution = metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000) +const ( + ReadTypeSequential = "Sequential" + ReadTypeRandom = "Random" + ReadTypeParallel = "Parallel" +) + +// Pair of CacheHit and ReadType attributes +type CacheHitReadType struct { + CacheHit string + ReadType string +} + +// Pair of FSOp and ErrorCategory attributes +type FSOpsErrorCategory struct { + FSOps string + ErrorCategory string +} + // JoinShutdownFunc combines the provided shutdown functions into a single function. func JoinShutdownFunc(shutdownFns ...ShutdownFn) ShutdownFn { return func(ctx context.Context) error { @@ -54,23 +72,23 @@ func (a *MetricAttr) String() string { type GCSMetricHandle interface { GCSReadBytesCount(ctx context.Context, inc int64) - GCSReaderCount(ctx context.Context, inc int64, attrs []MetricAttr) - GCSRequestCount(ctx context.Context, inc int64, attrs []MetricAttr) - GCSRequestLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) - GCSReadCount(ctx context.Context, inc int64, attrs []MetricAttr) - GCSDownloadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) + GCSReaderCount(ctx context.Context, inc int64, ioMethod string) + GCSRequestCount(ctx context.Context, inc int64, gcsMethod string) + GCSRequestLatency(ctx context.Context, latency time.Duration, gcsMethod string) + GCSReadCount(ctx context.Context, inc int64, readType string) + GCSDownloadBytesCount(ctx context.Context, inc int64, readType string) } type OpsMetricHandle interface { - OpsCount(ctx context.Context, inc int64, attrs []MetricAttr) - OpsLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) - OpsErrorCount(ctx context.Context, inc int64, attrs []MetricAttr) + OpsCount(ctx context.Context, inc int64, fsOp string) + OpsLatency(ctx context.Context, latency time.Duration, fsOp string) + OpsErrorCount(ctx context.Context, inc int64, attrs FSOpsErrorCategory) } type FileCacheMetricHandle interface { - FileCacheReadCount(ctx context.Context, inc int64, attrs []MetricAttr) - FileCacheReadBytesCount(ctx context.Context, inc int64, attrs []MetricAttr) - FileCacheReadLatency(ctx context.Context, latency time.Duration, attrs []MetricAttr) + FileCacheReadCount(ctx context.Context, inc int64, attrs CacheHitReadType) + FileCacheReadBytesCount(ctx context.Context, inc int64, readType string) + FileCacheReadLatency(ctx context.Context, latency time.Duration, cacheHit string) } type MetricHandle interface { GCSMetricHandle @@ -79,6 +97,6 @@ type MetricHandle interface { } func CaptureGCSReadMetrics(ctx context.Context, metricHandle MetricHandle, readType string, requestedDataSize int64) { - metricHandle.GCSReadCount(ctx, 1, []MetricAttr{{Key: ReadType, Value: readType}}) - metricHandle.GCSDownloadBytesCount(ctx, requestedDataSize, []MetricAttr{{Key: ReadType, Value: readType}}) + metricHandle.GCSReadCount(ctx, 1, readType) + metricHandle.GCSDownloadBytesCount(ctx, requestedDataSize, readType) } diff --git a/common/telemetry_test.go b/common/telemetry_test.go index 09384c4002..980b7ba36e 100644 --- a/common/telemetry_test.go +++ b/common/telemetry_test.go @@ -21,6 +21,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" ) func TestJoinShutdownFunc(t *testing.T) { @@ -88,8 +91,8 @@ func TestJoinShutdownFunc(t *testing.T) { } type int64DataPoint struct { - v int64 - attrs []MetricAttr + v int64 + attr metric.MeasurementOption } type fakeMetricHandle struct { @@ -98,17 +101,17 @@ type fakeMetricHandle struct { GCSDownloadBytesCounter []int64DataPoint } -func (f *fakeMetricHandle) GCSReadCount(ctx context.Context, inc int64, attrs []MetricAttr) { +func (f *fakeMetricHandle) GCSReadCount(ctx context.Context, inc int64, readType string) { f.GCSReadBytesCounter = append(f.GCSReadBytesCounter, int64DataPoint{ - v: inc, - attrs: attrs, + v: inc, + attr: metric.WithAttributes(attribute.String("read_type", readType)), }) } -func (f *fakeMetricHandle) GCSDownloadBytesCount(ctx context.Context, requestedDataSize int64, attrs []MetricAttr) { +func (f *fakeMetricHandle) GCSDownloadBytesCount(ctx context.Context, requestedDataSize int64, readType string) { f.GCSDownloadBytesCounter = append(f.GCSDownloadBytesCounter, int64DataPoint{ - v: requestedDataSize, - attrs: attrs, + v: requestedDataSize, + attr: metric.WithAttributes(attribute.String("read_type", readType)), }) } @@ -121,11 +124,11 @@ func TestCaptureGCSReadMetrics(t *testing.T) { require.Len(t, metricHandle.GCSReadBytesCounter, 1) require.Len(t, metricHandle.GCSDownloadBytesCounter, 1) assert.Equal(t, metricHandle.GCSReadBytesCounter[0], int64DataPoint{ - v: 1, - attrs: []MetricAttr{{Key: ReadType, Value: "Sequential"}}, + v: 1, + attr: metric.WithAttributes(attribute.String("read_type", "Sequential")), }) assert.Equal(t, metricHandle.GCSDownloadBytesCounter[0], int64DataPoint{ - v: 64, - attrs: []MetricAttr{{Key: ReadType, Value: "Sequential"}}, + v: 64, + attr: metric.WithAttributes(attribute.String("read_type", "Sequential")), }) } diff --git a/internal/fs/wrappers/monitoring.go b/internal/fs/wrappers/monitoring.go index fc8da54d42..614b7bc279 100644 --- a/internal/fs/wrappers/monitoring.go +++ b/internal/fs/wrappers/monitoring.go @@ -226,17 +226,17 @@ func categorize(err error) string { // Records file system operation count, failed operation count and the operation latency. func recordOp(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time, fsErr error) { - metricHandle.OpsCount(ctx, 1, []common.MetricAttr{{Key: common.FSOp, Value: method}}) + metricHandle.OpsCount(ctx, 1, method) // Recording opErrorCount. if fsErr != nil { errCategory := categorize(fsErr) - metricHandle.OpsErrorCount(ctx, 1, []common.MetricAttr{ - {Key: common.FSOp, Value: method}, - {Key: common.FSErrCategory, Value: errCategory}}, - ) + metricHandle.OpsErrorCount(ctx, 1, common.FSOpsErrorCategory{ + FSOps: method, + ErrorCategory: errCategory, + }) } - metricHandle.OpsLatency(ctx, time.Since(start), []common.MetricAttr{{Key: common.FSOp, Value: method}}) + metricHandle.OpsLatency(ctx, time.Since(start), method) } // WithMonitoring takes a FileSystem, returns a FileSystem with monitoring diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index 4167496056..1d9252daf5 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -207,13 +207,13 @@ func (fc *FileCacheReader) ReadAt(ctx context.Context, p []byte, offset int64) ( } func captureFileCacheMetrics(ctx context.Context, metricHandle common.MetricHandle, readType string, readDataSize int, cacheHit bool, readLatency time.Duration) { - metricHandle.FileCacheReadCount(ctx, 1, []common.MetricAttr{ - {Key: common.ReadType, Value: readType}, - {Key: common.CacheHit, Value: strconv.FormatBool(cacheHit)}, + metricHandle.FileCacheReadCount(ctx, 1, common.CacheHitReadType{ + ReadType: readType, + CacheHit: strconv.FormatBool(cacheHit), }) - metricHandle.FileCacheReadBytesCount(ctx, int64(readDataSize), []common.MetricAttr{{Key: common.ReadType, Value: readType}}) - metricHandle.FileCacheReadLatency(ctx, readLatency, []common.MetricAttr{{Key: common.CacheHit, Value: strconv.FormatBool(cacheHit)}}) + metricHandle.FileCacheReadBytesCount(ctx, int64(readDataSize), readType) + metricHandle.FileCacheReadLatency(ctx, readLatency, strconv.FormatBool(cacheHit)) } func (fc *FileCacheReader) Destroy() { diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index 3f8733bb0e..c362332b60 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -26,9 +26,9 @@ import ( // recordRequest records a request and its latency. func recordRequest(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time) { - metricHandle.GCSRequestCount(ctx, 1, []common.MetricAttr{{Key: common.GCSMethod, Value: method}}) + metricHandle.GCSRequestCount(ctx, 1, method) - metricHandle.GCSRequestLatency(ctx, time.Since(start), []common.MetricAttr{{Key: common.GCSMethod, Value: method}}) + metricHandle.GCSRequestLatency(ctx, time.Since(start), method) } func CaptureMultiRangeDownloaderMetrics(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time) { @@ -213,7 +213,7 @@ func (mb *monitoringBucket) NewMultiRangeDownloader( // recordReader increments the reader count when it's opened or closed. func recordReader(ctx context.Context, metricHandle common.MetricHandle, ioMethod string) { - metricHandle.GCSReaderCount(ctx, 1, []common.MetricAttr{{Key: common.IOMethod, Value: ioMethod}}) + metricHandle.GCSReaderCount(ctx, 1, ioMethod) } // Monitoring on the object reader diff --git a/internal/util/util.go b/internal/util/util.go index e8a63c4082..b4650b22d0 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -23,15 +23,20 @@ import ( "path/filepath" "strings" "time" + + "github.com/googlecloudplatform/gcsfuse/v3/common" ) const ( - GCSFUSE_PARENT_PROCESS_DIR = "gcsfuse-parent-process-dir" - + // TODO: Remove these constants in favor of common.* constants. // Constants for read types - Sequential/Random - Sequential = "Sequential" - Random = "Random" - Parallel = "Parallel" + Sequential = common.ReadTypeSequential + Random = common.ReadTypeRandom + Parallel = common.ReadTypeParallel +) + +const ( + GCSFUSE_PARENT_PROCESS_DIR = "gcsfuse-parent-process-dir" MaxMiBsInUint64 uint64 = math.MaxUint64 >> 20 MaxMiBsInInt64 int64 = math.MaxInt64 >> 20 diff --git a/tools/integration_tests/monitoring/prom_test.go b/tools/integration_tests/monitoring/prom_test.go index e8669c7f26..23392f386d 100644 --- a/tools/integration_tests/monitoring/prom_test.go +++ b/tools/integration_tests/monitoring/prom_test.go @@ -231,6 +231,7 @@ func (testSuite *PromTest) TestReadMetrics() { _, err := os.ReadFile(path.Join(testSuite.mountPoint, "hello/hello.txt")) require.NoError(testSuite.T(), err) + assertNonZeroCountMetric(testSuite, "file_cache_read_bytes_count", "read_type", "Sequential") assertNonZeroCountMetric(testSuite, "file_cache_read_count", "cache_hit", "false") assertNonZeroCountMetric(testSuite, "file_cache_read_count", "read_type", "Sequential") assertNonZeroHistogramMetric(testSuite, "file_cache_read_latencies", "cache_hit", "false") @@ -244,7 +245,7 @@ func (testSuite *PromTest) TestReadMetrics() { assertNonZeroCountMetric(testSuite, "gcs_download_bytes_count", "", "") assertNonZeroCountMetric(testSuite, "gcs_read_bytes_count", "", "") assertNonZeroHistogramMetric(testSuite, "gcs_request_latencies", "gcs_method", "NewReader") - //TODO: file_cache_read_bytes_count should be added once with waitForDownload is true same as sequential for default pd, + assertNonZeroHistogramMetric(testSuite, "gcs_request_latencies", "gcs_method", "NewReader") } func TestPromOTELSuite(t *testing.T) { From 1828388a30a98c22dc4d427c48ec5188b8feff82 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Fri, 20 Jun 2025 14:41:11 +0530 Subject: [PATCH 0504/1298] Refactor Logging & fix jacobsa/fuse logger initialization based on gcsfuse log-severity. (#3415) * refactor & fix jcobsa fuse logging initialized based on log-severity. * fix review comment * fix review comments * fix review comments --- cfg/decode_hook_test.go | 2 +- cfg/types.go | 40 ++++++++++++++++++++++---- cfg/validate_test.go | 63 +++++++++++++++++++++++++++++++++++++++++ cmd/mount.go | 15 ++++++++-- cmd/mount_test.go | 48 +++++++++++++++++++++++++++++++ 5 files changed, 159 insertions(+), 9 deletions(-) diff --git a/cfg/decode_hook_test.go b/cfg/decode_hook_test.go index f00d67e66f..adf96f0e7d 100644 --- a/cfg/decode_hook_test.go +++ b/cfg/decode_hook_test.go @@ -273,7 +273,7 @@ func TestParsingError(t *testing.T) { { name: "LogSeverity", args: []string{"--logSeverityParam=abc"}, - errMsg: "invalid logseverity value: abc. It can only assume values in the list: [TRACE DEBUG INFO WARNING ERROR OFF]", + errMsg: "invalid log severity level: abc. Must be one of [TRACE, DEBUG, INFO, WARNING, ERROR, OFF]", }, { name: "Protocol", diff --git a/cfg/types.go b/cfg/types.go index 91c00c7dac..e0a75adafd 100644 --- a/cfg/types.go +++ b/cfg/types.go @@ -63,17 +63,45 @@ func (p *Protocol) UnmarshalText(text []byte) error { // "TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "OFF" type LogSeverity string +// Constants for all supported log severities. +const ( + TraceLogSeverity LogSeverity = "TRACE" + DebugLogSeverity LogSeverity = "DEBUG" + InfoLogSeverity LogSeverity = "INFO" + WarningLogSeverity LogSeverity = "WARNING" + ErrorLogSeverity LogSeverity = "ERROR" + OffLogSeverity LogSeverity = "OFF" +) + +// severityRanking maps each level to an integer for validation and comparison. +var severityRanking = map[LogSeverity]int{ + TraceLogSeverity: 0, + DebugLogSeverity: 1, + InfoLogSeverity: 2, + WarningLogSeverity: 3, + ErrorLogSeverity: 4, + OffLogSeverity: 5, +} + func (l *LogSeverity) UnmarshalText(text []byte) error { - textStr := string(text) - level := strings.ToUpper(textStr) - v := []string{"TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "OFF"} - if !slices.Contains(v, level) { - return fmt.Errorf("invalid logseverity value: %s. It can only assume values in the list: %v", textStr, v) + level := LogSeverity(strings.ToUpper(string(text))) + if _, ok := severityRanking[level]; !ok { + return fmt.Errorf("invalid log severity level: %s. Must be one of [TRACE, DEBUG, INFO, WARNING, ERROR, OFF]", text) } - *l = LogSeverity(level) + *l = level return nil } +// Rank returns the integer representation of the severity rank. +// Returns -1 if the severity is unknown. +func (l LogSeverity) Rank() int { + if rank, ok := severityRanking[l]; ok { + return rank + } + // This case should ideally not be reached as LogSeverity configs are validated before mounting. + return -1 +} + // ResolvedPath represents a file-path which is an absolute path and is resolved // based on the value of GCSFUSE_PARENT_PROCESS_DIR env var. type ResolvedPath string diff --git a/cfg/validate_test.go b/cfg/validate_test.go index 58cc5db1c0..4b77264b9d 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -664,3 +664,66 @@ func TestValidateMetrics(t *testing.T) { }) } } + +func TestValidateLogSeverityRanks(t *testing.T) { + t.Parallel() + testCases := []struct { + logSev string + wantLogSevRank int + wantLogSev LogSeverity + wantErr bool + }{ + { + logSev: "off", + wantLogSevRank: 5, + wantLogSev: OffLogSeverity, + }, + { + logSev: "error", + wantLogSevRank: 4, + wantLogSev: ErrorLogSeverity, + }, + { + logSev: "warning", + wantLogSevRank: 3, + wantLogSev: WarningLogSeverity, + }, + { + logSev: "info", + wantLogSevRank: 2, + wantLogSev: InfoLogSeverity, + }, + { + logSev: "debug", + wantLogSevRank: 1, + wantLogSev: DebugLogSeverity, + }, + { + logSev: "trace", + wantLogSevRank: 0, + wantLogSev: TraceLogSeverity, + }, + { + logSev: "invalid", + wantLogSevRank: -1, + wantErr: true, + }, + } + for _, tc := range testCases { + tc := tc + t.Run(tc.logSev, func(t *testing.T) { + t.Parallel() + level := LogSeverity(tc.logSev) + + err := level.UnmarshalText([]byte(tc.logSev)) + + if tc.wantErr { + assert.Error(t, err) + assert.Equal(t, -1, level.Rank()) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.wantLogSev.Rank(), level.Rank()) + } + }) + } +} diff --git a/cmd/mount.go b/cmd/mount.go index 77a13f2b56..3192becb3f 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -174,7 +174,18 @@ func getFuseMountConfig(fsName string, newConfig *cfg.Config) *fuse.MountConfig DisableWritebackCaching: newConfig.Write.EnableStreamingWrites, } - mountCfg.ErrorLogger = logger.NewLegacyLogger(logger.LevelError, "fuse: ") - mountCfg.DebugLogger = logger.NewLegacyLogger(logger.LevelTrace, "fuse_debug: ") + // GCSFuse to Jacobsa Fuse Log Level mapping: + // OFF OFF + // ERROR ERROR + // WARNING ERROR + // INFO ERROR + // DEBUG ERROR + // TRACE TRACE + if newConfig.Logging.Severity.Rank() <= cfg.ErrorLogSeverity.Rank() { + mountCfg.ErrorLogger = logger.NewLegacyLogger(logger.LevelError, "fuse: ") + } + if newConfig.Logging.Severity.Rank() <= cfg.TraceLogSeverity.Rank() { + mountCfg.DebugLogger = logger.NewLegacyLogger(logger.LevelTrace, "fuse_debug: ") + } return mountCfg } diff --git a/cmd/mount_test.go b/cmd/mount_test.go index 5c781ec36f..d993501190 100644 --- a/cmd/mount_test.go +++ b/cmd/mount_test.go @@ -66,3 +66,51 @@ func TestGetFuseMountConfig_MountOptionsFormattedCorrectly(t *testing.T) { assert.True(t, fuseMountCfg.EnableParallelDirOps) // Default true unless explicitly disabled } } + +func TestGetFuseMountConfig_LoggerInitializationInFuse(t *testing.T) { + testCases := []struct { + name string + gcsFuseLogLevel string + shouldInitializeTrace bool + shouldInitializeError bool + }{ + { + name: "GcsFuseOffLogLevelShouldNotInitializeAnyLogger", + gcsFuseLogLevel: "OFF", + shouldInitializeTrace: false, + shouldInitializeError: false, + }, + { + name: "GcsFuseErrorLogLevelShouldInitializeErrorLoggerOnly", + gcsFuseLogLevel: "ERROR", + shouldInitializeTrace: false, + shouldInitializeError: true, + }, + { + name: "GcsFuseDebugLogLevelShouldInitializeErrorLoggerOnly", + gcsFuseLogLevel: "DEBUG", + shouldInitializeTrace: false, + shouldInitializeError: true, + }, + { + name: "GcsFuseTraceLogLevelShouldInitializeBothLogger", + gcsFuseLogLevel: "TRACE", + shouldInitializeTrace: true, + shouldInitializeError: true, + }, + } + + fsName := "mybucket" + for _, tc := range testCases { + newConfig := &cfg.Config{ + Logging: cfg.LoggingConfig{ + Severity: cfg.LogSeverity(tc.gcsFuseLogLevel), + }, + } + + fuseMountCfg := getFuseMountConfig(fsName, newConfig) + + assert.Equal(t, tc.shouldInitializeError, fuseMountCfg.ErrorLogger != nil) + assert.Equal(t, tc.shouldInitializeTrace, fuseMountCfg.DebugLogger != nil) + } +} From b190907165ab7e124e6da8916a2a34cec3c60a13 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Fri, 20 Jun 2025 15:35:49 +0530 Subject: [PATCH 0505/1298] [Buffered reader] Implement static workerpool (#3423) * [Buffered reader] Implement static workerpool * More unit test * Fixing unit test * Review comments * Replace context.Cance with a channel --- internal/workerpool/static_worker_pool.go | 140 ++++++++++++++++ .../workerpool/static_worker_pool_test.go | 156 ++++++++++++++++++ internal/workerpool/worker_pool.go | 31 ++++ 3 files changed, 327 insertions(+) create mode 100644 internal/workerpool/static_worker_pool.go create mode 100644 internal/workerpool/static_worker_pool_test.go create mode 100644 internal/workerpool/worker_pool.go diff --git a/internal/workerpool/static_worker_pool.go b/internal/workerpool/static_worker_pool.go new file mode 100644 index 0000000000..c11d85ef53 --- /dev/null +++ b/internal/workerpool/static_worker_pool.go @@ -0,0 +1,140 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package workerpool + +import ( + "fmt" + "sync" + + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" +) + +// staticWorkerPool starts all the workers (goroutines) on startup and keeps them running. +// It keep two types of workers - priority and normal. Priority workers will only +// execute tasks that are marked as urgent while scheduling. Normal workers will +// execute both urgent and normal tasks, but gives precedence to urgent task. +type staticWorkerPool struct { + priorityWorker uint32 // Number of priority workers in this pool. + normalWorker uint32 // Number of normal workers in this pool. + + // Stop channel to notify all the workers to stop. + stop chan bool + + // Wait group to wait for all workers to finish. + wg sync.WaitGroup + + // Channels for normal and priority tasks. + priorityCh chan Task + normalCh chan Task +} + +// NewStaticWorkerPool creates a new thread pool +func NewStaticWorkerPool(priorityWorker uint32, normalWorker uint32) (*staticWorkerPool, error) { + totalWorkers := priorityWorker + normalWorker + if totalWorkers == 0 { + return nil, fmt.Errorf("staticWorkerPool: can't create with 0 workers, priority: %d, normal: %d", priorityWorker, normalWorker) + } + + logger.Infof("staticWorkerPool: creating with %d normal, and %d priority workers.", normalWorker, priorityWorker) + + return &staticWorkerPool{ + priorityWorker: priorityWorker, + normalWorker: normalWorker, + stop: make(chan bool), + // Keep the channel capacity large enough to handle burst of tasks. + priorityCh: make(chan Task, priorityWorker*200), + normalCh: make(chan Task, normalWorker*5000), + }, nil +} + +// Start all the workers and wait till they start receiving requests +func (swp *staticWorkerPool) Start() { + for i := uint32(0); i < swp.priorityWorker; i++ { + swp.wg.Add(1) + go swp.do(true) + } + + for i := uint32(0); i < swp.normalWorker; i++ { + swp.wg.Add(1) + go swp.do(false) + } +} + +// Stop all the workers threads and wait for them to finish processing. +func (swp *staticWorkerPool) Stop() { + // Notify all workers to stop. + logger.Infof("staticWorkerPool: stopping all the workers.") + close(swp.stop) + + swp.wg.Wait() + + // Close the channel after all workers are done. + close(swp.priorityCh) + close(swp.normalCh) +} + +// Schedule schedules tasks to the worker pool. +// Pass urgent as true for priority scheduling. +func (swp *staticWorkerPool) Schedule(urgent bool, task Task) { + // urgent specifies the priority of this task. + // true means high priority and false means low priority + if urgent { + swp.priorityCh <- task + } else { + swp.normalCh <- task + } +} + +// do is the core routine that runs in each worker thread. +// It will keep listening to the channel for tasks and execute them. +func (swp *staticWorkerPool) do(priority bool) { + defer swp.wg.Done() + + if priority { + // Worker only listens to the priority channel. + for { + select { + case <-swp.stop: + return + default: + select { + case <-swp.stop: + return + case task := <-swp.priorityCh: + task.Execute() + } + } + } + } else { + // Worker listens to both channels but gives priority to the priority channel. + for { + select { + case <-swp.stop: + return + case task := <-swp.priorityCh: + task.Execute() + default: + select { + case <-swp.stop: + return + case task := <-swp.priorityCh: + task.Execute() + case task := <-swp.normalCh: + task.Execute() + } + } + } + } +} diff --git a/internal/workerpool/static_worker_pool_test.go b/internal/workerpool/static_worker_pool_test.go new file mode 100644 index 0000000000..89634eadca --- /dev/null +++ b/internal/workerpool/static_worker_pool_test.go @@ -0,0 +1,156 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package workerpool + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type dummyTask struct { + executed bool +} + +func (d *dummyTask) Execute() { + d.executed = true +} + +func TestNewStaticWorkerPool_Success(t *testing.T) { + tests := []struct { + name string + priorityWorker uint32 + normalWorker uint32 + }{ + {"valid_workers", 5, 10}, + {"zero_normal_worker", 1, 0}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + pool, err := NewStaticWorkerPool(uint32(tc.priorityWorker), uint32(tc.normalWorker)) + + assert.NoError(t, err) + assert.NotNil(t, pool) + assert.Equal(t, tc.priorityWorker, pool.priorityWorker) + assert.Equal(t, tc.normalWorker, pool.normalWorker) + pool.Stop() // Clean up + }) + } +} + +func TestNewStaticWorkerPool_Failure(t *testing.T) { + pool, err := NewStaticWorkerPool(0, 0) + + assert.Error(t, err) + assert.Nil(t, pool) + assert.Panics(t, pool.Stop, "Stop should panic if pool is nil") +} + +func TestStaticWorkerPool_Start(t *testing.T) { + pool, err := NewStaticWorkerPool(2, 3) + require.NoError(t, err) + require.NotNil(t, pool) + + pool.Start() + defer pool.Stop() + + // Add a task in the channel and later see, that channel will be empty after execution. + dt := &dummyTask{} + pool.priorityCh <- dt + // Wait for the task to be executed. + assert.Eventually(t, func() bool { + return dt.executed + }, 100*time.Millisecond, time.Millisecond, "Task was not executed in time.") + assert.Equal(t, 0, len(pool.priorityCh), "Priority channel should be empty after task execution.") +} + +func TestStaticWorkerPool_SchedulePriorityTask(t *testing.T) { + pool, err := NewStaticWorkerPool(2, 3) + require.NoError(t, err) + require.NotNil(t, pool) + pool.Start() + defer pool.Stop() + + dt := &dummyTask{} + pool.Schedule(true, dt) + + // Wait for the task to be executed. + assert.Eventually(t, func() bool { + return dt.executed + }, 100*time.Millisecond, time.Millisecond, "Task was not executed in time.") +} + +func TestStaticWorkerPool_ScheduleNormalTask(t *testing.T) { + pool, err := NewStaticWorkerPool(2, 3) + require.NoError(t, err) + require.NotNil(t, pool) + pool.Start() + defer pool.Stop() + + dt := &dummyTask{} + pool.Schedule(false, dt) + + // Wait for the task to be executed. + require.Eventually(t, func() bool { + return dt.executed + }, 100*time.Millisecond, time.Millisecond, "Priority task was not executed in time.") +} + +func TestStaticWorkerPool_HighNumberOfTasks(t *testing.T) { + pool, err := NewStaticWorkerPool(5, 10) + require.NoError(t, err) + require.NotNil(t, pool) + pool.Start() + defer pool.Stop() + + // Schedule a large number of tasks. + for i := 0; i < 100; i++ { + dt := &dummyTask{} + pool.Schedule(i%2 == 0, dt) // Alternate between priority and normal tasks + } + + // Wait for all tasks to be executed. + assert.Eventually(t, func() bool { + return len(pool.priorityCh) == 0 && len(pool.normalCh) == 0 + }, 500*time.Millisecond, 10*time.Millisecond, "Not all tasks were executed in time.") +} + +func TestStaticWorkerPool_ScheduleAfterStop(t *testing.T) { + pool, err := NewStaticWorkerPool(2, 3) + require.NoError(t, err) + require.NotNil(t, pool) + pool.Start() + + pool.Stop() + + assert.Panics(t, func() { pool.Schedule(true, &dummyTask{}) }, "Should panic when scheduling after cancel.") +} + +func TestStaticWorkerPool_Stop(t *testing.T) { + pool, err := NewStaticWorkerPool(2, 3) + require.NoError(t, err) + require.NotNil(t, pool) + pool.Start() + + // Stop the pool and check if channels are closed. + pool.Stop() + + assert.Panics(t, func() { pool.stop <- true }, "normalCh channel is not closed.") + assert.Panics(t, func() { pool.normalCh <- &dummyTask{} }, "normalCh channel is not closed.") + assert.Panics(t, func() { pool.priorityCh <- &dummyTask{} }, "priorityCh channel is not closed.") +} diff --git a/internal/workerpool/worker_pool.go b/internal/workerpool/worker_pool.go new file mode 100644 index 0000000000..28d615393c --- /dev/null +++ b/internal/workerpool/worker_pool.go @@ -0,0 +1,31 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package workerpool + +// Task interface defines the contract for a runnable task. +type Task interface { + Execute() +} + +type WorkerPool interface { + // Start initializes the worker pool and prepares it to accept tasks. + Start() + + // Stop gracefully shuts down the worker pool, waiting for all tasks to complete. + Stop() + + // Schedule adds a task to the worker pool for execution. + Schedule(urgent bool, task Task) +} From 14cb1456a6b4190f187895b61dde82a1cd461ac6 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Fri, 20 Jun 2025 16:24:55 +0530 Subject: [PATCH 0506/1298] Disable streaming writes for local file packages. (#3430) --- tools/integration_tests/run_tests_mounted_directory.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index c38246bfb8..d0de122d55 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -308,12 +308,12 @@ sudo umount $MOUNT_DIR # package local_file # Run test with static mounting. (flags: --implicit-dirs=true) -gcsfuse --implicit-dirs=true --rename-dir-limit=3 $TEST_BUCKET_NAME $MOUNT_DIR +gcsfuse --implicit-dirs=true --rename-dir-limit=3 --enable-streaming-writes=false $TEST_BUCKET_NAME $MOUNT_DIR GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/local_file/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR # Run test with static mounting. (flags: --implicit-dirs=false) -gcsfuse --implicit-dirs=false --rename-dir-limit=3 $TEST_BUCKET_NAME $MOUNT_DIR +gcsfuse --implicit-dirs=false --rename-dir-limit=3 --enable-streaming-writes=false $TEST_BUCKET_NAME $MOUNT_DIR GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/local_file/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --testbucket=$TEST_BUCKET_NAME ${ZONAL_BUCKET_ARG} sudo umount $MOUNT_DIR From 341c2a0f9079846227eb4bb41cd9e13382c9f209 Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Fri, 20 Jun 2025 12:07:48 +0000 Subject: [PATCH 0507/1298] Removing files created on disk after TestReadFilesConcurrently test (#3431) --- .../read_large_files/concurrent_read_files_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/integration_tests/read_large_files/concurrent_read_files_test.go b/tools/integration_tests/read_large_files/concurrent_read_files_test.go index 900a54bf29..f607a6586c 100644 --- a/tools/integration_tests/read_large_files/concurrent_read_files_test.go +++ b/tools/integration_tests/read_large_files/concurrent_read_files_test.go @@ -62,6 +62,9 @@ func TestReadFilesConcurrently(t *testing.T) { // Wait on threads to end. err := eG.Wait() + for i := 0; i < NumberOfFilesInLocalDiskForConcurrentRead; i++ { + operations.RemoveFile(filesPathInLocalDisk[i]) + } if err != nil { t.Fatalf("Error: %v", err) } From 07a1ee6087752a9f5459d2128763d46b259559a1 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Fri, 20 Jun 2025 22:50:53 +0530 Subject: [PATCH 0508/1298] [Buffered Reader] Implement Generic Queue (#3422) * Generic Queue Implementation for Buffered Read * Adding unit test for NewQueue * Review comments * Review comments * Minor panic message change --- common/queue.go | 102 ++++++++++++++++++++++++++++++++ common/queue_test.go | 134 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 common/queue.go create mode 100644 common/queue_test.go diff --git a/common/queue.go b/common/queue.go new file mode 100644 index 0000000000..d29e2f6cb6 --- /dev/null +++ b/common/queue.go @@ -0,0 +1,102 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +// Queue is a generic interface for a queue data structure. +type Queue[T any] interface { + // IsEmpty checks if the queue is empty. + IsEmpty() bool + + // Peek returns the front of the queue without removing it. + // Panics if the queue is empty. + Peek() T + + // Push puts an item on the end of the queue. + Push(value T) + + // Pop removes and returns the front item from the queue. + // Panics if the queue is empty. + Pop() T + + // Len returns the number of items in the queue. + Len() int +} + +// node represents a node in the queue. +type node[T any] struct { + value T + next *node[T] +} + +// linkedListQueue is a linked list implementation of a queue. +type linkedListQueue[T any] struct { + start, end *node[T] + size int +} + +// NewQueue creates a new empty queue. +func NewLinkedListQueue[T any]() Queue[T] { + return &linkedListQueue[T]{} +} + +// IsEmpty returns true if the queue is empty. +func (q *linkedListQueue[T]) IsEmpty() bool { + return q.size == 0 +} + +// Peek returns the front of the queue without removing it. +// Panics if the queue is empty. +func (q *linkedListQueue[T]) Peek() T { + if q.size == 0 { + panic("Peek called on an empty queue.") + } + return q.start.value +} + +// Push puts an item on the end of the queue. +func (q *linkedListQueue[T]) Push(value T) { + n := &node[T]{value, nil} + if q.size == 0 { + q.start = n + q.end = n + } else { + q.end.next = n + q.end = n + } + q.size++ +} + +// Pop removes and returns the front item from the queue. +// Panics if the queue is empty. +func (q *linkedListQueue[T]) Pop() T { + if q.size == 0 { + panic("Pop called on an empty queue.") + } + + n := q.start + if q.size == 1 { + q.start = nil + q.end = nil + } else { + q.start = q.start.next + } + q.size-- + return n.value +} + +// Len returns the number of items in the queue. +func (q *linkedListQueue[T]) Len() int { + return q.size +} diff --git a/common/queue_test.go b/common/queue_test.go new file mode 100644 index 0000000000..6b38f824a2 --- /dev/null +++ b/common/queue_test.go @@ -0,0 +1,134 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewLinkedListQueue(t *testing.T) { + q := NewLinkedListQueue[int]() + + assert.NotNil(t, q, "NewLinkedListQueue() should return a non-nil queue.") + assert.True(t, q.IsEmpty(), "A new queue should be empty.") + assert.Equal(t, 0, q.Len(), "A new queue should have a size of 0.") +} + +func TestLinkedListQueue_Push(t *testing.T) { + q := NewLinkedListQueue[int]() + + q.Push(4) + q.Push(5) + + assert.Equal(t, 4, q.Peek()) + assert.False(t, q.IsEmpty()) +} + +func TestLinkedListQueue_SinglePop(t *testing.T) { + q := NewLinkedListQueue[int]() + q.Push(4) + q.Push(5) + require.Equal(t, 4, q.Peek()) + require.False(t, q.IsEmpty()) + + val := q.Pop() + + assert.Equal(t, 4, val) + assert.Equal(t, 5, q.Peek()) +} + +func TestLinkedListQueue_MultiplePops(t *testing.T) { + q := NewLinkedListQueue[int]() + q.Push(4) + q.Push(5) + require.Equal(t, 4, q.Peek()) + require.False(t, q.IsEmpty()) + val := q.Pop() + require.Equal(t, 4, val) + require.Equal(t, 5, q.Peek()) + + val = q.Pop() + + assert.Equal(t, 5, val) + assert.True(t, q.IsEmpty()) +} + +func TestLinkedListQueue_PopEmptyQueue(t *testing.T) { + assert.Panics(t, func() { + NewLinkedListQueue[int]().Pop() + }, "Pop should panic when called on an empty queue.") +} + +func TestLinkedListQueue_Peek(t *testing.T) { + q := NewLinkedListQueue[int]() + q.Push(4) + require.Equal(t, 1, q.Len()) + + val := q.Peek() + + assert.Equal(t, 4, val) + assert.Equal(t, 1, q.Len()) // Length should remain unchanged. + assert.False(t, q.IsEmpty()) +} + +func TestLinkedListQueue_PeekEmptyQueue(t *testing.T) { + assert.Panics(t, func() { + NewLinkedListQueue[int]().Peek() + }, "Peek should panic when called on an empty queue.") +} + +func TestLinkedListQueue_IsEmptyTrue(t *testing.T) { + q := NewLinkedListQueue[int]() + q.Push(4) + q.Pop() + + assert.True(t, q.IsEmpty()) +} + +func TestLinkedListQueue_IsEmptyFalse(t *testing.T) { + q := NewLinkedListQueue[int]() + q.Push(4) + + assert.False(t, q.IsEmpty()) +} + +func TestLinkedListQueue_Len(t *testing.T) { + q := NewLinkedListQueue[int]() + assert.Equal(t, 0, q.Len()) + + q.Push(4) + assert.Equal(t, 1, q.Len()) + + q.Push(5) + assert.Equal(t, 2, q.Len()) + + q.Push(6) + assert.Equal(t, 3, q.Len()) + + val := q.Pop() + assert.Equal(t, 4, val) + assert.Equal(t, 2, q.Len()) + + val = q.Pop() + assert.Equal(t, 5, val) + assert.Equal(t, 1, q.Len()) + + val = q.Pop() + assert.Equal(t, 6, val) + assert.Equal(t, 0, q.Len()) +} From a95f8a84ad8d81f170151f6b89ba60a89e209754 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 23 Jun 2025 10:23:39 +0530 Subject: [PATCH 0509/1298] update seek after existing reader read fail (#3434) --- internal/gcsx/client_readers/gcs_reader.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/gcsx/client_readers/gcs_reader.go b/internal/gcsx/client_readers/gcs_reader.go index 411f10d806..d227509a2d 100644 --- a/internal/gcsx/client_readers/gcs_reader.go +++ b/internal/gcsx/client_readers/gcs_reader.go @@ -103,12 +103,6 @@ func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.R gr.rangeReader.invalidateReaderIfMisalignedOrTooSmall(offset, p) - // If the data can't be served from the existing reader, then we need to update the seeks. - // If current offset is not same as expected offset, it's a random read. - if gr.expectedOffset != 0 && gr.expectedOffset != offset { - gr.seeks++ - } - readReq := &gcsx.GCSReaderRequest{ Buffer: p, Offset: offset, @@ -128,6 +122,12 @@ func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.R return readerResponse, err } + // If the data can't be served from the existing reader, then we need to update the seeks. + // If current offset is not same as expected offset, it's a random read. + if gr.expectedOffset != 0 && gr.expectedOffset != offset { + gr.seeks++ + } + // If we don't have a reader, determine whether to read from RangeReader or MultiRangeReader. end, err := gr.getReadInfo(offset, int64(len(p))) if err != nil { From e813fe7fef7f701a0359695c595028c7ae1d872f Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Mon, 23 Jun 2025 14:48:43 +0530 Subject: [PATCH 0510/1298] appending to unfinalized objects in ZB using streaming writes flow with takeover writer (#3389) * fix in listobj * Update internal/storage/bucket_handle.go Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> * init bwh for appends * pass generation of object * init BWH size to size of appendable object * Revert "pass generation of object" This reverts commit d51ef560bbc279e16d182236e21d0b60c8964105. * UT for SetTotalSize() * UTs for upload handler * UTs for openMode * UTs for areBufferedWritesSupported * update to bwh init cond * logic improvements * simplify logic in writefile * cleanup * condition update and test fixes * do not acquire inode lock within filehandle.Write method --------- Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> --- .../bufferedwrites/buffered_write_handler.go | 6 +- internal/bufferedwrites/upload_handler.go | 14 ++- .../bufferedwrites/upload_handler_test.go | 113 +++++++++++++++++- internal/fs/fs.go | 42 +++---- internal/fs/handle/file.go | 9 +- internal/fs/handle/file_test.go | 30 +++++ internal/fs/inode/file.go | 22 +++- .../fs/inode/file_streaming_writes_test.go | 8 +- internal/fs/inode/file_test.go | 81 ++++++++++++- 9 files changed, 284 insertions(+), 41 deletions(-) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index e423f41468..277f1e0467 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -105,6 +105,10 @@ func NewBWHandler(req *CreateBWHandlerRequest) (bwh BufferedWriteHandler, err er if err != nil { return } + var size int64 + if req.Object != nil { + size = int64(req.Object.Size) + } bwh = &bufferedWriteHandlerImpl{ current: nil, @@ -118,7 +122,7 @@ func NewBWHandler(req *CreateBWHandlerRequest) (bwh BufferedWriteHandler, err er BlockSize: req.BlockSize, ChunkTransferTimeoutSecs: req.ChunkTransferTimeoutSecs, }), - totalSize: 0, + totalSize: size, mtime: time.Now(), truncatedSize: -1, } diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index eccc6c62b5..2ef5e43e3b 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -107,7 +107,16 @@ func (uh *UploadHandler) createObjectWriter() (err error) { // (and context will be cancelled) by the time complete upload is done. var ctx context.Context ctx, uh.cancelFunc = context.WithCancel(context.Background()) - uh.writer, err = uh.bucket.CreateObjectChunkWriter(ctx, req, int(uh.blockSize), nil) + if uh.bucket.BucketType().Zonal && (uh.obj != nil && uh.obj.Finalized.IsZero()) { + chunkWriterReq := gcs.CreateObjectChunkWriterRequest{ + CreateObjectRequest: *req, + ChunkSize: int(uh.blockSize), + Offset: int64(uh.obj.Size), + } + uh.writer, err = uh.bucket.CreateAppendableObjectWriter(ctx, &chunkWriterReq) + } else { + uh.writer, err = uh.bucket.CreateObjectChunkWriter(ctx, req, int(uh.blockSize), nil) + } return } @@ -168,8 +177,7 @@ func (uh *UploadHandler) Finalize() (*gcs.MinObject, error) { func (uh *UploadHandler) ensureWriter() error { if uh.writer == nil { - err := uh.createObjectWriter() - if err != nil { + if err := uh.createObjectWriter(); err != nil { return fmt.Errorf("createObjectWriter failed for object %s: %w", uh.objectName, err) } } diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 9c805efe4c..f22c5dc745 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -32,10 +32,14 @@ import ( ) const ( - blockSize = 1024 - maxBlocks int64 = 5 + blockSize = 1024 + maxBlocks int64 = 5 + objectName = "testObject" + objectSize uint64 = 1024 ) +var finalized = time.Date(2025, time.June, 18, 23, 30, 0, 0, time.UTC) + type UploadHandlerTest struct { uh *UploadHandler blockPool *block.BlockPool @@ -67,10 +71,102 @@ func (t *UploadHandlerTest) SetupSubTest() { t.SetupTest() } +func (t *UploadHandlerTest) createUploadHandlerWithObjectOfGivenSize(size uint64, finalized time.Time) { + t.uh = newUploadHandler(&CreateUploadHandlerRequest{ + Object: &gcs.Object{ + Name: objectName, + Size: size, + Finalized: finalized, + }, + ObjectName: "testObject", + Bucket: t.mockBucket, + FreeBlocksCh: t.blockPool.FreeBlocksChannel(), + MaxBlocksPerFile: maxBlocks, + BlockSize: blockSize, + ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, + }) +} + +func (t *UploadHandlerTest) TestCreateObjectWriter_CreateAppendableObjectWriterCalled() { + t.createUploadHandlerWithObjectOfGivenSize(objectSize, time.Time{}) + t.mockBucket.On("BucketType").Return(gcs.BucketType{Zonal: true}) + t.mockBucket.On("CreateAppendableObjectWriter", mock.Anything, mock.Anything).Return(&storagemock.Writer{}, nil) + + _ = t.uh.createObjectWriter() + + t.mockBucket.AssertCalled(t.T(), "CreateAppendableObjectWriter", mock.Anything, mock.Anything) +} + +func (t *UploadHandlerTest) TestCreateObjectWriter_CreateObjectChunkWriterCalled() { + t.createUploadHandlerWithObjectOfGivenSize(0, finalized) + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) + t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything).Return(&storagemock.Writer{}, nil) + + _ = t.uh.createObjectWriter() + + t.mockBucket.AssertCalled(t.T(), "CreateObjectChunkWriter", mock.Anything, mock.Anything) +} + +func (t *UploadHandlerTest) TestCreateObjectWriter_CreateObjectChunkWriterCalledForLocalFile() { + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) + t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything).Return(&storagemock.Writer{}, nil) + + _ = t.uh.createObjectWriter() + + t.mockBucket.AssertCalled(t.T(), "CreateObjectChunkWriter", mock.Anything, mock.Anything) +} + +func (t *UploadHandlerTest) TestEnsureWriter_CreateAppendableWriterIsSuccessful() { + t.createUploadHandlerWithObjectOfGivenSize(objectSize, time.Time{}) + t.mockBucket.On("BucketType").Return(gcs.BucketType{Zonal: true}) + writer := &storagemock.Writer{} + t.mockBucket.On("CreateAppendableObjectWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) + + err := t.uh.createObjectWriter() + + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.uh.writer) +} +func (t *UploadHandlerTest) TestEnsureWriter_CreateAppendableWriterReturnsError() { + t.createUploadHandlerWithObjectOfGivenSize(objectSize, time.Time{}) + t.mockBucket.On("BucketType").Return(gcs.BucketType{Zonal: true}) + expectedErr := fmt.Errorf("createAppendableObjectWriter failed") + t.mockBucket.On("CreateAppendableObjectWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, expectedErr) + + err := t.uh.ensureWriter() + + assert.NotNil(t.T(), err) + assert.Nil(t.T(), t.uh.writer) +} + +func (t *UploadHandlerTest) TestEnsureWriter_CreateObjectChunkWriterIsSuccessful() { + t.createUploadHandlerWithObjectOfGivenSize(0, finalized) + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) + writer := &storagemock.Writer{} + t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) + + err := t.uh.ensureWriter() + + assert.Nil(t.T(), err) + assert.NotNil(t.T(), t.uh.writer) +} +func (t *UploadHandlerTest) TestEnsureWriter_CreateObjectChunkWriterReturnsError() { + t.createUploadHandlerWithObjectOfGivenSize(0, finalized) + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) + expectedErr := fmt.Errorf("createObjectChunkWriter failed") + t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, expectedErr) + + err := t.uh.ensureWriter() + + assert.NotNil(t.T(), err) + assert.Nil(t.T(), t.uh.writer) +} + func (t *UploadHandlerTest) TestMultipleBlockUpload() { // CreateObjectChunkWriter -- should be called once. writer := &storagemock.Writer{} mockObj := &gcs.MinObject{} + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, nil) @@ -99,6 +195,7 @@ func (t *UploadHandlerTest) TestUploadWhenCreateObjectWriterFails() { b, err := t.blockPool.Get() require.NoError(t.T(), err) // CreateObjectChunkWriter -- should be called once. + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("taco")) // Upload the block. @@ -124,6 +221,7 @@ func (t *UploadHandlerTest) TestFinalizeWithWriterAlreadyPresent() { func (t *UploadHandlerTest) TestFinalizeWithNoWriter() { writer := &storagemock.Writer{} + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) mockObj := &gcs.MinObject{} @@ -137,6 +235,7 @@ func (t *UploadHandlerTest) TestFinalizeWithNoWriter() { } func (t *UploadHandlerTest) TestFinalizeWithNoWriterWhenCreateObjectWriterFails() { + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("taco")) assert.Nil(t.T(), t.uh.writer) @@ -153,6 +252,7 @@ func (t *UploadHandlerTest) TestFinalizeWhenFinalizeUploadFails() { t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) mockObj := &gcs.MinObject{} + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("FinalizeUpload", mock.Anything, writer).Return(mockObj, fmt.Errorf("taco")) obj, err := t.uh.Finalize() @@ -177,6 +277,7 @@ func (t *UploadHandlerTest) TestFlushWithWriterAlreadyPresent() { func (t *UploadHandlerTest) TestFlushWithNoWriter() { writer := &storagemock.Writer{} + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) mockObject := &gcs.MinObject{Size: 10} @@ -189,6 +290,7 @@ func (t *UploadHandlerTest) TestFlushWithNoWriter() { } func (t *UploadHandlerTest) TestFlushWithNoWriterWhenCreateObjectWriterFails() { + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, fmt.Errorf("taco")) assert.Nil(t.T(), t.uh.writer) @@ -202,6 +304,7 @@ func (t *UploadHandlerTest) TestFlushWithNoWriterWhenCreateObjectWriterFails() { func (t *UploadHandlerTest) TestFlushWhenFlushPendingWritesFails() { writer := &storagemock.Writer{} + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) assert.Nil(t.T(), t.uh.writer) var minObj *gcs.MinObject = nil @@ -223,6 +326,7 @@ func (t *UploadHandlerTest) TestUploadSingleBlockThrowsErrorInCopy() { require.NoError(t.T(), err) // CreateObjectChunkWriter -- should be called once. writer := &storagemock.Writer{} + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) // First write will be an error and Close will be successful. writer.On("Write", mock.Anything).Return(0, fmt.Errorf("taco")).Once() @@ -247,6 +351,7 @@ func (t *UploadHandlerTest) TestUploadMultipleBlocksThrowsErrorInCopy() { } // CreateObjectChunkWriter -- should be called once. writer := &storagemock.Writer{} + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) // Second write will be an error and rest of the operations will be successful. writer. @@ -315,6 +420,7 @@ func TestUploadErrorReturnsNil(t *testing.T) { func (t *UploadHandlerTest) TestMultipleBlockAwaitBlocksUpload() { // CreateObjectChunkWriter -- should be called once. writer := &storagemock.Writer{} + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(writer, nil) // Upload the blocks. for _, b := range t.createBlocks(5) { @@ -348,11 +454,13 @@ func (t *UploadHandlerTest) TestCreateObjectChunkWriterIsCalledWithCorrectReques Generation: 10, MetaGeneration: 20, Acl: nil, + Finalized: finalized, } // CreateObjectChunkWriter -- should be called once with correct request parameters. writer := &storagemock.Writer{} mockObj := &gcs.Object{} + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.MatchedBy(func(req *gcs.CreateObjectRequest) bool { @@ -381,6 +489,7 @@ func (t *UploadHandlerTest) TestCreateObjectChunkWriterIsCalledWithCorrectReques // CreateObjectChunkWriter -- should be called once with correct request parameters. writer := &storagemock.Writer{} mockObj := &gcs.Object{} + t.mockBucket.On("BucketType").Return(gcs.BucketType{}) t.mockBucket.On("CreateObjectChunkWriter", mock.Anything, mock.MatchedBy(func(req *gcs.CreateObjectRequest) bool { diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 7b266d35c9..9bf6842cca 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1219,8 +1219,8 @@ func (fs *fileSystem) syncFile( // // LOCKS_EXCLUDED(fs.mu) // LOCKS_REQUIRED(f.mu) -func (fs *fileSystem) createBufferedWriteHandlerAndSyncOrTempWriter(ctx context.Context, f *inode.FileInode) error { - err := fs.initBufferedWriteHandlerAndSyncFileIfEligible(ctx, f) +func (fs *fileSystem) createBufferedWriteHandlerAndSyncOrTempWriter(ctx context.Context, f *inode.FileInode, openMode util.OpenMode) error { + err := fs.initBufferedWriteHandlerAndSyncFileIfEligible(ctx, f, openMode) if err != nil { return err } @@ -1235,8 +1235,8 @@ func (fs *fileSystem) createBufferedWriteHandlerAndSyncOrTempWriter(ctx context. // // LOCKS_EXCLUDED(fs.mu) // LOCKS_REQUIRED(f.mu) -func (fs *fileSystem) initBufferedWriteHandlerAndSyncFileIfEligible(ctx context.Context, f *inode.FileInode) error { - initialized, err := f.InitBufferedWriteHandlerIfEligible(ctx) +func (fs *fileSystem) initBufferedWriteHandlerAndSyncFileIfEligible(ctx context.Context, f *inode.FileInode, openMode util.OpenMode) error { + initialized, err := f.InitBufferedWriteHandlerIfEligible(ctx, openMode) if err != nil { return err } @@ -1568,7 +1568,7 @@ func (fs *fileSystem) SetInodeAttributes( // Truncate files. if isFile && op.Size != nil { // Initialize BWH if eligible and Sync file inode. - err = fs.initBufferedWriteHandlerAndSyncFileIfEligible(ctx, file) + err = fs.initBufferedWriteHandlerAndSyncFileIfEligible(ctx, file, util.Write) if err != nil { return } @@ -1800,7 +1800,7 @@ func (fs *fileSystem) createLocalFile(ctx context.Context, parentID fuseops.Inod fs.mu.Unlock() defer fs.mu.Lock() fileInode.Lock() - err = fs.createBufferedWriteHandlerAndSyncOrTempWriter(ctx, fileInode) + err = fs.createBufferedWriteHandlerAndSyncOrTempWriter(ctx, fileInode, util.Write) fileInode.Unlock() if err != nil { return @@ -2658,32 +2658,28 @@ func (fs *fileSystem) WriteFile( defer cancel() } - if fs.newConfig.Write.ExperimentalEnableRapidAppends { - fs.mu.Lock() - fh := fs.handles[op.Handle].(*handle.FileHandle) - fs.mu.Unlock() - - //TODO: Initialize BWH before invoking write() - if _, err := fh.Write(ctx, op.Data, op.Offset); err != nil { - return err - } - return - } - // Find the inode. + // Find the inode( and file handle in case of appends). fs.mu.Lock() + fh := fs.handles[op.Handle].(*handle.FileHandle) in := fs.fileInodeOrDie(op.Inode) fs.mu.Unlock() + var gcsSynced bool in.Lock() defer in.Unlock() - - err = fs.initBufferedWriteHandlerAndSyncFileIfEligible(ctx, in) + if err = fs.initBufferedWriteHandlerAndSyncFileIfEligible(ctx, in, fh.OpenMode()); err != nil { + return + } + if fs.newConfig.Write.ExperimentalEnableRapidAppends { + // Serve the request via the file handle. + gcsSynced, err = fh.Write(ctx, op.Data, op.Offset) + } else { + // Serve the request. + gcsSynced, err = in.Write(ctx, op.Data, op.Offset, util.Write) + } if err != nil { return } - - // Serve the request. - gcsSynced, err := in.Write(ctx, op.Data, op.Offset, util.Write) // Sync the inode if finalize during write is successful // even if the write operation later resulted in error. if gcsSynced { diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index be774e9a82..2e3b02a64c 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -217,11 +217,8 @@ func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequen // which is used for determining write path. For e.g. in case of append mode for // unfinalized objects in zonal buckets, streaming writes is used. // Note that the writes are still done at the inode level. -// LOCKS_EXCLUDED(fh.inode) +// LOCKS_REQUIRED(fh.inode) func (fh *FileHandle) Write(ctx context.Context, data []byte, offset int64) (bool, error) { - fh.inode.Lock() - defer fh.inode.Unlock() - return fh.inode.Write(ctx, data, offset, fh.openMode) } @@ -332,3 +329,7 @@ func (fh *FileHandle) destroyReadManager() { fh.readManager.Destroy() fh.readManager = nil } + +func (fh *FileHandle) OpenMode() util.OpenMode { + return fh.openMode +} diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 96ad3850c3..226e1825a8 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -313,3 +313,33 @@ func (t *fileTest) Test_Read_FallbackToInode() { assert.Equal(t.T(), objectData, output[:n]) mockR.AssertExpectations(t.T()) } + +func (t *fileTest) TestOpenMode() { + testCases := []struct { + name string + openMode util.OpenMode + }{ + { + name: "OpenModeRead", + openMode: util.Read, + }, + { + name: "OpenModeWrite", + openMode: util.Write, + }, + { + name: "OpenModeAppend", + openMode: util.Append, + }, + } + for _, tc := range testCases { + parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + config := &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: false}} + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj", nil, false) + fh := NewFileHandle(in, nil, false, nil, tc.openMode, &cfg.ReadConfig{}) + + openMode := fh.OpenMode() + + assert.Equal(t.T(), tc.openMode, openMode) + } +} diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 9fd4651ad5..23c33ba4a0 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -1006,14 +1006,14 @@ func (f *FileInode) CreateEmptyTempFile(ctx context.Context) (err error) { // Initializes Buffered Write Handler if the file inode is eligible and returns // initialized as true when the new instance of buffered writer handler is created. -func (f *FileInode) InitBufferedWriteHandlerIfEligible(ctx context.Context) (bool, error) { +func (f *FileInode) InitBufferedWriteHandlerIfEligible(ctx context.Context, openMode util.OpenMode) (bool, error) { // bwh already initialized, do nothing. if f.bwh != nil { return false, nil } tempFileInUse := f.content != nil - if f.src.Size != 0 || !f.config.Write.EnableStreamingWrites || tempFileInUse { + if !f.config.Write.EnableStreamingWrites || tempFileInUse { // bwh should not be initialized under these conditions. return false, nil } @@ -1027,6 +1027,10 @@ func (f *FileInode) InitBufferedWriteHandlerIfEligible(ctx context.Context) (boo } } + if !f.areBufferedWritesSupported(openMode, latestGcsObj) { + return false, nil + } + if f.bwh == nil { f.bwh, err = bufferedwrites.NewBWHandler(&bufferedwrites.CreateBWHandlerRequest{ Object: latestGcsObj, @@ -1049,3 +1053,17 @@ func (f *FileInode) InitBufferedWriteHandlerIfEligible(ctx context.Context) (boo } return false, nil } + +func (f *FileInode) areBufferedWritesSupported(openMode util.OpenMode, obj *gcs.Object) bool { + // For new files and existing files of size 0, buffered writes are always supported. + if f.local || obj.Size == 0 { + return true + } + if !f.config.Write.ExperimentalEnableRapidAppends { + return false + } + if openMode == util.Append && f.bucket.BucketType().Zonal && obj.Finalized.IsZero() { + return true + } + return false +} diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 5cc62d8e82..09c926e7b7 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -70,7 +70,7 @@ type FileStreamingWritesZonalBucketTest struct { func (t *FileStreamingWritesCommon) createBufferedWriteHandler() { // Initialize BWH for local inode created above. - initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx, util.Write) require.NoError(t.T(), err) assert.True(t.T(), initialized) assert.NotNil(t.T(), t.in.bwh) @@ -180,7 +180,7 @@ func (t *FileStreamingWritesCommon) TestflushUsingBufferedWriteHandlerOnZeroSize require.NoError(t.T(), err) assert.Nil(t.T(), t.in.bwh) - initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx, util.Write) require.NoError(t.T(), err) assert.True(t.T(), initialized) @@ -196,7 +196,7 @@ func (t *FileStreamingWritesCommon) TestflushUsingBufferedWriteHandlerOnNonZeroS require.NoError(t.T(), err) assert.Nil(t.T(), t.in.bwh) - initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx, util.Write) require.NoError(t.T(), err) assert.False(t.T(), initialized) @@ -839,6 +839,8 @@ func (t *FakeBufferedWriteHandler) Truncate(_ int64) error { return nil } func (t *FakeBufferedWriteHandler) Destroy() error { return nil } func (t *FakeBufferedWriteHandler) Unlink() {} +func (t *FakeBufferedWriteHandler) SetTotalSize() {} + func (t *FileStreamingWritesTest) TestWriteUsingBufferedWritesFails() { t.createBufferedWriteHandler() assert.True(t.T(), t.in.IsLocal()) diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index bc7e68f9cb..180f9d8587 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -160,7 +160,7 @@ func (t *FileTest) createInodeWithLocalParam(fileName string, local bool) { func (t *FileTest) createBufferedWriteHandler(shouldInitialize bool) { // Initialize BWH for local inode created above. - initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx, util.Write) require.NoError(t.T(), err) assert.Equal(t.T(), shouldInitialize, initialized) if shouldInitialize { @@ -180,6 +180,81 @@ func (t *FileTest) TestName() { assert.Equal(t.T(), fileName, t.in.Name().GcsObjectName()) } +func (t *FileTest) TestAreBufferedWritesSupported() { + finalizedTime := time.Date(2025, time.June, 18, 23, 30, 0, 0, time.UTC) + unFinalizedTime := time.Time{} + nonNilContents := "taco" + testCases := []struct { + name string + content string + openMode util.OpenMode + bucketType gcs.BucketType + finalized time.Time + supported bool + }{ + { + name: "AppendToFinalizedObjOnZB", + content: nonNilContents, + bucketType: gcs.BucketType{Zonal: true}, + finalized: finalizedTime, + openMode: util.Append, + supported: false, + }, + { + name: "AppendToUnfinalizedObjOnZB", + content: nonNilContents, + bucketType: gcs.BucketType{Zonal: true}, + finalized: unFinalizedTime, + openMode: util.Append, + supported: true, + }, + { + name: "AppendToObjOnNonZB", + content: nonNilContents, + bucketType: gcs.BucketType{}, + finalized: finalizedTime, + openMode: util.Append, + supported: false, + }, + { + name: "WriteToObjOnNonZB", + content: nonNilContents, + bucketType: gcs.BucketType{}, + finalized: finalizedTime, + openMode: util.Write, + supported: false, + }, + { + name: "WriteToEmptyObj", + content: "", + bucketType: gcs.BucketType{}, + finalized: finalizedTime, + openMode: util.Write, + supported: true, + }, + } + for _, tc := range testCases { + t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", tc.bucketType) + // Set up the backing object. + var err error + t.initialContents = tc.content + object, err := storageutil.CreateObject( + t.ctx, + t.bucket, + fileName, + []byte(t.initialContents)) + assert.Nil(t.T(), err) + object.Finalized = tc.finalized + t.backingObj = storageutil.ConvertObjToMinObject(object) + t.createInode() + t.in.config.Write.ExperimentalEnableRapidAppends = true + + isSupported := t.in.areBufferedWritesSupported(tc.openMode, object) + + assert.Equal(t.T(), tc.supported, isSupported) + } +} + func (t *FileTest) TestInitialSourceGeneration() { sg := t.in.SourceGeneration() assert.Equal(t.T(), t.backingObj.Generation, sg.Object) @@ -1500,7 +1575,7 @@ func (t *FileTest) TestInitBufferedWriteHandlerIfEligibleShouldNotCreateBWHNonEm // Enabling buffered writes. t.in.config = &cfg.Config{Write: *getWriteConfig()} - initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx, util.Write) assert.NoError(t.T(), err) assert.Nil(t.T(), t.in.bwh) @@ -1599,7 +1674,7 @@ func (t *FileTest) TestInitBufferedWriteHandlerWithInvalidConfigWhenStreamingWri t.createInodeWithLocalParam("test", true) t.in.config = &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: true}} - initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx) + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx, util.Write) assert.True(t.T(), strings.Contains(err.Error(), "invalid configuration")) assert.False(t.T(), initialized) From ceed447ec39b3534d3f1211d63da0eda53cdd10d Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Mon, 23 Jun 2025 17:20:13 +0530 Subject: [PATCH 0511/1298] remove streaming writes stall retries & fix e2e (#3404) --- internal/storage/bucket_handle.go | 3 ++- .../emulator_tests/write_stall/writes_stall_on_sync_test.go | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index a4ecc9cce9..2b4d791d28 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -258,7 +258,8 @@ func (bh *bucketHandle) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cr wc := &ObjectWriter{obj.NewWriter(ctx)} wc.ChunkSize = chunkSize wc.Writer = storageutil.SetAttrsInWriter(wc.Writer, req) - wc.ChunkTransferTimeout = time.Duration(req.ChunkTransferTimeoutSecs) * time.Second + // TODO(b/424091803): Uncomment once chunk transfer timeout issue in resumable uploads is fixed in dependencies. + // wc.ChunkTransferTimeout = time.Duration(req.ChunkTransferTimeoutSecs) * time.Second if callBack == nil { callBack = func(bytesUploadedSoFar int64) { logger.Tracef("gcs: Req %#16x: -- UploadBlock(%q): %20v bytes uploaded so far", ctx.Value(gcs.ReqIdField), req.Name, bytesUploadedSoFar) diff --git a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go index 820f64616f..248fd6bb5d 100644 --- a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go @@ -101,9 +101,10 @@ func TestChunkTransferTimeoutInfinity(t *testing.T) { } func TestChunkTransferTimeout(t *testing.T) { + // TODO(b/424091803): Enable streaming writes once chunk transfer timeout issue in resumable uploads is fixed in dependencies. flagSets := [][]string{ - {}, - {"--chunk-transfer-timeout-secs=5"}, + {"--enable-streaming-writes=false"}, + {"--enable-streaming-writes=false", "--chunk-transfer-timeout-secs=5"}, } stallScenarios := []struct { From 4bc1cd3c0ca42c008039115b0b51e7cda0992226 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 24 Jun 2025 11:27:15 +0530 Subject: [PATCH 0512/1298] Add PR reminder to gcsfuse. (#3441) --- .github/scripts/reminder.js | 82 +++++++++++++++++++++++++++++++ .github/workflows/pr-reminder.yml | 34 +++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 .github/scripts/reminder.js create mode 100644 .github/workflows/pr-reminder.yml diff --git a/.github/scripts/reminder.js b/.github/scripts/reminder.js new file mode 100644 index 0000000000..a97ea5af03 --- /dev/null +++ b/.github/scripts/reminder.js @@ -0,0 +1,82 @@ +// This script uses the Octokit library to interact with the GitHub API. +const { getOctokit } = require('@actions/github'); +// The context object provides information about the workflow run. +const { context } = require('@actions/github'); + +// This is the main function that will be executed. +async function run() { + try { + // ------------------ CONFIGURATION ------------------ + // Label that triggers the reminder. + const REMINDER_LABEL = 'remind-reviewers'; + // Inactivity time in hours. + const INACTIVITY_HOURS = 24; + // The message to post on the pull request. + const REMINDER_MESSAGE = `Hi {reviewers}, your feedback is needed to move this pull request forward. This automated reminder was triggered because there has been no activity for over ${INACTIVITY_HOURS} hours. Please provide your input when you have a moment. Thank you!`; + // --------------------------------------------------- + + // Get the GitHub token from environment variables. It's passed in from the workflow file. + const token = process.env.GITHUB_TOKEN; + if (!token) { + throw new Error("GITHUB_TOKEN is not set. The workflow must pass it as an environment variable."); + } + + // Create an authenticated Octokit client. + const octokit = getOctokit(token); + // Remove 10 minutes from inactivity time to not account previous reminders as activity. + const INACTIVITY_MS = INACTIVITY_HOURS * 60 * 60 * 1000 - 10 * 60 * 1000; + const now = new Date().getTime(); + + // Get all open pull requests + const { data: pullRequests } = await octokit.rest.pulls.list({ + owner: context.repo.owner, + repo: context.repo.repo, + state: 'open', + }); + + console.log(`Found ${pullRequests.length} open pull requests.`); + + for (const pr of pullRequests) { + const hasReminderLabel = pr.labels.some(label => label.name === REMINDER_LABEL); + if (!hasReminderLabel) { + console.log(`PR #${pr.number} ('${pr.title}') does not have the '${REMINDER_LABEL}' label. Skipping.`); + continue; + } + + if (pr.draft) { + console.log(`PR #${pr.number} ('${pr.title}') is a draft. Skipping.`); + continue; + } + + const updatedAt = new Date(pr.updated_at).getTime(); + const isInactive = (now - updatedAt) > INACTIVITY_MS; + if (!isInactive) { + console.log(`PR #${pr.number} ('${pr.title}') is not inactive yet. Last updated at ${pr.updated_at}. Skipping.`); + continue; + } + + const requestedReviewers = pr.requested_reviewers.map(reviewer => `@${reviewer.login}`).join(', '); + if (requestedReviewers.length === 0) { + console.log(`PR #${pr.number} ('${pr.title}') is inactive but has no requested reviewers. Skipping.`); + continue; + } + + const finalMessage = REMINDER_MESSAGE.replace('{reviewers}', requestedReviewers); + console.log(`PR #${pr.number} ('${pr.title}') is inactive. Posting a reminder comment.`); + + await octokit.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body: finalMessage + }); + } + } catch (error) { + // If the script fails, log the error message. + console.error(error.message); + process.exit(1); + } +} + +// Execute the main function. +run(); diff --git a/.github/workflows/pr-reminder.yml b/.github/workflows/pr-reminder.yml new file mode 100644 index 0000000000..465ba93d4a --- /dev/null +++ b/.github/workflows/pr-reminder.yml @@ -0,0 +1,34 @@ +name: PR Review Reminder + +on: + # Allow manual runs. + workflow_dispatch: + schedule: + # Runs at 9:30 UTC, which is 2:30 PM IST weekdays. + - cron: '30 9 * * 1-5' + +jobs: + remind: + runs-on: ubuntu-latest + # These permissions are required for the action to post comments on pull requests. + permissions: + pull-requests: write + issues: write + + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install Dependencies + run: npm install @actions/github @actions/core + + - name: Run Reminder Script + run: node .github/scripts/reminder.js + env: + # The GITHUB_TOKEN is automatically created by Actions. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 6c8528b61a958d0d8b720887af18f3e21b4440e6 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Tue, 24 Jun 2025 13:58:30 +0530 Subject: [PATCH 0513/1298] add pagination for review reminder github action (#3443) --- .github/scripts/reminder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/reminder.js b/.github/scripts/reminder.js index a97ea5af03..cc2725e511 100644 --- a/.github/scripts/reminder.js +++ b/.github/scripts/reminder.js @@ -28,7 +28,7 @@ async function run() { const now = new Date().getTime(); // Get all open pull requests - const { data: pullRequests } = await octokit.rest.pulls.list({ + const pullRequests = await octokit.paginate(octokit.rest.pulls.list, { owner: context.repo.owner, repo: context.repo.repo, state: 'open', From e79bcadfbd61c422432fe8366dac5f0c07cfa1a8 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 24 Jun 2025 08:37:51 +0000 Subject: [PATCH 0514/1298] make codecov upload optional (#3442) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3952a29af5..42cf1b1278 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: uses: codecov/codecov-action@v4.3.1 timeout-minutes: 5 with: - fail_ci_if_error: true + fail_ci_if_error: false token: ${{ secrets.CODECOV_TOKEN }} flags: unittests From c5e0b9c36e53400be94053d0e447db82e314c628 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 24 Jun 2025 16:37:05 +0530 Subject: [PATCH 0515/1298] Unit tests for Appends to unfinalized object using BW (#3424) * fix in listobj * Update internal/storage/bucket_handle.go Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> * init BWH size to size of appendable object * UT for SetTotalSize() * UTs for areBufferedWritesSupported * logic improvements * cleanup * condition update and test fixes * do not acquire inode lock within filehandle.Write method * UTs for appends to unfinalized obj * nits * chore: Trigger CI/CD --------- Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> --- internal/fs/inode/file_test.go | 75 ++++++++++++++++++++++++++------- internal/storage/fake/bucket.go | 25 ++++++++--- 2 files changed, 78 insertions(+), 22 deletions(-) diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 180f9d8587..097957c1df 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -158,9 +158,9 @@ func (t *FileTest) createInodeWithLocalParam(fileName string, local bool) { t.in.Lock() } -func (t *FileTest) createBufferedWriteHandler(shouldInitialize bool) { +func (t *FileTest) createBufferedWriteHandler(shouldInitialize bool, openMode util.OpenMode) { // Initialize BWH for local inode created above. - initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx, util.Write) + initialized, err := t.in.InitBufferedWriteHandlerIfEligible(t.ctx, openMode) require.NoError(t.T(), err) assert.Equal(t.T(), shouldInitialize, initialized) if shouldInitialize { @@ -803,6 +803,40 @@ func (t *FileTest) TestAppendThenSync() { } } +func (t *FileTest) TestAppendToUnfinalizedObjInZB() { + // Set up the Zonal Bucket + t.bucket = fake.NewFakeBucket(&t.clock, "some_bucket", gcs.BucketType{Zonal: true}) + // Set up the backing unfinalized object. + var err error + t.initialContents = "lychee" + object, err := storageutil.CreateObject( + t.ctx, + t.bucket, + fileName, + []byte(t.initialContents)) + assert.Nil(t.T(), err) + object.Finalized = time.Time{} + t.backingObj = storageutil.ConvertObjToMinObject(object) + t.createInode() + t.in.config = &cfg.Config{Write: *getWriteConfigWithEnabledRapidAppends()} + assert.Nil(t.T(), t.in.content) + t.createBufferedWriteHandler(true, util.Append) + assert.NotNil(t.T(), t.in.bwh) + + gcsSynced, err := t.in.Write(t.ctx, []byte("juice"), int64(len(t.initialContents)), util.Append) + assert.Nil(t.T(), err) + assert.False(t.T(), gcsSynced) + + gcsSynced, err = t.in.Sync(t.ctx) + require.NoError(t.T(), err) + assert.True(t.T(), gcsSynced) + + // Read the object contents through back-door. + contents, err := storageutil.ReadObject(t.ctx, t.bucket, t.in.Name().GcsObjectName()) + require.NoError(t.T(), err) + assert.Equal(t.T(), "lycheejuice", string(contents)) +} + func (t *FileTest) TestTruncateDownwardThenSync() { testcases := []struct { name string @@ -1031,7 +1065,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() assert.Equal(t.T(), uint64(0), attrs.Size) if tc.performWrite { - t.createBufferedWriteHandler(true) + t.createBufferedWriteHandler(true, util.Write) gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) assert.False(t.T(), gcsSynced) @@ -1041,7 +1075,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) } - t.createBufferedWriteHandler(!tc.performWrite) + t.createBufferedWriteHandler(!tc.performWrite, util.Write) gcsSynced, err := t.in.Truncate(t.ctx, 10) @@ -1085,7 +1119,7 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable assert.Equal(t.T(), uint64(0), attrs.Size) if tc.performWrite { - t.createBufferedWriteHandler(true) + t.createBufferedWriteHandler(true, util.Write) gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) assert.False(t.T(), gcsSynced) @@ -1095,7 +1129,7 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) } - t.createBufferedWriteHandler(!tc.performWrite) + t.createBufferedWriteHandler(!tc.performWrite, util.Write) gcsSynced, err := t.in.Truncate(t.ctx, 10) @@ -1155,7 +1189,7 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) - t.createBufferedWriteHandler(true) + t.createBufferedWriteHandler(true, util.Write) gcsSynced, err := t.in.Write(t.ctx, []byte("hihello"), 0, util.Write) assert.Nil(t.T(), err) assert.False(t.T(), gcsSynced) @@ -1168,7 +1202,7 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { require.NoError(t.T(), err) assert.True(t.T(), gcsSynced) - t.createBufferedWriteHandler(false) + t.createBufferedWriteHandler(false, util.Write) }) } } @@ -1473,7 +1507,7 @@ func (t *FileTest) TestSetMtimeForLocalFileWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.config = &cfg.Config{Write: *getWriteConfig()} - t.createBufferedWriteHandler(true) + t.createBufferedWriteHandler(true, util.Write) // Set mtime. mtime := time.Now().UTC().Add(123 * time.Second) @@ -1561,7 +1595,7 @@ func (t *FileTest) TestCreateEmptyTempFileWhenBWHIsNotNil() { t.createInodeWithEmptyObject() } t.in.config = &cfg.Config{Write: *getWriteConfig()} - t.createBufferedWriteHandler(true) + t.createBufferedWriteHandler(true, util.Write) err := t.in.CreateEmptyTempFile(t.ctx) @@ -1630,7 +1664,7 @@ func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.config = &cfg.Config{Write: *getWriteConfig()} - t.createBufferedWriteHandler(true) + t.createBufferedWriteHandler(true, util.Write) } if tc.fileType == EmptyGCSFile { @@ -1639,7 +1673,7 @@ func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { } if tc.performWrite { - t.createBufferedWriteHandler(tc.fileType != LocalFile) + t.createBufferedWriteHandler(tc.fileType != LocalFile, util.Write) gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) assert.False(t.T(), gcsSynced) @@ -1685,7 +1719,7 @@ func (t *FileTest) TestWriteToLocalFileWhenStreamingWritesAreEnabled() { // Create a local file inode. t.createInodeWithLocalParam("test", true) t.in.config = &cfg.Config{Write: *getWriteConfig()} - t.createBufferedWriteHandler(true) + t.createBufferedWriteHandler(true, util.Write) gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) @@ -1701,7 +1735,7 @@ func (t *FileTest) TestMultipleWritesToLocalFileWhenStreamingWritesAreEnabled() t.createInodeWithLocalParam("test", true) createTime := t.in.mtimeClock.Now() t.in.config = &cfg.Config{Write: *getWriteConfig()} - t.createBufferedWriteHandler(true) + t.createBufferedWriteHandler(true, util.Write) gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) @@ -1724,7 +1758,7 @@ func (t *FileTest) TestWriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() t.in.config = &cfg.Config{Write: *getWriteConfig()} createTime := t.in.mtimeClock.Now() - t.createBufferedWriteHandler(true) + t.createBufferedWriteHandler(true, util.Write) gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) @@ -1755,7 +1789,7 @@ func (t *FileTest) TestSetMtimeOnEmptyGCSFileWhenStreamingWritesAreEnabled() { func (t *FileTest) TestSetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEnabled() { t.createInodeWithEmptyObject() t.in.config = &cfg.Config{Write: *getWriteConfig()} - t.createBufferedWriteHandler(true) + t.createBufferedWriteHandler(true, util.Write) // Initiate write call. gcsSynced, err := t.in.Write(t.ctx, []byte("hi"), 0, util.Write) assert.Nil(t.T(), err) @@ -1821,3 +1855,12 @@ func getWriteConfig() *cfg.WriteConfig { EnableStreamingWrites: true, } } + +func getWriteConfigWithEnabledRapidAppends() *cfg.WriteConfig { + return &cfg.WriteConfig{ + MaxBlocksPerFile: 10, + BlockSizeMb: 10, + EnableStreamingWrites: true, + ExperimentalEnableRapidAppends: true, + } +} diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index da6b5d9dbf..5122a67066 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -392,18 +392,32 @@ func preconditionChecks(b *bucket, req *gcs.CreateObjectRequest, contents []byte } func createOrUpdateFakeObject(b *bucket, req *gcs.CreateObjectRequest, contents []byte) (o *gcs.Object, err error) { - // Create an object record from the given attributes. - var fo fakeObject = b.mintObject(req, contents) - o = copyObject(&fo.metadata) - + var fo fakeObject // Replace an entry in or add an entry to our list of objects. existingIndex := b.objects.find(req.Name) if existingIndex < len(b.objects) { + var content []byte + if b.bucketType.Zonal { + // If the bucket type is zonal, then we will update the fake object with the appended content. + b.mu.Unlock() + existingContent, err := storageutil.ReadObject(context.Background(), b, req.Name) + b.mu.Lock() + if err != nil { + return nil, fmt.Errorf("error while reading existing content in fake object : %v", err) + } + content = append(existingContent, contents...) + } else { + // If the bucket type is not zonal, then we will overwrite. + content = contents + } + fo = b.mintObject(req, content) b.objects[existingIndex] = fo } else { + fo = b.mintObject(req, contents) b.objects = append(b.objects, fo) sort.Sort(b.objects) } + o = copyObject(&fo.metadata) if b.BucketType().Hierarchical { b.addFolderEntry(req.Name) @@ -707,7 +721,7 @@ func (b *bucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (*gcs.Min b.mu.Lock() defer b.mu.Unlock() - offset, err := w.Flush() + _, err := w.Flush() if err != nil { return nil, err } @@ -716,7 +730,6 @@ func (b *bucket) FlushPendingWrites(ctx context.Context, w gcs.Writer) (*gcs.Min if !ok { return nil, fmt.Errorf("could not type assert gcs.Writer to FakeObjectWriter") } - fakeObjectWriter.Object.Size = uint64(offset) return fakeObjectWriter.Object, nil } From afde0e2fa1e8531895276c0b834945afb2893eec Mon Sep 17 00:00:00 2001 From: Siddhartha Bagaria Date: Tue, 24 Jun 2025 20:49:46 -0700 Subject: [PATCH 0516/1298] Exclude files from cache based on name (#2043) * Exclude files from cache based on name The current logic to assume that files are meant for small random reads if the first read to them has a non-zero offset assumes the absence of file headers at the beginning of the file, and/or the absence of magic number checks. The new option to specify such files by regular expression pattern will allow the users to mark such files as random access only, even when their headers are fetched first. * Review comments * Sync new flag documentation with config yaml * fix tests --- cfg/config.go | 12 ++ cfg/params.yaml | 7 + cfg/validate.go | 4 + cfg/validate_test.go | 29 +++ cmd/root_test.go | 4 +- internal/cache/file/cache_handler.go | 35 +++- internal/cache/file/cache_handler_test.go | 21 ++- internal/cache/util/util.go | 1 + internal/fs/fs.go | 2 +- internal/fs/read_cache_test.go | 34 +++- internal/gcsx/file_cache_reader.go | 2 + internal/gcsx/file_cache_reader_test.go | 2 +- internal/gcsx/prefix_bucket.go | 4 + internal/gcsx/random_reader.go | 3 + internal/gcsx/random_reader_stretchr_test.go | 2 +- internal/gcsx/random_reader_test.go | 2 +- .../gcsx/read_manager/read_manager_test.go | 2 +- internal/monitor/bucket.go | 4 + internal/ratelimit/throttled_bucket.go | 4 + internal/storage/bucket_handle.go | 4 + internal/storage/caching/fast_stat_bucket.go | 4 + internal/storage/debug_bucket.go | 4 + internal/storage/fake/bucket.go | 4 + internal/storage/gcs/bucket.go | 7 + internal/storage/mock/testify_mock_bucket.go | 5 + internal/storage/mock_bucket.go | 4 + internal/storage/testify_mock_bucket.go | 5 + .../cache_file_for_exclude_regex_test.go | 170 ++++++++++++++++++ 28 files changed, 370 insertions(+), 11 deletions(-) create mode 100644 tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go diff --git a/cfg/config.go b/cfg/config.go index 1bcb41c9b9..c6c359d011 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -96,6 +96,8 @@ type FileCacheConfig struct { EnableParallelDownloads bool `yaml:"enable-parallel-downloads"` + ExperimentalExcludeRegex string `yaml:"experimental-exclude-regex"` + ExperimentalParallelDownloadsDefaultOn bool `yaml:"experimental-parallel-downloads-default-on"` MaxParallelDownloads int64 `yaml:"max-parallel-downloads"` @@ -445,6 +447,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("file-cache-enable-parallel-downloads", "", false, "Enable parallel downloads.") + flagSet.StringP("file-cache-experimental-exclude-regex", "", "", "Exclude file paths (in the format bucket_name/object_key) specified by this regex from file caching.") + + if err := flagSet.MarkHidden("file-cache-experimental-exclude-regex"); err != nil { + return err + } + flagSet.BoolP("file-cache-experimental-parallel-downloads-default-on", "", true, "Enable parallel downloads by default on experimental basis.") if err := flagSet.MarkHidden("file-cache-experimental-parallel-downloads-default-on"); err != nil { @@ -816,6 +824,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("file-cache.experimental-exclude-regex", flagSet.Lookup("file-cache-experimental-exclude-regex")); err != nil { + return err + } + if err := v.BindPFlag("file-cache.experimental-parallel-downloads-default-on", flagSet.Lookup("file-cache-experimental-parallel-downloads-default-on")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 02f890021e..ffd25ab9ff 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -138,6 +138,13 @@ usage: "Enable parallel downloads." default: false +- config-path: "file-cache.experimental-exclude-regex" + flag-name: "file-cache-experimental-exclude-regex" + type: "string" + usage: "Exclude file paths (in the format bucket_name/object_key) specified by this regex from file caching." + default: "" + hide-flag: true + - config-path: "file-cache.experimental-parallel-downloads-default-on" flag-name: "file-cache-experimental-parallel-downloads-default-on" type: "bool" diff --git a/cfg/validate.go b/cfg/validate.go index e3c9fd581f..671980b345 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "math" + "regexp" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) @@ -77,6 +78,9 @@ func isValidFileCacheConfig(config *FileCacheConfig) error { if config.DownloadChunkSizeMb < 1 { return errors.New(DownloadChunkSizeMBInvalidValueError) } + if _, err := regexp.Compile(config.ExperimentalExcludeRegex); err != nil { + return fmt.Errorf("invalid regex value %q provided for experimental-exclude-regex", config.ExperimentalExcludeRegex) + } return nil } diff --git a/cfg/validate_test.go b/cfg/validate_test.go index 4b77264b9d..6922a2ec54 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -45,6 +45,13 @@ func validFileCacheConfig(t *testing.T) FileCacheConfig { } } +func validFileCacheConfigWithExcludeRegex(t *testing.T, r string) FileCacheConfig { + t.Helper() + cfg := validFileCacheConfig(t) + cfg.ExperimentalExcludeRegex = r + return cfg +} + func TestValidateConfigSuccessful(t *testing.T) { testCases := []struct { name string @@ -179,6 +186,21 @@ func TestValidateConfigSuccessful(t *testing.T) { }, }, }, + { + name: "valid_file_cache_exclude_config", + config: &Config{ + Logging: LoggingConfig{LogRotate: validLogRotateConfig()}, + CacheDir: "/some/valid/path", + FileCache: validFileCacheConfigWithExcludeRegex(t, ".*"), + GcsConnection: GcsConnectionConfig{ + CustomEndpoint: "https://bing.com/search?q=dotnet", + SequentialReadSizeMb: 200, + }, + MetadataCache: MetadataCacheConfig{ + ExperimentalMetadataPrefetchOnMount: "disabled", + }, + }, + }, { name: "valid_chunk_transfer_timeout_secs", config: &Config{ @@ -385,6 +407,13 @@ func TestValidateConfig_ErrorScenarios(t *testing.T) { }, }, }, + { + name: "file_cache_exclude_regex", + config: &Config{ + Logging: LoggingConfig{LogRotate: validLogRotateConfig()}, + FileCache: validFileCacheConfigWithExcludeRegex(t, "["), + }, + }, { name: "chunk_transfer_timeout_in_negative", config: &Config{ diff --git a/cmd/root_test.go b/cmd/root_test.go index 4a596d2b06..a23f1cd8a3 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -400,7 +400,7 @@ func TestArgsParsing_FileCacheFlags(t *testing.T) { }{ { name: "Test file cache flags.", - args: []string{"gcsfuse", "--file-cache-cache-file-for-range-read", "--file-cache-download-chunk-size-mb=20", "--file-cache-enable-crc", "--cache-dir=/some/valid/dir", "--file-cache-enable-parallel-downloads", "--file-cache-max-parallel-downloads=40", "--file-cache-max-size-mb=100", "--file-cache-parallel-downloads-per-file=2", "--file-cache-enable-o-direct=false", "abc", "pqr"}, + args: []string{"gcsfuse", "--file-cache-cache-file-for-range-read", "--file-cache-download-chunk-size-mb=20", "--file-cache-enable-crc", "--cache-dir=/some/valid/dir", "--file-cache-experimental-exclude-regex=.*", "--file-cache-enable-parallel-downloads", "--file-cache-max-parallel-downloads=40", "--file-cache-max-size-mb=100", "--file-cache-parallel-downloads-per-file=2", "--file-cache-enable-o-direct=false", "abc", "pqr"}, expectedConfig: &cfg.Config{ CacheDir: "/some/valid/dir", FileCache: cfg.FileCacheConfig{ @@ -408,6 +408,7 @@ func TestArgsParsing_FileCacheFlags(t *testing.T) { DownloadChunkSizeMb: 20, EnableCrc: true, EnableParallelDownloads: true, + ExperimentalExcludeRegex: ".*", ExperimentalParallelDownloadsDefaultOn: true, MaxParallelDownloads: 40, MaxSizeMb: 100, @@ -426,6 +427,7 @@ func TestArgsParsing_FileCacheFlags(t *testing.T) { DownloadChunkSizeMb: 200, EnableCrc: false, EnableParallelDownloads: false, + ExperimentalExcludeRegex: "", ExperimentalParallelDownloadsDefaultOn: true, MaxParallelDownloads: int64(max(16, 2*runtime.NumCPU())), MaxSizeMb: -1, diff --git a/internal/cache/file/cache_handler.go b/internal/cache/file/cache_handler.go index 835dbd5e42..a119abad07 100644 --- a/internal/cache/file/cache_handler.go +++ b/internal/cache/file/cache_handler.go @@ -17,6 +17,8 @@ package file import ( "fmt" "os" + "path" + "regexp" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" @@ -51,9 +53,22 @@ type CacheHandler struct { // mu guards the handling of insertion into and eviction from file cache. mu locker.Locker + + // excludeRegex is the compiled regex for excluding files from cache + excludeRegex *regexp.Regexp } -func NewCacheHandler(fileInfoCache *lru.Cache, jobManager *downloader.JobManager, cacheDir string, filePerm os.FileMode, dirPerm os.FileMode) *CacheHandler { +func NewCacheHandler(fileInfoCache *lru.Cache, jobManager *downloader.JobManager, cacheDir string, filePerm os.FileMode, dirPerm os.FileMode, excludeRegex string) *CacheHandler { + var compiledRegex *regexp.Regexp + if excludeRegex != "" { + var err error + compiledRegex, err = regexp.Compile(excludeRegex) + if err != nil { + logger.Warnf("Failed to compile exclude regex %q: %v", excludeRegex, err) + compiledRegex = nil + } + } + return &CacheHandler{ fileInfoCache: fileInfoCache, jobManager: jobManager, @@ -61,6 +76,7 @@ func NewCacheHandler(fileInfoCache *lru.Cache, jobManager *downloader.JobManager filePerm: filePerm, dirPerm: dirPerm, mu: locker.New("FileCacheHandler", func() {}), + excludeRegex: compiledRegex, } } @@ -202,6 +218,11 @@ func (chr *CacheHandler) GetCacheHandle(object *gcs.MinObject, bucket gcs.Bucket chr.mu.Lock() defer chr.mu.Unlock() + // Check if file should be excluded from cache + if chr.shouldExcludeFromCache(bucket, object) { + return nil, util.ErrFileExcludedFromCacheByRegex + } + // If cacheForRangeRead is set to False, initialOffset is non-zero (i.e. random read) // and entry for file doesn't already exist in fileInfoCache then no need to // create file in cache. @@ -274,3 +295,15 @@ func (chr *CacheHandler) Destroy() (err error) { chr.jobManager.Destroy() return } + +// shouldExcludeFromCache checks if the object should be excluded from cache +// based on the configured regex pattern. +func (chr *CacheHandler) shouldExcludeFromCache(bucket gcs.Bucket, object *gcs.MinObject) bool { + if chr.excludeRegex == nil { + return false + } + + // Get the GCS name of the object and create the cloud path in the format bucket/object. + cloudPath := path.Join(bucket.Name(), bucket.GCSName(object)) + return chr.excludeRegex.MatchString(cloudPath) +} diff --git a/internal/cache/file/cache_handler_test.go b/internal/cache/file/cache_handler_test.go index 42107f4190..76fd98c291 100644 --- a/internal/cache/file/cache_handler_test.go +++ b/internal/cache/file/cache_handler_test.go @@ -88,7 +88,7 @@ func initializeCacheHandlerTestArgs(t *testing.T, fileCacheConfig *cfg.FileCache util.DefaultDirPerm, cacheDir, DefaultSequentialReadSizeMb, fileCacheConfig, common.NewNoopMetrics()) // Mocked cached handler object. - cacheHandler := NewCacheHandler(cache, jobManager, cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) + cacheHandler := NewCacheHandler(cache, jobManager, cacheDir, util.DefaultFilePerm, util.DefaultDirPerm, fileCacheConfig.ExperimentalExcludeRegex) // Follow consistency, local-cache file, entry in fileInfo cache and job should exist initially. fileInfoKeyName := addTestFileInfoEntryInCache(t, cache, object, storage.TestBucketName) @@ -540,6 +540,25 @@ func Test_GetCacheHandle_IfLocalFileGetsDeleted(t *testing.T) { assert.Equal(t, downloader.NotStarted, existingJob.GetStatus().Name) } +func Test_GetCacheHandle_ExcludeFromCache(t *testing.T) { + regex := ".*object_1" + cacheDir := path.Join(os.Getenv("HOME"), "CacheHandlerTest/dir") + chTestArgs := initializeCacheHandlerTestArgs(t, &cfg.FileCacheConfig{EnableCrc: true, ExperimentalExcludeRegex: regex}, cacheDir) + + // Check cache handle is not created for excluded file + chTestArgs.object.Name = "object_1" + cacheHandle, err := chTestArgs.cacheHandler.GetCacheHandle(chTestArgs.object, chTestArgs.bucket, false, 0) + assert.True(t, errors.Is(err, util.ErrFileExcludedFromCacheByRegex)) + assert.Nil(t, cacheHandle) + + // Check cache handle is created for file not excluded. + chTestArgs.object.Name = "object_2" + cacheHandle, err = chTestArgs.cacheHandler.GetCacheHandle(chTestArgs.object, chTestArgs.bucket, false, 0) + assert.NoError(t, err) + assert.Nil(t, cacheHandle.validateCacheHandle()) + +} + func Test_GetCacheHandle_CacheForRangeRead(t *testing.T) { tbl := []struct { name string diff --git a/internal/cache/util/util.go b/internal/cache/util/util.go index 6cf8ee6c28..f1674bcbd3 100644 --- a/internal/cache/util/util.go +++ b/internal/cache/util/util.go @@ -38,6 +38,7 @@ var ( ErrFallbackToGCS = errors.New("read via gcs") ErrFileNotPresentInCache = errors.New("file is not present in cache") ErrCacheHandleNotRequiredForRandomRead = errors.New("cacheFileForRangeRead is false, read type random read and fileInfo entry is absent") + ErrFileExcludedFromCacheByRegex = errors.New("file excluded from cache by regex") ) const ( diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 9bf6842cca..5eaa33f7bf 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -249,7 +249,7 @@ func createFileCacheHandler(serverCfg *ServerConfig) (fileCacheHandler *file.Cac } jobManager := downloader.NewJobManager(fileInfoCache, filePerm, dirPerm, cacheDir, serverCfg.SequentialReadSizeMb, &serverCfg.NewConfig.FileCache, serverCfg.MetricHandle) - fileCacheHandler = file.NewCacheHandler(fileInfoCache, jobManager, cacheDir, filePerm, dirPerm) + fileCacheHandler = file.NewCacheHandler(fileInfoCache, jobManager, cacheDir, filePerm, dirPerm, serverCfg.NewConfig.FileCache.ExperimentalExcludeRegex) return } diff --git a/internal/fs/read_cache_test.go b/internal/fs/read_cache_test.go index 2bf75aa9a8..5f1e860ffc 100644 --- a/internal/fs/read_cache_test.go +++ b/internal/fs/read_cache_test.go @@ -57,6 +57,7 @@ func init() { var CacheDir = path.Join(os.Getenv("HOME"), "cache-dir") var FileCacheDir = path.Join(CacheDir, util.FileCache) +var CacheExcludeName = "do_not_cache" // A collection of tests for a file system where the file cache is enabled // with cache-file-for-range-read set to False. @@ -68,9 +69,10 @@ func (t *FileCacheTest) SetUpTestSuite() { t.serverCfg.ImplicitDirectories = true t.serverCfg.NewConfig = &cfg.Config{ FileCache: cfg.FileCacheConfig{ - MaxSizeMb: FileCacheSizeInMb, - CacheFileForRangeRead: false, - EnableCrc: true, + MaxSizeMb: FileCacheSizeInMb, + CacheFileForRangeRead: false, + EnableCrc: true, + ExperimentalExcludeRegex: CacheExcludeName, }, CacheDir: cfg.ResolvedPath(CacheDir), } @@ -191,6 +193,28 @@ func sequentialToRandomReadShouldPopulateCache(t *fsTest) { AssertTrue(reflect.DeepEqual(string(cachedContent[:100]), objectContent[:100])) } +func excludedFileShouldNotPopulateCache(t *fsTest, cacheDir string) { + objectContent := generateRandomString(DefaultObjectSizeInMb * util.MiB) + objects := map[string]string{CacheExcludeName: objectContent} + err := t.createObjects(objects) + AssertEq(nil, err) + filePath := path.Join(mntDir, CacheExcludeName) + file, err := os.OpenFile(filePath, os.O_RDWR|syscall.O_DIRECT, util.DefaultFilePerm) + defer closeFile(file) + AssertEq(nil, err) + + // reading object with name matching the exclude regex should not cache the object into file. + buf := make([]byte, len(objectContent)) + _, err = file.Read(buf) + AssertEq(nil, err) + AssertEq(objectContent, string(buf)) + + objectPath := util.GetObjectPath(bucket.Name(), CacheExcludeName) + downloadPath := util.GetDownloadPath(cacheDir, objectPath) + _, err = os.Stat(downloadPath) + AssertTrue(os.IsNotExist(err)) +} + func (t *FileCacheTest) ReadShouldChangeLRU() { objectName1 := DefaultObjectName + "1" objectContent1 := generateRandomString(DefaultObjectSizeInMb * util.MiB) @@ -257,6 +281,10 @@ func (t *FileCacheTest) SequentialToRandomReadShouldPopulateCache() { sequentialToRandomReadShouldPopulateCache(&t.fsTest) } +func (t *FileCacheTest) ExcludedFileShouldNotPopulateCache() { + excludedFileShouldNotPopulateCache(&t.fsTest, FileCacheDir) +} + func (t *FileCacheTest) CacheFilePermission() { cacheFilePermissionTest(&t.fsTest, util.DefaultFilePerm) } diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index 1d9252daf5..2e1093dd7b 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -143,6 +143,8 @@ func (fc *FileCacheReader) tryReadingFromFileCache(ctx context.Context, p []byte isSequential = false err = nil return 0, false, nil + case errors.Is(err, cacheUtil.ErrFileExcludedFromCacheByRegex): + return 0, false, nil default: err = fmt.Errorf("tryReadingFromFileCache: GetCacheHandle failed: %w", err) return 0, false, err diff --git a/internal/gcsx/file_cache_reader_test.go b/internal/gcsx/file_cache_reader_test.go index 368b8fc337..67de87585a 100644 --- a/internal/gcsx/file_cache_reader_test.go +++ b/internal/gcsx/file_cache_reader_test.go @@ -72,7 +72,7 @@ func (t *fileCacheReaderTest) SetupTest() { t.cacheDir = path.Join(os.Getenv("HOME"), "test_cache_dir") lruCache := lru.NewCache(cacheMaxSize) t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{EnableCrc: false}, common.NewNoopMetrics()) - t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) + t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm, "") t.reader = NewFileCacheReader(t.object, t.mockBucket, t.cacheHandler, true, common.NewNoopMetrics()) t.ctx = context.Background() } diff --git a/internal/gcsx/prefix_bucket.go b/internal/gcsx/prefix_bucket.go index 095ca0358d..b7884ce3ed 100644 --- a/internal/gcsx/prefix_bucket.go +++ b/internal/gcsx/prefix_bucket.go @@ -327,3 +327,7 @@ func (b *prefixBucket) NewMultiRangeDownloader( mrd, err = b.wrapped.NewMultiRangeDownloader(ctx, mReq) return } + +func (b *prefixBucket) GCSName(object *gcs.MinObject) string { + return b.wrappedName(b.wrapped.GCSName(object)) +} diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 042490986e..182bd2f96e 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -268,6 +268,9 @@ func (rr *randomReader) tryReadingFromFileCache(ctx context.Context, // False and there doesn't already exist file in cache. isSeq = false return 0, false, nil + } else if errors.Is(err, cacheutil.ErrFileExcludedFromCacheByRegex) { + // Fall back to GCS if the file is explicitly excluded from cache. + return 0, false, nil } return 0, false, fmt.Errorf("tryReadingFromFileCache: while creating CacheHandle instance: %w", err) diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 10051013b6..b282e68e8f 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -76,7 +76,7 @@ func (t *RandomReaderStretchrTest) SetupTest() { t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{ EnableCrc: false, }, nil) - t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) + t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm, "") // Set up the reader. rr := NewRandomReader(t.object, t.mockBucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics(), nil, nil) diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index c1fa9a8486..189e46cfcb 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -178,7 +178,7 @@ func (t *RandomReaderTest) SetUp(ti *TestInfo) { t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{ EnableCrc: false, }, common.NewNoopMetrics()) - t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) + t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm, "") // Set up the reader. rr := NewRandomReader(t.object, t.bucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics(), nil, nil) diff --git a/internal/gcsx/read_manager/read_manager_test.go b/internal/gcsx/read_manager/read_manager_test.go index da05d10d7b..ade63177bb 100644 --- a/internal/gcsx/read_manager/read_manager_test.go +++ b/internal/gcsx/read_manager/read_manager_test.go @@ -64,7 +64,7 @@ func (t *readManagerTest) readManagerConfig(fileCacheEnable bool) *ReadManagerCo cacheDir := path.Join(os.Getenv("HOME"), "test_cache_dir") lruCache := lru.NewCache(cacheMaxSize) jobManager := downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{EnableCrc: false}, common.NewNoopMetrics()) - config.FileCacheHandler = file.NewCacheHandler(lruCache, jobManager, cacheDir, util.DefaultFilePerm, util.DefaultDirPerm) + config.FileCacheHandler = file.NewCacheHandler(lruCache, jobManager, cacheDir, util.DefaultFilePerm, util.DefaultDirPerm, "") } else { config.FileCacheHandler = nil } diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index c362332b60..638dc87ee6 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -211,6 +211,10 @@ func (mb *monitoringBucket) NewMultiRangeDownloader( return } +func (mb *monitoringBucket) GCSName(obj *gcs.MinObject) string { + return mb.wrapped.GCSName(obj) +} + // recordReader increments the reader count when it's opened or closed. func recordReader(ctx context.Context, metricHandle common.MetricHandle, ioMethod string) { metricHandle.GCSReaderCount(ctx, 1, ioMethod) diff --git a/internal/ratelimit/throttled_bucket.go b/internal/ratelimit/throttled_bucket.go index 00270a82a5..9745099743 100644 --- a/internal/ratelimit/throttled_bucket.go +++ b/internal/ratelimit/throttled_bucket.go @@ -297,6 +297,10 @@ func (b *throttledBucket) NewMultiRangeDownloader( return } +func (b *throttledBucket) GCSName(obj *gcs.MinObject) string { + return b.wrapped.GCSName(obj) +} + //////////////////////////////////////////////////////////////////////// // readerCloser //////////////////////////////////////////////////////////////////////// diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 2b4d791d28..4eaf2542c5 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -722,6 +722,10 @@ func (bh *bucketHandle) NewMultiRangeDownloader( return } +func (bh *bucketHandle) GCSName(obj *gcs.MinObject) string { + return obj.Name +} + func isStorageConditionsNotEmpty(conditions storage.Conditions) bool { return conditions != (storage.Conditions{}) } diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index 8f6c7ddb72..adde06d5ae 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -514,3 +514,7 @@ func (b *fastStatBucket) NewMultiRangeDownloader( mrd, err = b.wrapped.NewMultiRangeDownloader(ctx, req) return } + +func (b *fastStatBucket) GCSName(obj *gcs.MinObject) string { + return b.wrapped.GCSName(obj) +} diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index 7289cde2b4..26bbd4425d 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -376,3 +376,7 @@ func (b *debugBucket) NewMultiRangeDownloader( } return } + +func (b *debugBucket) GCSName(obj *gcs.MinObject) string { + return obj.Name +} diff --git a/internal/storage/fake/bucket.go b/internal/storage/fake/bucket.go index 5122a67066..a254714e63 100644 --- a/internal/storage/fake/bucket.go +++ b/internal/storage/fake/bucket.go @@ -1265,3 +1265,7 @@ func (b *bucket) NewMultiRangeDownloader( return &fakeMultiRangeDownloader{obj: &obj}, nil } + +func (b *bucket) GCSName(obj *gcs.MinObject) string { + return obj.Name +} diff --git a/internal/storage/gcs/bucket.go b/internal/storage/gcs/bucket.go index 28afd7cee7..bb8b84a4e8 100644 --- a/internal/storage/gcs/bucket.go +++ b/internal/storage/gcs/bucket.go @@ -191,4 +191,11 @@ type Bucket interface { RenameFolder(ctx context.Context, folderName string, destinationFolderId string) (*Folder, error) CreateFolder(ctx context.Context, folderName string) (*Folder, error) + + // GCSName returns the original GCS name for the object. + // + // Some Bucket implementations modify the Name field of the MinObject before + // returning it, in which case, users must use this function to get the + // original name. + GCSName(object *MinObject) string } diff --git a/internal/storage/mock/testify_mock_bucket.go b/internal/storage/mock/testify_mock_bucket.go index 8d05af81b7..48b5129d43 100644 --- a/internal/storage/mock/testify_mock_bucket.go +++ b/internal/storage/mock/testify_mock_bucket.go @@ -156,3 +156,8 @@ func (m *TestifyMockBucket) NewMultiRangeDownloader( } return nil, args.Error(1) } + +func (m *TestifyMockBucket) GCSName(obj *gcs.MinObject) string { + args := m.Called(obj) + return args.Get(0).(string) +} diff --git a/internal/storage/mock_bucket.go b/internal/storage/mock_bucket.go index 18033c510b..d163963d13 100644 --- a/internal/storage/mock_bucket.go +++ b/internal/storage/mock_bucket.go @@ -574,6 +574,10 @@ func (m *mockBucket) RenameFolder(ctx context.Context, folderName string, destin return } +func (m *mockBucket) GCSName(obj *gcs.MinObject) string { + return obj.Name +} + func (m *mockBucket) NewMultiRangeDownloader( p0 context.Context, p1 *gcs.MultiRangeDownloaderRequest) (o0 gcs.MultiRangeDownloader, o1 error) { // Get a file name and line number for the caller. diff --git a/internal/storage/testify_mock_bucket.go b/internal/storage/testify_mock_bucket.go index 9ba7fdd7b9..a97e3b6d4a 100644 --- a/internal/storage/testify_mock_bucket.go +++ b/internal/storage/testify_mock_bucket.go @@ -156,3 +156,8 @@ func (m *TestifyMockBucket) NewMultiRangeDownloader( } return nil, args.Error(1) } + +func (m *TestifyMockBucket) GCSName(obj *gcs.MinObject) string { + args := m.Called(obj) + return args.Get(0).(string) +} diff --git a/tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go b/tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go new file mode 100644 index 0000000000..61ed387e2f --- /dev/null +++ b/tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go @@ -0,0 +1,170 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_cache + +import ( + "context" + "fmt" + "log" + "path" + "testing" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" +) + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type cacheFileForExcludeRegexTest struct { + flags []string + storageClient *storage.Client + ctx context.Context +} + +func (s *cacheFileForExcludeRegexTest) Setup(t *testing.T) { + setupForMountedDirectoryTests() + // Clean up the cache directory path as gcsfuse don't clean up on mounting. + operations.RemoveDir(cacheDirPath) + mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient, testDirName) +} + +func (s *cacheFileForExcludeRegexTest) Teardown(t *testing.T) { + setup.SaveGCSFuseLogFileInCaseOfFailure(t) + setup.UnmountGCSFuseAndDeleteLogFile(rootDir) +} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +func (s *cacheFileForExcludeRegexTest) TestReadsForExcludedFile(t *testing.T) { + testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSizeForRangeRead, t) + + expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, zeroOffset, t) + expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset1000, t) + + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) + validate(expectedOutcome1, structuredReadLogs[0], true, false, 1, t) + validate(expectedOutcome2, structuredReadLogs[1], false, false, 1, t) + validateFileIsNotCached(testFileName, t) +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestCacheFileForExcludeRegexTest(t *testing.T) { + ts := &cacheFileForExcludeRegexTest{ctx: context.Background()} + // Create storage client before running tests. + closeStorageClient := client.CreateStorageClientWithCancel(&ts.ctx, &ts.storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + t.Errorf("closeStorageClient failed: %v", err) + } + }() + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + // Run with cache directory pointing to RAM based dir + ramCacheDir := path.Join("/dev/shm", cacheDirName) + + tests := []struct { + flags gcsfuseTestFlags + onlyDirTest bool + }{ + { + flags: gcsfuseTestFlags{ + cliFlags: []string{"--implicit-dirs", "--file-cache-experimental-exclude-regex=."}, + cacheSize: cacheCapacityForRangeReadTestInMiB, + cacheFileForRangeRead: false, + fileName: configFileName, + enableParallelDownloads: false, + enableODirect: false, + cacheDirPath: getDefaultCacheDirPathForTests(), + }, + }, + { + flags: gcsfuseTestFlags{ + cliFlags: []string{"--file-cache-experimental-exclude-regex=."}, + cacheSize: cacheCapacityForRangeReadTestInMiB, + cacheFileForRangeRead: false, + fileName: configFileName, + enableParallelDownloads: false, + enableODirect: false, + cacheDirPath: ramCacheDir, + }, + }, + { + flags: gcsfuseTestFlags{ + cliFlags: []string{"--file-cache-experimental-exclude-regex=."}, + cacheSize: cacheCapacityForRangeReadTestInMiB, + cacheFileForRangeRead: true, + fileName: configFileName, + enableParallelDownloads: false, + enableODirect: false, + cacheDirPath: ramCacheDir, + }, + }, + { + flags: gcsfuseTestFlags{ + // Exclude regex is set to bucket name as the prefix of the string, so should exclude all objects. + cliFlags: []string{fmt.Sprintf("--file-cache-experimental-exclude-regex=^%s/", setup.TestBucket())}, + cacheSize: cacheCapacityForRangeReadTestInMiB, + cacheFileForRangeRead: true, + fileName: configFileName, + enableParallelDownloads: false, + enableODirect: false, + cacheDirPath: ramCacheDir, + }, + }, + { + flags: gcsfuseTestFlags{ + // Exclude regex is set to the only-dir value which is not present in local paths, but should be present in all cloud paths. + cliFlags: []string{fmt.Sprintf("--file-cache-experimental-exclude-regex=^%s/%s/", setup.TestBucket(), onlyDirMounted)}, + cacheSize: cacheCapacityForRangeReadTestInMiB, + cacheFileForRangeRead: true, + fileName: configFileName, + enableParallelDownloads: false, + enableODirect: false, + cacheDirPath: ramCacheDir, + }, + onlyDirTest: true, + }, + } + for _, test := range tests { + test.flags = appendClientProtocolConfigToFlagSet([]gcsfuseTestFlags{test.flags})[0] + if test.onlyDirTest && setup.OnlyDirMounted() == "" { + continue + } + configFilePath := createConfigFile(&test.flags) + ts.flags = []string{"--config-file=" + configFilePath} + if test.flags.cliFlags != nil { + ts.flags = append(ts.flags, test.flags.cliFlags...) + } + log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) + } +} From 757cd599375cc1522ce215ffad4d6a90c49d300b Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:24:13 +0530 Subject: [PATCH 0517/1298] Add new flag --enable-readdirplus for enabling readdirplus (#3438) * Add new flag --enable-readdirplus for enabling readdirplus * Added --experimental- prefix to flag * Update cfg/params.yaml * Updates after running go generate ./... goimports -w . go fmt ./... go vet ./... go build . --------- Co-authored-by: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> --- cfg/config.go | 12 ++++ cfg/params.yaml | 7 +++ cmd/root_test.go | 144 +++++++++++++++++++++++++---------------------- 3 files changed, 96 insertions(+), 67 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index c6c359d011..829ee9a532 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -114,6 +114,8 @@ type FileSystemConfig struct { DisableParallelDirops bool `yaml:"disable-parallel-dirops"` + ExperimentalEnableReaddirplus bool `yaml:"experimental-enable-readdirplus"` + FileMode Octal `yaml:"file-mode"` FuseOptions []string `yaml:"fuse-options"` @@ -405,6 +407,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } + flagSet.BoolP("experimental-enable-readdirplus", "", false, "Enables ReadDirPlus capability") + + if err := flagSet.MarkHidden("experimental-enable-readdirplus"); err != nil { + return err + } + flagSet.IntP("experimental-grpc-conn-pool-size", "", 1, "The number of gRPC channel in grpc client.") if err := flagSet.MarkDeprecated("experimental-grpc-conn-pool-size", "Experimental flag: can be removed in a minor release."); err != nil { @@ -788,6 +796,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("file-system.experimental-enable-readdirplus", flagSet.Lookup("experimental-enable-readdirplus")); err != nil { + return err + } + if err := v.BindPFlag("gcs-connection.grpc-conn-pool-size", flagSet.Lookup("experimental-grpc-conn-pool-size")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index ffd25ab9ff..cb732b3851 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -190,6 +190,13 @@ default: false hide-flag: true +- config-path: "file-system.experimental-enable-readdirplus" + flag-name: "experimental-enable-readdirplus" + type: "bool" + usage: "Enables ReadDirPlus capability" + default: false + hide-flag: true + - config-path: "file-system.file-mode" flag-name: "file-mode" type: "octal" diff --git a/cmd/root_test.go b/cmd/root_test.go index a23f1cd8a3..a282aef301 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -740,20 +740,21 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { }{ { name: "normal", - args: []string{"gcsfuse", "--dir-mode=0777", "--disable-parallel-dirops", "--file-mode=0666", "--o", "ro", "--gid=7", "--ignore-interrupts=false", "--kernel-list-cache-ttl-secs=300", "--rename-dir-limit=10", "--temp-dir=~/temp", "--uid=8", "--precondition-errors=false", "abc", "pqr"}, + args: []string{"gcsfuse", "--dir-mode=0777", "--disable-parallel-dirops", "--experimental-enable-readdirplus", "--file-mode=0666", "--o", "ro", "--gid=7", "--ignore-interrupts=false", "--kernel-list-cache-ttl-secs=300", "--rename-dir-limit=10", "--temp-dir=~/temp", "--uid=8", "--precondition-errors=false", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileSystem: cfg.FileSystemConfig{ - DirMode: 0777, - DisableParallelDirops: true, - FileMode: 0666, - FuseOptions: []string{"ro"}, - Gid: 7, - IgnoreInterrupts: false, - KernelListCacheTtlSecs: 300, - RenameDirLimit: 10, - TempDir: cfg.ResolvedPath(path.Join(hd, "temp")), - PreconditionErrors: false, - Uid: 8, + DirMode: 0777, + DisableParallelDirops: true, + ExperimentalEnableReaddirplus: true, + FileMode: 0666, + FuseOptions: []string{"ro"}, + Gid: 7, + IgnoreInterrupts: false, + KernelListCacheTtlSecs: 300, + RenameDirLimit: 10, + TempDir: cfg.ResolvedPath(path.Join(hd, "temp")), + PreconditionErrors: false, + Uid: 8, }, }, }, @@ -762,17 +763,18 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { args: []string{"gcsfuse", "--dir-mode=777", "--file-mode=666", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileSystem: cfg.FileSystemConfig{ - DirMode: 0777, - DisableParallelDirops: false, - FileMode: 0666, - FuseOptions: []string{}, - Gid: -1, - IgnoreInterrupts: true, - KernelListCacheTtlSecs: 0, - RenameDirLimit: 0, - TempDir: "", - PreconditionErrors: true, - Uid: -1, + DirMode: 0777, + DisableParallelDirops: false, + ExperimentalEnableReaddirplus: false, + FileMode: 0666, + FuseOptions: []string{}, + Gid: -1, + IgnoreInterrupts: true, + KernelListCacheTtlSecs: 0, + RenameDirLimit: 0, + TempDir: "", + PreconditionErrors: true, + Uid: -1, }, }, }, @@ -781,17 +783,18 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { args: []string{"gcsfuse", "--dir-mode=777", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "--file-mode=666", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileSystem: cfg.FileSystemConfig{ - DirMode: 0777, - DisableParallelDirops: false, - FileMode: 0666, - FuseOptions: []string{}, - Gid: -1, - IgnoreInterrupts: true, - KernelListCacheTtlSecs: 0, - RenameDirLimit: 200000, - TempDir: "", - PreconditionErrors: true, - Uid: -1, + DirMode: 0777, + DisableParallelDirops: false, + ExperimentalEnableReaddirplus: false, + FileMode: 0666, + FuseOptions: []string{}, + Gid: -1, + IgnoreInterrupts: true, + KernelListCacheTtlSecs: 0, + RenameDirLimit: 200000, + TempDir: "", + PreconditionErrors: true, + Uid: -1, }, }, }, @@ -800,17 +803,18 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { args: []string{"gcsfuse", "--dir-mode=777", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=true", "--file-mode=666", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileSystem: cfg.FileSystemConfig{ - DirMode: 0777, - DisableParallelDirops: false, - FileMode: 0666, - FuseOptions: []string{}, - Gid: -1, - IgnoreInterrupts: true, - KernelListCacheTtlSecs: 0, - RenameDirLimit: 0, - TempDir: "", - PreconditionErrors: true, - Uid: -1, + DirMode: 0777, + DisableParallelDirops: false, + ExperimentalEnableReaddirplus: false, + FileMode: 0666, + FuseOptions: []string{}, + Gid: -1, + IgnoreInterrupts: true, + KernelListCacheTtlSecs: 0, + RenameDirLimit: 0, + TempDir: "", + PreconditionErrors: true, + Uid: -1, }, }, }, @@ -819,17 +823,18 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { args: []string{"gcsfuse", "--dir-mode=777", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "--rename-dir-limit=15000", "--file-mode=666", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileSystem: cfg.FileSystemConfig{ - DirMode: 0777, - DisableParallelDirops: false, - FileMode: 0666, - FuseOptions: []string{}, - Gid: -1, - IgnoreInterrupts: true, - KernelListCacheTtlSecs: 0, - RenameDirLimit: 15000, - TempDir: "", - PreconditionErrors: true, - Uid: -1, + DirMode: 0777, + DisableParallelDirops: false, + ExperimentalEnableReaddirplus: false, + FileMode: 0666, + FuseOptions: []string{}, + Gid: -1, + IgnoreInterrupts: true, + KernelListCacheTtlSecs: 0, + RenameDirLimit: 15000, + TempDir: "", + PreconditionErrors: true, + Uid: -1, }, }, }, @@ -838,17 +843,18 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { args: []string{"gcsfuse", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileSystem: cfg.FileSystemConfig{ - DirMode: 0755, - DisableParallelDirops: false, - FileMode: 0644, - FuseOptions: []string{}, - Gid: -1, - IgnoreInterrupts: true, - KernelListCacheTtlSecs: 0, - RenameDirLimit: 0, - TempDir: "", - PreconditionErrors: true, - Uid: -1, + DirMode: 0755, + DisableParallelDirops: false, + ExperimentalEnableReaddirplus: false, + FileMode: 0644, + FuseOptions: []string{}, + Gid: -1, + IgnoreInterrupts: true, + KernelListCacheTtlSecs: 0, + RenameDirLimit: 0, + TempDir: "", + PreconditionErrors: true, + Uid: -1, }, }, }, @@ -898,6 +904,10 @@ func TestArgsParsing_FileSystemFlagsThrowsError(t *testing.T) { name: "invalid_disable_parallel_dirops", args: []string{"gcsfuse", "--disable-parallel-dirops=abc", "abc", "pqr"}, }, + { + name: "invalid_experimental_enable_readdirplus", + args: []string{"gcsfuse", "--experimental-enable-readdirplus=abc", "abc", "pqr"}, + }, } for _, tc := range tests { From b693139a44d724a58d1458ec86340cdfbba759c0 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 25 Jun 2025 10:51:04 +0530 Subject: [PATCH 0518/1298] Avoid creating unnecessary contexts in fs.go (#3440) --- internal/fs/fs.go | 60 ++++++++++---------------------------- internal/util/util.go | 8 ----- internal/util/util_test.go | 15 ---------- 3 files changed, 15 insertions(+), 68 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 5eaa33f7bf..7ea661bee0 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1479,9 +1479,7 @@ func (fs *fileSystem) LookUpInode( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Find the parent directory in question. fs.mu.Lock() @@ -1515,9 +1513,7 @@ func (fs *fileSystem) GetInodeAttributes( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Find the inode. fs.mu.Lock() @@ -1543,9 +1539,7 @@ func (fs *fileSystem) SetInodeAttributes( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Find the inode. fs.mu.Lock() @@ -1619,9 +1613,7 @@ func (fs *fileSystem) MkDir( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Find the parent. fs.mu.Lock() @@ -1678,9 +1670,7 @@ func (fs *fileSystem) MkNode( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } if (op.Mode & (iofs.ModeNamedPipe | iofs.ModeSocket)) != 0 { return syscall.ENOTSUP @@ -1819,9 +1809,7 @@ func (fs *fileSystem) CreateFile( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Create the child. var child inode.Inode @@ -1870,9 +1858,7 @@ func (fs *fileSystem) CreateSymlink( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Find the parent. fs.mu.Lock() @@ -1940,9 +1926,7 @@ func (fs *fileSystem) RmDir( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Find the parent. fs.mu.Lock() @@ -2048,9 +2032,7 @@ func (fs *fileSystem) Rename( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Find the old and new parents. fs.mu.Lock() @@ -2409,9 +2391,7 @@ func (fs *fileSystem) Unlink( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } fs.mu.Lock() @@ -2506,9 +2486,7 @@ func (fs *fileSystem) ReadDir( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Find the handle. fs.mu.Lock() @@ -2588,9 +2566,7 @@ func (fs *fileSystem) ReadFile( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Save readOp in context for access in logs. ctx = context.WithValue(ctx, gcsx.ReadOp, op) @@ -2653,9 +2629,7 @@ func (fs *fileSystem) WriteFile( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Find the inode( and file handle in case of appends). @@ -2695,9 +2669,7 @@ func (fs *fileSystem) SyncFile( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Find the inode. fs.mu.Lock() @@ -2728,9 +2700,7 @@ func (fs *fileSystem) FlushFile( if fs.newConfig.FileSystem.IgnoreInterrupts { // When ignore interrupts config is set, we are creating a new context not // cancellable by parent context. - var cancel context.CancelFunc - ctx, cancel = util.IsolateContextFromParentContext(ctx) - defer cancel() + ctx = context.Background() } // Find the inode. fs.mu.Lock() diff --git a/internal/util/util.go b/internal/util/util.go index b4650b22d0..d6e317bfe5 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -15,7 +15,6 @@ package util import ( - "context" "fmt" "math" "os" @@ -105,10 +104,3 @@ func BytesToHigherMiBs(bytes uint64) uint64 { const bytesInOneMiB uint64 = 1 << 20 return uint64(math.Ceil(float64(bytes) / float64(bytesInOneMiB))) } - -// IsolateContextFromParentContext creates a copy of the parent context which is -// not cancelled when parent context is cancelled. -func IsolateContextFromParentContext(ctx context.Context) (context.Context, context.CancelFunc) { - ctx = context.WithoutCancel(ctx) - return context.WithCancel(ctx) -} diff --git a/internal/util/util_test.go b/internal/util/util_test.go index cbd6b1f8c1..c30363e0f4 100644 --- a/internal/util/util_test.go +++ b/internal/util/util_test.go @@ -15,7 +15,6 @@ package util import ( - "context" "math" "os" "path/filepath" @@ -214,17 +213,3 @@ func (ts *UtilTest) TestBytesToHigherMiBs() { assert.Equal(ts.T(), tc.mib, BytesToHigherMiBs(tc.bytes)) } } - -func (ts *UtilTest) TestIsolateContextFromParentContext() { - parentCtx, parentCtxCancel := context.WithCancel(context.Background()) - - // Call the method and cancel the parent context. - newCtx, newCtxCancel := IsolateContextFromParentContext(parentCtx) - parentCtxCancel() - - // Validate new context is not cancelled after parent's cancellation. - assert.NoError(ts.T(), newCtx.Err()) - // Cancel the new context and validate. - newCtxCancel() - assert.ErrorIs(ts.T(), newCtx.Err(), context.Canceled) -} From b254639f964203b8a9215035852c84f150d4b8d3 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:30:52 +0530 Subject: [PATCH 0519/1298] v3 documentation changes - Streaming writes (#3382) * v3 documentation changes * revert formatting change * Update docs/semantics.md --- README.md | 44 ++++++++++++--- docs/semantics.md | 119 +++++++++++++++++++++------------------- docs/troubleshooting.md | 13 +++++ 3 files changed, 110 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index 29573aceb6..5fdfcdd04b 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,48 @@ # Current status -Starting with V1.0, Cloud Storage FUSE is Generally Available and supported by Google, provided that it is used within its documented supported applications, platforms, and limits. Support requests, feature requests, and general questions should be submitted as a support request via Google Cloud support channels or via GitHub [here](https://github.com/GoogleCloudPlatform/gcsfuse/issues). +Cloud Storage FUSE continues to evolve with significant enhancements in v2 and v3, and is Generally Available and +supported by Google starting with v1.0, Cloud Storage FUSE is Generally Available and supported by Google, provided that +it is used within its documented supported applications, platforms, and limits. Support requests, feature requests, and +general questions should be submitted as a support request via Google Cloud support channels or via +GitHub[here](https://github.com/GoogleCloudPlatform/gcsfuse/issues). -Cloud Storage FUSE is open source software, released under the +Cloud Storage FUSE is open source software, released under the [Apache license](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/LICENSE). -## _New_ Cloud Storage FUSE V2.x features +## Cloud Storage Fuse v3 features + +### Streaming Writes + +Streaming writes is the new default write path that uploads data directly to Google Cloud Storage (GCS) as it is +written. +The previous write path temporarily staged the entire write in a local file, uploading to GCS on close or fsync. +This reduces both latency and disk space usage, making it particularly beneficial for large, sequential writes, such as +checkpoint writes, which can be up to _**40% faster**_, as observed in training runs. +See [streaming writes](https://github.com/googlecloudplatform/gcsfuse/blob/master/docs/semantics.md#with-streaming-writes) +for more details. + +### File Cache Parallel Downloads (Default) + +Parallel downloads uses multiple workers to download a file in parallel using the file cache directory as a prefetch +buffer. We recommend using parallel downloads for single-threaded read scenarios that load large files such as model +serving and checkpoint restores, with up to _**9x faster model load times**_. +See [Using Parallel Downloads](https://cloud.google.com/storage/docs/cloud-storage-fuse/file-caching#configure-parallel-downloads) +for more details. + +### Automatic Optimization for High-Performance Machine Types + +GCSFuse now automatically optimizes its configuration when running on specific high-performance Google Cloud machine +types to maximize performance for demanding workloads and effectively utilize the machine's capability. Manually set +values at the time of mount will override these defaults. + +## Cloud Storage FUSE v2 features + Cloud Storage FUSE V2 provides important stability, functionality, and performance enhancements. + ### File Cache The file cache allows repeat file reads to be served from a local, faster cache storage of choice, such as a Local SSD, Persistent Disk, or even in-memory /tmpfs. The Cloud Storage FUSE file cache makes AI/ML training faster and more cost-effective by reducing the time spent waiting for data, with up to _**2.3x faster training time and 3.4x higher throughput**_ observed in training runs. This is especially valuable for multi epoch training and can serve small and random I/O operations significantly faster. The file cache feature is disabled by default and is enabled by passing a directory to 'cache-dir'. See [overview of caching](https://cloud.google.com/storage/docs/gcsfuse-cache) for more details. -### Parallel Downloads -Parallel downloads uses multiple workers to download a file in parallel using the file cache directory as a prefetch buffer. We recommend using parallel downloads for single-threaded read scenarios that load large files such as model serving and checkpoint restores, with up to _**9x faster model load times**_ . See [Using Parallel Downloads](https://cloud.google.com/storage/docs/cloud-storage-fuse/file-caching#configure-parallel-downloads) for more details. - -### Streaming Writes -Streaming writes is a new write path that uploads data directly to Google Cloud Storage (GCS) as it's written. The previous, and currently default write path temporarily stages the entire write in a local directory, uploading to GCS on close/fsync. This reduces both latency and disk space usage, making it particularly beneficial for large, sequential writes such as checkpoint writes which are up to _**40% faster with streaming writes,**_ as observed in training runs. See [streaming wrties](https://github.com/googlecloudplatform/gcsfuse/blob/master/docs/semantics.md#with-streaming-writes) for more details. - # ABOUT ## What is Cloud Storage FUSE? diff --git a/docs/semantics.md b/docs/semantics.md index 0e5a74446d..88c22b94c3 100644 --- a/docs/semantics.md +++ b/docs/semantics.md @@ -4,11 +4,71 @@ Cloud Storage FUSE makes API calls to Cloud Storage to read an object directly, without downloading it to a local directory. A TCP connection is established, in which the entire object, or just portions as specified by the application/operating system via an offset, can be read back. -Files that have not been modified are read portion by portion on demand. Cloud Storage FUSE uses a heuristic to detect when a file is being read sequentially, and will issue fewer, larger read requests to Cloud Storage in this case, increasing performance. +Files that have not been modified are read portion by portion on demand. Cloud Storage FUSE uses a heuristic to detect when a file is being read sequentially, and will issue fewer, larger read requests to Cloud Storage in this case, increasing performance. ## Writes -### Default Write Path +Starting with v3.0, streaming writes is the default write path. For more details, see the `With Streaming Writes` +section below. You can revert to the previous default write path (staging writes to a temporary file on disk) using the +`--enable-streaming-writes=false` flag or `write:enable-streaming-writes: false` in the config file. + +### Streaming Writes - Default Write Path + +Starting with version 2.9.1, and becoming the default in v3.0.0, GCSFuse supports streaming-writes, which is a new write +path that uploads data directly to Google Cloud Storage (GCS) as it's written without fully staging the file in the +temp-dir. This reduces both latency and disk space usage, making it particularly beneficial for large, sequential writes +such as checkpoints. Streaming writes can be enabled using `--enable-streaming-writes` flag or +`write:enable-streaming-writes:true` in the config file (Default starting GCSFuse v3.0.0). + +**Memory Usage:** Each file opened for streaming writes will consume +approximately 64MiB of RAM during the upload process. This memory is released +when the file handle is closed. This should be considered when planning resource +allocation for applications using streaming writes. + +Memory usage can be controlled using the `--write-global-max-blocks` flag or `write:global-max-blocks` config. The +default value is 4 for low-spec machines and 1600 for high-spec machines. One block is used per file, which means that +on low-spec machines, writes will automatically fall back to legacy staged writes if more than 4 files are concurrently +opened for streaming writes. + +#### Note on Streaming Writes: + +- **New files, Sequential Writes:** Streaming writes are designed for sequential + writes to a new file only. Modifying existing files, or doing out-of-order + writes (whether from the same file handle or concurrent writes from multiple + file handles) will cause GCSFuse to automatically revert to the existing write + path of staging writes to a temporary file on disk. An informational log + message will be emitted when this fallback occurs. + +- **Concurrent Writes to the Same File:** While concurrent writes to the same + file are possible, they are not the primary use case for this initial phase of + streaming writes. If a (rare, often server-related) error occurs during + concurrent writes, all file handles must be closed before any future writes + can resume. This phase of streaming writes is optimized for single-stream + writes to new files, such as for AI/ML checkpointing. + +- **File System Semantics Change:** + - **FSync operation does not finalize the object:** When streaming writes + are enabled, the fsync operation will not finalize the object on GCS. + Instead, the object will be finalized only when the file is closed. + Only finalized objects are visible to the end user. This is a key + difference from the default non-streaming-writes behavior and should be considered when + using streaming writes. Relying on fsync for data durability with + streaming writes enabled is not recommended. Data is guaranteed to be + on GCS only after the file is closed. + - **Rename Operation Syncs the File:** Rename operation on a file undergoing + writes via streaming writes will be finalized and then renamed. This means + that any follow up writes will automatically revert to the existing + behavior of staging writes to a temporary file on disk. + - **Read Operations During Write:** Reads are now supported on files that are being + written to with streaming writes. However, performing a read operation will finalize the object on GCS. Any + subsequent write operations to that file will then automatically revert to legacy staged writes. Applications should + generally avoid reading from a file while it is being written to using streaming writes, as this will prematurely + finalize the object. + - **Truncate During Writes:** If a file is truncated downwards using truncate() or ftruncate() while streaming + writes + are in progress, the file on GCS is finalized, and any subsequent writes revert to legacy staged writes. + +### Staged Writes - Legacy Write Path Files are written locally as a temporary file (temp-file for short) whose location is controlled by the flag `--temp-dir`. Upon closing or fsyncing @@ -47,61 +107,6 @@ when writing large files. error. Then the temp-file will not be deleted until you do an fsync for that file, or unmount the bucket. -### With Streaming Writes - -Starting with version 2.9.1, GCSFuse supports streaming-writes, which is a new -write -path that uploads data directly to Google Cloud Storage (GCS) as it's written -without fully staging the file in the temp-dir. This reduces both latency and -disk space usage, making it particularly beneficial for large, sequential writes -such as checkpoints. Streaming writes can be enabled using -`--enable-streaming-writes` flag or `write:enable-streaming-writes:true` in the -config file. - -**Memory Usage:** Each file opened for streaming writes will consume -approximately 64MB of RAM during the upload process. This memory is released -when the file handle is closed. This should be considered when planning resource -allocation for applications using streaming writes. - -#### Note on Streaming Writes: - -- **New files, Sequential Writes:** Streaming writes are designed for sequential - writes to a new file only. Modifying existing files, or doing out-of-order - writes (whether from the same file handle or concurrent writes from multiple - file handles) will cause GCSFuse to automatically revert to the existing write - path of staging writes to a temporary file on disk. An informational log - message will be emitted when this fallback occurs. - -- **Concurrent Writes to the Same File:** While concurrent writes to the same - file are possible, they are not the primary use case for this initial phase of - streaming writes. If a (rare, often server-related) error occurs during - concurrent writes, all file handles must be closed before any future writes - can resume. This phase of streaming writes is optimized for single-stream - writes to new files, such as for AI/ML checkpointing. - -- **File System Semantics Change:** - - **FSync operation does not finalize the object:** When streaming writes - are enabled, the fsync operation will not finalize the object on GCS. - Instead, the object will be finalized only when the file is closed. - Only finalized objects are visible to the end user. This is a key - difference from the default non-streaming-writes behavior and should be considered when - using streaming writes. Relying on fsync for data durability with - streaming writes enabled is not recommended. Data is guaranteed to be - on GCS only after the file is closed. - - **Rename Operation Syncs the File:** Rename operation on a file undergoing - writes via streaming writes will be finalized and then renamed. This means - that any follow up writes will automatically revert to the existing - behavior of staging writes to a temporary file on disk. - - **Read Operations During Write:** Today the application can read the data - when the writes are in progress for that file. With buffered writes, the - application will not be able to read the file until the corresponding GCS - object is finalized i.e., fclose() is called. Applications should not read from a file while it is being written to - using streaming writes. - - **Write Stalls and Chunk Uploads:** Streaming writes do not currently - implement chunk-level timeouts or retries. Write operations may stall, and - chunk uploads that encounter errors will eventually fail after the default - 32-second deadline. - ___ # Concurrency diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index b97d548768..278fd7aa82 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -185,3 +185,16 @@ performance bottleneck. \ Starting with [version 2.12.0](https://github.com/GoogleCloudPlatform/gcsfuse/releases/tag/v2.12.0), you might observe a slight increase in CPU utilization when the file cache is enabled. This occurs because GCSFuse uses parallel threads to download data to the read cache. While this dramatically improves read performance, it may consume slightly more CPU than in previous versions. If this increased CPU usage negatively impacts your workload's performance, you can disable this behavior by setting the `file-cache:enable-parallel-downloads` configuration option to `false`. + +### Writes still using staged writes even though streaming writes are enabled. +If you observe that GCSFuse is still utilizing staged writes despite streaming writes being enabled, several factors could be at play. + +- **Global Max Blocks Limit Reached:** You might encounter warning logs indicating that streaming write blocks cannot be allocated because the global maximum blocks limit has been reached. In such cases, consider increasing the `--write-global-max-blocks` limit if sufficient memory resources are available. + +- **Unsupported Write Operations:** Streaming writes only work for sequential writes to new or empty files. GCSFuse will automatically revert to staged writes for the following scenarios: + - Modifying existing files (non-zero size). + - Performing out-of-order writes. + - Reading from a file while writes are in progress (this action finalizes the object and subsequent writes will fall back). + - Truncating a file downwards while writes are in progress (this action also finalizes the object and subsequent writes will fall back). + +An informational log message will be emitted by GCSFuse whenever a fallback to staged writes occurs, providing details on the reason. \ No newline at end of file From 14ecb15b5f1004437c5273cd33ca455ad04e792b Mon Sep 17 00:00:00 2001 From: codechanges Date: Wed, 25 Jun 2025 14:25:36 +0530 Subject: [PATCH 0520/1298] Add Recommended Sample GCSFuse config YAML for different workflows (#3426) * Add tuned config yamls for GCSFuse * Add cache dir for file cache * add message for ttl * changed structure * new line * add comments for flags * Add readme file * add section on read_ahead_kb * minor changes * minor change --- samples/gcsfuse_config/README.md | 72 +++++++++++++++++++ .../gpu/config_file/checkpointing.yaml | 13 ++++ .../gpu/config_file/serving.yaml | 11 +++ .../gpu/config_file/training.yaml | 11 +++ .../tpu/config_file/checkpointing.yaml | 13 ++++ .../tpu/config_file/serving.yaml | 11 +++ .../tpu/config_file/training.yaml | 11 +++ 7 files changed, 142 insertions(+) create mode 100644 samples/gcsfuse_config/README.md create mode 100644 samples/gcsfuse_config/gpu/config_file/checkpointing.yaml create mode 100644 samples/gcsfuse_config/gpu/config_file/serving.yaml create mode 100644 samples/gcsfuse_config/gpu/config_file/training.yaml create mode 100644 samples/gcsfuse_config/tpu/config_file/checkpointing.yaml create mode 100644 samples/gcsfuse_config/tpu/config_file/serving.yaml create mode 100644 samples/gcsfuse_config/tpu/config_file/training.yaml diff --git a/samples/gcsfuse_config/README.md b/samples/gcsfuse_config/README.md new file mode 100644 index 0000000000..e81f50bd12 --- /dev/null +++ b/samples/gcsfuse_config/README.md @@ -0,0 +1,72 @@ +# **GCSFuse Sample Configurations** + +This directory contains sample GCSFuse configuration files optimized for common machine learning workloads. These configurations are intended for use with vanilla GCSFuse and may require minor adjustments for your specific environment. + +The configurations are organized into two main directories: + +* **GPU:** Configurations optimized for GPU-based workloads. +* **TPU:** Configurations optimized for TPU-based workloads. + +Within each directory, you will find configurations tailored for the following workflows: + +1. **Model Training:** Optimized for training machine learning models. +2. **Model Serving/Inference:** Optimized for serving predictions with trained models. +3. **Checkpointing:** Designed for saving model state during training, also applicable to JAX Just-In-Time (JIT) Cache workflows. + +Choose the configuration that best matches your hardware platform (GPU or TPU) and workload. + +## **Deployment Instructions** + +To use these sample configurations, you can use the `gcsfuse` command with the `--config-file` flag. + +For example, to mount a bucket for model training on a GPU machine: + +``` +gcsfuse --config-file GPU/training.yaml +``` + +Replace `` with the name of your GCS bucket and `` with the desired mount point on your local filesystem. + +### **Usage with GCE, SLURM, and Ansible** + +These configurations are designed to be versatile and can be used in various environments, including Google Compute Engine (GCE) instances and SLURM clusters. + +#### **GCE and SLURM** + +In a GCE or SLURM environment, you can use the same `gcsfuse` command to mount your GCS buckets on your compute nodes. It's recommended to automate this process as part of your instance startup scripts or SLURM job prolog scripts. This ensures that the required GCS buckets are mounted and available to your applications before they start. + +Here is an [example](https://github.com/GoogleCloudPlatform/cluster-toolkit/blob/51c51f2c83383a8f241cd0ef8a8998413393bff5/examples/hypercompute_clusters/a3u-slurm-ubuntu-gcs/a3u-slurm-ubuntu-gcs.yaml#L193) of using config with Ansible. + +## **Key Considerations** + +* **File Cache:** + + * For GPU-based workloads, it is highly recommended to use a Local SSD (LSSD) for the file cache directory for optimal performance. + * For TPU-based workloads, using a RAM disk for the file cache can provide a significant speed advantage. +* **Metadata Cache TTL:** + + * The sample configurations may use a long or infinite Time-to-Live (TTL) for the metadata cache and there might be consistency implications. More details can be found [here](https://cloud.google.com/storage/docs/cloud-storage-fuse/performance#increase-metadata-cache-values). +* **Pre-populating Metadata Cache:** + + * To improve the performance of subsequent file and directory lookups, you can pre-populate the metadata cache after mounting the bucket by running the following command. More details can be found [here](https://cloud.google.com/storage/docs/cloud-storage-fuse/performance#pre-populate-the-metadata-cache): + +``` + ls -R > /dev/null +``` +* **Tuning `read-ahead-kb`** + + * The `read-ahead-kb` flag controls the size of kernel read-ahead buffer which in turns impacts number of requests to GCSFuse. Tuning this value can significantly improve performance for sequential reads of large files. + * A good starting point for `read-ahead-kb` is to set it to a value slightly larger than the average read size of your application. Typical recommendation is to set it to `1024`. +``` + sudo sh -c 'echo 1024 > /sys/block/sda/queue/read_ahead_kb' +``` + + +## **Prerequisites and Notes** + +* **Placeholders:** Remember to replace all placeholder values within the YAML configuration files with your specific environment details before use. +* **Permissions:** Ensure that the service account or user running GCSFuse has the necessary permissions to access the specified GCS bucket. + +## **Further Information** + +For comprehensive details on GCSFuse configuration and performance tuning, please consult the official documentation: [https://cloud.google.com/storage/docs/gcsfuse](https://cloud.google.com/storage/docs/gcsfuse) diff --git a/samples/gcsfuse_config/gpu/config_file/checkpointing.yaml b/samples/gcsfuse_config/gpu/config_file/checkpointing.yaml new file mode 100644 index 0000000000..726f01a696 --- /dev/null +++ b/samples/gcsfuse_config/gpu/config_file/checkpointing.yaml @@ -0,0 +1,13 @@ +implicit-dirs: true # Create implicit directories locally when accessed +cache-dir: /tmp # Use LSSD backing on GPU and RAM Disk backing on TPU +metadata-cache: + negative-ttl-secs: 0 # Disable caching for lookups of files/dirs that don't exist + ttl-secs: -1 # Keep cached metadata (file attributes, types) indefinitely time-wise + stat-cache-max-size-mb: -1 # Allow unlimited size for the file attribute (stat) cache + type-cache-max-size-mb: -1 # Allow unlimited size for the file/directory type cache +file-cache: + max-size-mb: -1 # Allow unlimited size for the file content cache + cache-file-for-range-read: true # Cache the entire file when any part is read sequentially + enable-parallel-downloads: true # Use multiple streams to download file content faster +write: + enable-streaming-writes: true # Enable streaming writes diff --git a/samples/gcsfuse_config/gpu/config_file/serving.yaml b/samples/gcsfuse_config/gpu/config_file/serving.yaml new file mode 100644 index 0000000000..7c42b1492b --- /dev/null +++ b/samples/gcsfuse_config/gpu/config_file/serving.yaml @@ -0,0 +1,11 @@ +implicit-dirs: true # Create implicit directories locally when accessed +cache-dir: /tmp # Use LSSD backing on GPU and RAM Disk backing on TPU +metadata-cache: + negative-ttl-secs: 0 # Disable caching for lookups of files/dirs that don't exist + ttl-secs: -1 # Keep cached metadata (file attributes, types) indefinitely time-wise + stat-cache-max-size-mb: -1 # Allow unlimited size for the file attribute (stat) cache + type-cache-max-size-mb: -1 # Allow unlimited size for the file/directory type cache +file-cache: + max-size-mb: -1 # Allow unlimited size for the file content cache + cache-file-for-range-read: true # Cache the entire file when any part is read sequentially + enable-parallel-downloads: true # Use multiple streams to download file content faster diff --git a/samples/gcsfuse_config/gpu/config_file/training.yaml b/samples/gcsfuse_config/gpu/config_file/training.yaml new file mode 100644 index 0000000000..1167e8d81a --- /dev/null +++ b/samples/gcsfuse_config/gpu/config_file/training.yaml @@ -0,0 +1,11 @@ +implicit-dirs: true # Create implicit directories locally when accessed +metadata-cache: + negative-ttl-secs: 0 # Disable caching for lookups of files/dirs that don't exist + ttl-secs: -1 # Keep cached metadata (file attributes, types) indefinitely time-wise + stat-cache-max-size-mb: -1 # Allow unlimited size for the file attribute (stat) cache + type-cache-max-size-mb: -1 # Allow unlimited size for the file/directory type cache +# if enabling the file cache, uncomment out to use # +# cache-dir: /tmp # Use LSSD backing on GPU and RAM Disk backing on TPU +# file-cache: +# max-size-mb: # Allow DATASET_SIZE worth of size for the file content cache +# cache-file-for-range-read: true # Cache the entire file when any part is read sequentially diff --git a/samples/gcsfuse_config/tpu/config_file/checkpointing.yaml b/samples/gcsfuse_config/tpu/config_file/checkpointing.yaml new file mode 100644 index 0000000000..726f01a696 --- /dev/null +++ b/samples/gcsfuse_config/tpu/config_file/checkpointing.yaml @@ -0,0 +1,13 @@ +implicit-dirs: true # Create implicit directories locally when accessed +cache-dir: /tmp # Use LSSD backing on GPU and RAM Disk backing on TPU +metadata-cache: + negative-ttl-secs: 0 # Disable caching for lookups of files/dirs that don't exist + ttl-secs: -1 # Keep cached metadata (file attributes, types) indefinitely time-wise + stat-cache-max-size-mb: -1 # Allow unlimited size for the file attribute (stat) cache + type-cache-max-size-mb: -1 # Allow unlimited size for the file/directory type cache +file-cache: + max-size-mb: -1 # Allow unlimited size for the file content cache + cache-file-for-range-read: true # Cache the entire file when any part is read sequentially + enable-parallel-downloads: true # Use multiple streams to download file content faster +write: + enable-streaming-writes: true # Enable streaming writes diff --git a/samples/gcsfuse_config/tpu/config_file/serving.yaml b/samples/gcsfuse_config/tpu/config_file/serving.yaml new file mode 100644 index 0000000000..7c42b1492b --- /dev/null +++ b/samples/gcsfuse_config/tpu/config_file/serving.yaml @@ -0,0 +1,11 @@ +implicit-dirs: true # Create implicit directories locally when accessed +cache-dir: /tmp # Use LSSD backing on GPU and RAM Disk backing on TPU +metadata-cache: + negative-ttl-secs: 0 # Disable caching for lookups of files/dirs that don't exist + ttl-secs: -1 # Keep cached metadata (file attributes, types) indefinitely time-wise + stat-cache-max-size-mb: -1 # Allow unlimited size for the file attribute (stat) cache + type-cache-max-size-mb: -1 # Allow unlimited size for the file/directory type cache +file-cache: + max-size-mb: -1 # Allow unlimited size for the file content cache + cache-file-for-range-read: true # Cache the entire file when any part is read sequentially + enable-parallel-downloads: true # Use multiple streams to download file content faster diff --git a/samples/gcsfuse_config/tpu/config_file/training.yaml b/samples/gcsfuse_config/tpu/config_file/training.yaml new file mode 100644 index 0000000000..c1c75f90df --- /dev/null +++ b/samples/gcsfuse_config/tpu/config_file/training.yaml @@ -0,0 +1,11 @@ +implicit-dirs: true # Create implicit directories locally when accessed +metadata-cache: + negative-ttl-secs: 0 # Disable caching for lookups of files/dirs that don't exist + ttl-secs: -1 # Keep cached metadata (file attributes, types) indefinitely time-wise + stat-cache-max-size-mb: -1 # Allow unlimited size for the file attribute (stat) cache + type-cache-max-size-mb: -1 # Allow unlimited size for the file/directory type cache +# if enabling the file cache, uncomment out to use # +# cache-dir: /tmp # Use LSSD backing on GPU and RAM Disk backing on TPU +#file-cache: +# max-size-mb: # Allow DATASET_SIZE worth of size for the file content cache +# cache-file-for-range-read: true # Cache the entire file when any part is read sequentially From 8e048653a3af5bb82a27c5f13aeccaf2ca802fa3 Mon Sep 17 00:00:00 2001 From: codechanges Date: Wed, 25 Jun 2025 16:05:57 +0530 Subject: [PATCH 0521/1298] correct read_ahead_kb sample (#3449) --- samples/gcsfuse_config/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/gcsfuse_config/README.md b/samples/gcsfuse_config/README.md index e81f50bd12..4e94d5f004 100644 --- a/samples/gcsfuse_config/README.md +++ b/samples/gcsfuse_config/README.md @@ -58,7 +58,8 @@ Here is an [example](https://github.com/GoogleCloudPlatform/cluster-toolkit/blob * The `read-ahead-kb` flag controls the size of kernel read-ahead buffer which in turns impacts number of requests to GCSFuse. Tuning this value can significantly improve performance for sequential reads of large files. * A good starting point for `read-ahead-kb` is to set it to a value slightly larger than the average read size of your application. Typical recommendation is to set it to `1024`. ``` - sudo sh -c 'echo 1024 > /sys/block/sda/queue/read_ahead_kb' + export GCSFUSEMOUNT=/your/container/mountpoint + echo 1024 | sudo tee /sys/class/bdi/0:$(stat -c "%d" $GCSFUSEMOUNT)/read_ahead_kb ``` From b6b7a200b41149482e1f0f118b7a3aa2e8ace595 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 26 Jun 2025 09:40:34 +0530 Subject: [PATCH 0522/1298] Use read-type constants from "common" package instead of "internal/util" package. (#3452) --- internal/cache/file/downloader/job.go | 3 +- .../file/downloader/parallel_downloads_job.go | 3 +- internal/gcsx/client_readers/gcs_reader.go | 7 ++--- .../gcsx/client_readers/gcs_reader_test.go | 16 +++++------ internal/gcsx/client_readers/range_reader.go | 3 +- internal/gcsx/file_cache_reader.go | 5 ++-- internal/gcsx/random_reader.go | 13 ++++----- internal/gcsx/random_reader_stretchr_test.go | 28 +++++++++---------- internal/util/util.go | 10 ------- 9 files changed, 36 insertions(+), 52 deletions(-) diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index 62b5e57650..7487bae699 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -32,7 +32,6 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "golang.org/x/net/context" "golang.org/x/sync/semaphore" ) @@ -327,7 +326,7 @@ func (job *Job) downloadObjectToFile(cacheFile *os.File) (err error) { if newReader != nil { readHandle = newReader.ReadHandle() } - common.CaptureGCSReadMetrics(job.cancelCtx, job.metricsHandle, util.Sequential, newReaderLimit-start) + common.CaptureGCSReadMetrics(job.cancelCtx, job.metricsHandle, common.ReadTypeSequential, newReaderLimit-start) } maxRead := min(ReadChunkSize, newReaderLimit-start) diff --git a/internal/cache/file/downloader/parallel_downloads_job.go b/internal/cache/file/downloader/parallel_downloads_job.go index e6affb23c0..e74d3f4560 100644 --- a/internal/cache/file/downloader/parallel_downloads_job.go +++ b/internal/cache/file/downloader/parallel_downloads_job.go @@ -26,7 +26,6 @@ import ( cacheutil "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "golang.org/x/sync/errgroup" ) @@ -61,7 +60,7 @@ func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, e } }() - common.CaptureGCSReadMetrics(ctx, job.metricsHandle, util.Parallel, end-start) + common.CaptureGCSReadMetrics(ctx, job.metricsHandle, common.ReadTypeParallel, end-start) // Use standard copy function if O_DIRECT is disabled and memory aligned // buffer otherwise. diff --git a/internal/gcsx/client_readers/gcs_reader.go b/internal/gcsx/client_readers/gcs_reader.go index d227509a2d..2e111a6ac7 100644 --- a/internal/gcsx/client_readers/gcs_reader.go +++ b/internal/gcsx/client_readers/gcs_reader.go @@ -24,7 +24,6 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) // ReaderType represents different types of go-sdk gcs readers. @@ -90,7 +89,7 @@ func NewGCSReader(obj *gcs.MinObject, bucket gcs.Bucket, config *GCSReaderConfig sequentialReadSizeMb: config.SequentialReadSizeMb, rangeReader: NewRangeReader(obj, bucket, config.ReadConfig, config.MetricHandle), mrr: NewMultiRangeReader(obj, config.MetricHandle, config.MrdWrapper), - readType: util.Sequential, + readType: common.ReadTypeSequential, } } @@ -150,7 +149,7 @@ func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.R // readerType specifies the go-sdk interface to use for reads. func (gr *GCSReader) readerType(start int64, end int64, bucketType gcs.BucketType) ReaderType { bytesToBeRead := end - start - if gr.readType == util.Random && bytesToBeRead < maxReadSize && bucketType.Zonal { + if gr.readType == common.ReadTypeRandom && bytesToBeRead < maxReadSize && bucketType.Zonal { return MultiRangeReaderType } return RangeReaderType @@ -178,7 +177,7 @@ func (gr *GCSReader) getReadInfo(start int64, size int64) (int64, error) { func (gr *GCSReader) determineEnd(start int64) int64 { end := int64(gr.object.Size) if gr.seeks >= minSeeksForRandom { - gr.readType = util.Random + gr.readType = common.ReadTypeRandom averageReadBytes := gr.totalReadBytes / gr.seeks if averageReadBytes < maxReadSize { randomReadSize := int64(((averageReadBytes / MB) + 1) * MB) diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go index 3dedd469cd..afc79ffa88 100644 --- a/internal/gcsx/client_readers/gcs_reader_test.go +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -107,7 +107,7 @@ func (t *gcsReaderTest) Test_NewGCSReader() { assert.Equal(t.T(), object, gcsReader.object) assert.Equal(t.T(), t.mockBucket, gcsReader.bucket) - assert.Equal(t.T(), testUtil.Sequential, gcsReader.readType) + assert.Equal(t.T(), common.ReadTypeSequential, gcsReader.readType) } func (t *gcsReaderTest) Test_ReadAt_InvalidOffset() { @@ -290,7 +290,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Sequential, testUtil.Sequential}, + expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -298,7 +298,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Sequential, testUtil.Sequential}, + expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -306,7 +306,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Random, testUtil.Random, testUtil.Random}, + expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeRandom, common.ReadTypeRandom, common.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, { @@ -314,7 +314,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{testUtil.Sequential, testUtil.Sequential, testUtil.Random, testUtil.Random, testUtil.Random}, + expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeRandom, common.ReadTypeRandom, common.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, } @@ -325,7 +325,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { require.Equal(t.T(), len(tc.readRanges), len(tc.expectedReadTypes), "Test Parameter Error: readRanges and expectedReadTypes should have same length") t.gcsReader.mrr.isMRDInUse = false t.gcsReader.seeks = 0 - t.gcsReader.rangeReader.readType = testUtil.Sequential + t.gcsReader.rangeReader.readType = common.ReadTypeSequential t.gcsReader.expectedOffset = 0 t.object.Size = uint64(tc.dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) @@ -608,7 +608,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateZonalRandomReads() { t.gcsReader.rangeReader.reader = nil t.gcsReader.mrr.isMRDInUse = false t.gcsReader.seeks = 0 - t.gcsReader.rangeReader.readType = testUtil.Sequential + t.gcsReader.rangeReader.readType = common.ReadTypeSequential t.gcsReader.expectedOffset = 0 t.gcsReader.totalReadBytes = 0 t.object.Size = 20 * MiB @@ -640,7 +640,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateZonalRandomReads() { assert.NoError(t.T(), err) assert.Equal(t.T(), uint64(seeks), t.gcsReader.seeks) - assert.Equal(t.T(), testUtil.Random, t.gcsReader.readType) + assert.Equal(t.T(), common.ReadTypeRandom, t.gcsReader.readType) assert.Equal(t.T(), int64(readRange[1]), t.gcsReader.expectedOffset) } } diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index 1f6dab8663..89550dbb52 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -27,7 +27,6 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) const ( @@ -280,7 +279,7 @@ func (rr *RangeReader) startRead(start int64, end int64) error { rr.limit = end requestedDataSize := end - start - common.CaptureGCSReadMetrics(ctx, rr.metricHandle, util.Sequential, requestedDataSize) + common.CaptureGCSReadMetrics(ctx, rr.metricHandle, common.ReadTypeSequential, requestedDataSize) return nil } diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index 2e1093dd7b..cc1fb3afce 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -29,7 +29,6 @@ import ( cacheUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/fuse/fuseops" ) @@ -119,9 +118,9 @@ func (fc *FileCacheReader) tryReadingFromFileCache(ctx context.Context, p []byte logger.Tracef("%.13v -> %s", requestID, requestOutput) - readType := util.Random + readType := common.ReadTypeRandom if isSequential { - readType = util.Sequential + readType = common.ReadTypeSequential } captureFileCacheMetrics(ctx, fc.metricHandle, readType, bytesRead, cacheHit, executionTime) }() diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 182bd2f96e..835b43c519 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -30,7 +30,6 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" - "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/jacobsa/fuse/fuseops" "golang.org/x/net/context" ) @@ -112,7 +111,7 @@ func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb i limit: -1, seeks: 0, totalReadBytes: 0, - readType: util.Sequential, + readType: common.ReadTypeSequential, sequentialReadSizeMb: sequentialReadSizeMb, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, @@ -248,9 +247,9 @@ func (rr *randomReader) tryReadingFromFileCache(ctx context.Context, // Here rr.fileCacheHandle will not be nil since we return from the above in those cases. logger.Tracef("%.13v -> %s", requestId, requestOutput) - readType := util.Random + readType := common.ReadTypeRandom if isSeq { - readType = util.Sequential + readType = common.ReadTypeSequential } captureFileCacheMetrics(ctx, rr.metricHandle, readType, n, cacheHit, executionTime) }() @@ -512,7 +511,7 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { rr.limit = end requestedDataSize := end - start - common.CaptureGCSReadMetrics(ctx, rr.metricHandle, util.Sequential, requestedDataSize) + common.CaptureGCSReadMetrics(ctx, rr.metricHandle, common.ReadTypeSequential, requestedDataSize) return } @@ -550,7 +549,7 @@ func (rr *randomReader) getReadInfo( // (average read size in bytes rounded up to the next MiB). end = int64(rr.object.Size) if rr.seeks >= minSeeksForRandom { - rr.readType = util.Random + rr.readType = common.ReadTypeRandom averageReadBytes := rr.totalReadBytes / rr.seeks if averageReadBytes < maxReadSize { randomReadSize := int64(((averageReadBytes / MiB) + 1) * MiB) @@ -580,7 +579,7 @@ func (rr *randomReader) getReadInfo( // readerType specifies the go-sdk interface to use for reads. func readerType(readType string, start int64, end int64, bucketType gcs.BucketType) ReaderType { bytesToBeRead := end - start - if readType == util.Random && bytesToBeRead < maxReadSize && bucketType.Zonal { + if readType == common.ReadTypeRandom && bytesToBeRead < maxReadSize && bucketType.Zonal { return MultiRangeReader } return RangeReader diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index b282e68e8f..945937f5fc 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -141,7 +141,7 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo_Sequential() { end, err := t.rr.wrapped.getReadInfo(tc.start, 10) assert.NoError(t.T(), err) - assert.Equal(t.T(), testutil.Sequential, t.rr.wrapped.readType) + assert.Equal(t.T(), common.ReadTypeSequential, t.rr.wrapped.readType) assert.Equal(t.T(), tc.expectedEnd, end) }) } @@ -174,7 +174,7 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo_Random() { end, err := t.rr.wrapped.getReadInfo(tc.start, 10) assert.NoError(t.T(), err) - assert.Equal(t.T(), testutil.Random, t.rr.wrapped.readType) + assert.Equal(t.T(), common.ReadTypeRandom, t.rr.wrapped.readType) assert.Equal(t.T(), tc.expectedEnd, end) }) } @@ -191,7 +191,7 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { }{ { name: "ZonalBucketRandomRead", - readType: testutil.Random, + readType: common.ReadTypeRandom, start: 50, end: 68, bucketType: gcs.BucketType{Zonal: true}, @@ -199,7 +199,7 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { }, { name: "ZonalBucketRandomReadLargerThan8MB", - readType: testutil.Random, + readType: common.ReadTypeRandom, start: 0, end: 9 * MiB, bucketType: gcs.BucketType{Zonal: true}, @@ -207,7 +207,7 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { }, { name: "ZonalBucketSequentialRead", - readType: testutil.Sequential, + readType: common.ReadTypeSequential, start: 50, end: 68, bucketType: gcs.BucketType{Zonal: true}, @@ -215,7 +215,7 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { }, { name: "RegularBucketRandomRead", - readType: testutil.Random, + readType: common.ReadTypeRandom, start: 50, end: 68, bucketType: gcs.BucketType{Zonal: false}, @@ -223,7 +223,7 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { }, { name: "RegularBucketSequentialRead", - readType: testutil.Sequential, + readType: common.ReadTypeSequential, start: 50, end: 68, bucketType: gcs.BucketType{Zonal: false}, @@ -641,7 +641,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Sequential, testutil.Sequential}, + expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -649,7 +649,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Sequential, testutil.Sequential}, + expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -657,7 +657,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Random, testutil.Random, testutil.Random}, + expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeRandom, common.ReadTypeRandom, common.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, { @@ -665,7 +665,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{testutil.Sequential, testutil.Sequential, testutil.Random, testutil.Random, testutil.Random}, + expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeRandom, common.ReadTypeRandom, common.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, } @@ -676,7 +676,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { t.rr.wrapped.reader = nil t.rr.wrapped.isMRDInUse = false t.rr.wrapped.seeks = 0 - t.rr.wrapped.readType = testutil.Sequential + t.rr.wrapped.readType = common.ReadTypeSequential t.rr.wrapped.expectedOffset = 0 t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) @@ -706,7 +706,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateZonalRandomReads() { t.rr.wrapped.reader = nil t.rr.wrapped.isMRDInUse = false t.rr.wrapped.seeks = 0 - t.rr.wrapped.readType = testutil.Sequential + t.rr.wrapped.readType = common.ReadTypeSequential t.rr.wrapped.expectedOffset = 0 t.rr.wrapped.totalReadBytes = 0 t.object.Size = 20 * MiB @@ -737,7 +737,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateZonalRandomReads() { _, err := t.rr.wrapped.ReadAt(t.rr.ctx, buf, int64(readRange[0])) assert.NoError(t.T(), err) - assert.Equal(t.T(), testutil.Random, t.rr.wrapped.readType) + assert.Equal(t.T(), common.ReadTypeRandom, t.rr.wrapped.readType) assert.Equal(t.T(), int64(readRange[1]), t.rr.wrapped.expectedOffset) assert.Equal(t.T(), uint64(seeks), t.rr.wrapped.seeks) } diff --git a/internal/util/util.go b/internal/util/util.go index d6e317bfe5..748aaf34ac 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -22,16 +22,6 @@ import ( "path/filepath" "strings" "time" - - "github.com/googlecloudplatform/gcsfuse/v3/common" -) - -const ( - // TODO: Remove these constants in favor of common.* constants. - // Constants for read types - Sequential/Random - Sequential = common.ReadTypeSequential - Random = common.ReadTypeRandom - Parallel = common.ReadTypeParallel ) const ( From 45d13067e3ceaf9e917ddf68848e8e59d66f8789 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Fri, 27 Jun 2025 10:04:01 +0530 Subject: [PATCH 0523/1298] Add getopt in e2e script for enhanced command line arg handling. (#3433) * add getopt in e2e script * fix getopt option value validation * fix script in case of invalid option handling. * fix review comments * fix review comments --- .../presubmit_test/pr_perf_test/build.sh | 10 +- .../improved_run_e2e_tests.sh | 184 +++++++++++++----- 2 files changed, 138 insertions(+), 56 deletions(-) diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index 7ddd407103..b9faacee7e 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -20,16 +20,10 @@ readonly EXECUTE_INTEGRATION_TEST_LABEL="execute-integration-tests" readonly EXECUTE_INTEGRATION_TEST_LABEL_ON_ZB="execute-integration-tests-on-zb" readonly EXECUTE_PACKAGE_BUILD_TEST_LABEL="execute-package-build-tests" readonly EXECUTE_CHECKPOINT_TEST_LABEL="execute-checkpoint-test" -readonly RUN_E2E_TESTS_ON_INSTALLED_PACKAGE=false -readonly SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE=false readonly BUCKET_LOCATION=us-west4 -readonly RUN_TEST_ON_TPC_ENDPOINT=false readonly GO_VERSION="1.24.0" readonly REQUIRED_BASH_VERSION_FOR_E2E_SCRIPT="5.1" -# This flag, if set true, will indicate to underlying script to customize for a presubmit run. -readonly RUN_TESTS_WITH_PRESUBMIT_FLAG=true - curl https://api.github.com/repos/GoogleCloudPlatform/gcsfuse/pulls/$KOKORO_GITHUB_PULL_REQUEST_NUMBER >> pr.json perfTest=$(grep "$EXECUTE_PERF_TEST_LABEL" pr.json) integrationTests=$(grep "\"$EXECUTE_INTEGRATION_TEST_LABEL\"" pr.json) @@ -125,7 +119,7 @@ then echo "Running e2e tests on zonal bucket(s) ..." # $1 argument is refering to value of testInstalledPackage. - /usr/local/bin/bash ./tools/integration_tests/improved_run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG true + /usr/local/bin/bash ./tools/integration_tests/improved_run_e2e_tests.sh --bucket-location=$BUCKET_LOCATION --presubmit --zonal --track-resource-usage fi # Execute integration tests on non-zonal bucket(s). @@ -136,7 +130,7 @@ then echo "Running e2e tests on non-zonal bucket(s) ..." # $1 argument is refering to value of testInstalledPackage. - /usr/local/bin/bash ./tools/integration_tests/improved_run_e2e_tests.sh $RUN_E2E_TESTS_ON_INSTALLED_PACKAGE $SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE $BUCKET_LOCATION $RUN_TEST_ON_TPC_ENDPOINT $RUN_TESTS_WITH_PRESUBMIT_FLAG false + /usr/local/bin/bash ./tools/integration_tests/improved_run_e2e_tests.sh --bucket-location=$BUCKET_LOCATION --presubmit --track-resource-usage fi # Execute package build tests. diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh index ee85c53be8..58c23728e6 100755 --- a/tools/integration_tests/improved_run_e2e_tests.sh +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -15,15 +15,21 @@ # Script Usage Documentation usage() { - echo "Usage: $0 [RUN_TEST_ON_TPC_ENDPOINT] [RUN_TESTS_WITH_PRESUBMIT_FLAG] [RUN_TESTS_WITH_ZONAL_BUCKET] [BUILD_BINARY_IN_SCRIPT]" - echo " TEST_INSTALLED_PACKAGE: 'true' or 'false' to test installed gcsfuse package." - echo " SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE: 'true' or 'false' to skip few non-essential tests inside packages." - echo " BUCKET_LOCATION: The Google Cloud Storage bucket location (e.g., 'us-central1')." - echo " RUN_TEST_ON_TPC_ENDPOINT (optional): 'true' or 'false' to run tests on TPC endpoint (Default: 'false')." - echo " RUN_TESTS_WITH_PRESUBMIT_FLAG (optional): 'true' or 'false' to run tests with presubmit flag (Default: 'false')." - echo " RUN_TESTS_WITH_ZONAL_BUCKET (optional): 'true' or 'false' to run tests with zonal bucket (Default: 'false')." - echo " BUILD_BINARY_IN_SCRIPT (optional): 'true' or 'false' to build binary in script (Default: 'true')." - exit 1 + echo "Usage: $0 --bucket-location [options]" + echo " --bucket-location The Google Cloud Storage bucket location (e.g., 'us-central1')." + echo "" + echo "Options:" + echo " --test-installed-package Test installed gcsfuse package. (Default: false)" + echo " --skip-non-essential-tests Skip non-essential tests inside packages. (Default: false)" + echo " --tpc-endpoint Run tests on TPC endpoint. (Default: false)" + echo " --presubmit Run tests with presubmit flag. (Default: false)" + echo " --zonal Run tests with zonal bucket in --bucket-location region." + echo " The placement for Zonal buckets by deafault is Zone A of --bucket-location. (Default: false)" + echo " --no-build-binary-in-script To disable building gcsfuse binary in script. (Default: false)" + echo " --package-level-parallelism To adjust the number of packages to execute in parallel. (Default: 10)" + echo " --track-resource-usage To track resource(cpu/mem/disk) usage during e2e run. (Default: false)" + echo " --help Display this help and exit." + exit "$1" } # Logging Helpers @@ -55,7 +61,6 @@ readonly INTEGRATION_TEST_PACKAGE_DIR="./tools/integration_tests" readonly INTEGRATION_TEST_PACKAGE_TIMEOUT_IN_MINS=60 readonly TMP_PREFIX="gcsfuse_e2e" readonly ZONAL_BUCKET_SUPPORTED_LOCATIONS=("us-central1" "us-west4") -readonly PACKAGE_LEVEL_PARALLELISM=10 # Controls how many test packages are run in parallel for hns, flat or zonal buckets. readonly DELETE_BUCKET_PARALLELISM=10 # Controls how many buckets are deleted in parallel. # 6 second delay between creating buckets as both hns and flat runs create buckets in parallel. # Ref: https://cloud.google.com/storage/quotas#buckets @@ -75,25 +80,100 @@ PACKAGE_RUNTIME_STATS=$(mktemp "/tmp/${TMP_PREFIX}_package_stats_runtime.XXXXXX" RESOURCE_USAGE_FILE=$(mktemp "/tmp/${TMP_PREFIX}_system_resource_usage.XXXXXX") || { log_error "Unable to create system resource usage file"; exit 1; } # Argument Parsing and Assignments -if [ "$#" -lt 3 ]; then - log_error "Missing required arguments." - usage -fi -TEST_INSTALLED_PACKAGE="$1" -SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE="$2" -BUCKET_LOCATION="$3" -# Assign optional arguments with provided or default values -RUN_TEST_ON_TPC_ENDPOINT="${4:-false}" -RUN_TESTS_WITH_PRESUBMIT_FLAG="${5:-false}" -RUN_TESTS_WITH_ZONAL_BUCKET="${6:-false}" -BUILD_BINARY_IN_SCRIPT="${7:-true}" -if [ "$#" -gt 7 ]; then - log_error "Too many arguments." - usage +# Set default values for optional arguments +SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE=false +TEST_INSTALLED_PACKAGE=false +RUN_TEST_ON_TPC_ENDPOINT=false +RUN_TESTS_WITH_PRESUBMIT_FLAG=false +RUN_TESTS_WITH_ZONAL_BUCKET=false +BUILD_BINARY_IN_SCRIPT=true +TRACK_RESOURCE_USAGE=false +PACKAGE_LEVEL_PARALLELISM=10 # Controls how many test packages are run in parallel for hns, flat or zonal buckets. + +# Define options for getopt +# A long option name followed by a colon indicates it requires an argument. +LONG=bucket-location:,test-installed-package,skip-non-essential-tests,no-build-binary-in-script,test-on-tpc-endpoint,presubmit,zonal,package-level-parallelism:,track-resource-usage,help + +# Parse the options using getopt +# --options "" specifies that there are no short options. +PARSED=$(getopt --options "" --longoptions "$LONG" --name "$0" -- "$@") +if [[ $? -ne 0 ]]; then + # getopt will have already printed an error message + usage 1 fi +# Read the parsed options back into the positional parameters. +eval set -- "$PARSED" + +# Loop through the options and assign values to our variables +while (( $# >= 1 )); do + case "$1" in + --bucket-location) + BUCKET_LOCATION="$2" + shift 2 + ;; + --package-level-parallelism) + PACKAGE_LEVEL_PARALLELISM="$2" + shift 2 + ;; + --test-installed-package) + TEST_INSTALLED_PACKAGE=true + shift + ;; + --skip-non-essential-tests) + SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE=true + shift + ;; + --no-build-binary-in-script) + BUILD_BINARY_IN_SCRIPT=false + shift + ;; + --test-on-tpc-endpoint) + RUN_TEST_ON_TPC_ENDPOINT=true + shift + ;; + --presubmit) + RUN_TESTS_WITH_PRESUBMIT_FLAG=true + shift + ;; + --zonal) + RUN_TESTS_WITH_ZONAL_BUCKET=true + shift + ;; + --track-resource-usage) + TRACK_RESOURCE_USAGE=true + shift + ;; + --help) + usage 0 + ;; + --) + shift + break + ;; + *) + log_error "Unrecognized arguments [$*]." + usage 1 + ;; + esac +done + +# Validates option value to be non-empty and should not be another option name. +validate_option_value() { + local option=$1 + local value=$2 + if [[ -z "$value" || "$value" == -* ]]; then + log_error "Invalid or empty value [$value] for option $option." + usage 1 + fi +} + +# Validate long options which require values. +validate_option_value "--bucket-location" "$BUCKET_LOCATION" +validate_option_value "--package-level-parallelism" "$PACKAGE_LEVEL_PARALLELISM" + # Zonal Bucket location validation. -if [[ "${RUN_TESTS_WITH_ZONAL_BUCKET}" == "true" ]]; then +if ${RUN_TESTS_WITH_ZONAL_BUCKET}; then supported_bucket=false for location in "${ZONAL_BUCKET_SUPPORTED_LOCATIONS[@]}"; do if [[ "$BUCKET_LOCATION" == "$location" ]]; then @@ -101,7 +181,7 @@ if [[ "${RUN_TESTS_WITH_ZONAL_BUCKET}" == "true" ]]; then break fi done - if [[ "${supported_bucket}" == false ]]; then + if ! ${supported_bucket}; then log_error "Unsupported Bucket Location ${BUCKET_LOCATION} for Zonal Run. Supported Locations are: ${ZONAL_BUCKET_SUPPORTED_LOCATIONS[*]}" exit 1 fi @@ -286,8 +366,12 @@ safe_kill() { # Cleanup ensures each of the buckets created is destroyed and the temp files are cleaned up. clean_up() { - if ! safe_kill "$RESOURCE_USAGE_PID" "resource_usage.sh"; then - log_error "Failed to stop resource usage collection process (or it's already stopped): $RESOURCE_USAGE_PID" + if ${TRACK_RESOURCE_USAGE}; then + if ! safe_kill "$RESOURCE_USAGE_PID" "resource_usage.sh"; then + log_error "Failed to stop resource usage collection process (or it's already stopped)" + else + log_info "Resource usage collection process stopped." + fi fi if [ -n "${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" ] && [ -d "${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" ]; then log_info "Cleaning up GCSFuse build directory created by script: ${BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR}" @@ -394,7 +478,7 @@ test_package() { # Build go package test command. local go_test_cmd_parts=("GODEBUG=asyncpreemptoff=1" "go" "test" "-v" "-timeout=${INTEGRATION_TEST_PACKAGE_TIMEOUT_IN_MINS}m" "${INTEGRATION_TEST_PACKAGE_DIR}/${package_name}") - if [[ "$SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE" == "true" ]]; then + if ${SKIP_NON_ESSENTIAL_TESTS_ON_PACKAGE}; then go_test_cmd_parts+=("-short") fi if [[ "$package_name" == "benchmarking" ]]; then @@ -402,16 +486,16 @@ test_package() { fi # Test Binary flags after this. go_test_cmd_parts+=("-args" "--integrationTest" "--testbucket=${bucket_name}") - if [[ "$TEST_INSTALLED_PACKAGE" == "true" ]]; then + if ${TEST_INSTALLED_PACKAGE}; then go_test_cmd_parts+=("--testInstalledPackage") fi - if [[ "$RUN_TESTS_WITH_PRESUBMIT_FLAG" == "true" ]]; then + if ${RUN_TESTS_WITH_PRESUBMIT_FLAG}; then go_test_cmd_parts+=("--presubmit") fi if [[ "$bucket_type" == "$ZONAL" ]]; then go_test_cmd_parts+=("--zonal") fi - if [[ "$RUN_TEST_ON_TPC_ENDPOINT" == "true" ]]; then + if ${RUN_TEST_ON_TPC_ENDPOINT}; then go_test_cmd_parts+=("--testOnTPCEndPoint") fi if [[ -n "$BUILT_BY_SCRIPT_GCSFUSE_BUILD_DIR" ]]; then @@ -538,7 +622,7 @@ main() { log_info "" # Decide whether to build GCSFuse based on RUN_E2E_TESTS_ON_PACKAGE - if [ "$TEST_INSTALLED_PACKAGE" != "true" ] && [ "$BUILD_BINARY_IN_SCRIPT" == "true" ]; then + if [ ! ${TEST_INSTALLED_PACKAGE} ] && [ ${BUILD_BINARY_IN_SCRIPT} ]; then log_info "TEST_INSTALLED_PACKAGE is not 'true' (value: '${TEST_INSTALLED_PACKAGE}') and BUILD_BINARY_IN_SCRIPT is 'true'." log_info "Building GCSFuse inside script..." if ! build_gcsfuse_once; then @@ -552,17 +636,19 @@ main() { # Reset SECONDS to 0 SECONDS=0 - # Start collecting system resource usage in background. - log_info "Starting resource usage collection process." - ./tools/integration_tests/resource_usage.sh "COLLECT" "$RESOURCE_USAGE_FILE" & - RESOURCE_USAGE_PID=$! - log_info "Resource usage collection process started at PID: $RESOURCE_USAGE_PID" + if ${TRACK_RESOURCE_USAGE}; then + # Start collecting system resource usage in background. + log_info "Starting resource usage collection process." + ./tools/integration_tests/resource_usage.sh "COLLECT" "$RESOURCE_USAGE_FILE" & + RESOURCE_USAGE_PID=$! + log_info "Resource usage collection process started at PID: $RESOURCE_USAGE_PID" + fi local pids=() local overall_exit_code=0 - if [[ "${RUN_TESTS_WITH_ZONAL_BUCKET}" == "true" ]]; then + if ${RUN_TESTS_WITH_ZONAL_BUCKET}; then run_test_group "ZONAL" "TEST_PACKAGES_FOR_ZB" "$ZONAL" & pids+=($!) - elif [[ "${RUN_TEST_ON_TPC_ENDPOINT}" == "true" ]]; then + elif ${RUN_TEST_ON_TPC_ENDPOINT}; then # Override PROJECT_ID and BUCKET_LOCATION for TPC tests PROJECT_ID="$TPCZERO_PROJECT_ID" BUCKET_LOCATION="$TPC_BUCKET_LOCATION" @@ -585,13 +671,15 @@ main() { # Print package runtime stats table. ./tools/integration_tests/create_package_runtime_table.sh "$PACKAGE_RUNTIME_STATS" - # Kill resource usage background PID and print resource usage. - log_info "Stopping resource usage collection process: $RESOURCE_USAGE_PID" - if safe_kill "$RESOURCE_USAGE_PID" "resource_usage.sh"; then - log_info "Resource usage collection process stopped." - ./tools/integration_tests/resource_usage.sh "PRINT" "$RESOURCE_USAGE_FILE" - else - log_error "Failed to stop resource usage collection process (or it's already stopped)" + if ${TRACK_RESOURCE_USAGE}; then + # Kill resource usage background PID and print resource usage. + log_info "Stopping resource usage collection process: $RESOURCE_USAGE_PID" + if safe_kill "$RESOURCE_USAGE_PID" "resource_usage.sh"; then + log_info "Resource usage collection process stopped." + ./tools/integration_tests/resource_usage.sh "PRINT" "$RESOURCE_USAGE_FILE" + else + log_error "Failed to stop resource usage collection process (or it's already stopped)" + fi fi exit $overall_exit_code } From 95ef056974a6597d0820b68fffa60ee83965cf72 Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Fri, 27 Jun 2025 16:49:58 +0530 Subject: [PATCH 0524/1298] Add new experimental flag --experimental-enable-dentry-cache to file-system config (#3446) * Add new flag --enable-entry-timeout to file-system config * Changed flag name and modified usage for better understanding --- cfg/config.go | 12 ++++++++++++ cfg/params.yaml | 10 ++++++++++ cmd/root_test.go | 12 +++++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/cfg/config.go b/cfg/config.go index 829ee9a532..0d9a7433c3 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -114,6 +114,8 @@ type FileSystemConfig struct { DisableParallelDirops bool `yaml:"disable-parallel-dirops"` + ExperimentalEnableDentryCache bool `yaml:"experimental-enable-dentry-cache"` + ExperimentalEnableReaddirplus bool `yaml:"experimental-enable-readdirplus"` FileMode Octal `yaml:"file-mode"` @@ -401,6 +403,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-streaming-writes", "", true, "Enables streaming uploads during write file operation.") + flagSet.BoolP("experimental-enable-dentry-cache", "", false, "When enabled, it sets the Dentry cache entry timeout same as metadata-cache-ttl. This enables kernel to use cached entry to map the file paths to inodes, instead of making LookUpInode calls to GCSFuse.") + + if err := flagSet.MarkHidden("experimental-enable-dentry-cache"); err != nil { + return err + } + flagSet.BoolP("experimental-enable-json-read", "", false, "By default, GCSFuse uses the GCS XML API to get and read objects. When this flag is specified, GCSFuse uses the GCS JSON API instead.\"") if err := flagSet.MarkDeprecated("experimental-enable-json-read", "Experimental flag: could be dropped even in a minor release."); err != nil { @@ -792,6 +800,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("file-system.experimental-enable-dentry-cache", flagSet.Lookup("experimental-enable-dentry-cache")); err != nil { + return err + } + if err := v.BindPFlag("gcs-connection.experimental-enable-json-read", flagSet.Lookup("experimental-enable-json-read")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index cb732b3851..a8758cf1c2 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -190,6 +190,16 @@ default: false hide-flag: true +- config-path: "file-system.experimental-enable-dentry-cache" + flag-name: "experimental-enable-dentry-cache" + type: "bool" + usage: >- + When enabled, it sets the Dentry cache entry timeout same as metadata-cache-ttl. + This enables kernel to use cached entry to map the file paths to inodes, + instead of making LookUpInode calls to GCSFuse. + default: false + hide-flag: true + - config-path: "file-system.experimental-enable-readdirplus" flag-name: "experimental-enable-readdirplus" type: "bool" diff --git a/cmd/root_test.go b/cmd/root_test.go index a282aef301..59ab5f3f5e 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -740,11 +740,12 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { }{ { name: "normal", - args: []string{"gcsfuse", "--dir-mode=0777", "--disable-parallel-dirops", "--experimental-enable-readdirplus", "--file-mode=0666", "--o", "ro", "--gid=7", "--ignore-interrupts=false", "--kernel-list-cache-ttl-secs=300", "--rename-dir-limit=10", "--temp-dir=~/temp", "--uid=8", "--precondition-errors=false", "abc", "pqr"}, + args: []string{"gcsfuse", "--dir-mode=0777", "--disable-parallel-dirops", "--experimental-enable-dentry-cache", "--experimental-enable-readdirplus", "--file-mode=0666", "--o", "ro", "--gid=7", "--ignore-interrupts=false", "--kernel-list-cache-ttl-secs=300", "--rename-dir-limit=10", "--temp-dir=~/temp", "--uid=8", "--precondition-errors=false", "abc", "pqr"}, expectedConfig: &cfg.Config{ FileSystem: cfg.FileSystemConfig{ DirMode: 0777, DisableParallelDirops: true, + ExperimentalEnableDentryCache: true, ExperimentalEnableReaddirplus: true, FileMode: 0666, FuseOptions: []string{"ro"}, @@ -765,6 +766,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { FileSystem: cfg.FileSystemConfig{ DirMode: 0777, DisableParallelDirops: false, + ExperimentalEnableDentryCache: false, ExperimentalEnableReaddirplus: false, FileMode: 0666, FuseOptions: []string{}, @@ -785,6 +787,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { FileSystem: cfg.FileSystemConfig{ DirMode: 0777, DisableParallelDirops: false, + ExperimentalEnableDentryCache: false, ExperimentalEnableReaddirplus: false, FileMode: 0666, FuseOptions: []string{}, @@ -805,6 +808,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { FileSystem: cfg.FileSystemConfig{ DirMode: 0777, DisableParallelDirops: false, + ExperimentalEnableDentryCache: false, ExperimentalEnableReaddirplus: false, FileMode: 0666, FuseOptions: []string{}, @@ -825,6 +829,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { FileSystem: cfg.FileSystemConfig{ DirMode: 0777, DisableParallelDirops: false, + ExperimentalEnableDentryCache: false, ExperimentalEnableReaddirplus: false, FileMode: 0666, FuseOptions: []string{}, @@ -845,6 +850,7 @@ func TestArgsParsing_FileSystemFlags(t *testing.T) { FileSystem: cfg.FileSystemConfig{ DirMode: 0755, DisableParallelDirops: false, + ExperimentalEnableDentryCache: false, ExperimentalEnableReaddirplus: false, FileMode: 0644, FuseOptions: []string{}, @@ -908,6 +914,10 @@ func TestArgsParsing_FileSystemFlagsThrowsError(t *testing.T) { name: "invalid_experimental_enable_readdirplus", args: []string{"gcsfuse", "--experimental-enable-readdirplus=abc", "abc", "pqr"}, }, + { + name: "invalid_experimental_enable_dentry_cache", + args: []string{"gcsfuse", "--experimental-enable-dentry-cache=abc", "abc", "pqr"}, + }, } for _, tc := range tests { From 7ecc1a6256f5f0610a2be6749e3101c57d08dd79 Mon Sep 17 00:00:00 2001 From: Saikat Roychowdhury Date: Fri, 27 Jun 2025 19:13:09 +0000 Subject: [PATCH 0525/1298] update pv, pod specs for metadata prefetch mem overrides --- samples/gke-csi-yaml/gpu/checkpointing-pod.yaml | 3 ++- samples/gke-csi-yaml/gpu/serving-pod.yaml | 3 ++- samples/gke-csi-yaml/gpu/training-pod.yaml | 2 ++ samples/gke-csi-yaml/tpu/checkpointing-pod.yaml | 2 ++ samples/gke-csi-yaml/tpu/serving-pod.yaml | 2 ++ samples/gke-csi-yaml/tpu/training-pod.yaml | 2 ++ 6 files changed, 12 insertions(+), 2 deletions(-) diff --git a/samples/gke-csi-yaml/gpu/checkpointing-pod.yaml b/samples/gke-csi-yaml/gpu/checkpointing-pod.yaml index 6f5f5a887e..21fb1652ba 100644 --- a/samples/gke-csi-yaml/gpu/checkpointing-pod.yaml +++ b/samples/gke-csi-yaml/gpu/checkpointing-pod.yaml @@ -5,7 +5,8 @@ metadata: namespace: annotations: gke-gcsfuse/volumes: "true" - + # gke-gcsfuse/metadata-prefetch-memory-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect + # gke-gcsfuse/metadata-prefetch-cpu-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect spec: containers: # Add your workload container spec diff --git a/samples/gke-csi-yaml/gpu/serving-pod.yaml b/samples/gke-csi-yaml/gpu/serving-pod.yaml index d3a628c58e..512d9943c4 100644 --- a/samples/gke-csi-yaml/gpu/serving-pod.yaml +++ b/samples/gke-csi-yaml/gpu/serving-pod.yaml @@ -5,7 +5,8 @@ metadata: namespace: annotations: gke-gcsfuse/volumes: "true" - + # gke-gcsfuse/metadata-prefetch-memory-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect + # gke-gcsfuse/metadata-prefetch-cpu-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect spec: containers: # Your workload container spec diff --git a/samples/gke-csi-yaml/gpu/training-pod.yaml b/samples/gke-csi-yaml/gpu/training-pod.yaml index 38086b269a..53667a71c6 100644 --- a/samples/gke-csi-yaml/gpu/training-pod.yaml +++ b/samples/gke-csi-yaml/gpu/training-pod.yaml @@ -5,6 +5,8 @@ metadata: namespace: annotations: gke-gcsfuse/volumes: "true" + # gke-gcsfuse/metadata-prefetch-memory-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect + # gke-gcsfuse/metadata-prefetch-cpu-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect spec: containers: # Your workload container spec diff --git a/samples/gke-csi-yaml/tpu/checkpointing-pod.yaml b/samples/gke-csi-yaml/tpu/checkpointing-pod.yaml index 9170a7e79f..ec7b442d48 100644 --- a/samples/gke-csi-yaml/tpu/checkpointing-pod.yaml +++ b/samples/gke-csi-yaml/tpu/checkpointing-pod.yaml @@ -5,6 +5,8 @@ metadata: namespace: annotations: gke-gcsfuse/volumes: "true" + # gke-gcsfuse/metadata-prefetch-memory-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect + # gke-gcsfuse/metadata-prefetch-cpu-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect spec: containers: diff --git a/samples/gke-csi-yaml/tpu/serving-pod.yaml b/samples/gke-csi-yaml/tpu/serving-pod.yaml index d3a628c58e..b7e4b311e5 100644 --- a/samples/gke-csi-yaml/tpu/serving-pod.yaml +++ b/samples/gke-csi-yaml/tpu/serving-pod.yaml @@ -5,6 +5,8 @@ metadata: namespace: annotations: gke-gcsfuse/volumes: "true" + # gke-gcsfuse/metadata-prefetch-memory-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect + # gke-gcsfuse/metadata-prefetch-cpu-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect spec: containers: diff --git a/samples/gke-csi-yaml/tpu/training-pod.yaml b/samples/gke-csi-yaml/tpu/training-pod.yaml index 2daeb7ab88..6ef6768485 100644 --- a/samples/gke-csi-yaml/tpu/training-pod.yaml +++ b/samples/gke-csi-yaml/tpu/training-pod.yaml @@ -5,6 +5,8 @@ metadata: namespace: annotations: gke-gcsfuse/volumes: "true" + # gke-gcsfuse/metadata-prefetch-memory-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect + # gke-gcsfuse/metadata-prefetch-cpu-limit: "0" # min GKE version: `1.32.3-gke.1717000` for this annotation to take effect spec: containers: # Your workload container spec From 58c116ee2c7b12e05e7a847e00d4e3db101ebaaf Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Mon, 30 Jun 2025 14:27:16 +0530 Subject: [PATCH 0526/1298] fix typo and condition check (#3459) --- tools/integration_tests/improved_run_e2e_tests.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh index 58c23728e6..240a8fb3b6 100755 --- a/tools/integration_tests/improved_run_e2e_tests.sh +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -16,12 +16,12 @@ # Script Usage Documentation usage() { echo "Usage: $0 --bucket-location [options]" - echo " --bucket-location The Google Cloud Storage bucket location (e.g., 'us-central1')." + echo " --bucket-location The Google Cloud Storage bucket location (e.g., 'us-central1')." echo "" echo "Options:" echo " --test-installed-package Test installed gcsfuse package. (Default: false)" echo " --skip-non-essential-tests Skip non-essential tests inside packages. (Default: false)" - echo " --tpc-endpoint Run tests on TPC endpoint. (Default: false)" + echo " --test-on-tpc-endpoint Run tests on TPC endpoint. (Default: false)" echo " --presubmit Run tests with presubmit flag. (Default: false)" echo " --zonal Run tests with zonal bucket in --bucket-location region." echo " The placement for Zonal buckets by deafault is Zone A of --bucket-location. (Default: false)" @@ -215,7 +215,7 @@ TEST_PACKAGES_COMMON=( # "grpc_validation" "negative_stat_cache" "stale_handle" - "release_version" + "release_version" ) # Test packages for regional buckets. @@ -622,7 +622,7 @@ main() { log_info "" # Decide whether to build GCSFuse based on RUN_E2E_TESTS_ON_PACKAGE - if [ ! ${TEST_INSTALLED_PACKAGE} ] && [ ${BUILD_BINARY_IN_SCRIPT} ]; then + if (! ${TEST_INSTALLED_PACKAGE} ) && ${BUILD_BINARY_IN_SCRIPT}; then log_info "TEST_INSTALLED_PACKAGE is not 'true' (value: '${TEST_INSTALLED_PACKAGE}') and BUILD_BINARY_IN_SCRIPT is 'true'." log_info "Building GCSFuse inside script..." if ! build_gcsfuse_once; then From 94b76ce80cd293c1fc76a6b455ccdc5ffbbd5f39 Mon Sep 17 00:00:00 2001 From: codechanges Date: Tue, 1 Jul 2025 14:50:48 +0530 Subject: [PATCH 0527/1298] [GCSFuse 3.0] update public docs for ttl warning on high end machines (#3362) * update semantics doc for ttl warning * add details to semntics file * add links * minor change * minor change * minor change * resolve minor comment --- docs/semantics.md | 10 ++++++---- docs/troubleshooting.md | 5 ++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/semantics.md b/docs/semantics.md index 88c22b94c3..752e1f185b 100644 --- a/docs/semantics.md +++ b/docs/semantics.md @@ -182,10 +182,12 @@ The behavior of stat cache is controlled by the following flags/config parameter Positive and negative stat results will be cached for the specified amount of time. -Warning: Using stat caching breaks the consistency guarantees discussed in this document. It is safe only in the following situations: -- The mounted bucket is never modified. -- The mounted bucket is only modified on a single machine, via a single Cloud Storage FUSE mount. -- The mounted bucket is modified by multiple actors, but the user is confident that they don't need the guarantees discussed in this document. +Warnings: +- Using stat caching breaks the consistency guarantees discussed in this document. It is safe only in the following situations: + - The mounted bucket is never modified. + - The mounted bucket is only modified on a single machine, via a single Cloud Storage FUSE mount. + - The mounted bucket is modified by multiple actors, but the user is confident that they don't need the guarantees discussed in this document. +- On high performance machines GCSFuse sets TTL to infinite by default ([refer](https://cloud.google.com/storage/docs/cloud-storage-fuse/caching#cache-invalidation)). Please override it manually if your workload requires consistency guarantees. ## Type caching diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 278fd7aa82..4701aa9199 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -186,6 +186,9 @@ Starting with [version 2.12.0](https://github.com/GoogleCloudPlatform/gcsfuse/re If this increased CPU usage negatively impacts your workload's performance, you can disable this behavior by setting the `file-cache:enable-parallel-downloads` configuration option to `false`. +### Potential Stat Consistency Issues on high-performance machines with Default TTL +Starting with [version 3.0.0](https://github.com/GoogleCloudPlatform/gcsfuse/releases/tag/v3.0.0), On high-performance machines - gcsfuse will default to infinite stat cache TTL ([refer](https://cloud.google.com/storage/docs/cloud-storage-fuse/automated-configurations)), potentially causing stale file/directory information if the bucket is modified externally. If strict consistency is needed, manually set a finite TTL (e.g., --stat-cache-ttl 1m) to ensure metadata reflects recent changes. Consult [semantics](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/semantics.md) doc for more details. + ### Writes still using staged writes even though streaming writes are enabled. If you observe that GCSFuse is still utilizing staged writes despite streaming writes being enabled, several factors could be at play. @@ -197,4 +200,4 @@ If you observe that GCSFuse is still utilizing staged writes despite streaming w - Reading from a file while writes are in progress (this action finalizes the object and subsequent writes will fall back). - Truncating a file downwards while writes are in progress (this action also finalizes the object and subsequent writes will fall back). -An informational log message will be emitted by GCSFuse whenever a fallback to staged writes occurs, providing details on the reason. \ No newline at end of file +An informational log message will be emitted by GCSFuse whenever a fallback to staged writes occurs, providing details on the reason. From 299c67a12c999040603b68d3fed4d1770e209647 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 1 Jul 2025 16:29:34 +0530 Subject: [PATCH 0528/1298] Relax file-cache checks for unfinalized objects (#3401) * define IsUnfinalized() for MinObject minor code shortening * add unit tests for IsUnfinalized * fall back to gcs from cache-handler if cached-file-size < actual object-size * fix the check for IsFinalized * dont invalidate cache entry for unfinalized objects * fetch latest size for unfinalized objects in stat-cache This overrides the size stored in the stat-cache for unfinalized objects for unfinalized objects, and fetches their latest size from GCS. * Revert "fetch latest size for unfinalized objects in stat-cache" This reverts commit c1a2aa6185f9224fefd0241c1f643e76b791d904. * temp changes for cache offset check * changes for relaxed cache offset check for unfinalized object * remove falling back to gcs if cache download failed for unfinalized object * empty commit * minor refactoring * fix failing unit tests * address self-review comments in v1 * address some review comments * add unit test for unfinalized object in file_cache_reader_test * fix failure in cache_file_read_test for zonal unfinalized object read * Revert "fix failure in cache_file_read_test for zonal unfinalized object read" This reverts commit eb8e7c9a9cc13ea7adabc48f9becda55b0a8ff1c. * Invalidate unfinalized object cache entry if not fully downloaded * dont invalidate cache for unfinalized object too aggressively * add more unit tests * dont fall back to gcs too aggressively * add another unit test * address self-review comments * empty commit * improve a code comment * shorten a code comment * bail out early in cache read if offset > cache info entry file size * add/modify expected calls to BucketType in random-reader and cache-reader tests * add more unit tests for file-cache reader for unfinalized objects * fix unit tests * address self-comment * address Mohits review comments * empty commit * fix suggested by Mohit * Add zonal bucket test-version for all tests in file_cache_reader_test.go * remove redundant tests * fix some more tests in file_cache_reader_test.go * add zonal bucket UTs in random_reader_test.go and read_manager_test.go * implement part of Abhishek's suggestions in v1 * add missed changes in file_cache_read_test.go * minor cleanup in file_cache_reader_test.go * Revert "minor cleanup in file_cache_reader_test.go" This reverts commit cbe261d35746a560762edd52dee0f81f26d11059. * more unit tests for cached read of unfinalized object refactor * address abhishek review comments in unit test code --- internal/cache/file/cache_handle.go | 49 ++++- internal/cache/file/cache_handler.go | 2 +- internal/gcsx/file_cache_reader_test.go | 207 +++++++++++++++++- internal/gcsx/random_reader_test.go | 44 ++-- .../gcsx/read_manager/read_manager_test.go | 16 +- internal/storage/gcs/object.go | 4 + internal/storage/gcs/object_test.go | 13 ++ 7 files changed, 296 insertions(+), 39 deletions(-) diff --git a/internal/cache/file/cache_handle.go b/internal/cache/file/cache_handle.go index 8cf3350299..d45de4df37 100644 --- a/internal/cache/file/cache_handle.go +++ b/internal/cache/file/cache_handle.go @@ -88,19 +88,18 @@ func (fch *CacheHandle) shouldReadFromCache(jobStatus *downloader.JobStatus, req return err } -// validateEntryInFileInfoCache checks if entry is present for a given object in -// file info cache with same generation and at least requiredOffset. -// It returns nil if entry is present, otherwise returns an appropriate error. +// getFileInfoData returns the file-info cache entry for the given object +// in the associated bucket. // Whether to change the order in cache while lookup is controlled via // changeCacheOrder. -func (fch *CacheHandle) validateEntryInFileInfoCache(bucket gcs.Bucket, object *gcs.MinObject, requiredOffset uint64, changeCacheOrder bool) error { +func (fch *CacheHandle) getFileInfoData(bucket gcs.Bucket, object *gcs.MinObject, changeCacheOrder bool) (*data.FileInfo, error) { fileInfoKey := data.FileInfoKey{ BucketName: bucket.Name(), ObjectName: object.Name, } fileInfoKeyName, err := fileInfoKey.Key() if err != nil { - return fmt.Errorf("error while creating key for bucket %s and object %s: %w", bucket.Name(), object.Name, err) + return nil, fmt.Errorf("error while creating key for bucket %s and object %s: %w", bucket.Name(), object.Name, err) } var fileInfo lru.ValueType @@ -110,13 +109,30 @@ func (fch *CacheHandle) validateEntryInFileInfoCache(bucket gcs.Bucket, object * fileInfo = fch.fileInfoCache.LookUpWithoutChangingOrder(fileInfoKeyName) } if fileInfo == nil { - return fmt.Errorf("%w: no entry found in file info cache for key %v", util.ErrInvalidFileInfoCache, fileInfoKeyName) + return nil, fmt.Errorf("%w: no entry found in file info cache for key %v", util.ErrInvalidFileInfoCache, fileInfoKeyName) } // The generation check below is required because it may happen that file // being read is evicted from cache during or after reading the required offset // from local cached file to `dst` buffer. - fileInfoData := fileInfo.(data.FileInfo) + fileInfoData, ok := fileInfo.(data.FileInfo) + if !ok { + return nil, fmt.Errorf("getFileInfoData: failed to get fileInfoData from file-cache for %q: %w", object.Name, util.ErrInvalidFileHandle) + } + + return &fileInfoData, nil +} + +// validateEntryInFileInfoCache checks if entry is present for a given object in +// file info cache with same generation and at least requiredOffset. +// It returns nil if entry is present, otherwise returns an appropriate error. +// Whether to change the order in cache while lookup is controlled via +// changeCacheOrder. +func (fch *CacheHandle) validateEntryInFileInfoCache(bucket gcs.Bucket, object *gcs.MinObject, requiredOffset uint64, changeCacheOrder bool) error { + fileInfoData, err := fch.getFileInfoData(bucket, object, changeCacheOrder) + if err != nil { + return fmt.Errorf("validateEntryInFileInfoCache: failed to get fileInfoData for %q: %w", object.Name, err) + } if fileInfoData.ObjectGeneration != object.Generation { return fmt.Errorf("%w: generation of cached object: %v is different from required generation: %v", util.ErrInvalidFileInfoCache, fileInfoData.ObjectGeneration, object.Generation) } @@ -142,6 +158,23 @@ func (fch *CacheHandle) Read(ctx context.Context, bucket gcs.Bucket, object *gcs return 0, false, fmt.Errorf("wrong offset requested: %d, object size: %d", offset, object.Size) } + fileInfoData, errFileInfo := fch.getFileInfoData(bucket, object, false) + if errFileInfo != nil { + return 0, false, fmt.Errorf("%w Error in getCachedFileInfo: %v", util.ErrInvalidFileInfoCache, errFileInfo) + } + + // New check to ensure we bail out early in case the requested read-offset is beyond the cached size for an unfinalized object. + // If the end-offset is within the cached size, then the read will be served from the cache. + // If offset is within the cached size and end-offset is beyond the cached-size, then + // we will fallback to GCS in the later logic, but we should still create the cache download job even in that case. + // + // Note: Change the below check to `(offset + len(dst)) > int64(fileInfoData.FileSize))` if the below + // check causes a problem in any edge-case. + if bucket.BucketType().Zonal && object.IsUnfinalized() && offset >= int64(fileInfoData.FileSize) { + err = util.ErrFallbackToGCS + return + } + // Checking before updating the previous offset. isSequentialRead := fch.IsSequential(offset) waitForDownload := true @@ -198,7 +231,7 @@ func (fch *CacheHandle) Read(ctx context.Context, bucket gcs.Bucket, object *gcs // If fileDownloadJob is nil then it means either the job is successfully // completed or failed. The offset must be equal to size of object for job // to be completed. - err = fch.validateEntryInFileInfoCache(bucket, object, object.Size, false) + err = fch.validateEntryInFileInfoCache(bucket, object, fileInfoData.FileSize, false) if err != nil { return 0, false, err } diff --git a/internal/cache/file/cache_handler.go b/internal/cache/file/cache_handler.go index a119abad07..82984f914b 100644 --- a/internal/cache/file/cache_handler.go +++ b/internal/cache/file/cache_handler.go @@ -156,7 +156,7 @@ func (chr *CacheHandler) addFileInfoEntryAndCreateDownloadJob(object *gcs.MinObj // If offset in file info cache is less than object size and there is no // reference to download job then it means the job has failed. existingJob := chr.jobManager.GetJob(object.Name, bucket.Name()) - shouldInvalidate := (existingJob == nil) && (fileInfoData.Offset < object.Size) + shouldInvalidate := (existingJob == nil) && (fileInfoData.Offset < fileInfoData.FileSize) if (!shouldInvalidate) && (existingJob != nil) { existingJobStatus := existingJob.GetStatus().Name shouldInvalidate = (existingJobStatus == downloader.Failed) || (existingJobStatus == downloader.Invalid) diff --git a/internal/gcsx/file_cache_reader_test.go b/internal/gcsx/file_cache_reader_test.go index 67de87585a..f3941e76be 100644 --- a/internal/gcsx/file_cache_reader_test.go +++ b/internal/gcsx/file_cache_reader_test.go @@ -23,6 +23,7 @@ import ( "os" "path" "testing" + "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/common" @@ -42,6 +43,7 @@ import ( const ( testObject = "testObject" + testObject_unfinalized = "testObject_unfinalized" sequentialReadSizeInMb = 22 sequentialReadSizeInBytes = sequentialReadSizeInMb * MiB cacheMaxSize = 2 * sequentialReadSizeInMb * MiB @@ -49,17 +51,28 @@ const ( type fileCacheReaderTest struct { suite.Suite - ctx context.Context - object *gcs.MinObject - mockBucket *storage.TestifyMockBucket - cacheDir string - jobManager *downloader.JobManager - cacheHandler *file.CacheHandler - reader *FileCacheReader + ctx context.Context + object *gcs.MinObject + unfinalized_object *gcs.MinObject + mockBucket *storage.TestifyMockBucket + cacheDir string + jobManager *downloader.JobManager + cacheHandler *file.CacheHandler + reader *FileCacheReader + reader_unfinalized_object *FileCacheReader + bucketType gcs.BucketType } -func TestFileCacheReaderTestSuite(t *testing.T) { - suite.Run(t, new(fileCacheReaderTest)) +func TestNonZonalBucketFileCacheReaderTestSuite(t *testing.T) { + nonZonalBucketFileCacheReaderTestSuite := &fileCacheReaderTest{ + bucketType: gcs.BucketType{}} + suite.Run(t, nonZonalBucketFileCacheReaderTestSuite) +} + +func TestZonalFileCacheReaderTestSuite(t *testing.T) { + zonalBucketFileCacheReaderTestSuite := &fileCacheReaderTest{ + bucketType: gcs.BucketType{Zonal: true, Hierarchical: true}} + suite.Run(t, zonalBucketFileCacheReaderTestSuite) } func (t *fileCacheReaderTest) SetupTest() { @@ -67,6 +80,12 @@ func (t *fileCacheReaderTest) SetupTest() { Name: testObject, Size: 17, Generation: 1234, + Finalized: time.Date(2025, time.June, 27, 07, 22, 30, 0, time.UTC), + } + t.unfinalized_object = &gcs.MinObject{ + Name: testObject_unfinalized, + Size: 17, + Generation: 1234, } t.mockBucket = new(storage.TestifyMockBucket) t.cacheDir = path.Join(os.Getenv("HOME"), "test_cache_dir") @@ -74,6 +93,7 @@ func (t *fileCacheReaderTest) SetupTest() { t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{EnableCrc: false}, common.NewNoopMetrics()) t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm, "") t.reader = NewFileCacheReader(t.object, t.mockBucket, t.cacheHandler, true, common.NewNoopMetrics()) + t.reader_unfinalized_object = NewFileCacheReader(t.unfinalized_object, t.mockBucket, t.cacheHandler, true, common.NewNoopMetrics()) t.ctx = context.Background() } @@ -142,6 +162,7 @@ func (t *fileCacheReaderTest) Test_tryReadingFromFileCache_CacheHit() { rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) buf := make([]byte, t.object.Size) // First read will be a cache miss. n, cacheHit, err := t.reader.tryReadingFromFileCache(t.ctx, buf, 0) @@ -164,6 +185,7 @@ func (t *fileCacheReaderTest) Test_tryReadingFromFileCache_SequentialSubsequentR rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) start1 := 0 end1 := util.MiB require.Less(t.T(), end1, int(t.object.Size)) @@ -191,6 +213,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_SequentialRangeRead() { rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) start := 0 end := 10 require.Less(t.T(), end, int(t.object.Size)) @@ -232,6 +255,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCa // Mock for download job's NewReader call t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) buf := make([]byte, end-start) readerResponse, err := t.reader.ReadAt(t.ctx, buf, int64(start)) @@ -250,6 +274,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffset // Mock for download job's NewReader call t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) start1 := 0 end1 := util.MiB require.Less(t.T(), end1, int(t.object.Size)) @@ -280,6 +305,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffset rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) start1 := 0 end1 := util.MiB require.Less(t.T(), end1, int(t.object.Size)) @@ -313,6 +339,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_CacheMissDueToInvalidJob() { rc1 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rc1) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) buf := make([]byte, t.object.Size) readerResponse, err := t.reader.ReadAt(t.ctx, buf, 0) assert.NoError(t.T(), err) @@ -339,6 +366,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInv rd1 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd1) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) assert.NoError(t.T(), err) assert.Equal(t.T(), testContent, readerResponse.DataBuf) @@ -371,6 +399,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInv rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) assert.NoError(t.T(), err) assert.Equal(t.T(), readerResponse.DataBuf, testContent) @@ -396,6 +425,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_IfCacheFileGetsDeleted() { rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) assert.NoError(t.T(), err) assert.Equal(t.T(), readerResponse.DataBuf, testContent) @@ -419,6 +449,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_IfCacheFileGetsDeletedWithCacheHandleO rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) assert.NoError(t.T(), err) assert.Equal(t.T(), readerResponse.DataBuf, testContent) @@ -443,6 +474,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_FailedJobNextReadCreatesNewJobAndCache // This triggers a fallback to GCS reader. t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(nil, errors.New("")).Once() t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) // First ReadAt call: // - Should result in a FallbackToAnotherReader error. // - No data should be returned. @@ -478,11 +510,168 @@ func (t *fileCacheReaderTest) Test_ReadAt_NegativeOffsetShouldThrowError() { assert.Zero(t.T(), readerResponse.Size) } +func (t *fileCacheReaderTest) Test_ReadAt_OffsetBeyondObjectSizeShouldThrowEOFError() { + readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, 10), int64(t.object.Size)+1) + + assert.Error(t.T(), err) + assert.Zero(t.T(), readerResponse.Size) + assert.ErrorIs(t.T(), err, io.EOF) +} + +func (t *fileCacheReaderTest) skipForNonZonalBucket() { + t.T().Helper() + + if !t.bucketType.Zonal { + t.T().Skipf("Skipping test for non-zonal bucket type") + } +} + +func (t *fileCacheReaderTest) fullyReadOriginalSizeOfUnfinalizedObject(origObjectSize uint64) { + t.T().Helper() + t.mockBucket.On("Name").Return("test-bucket") + testContent := testutil.GenerateRandomBytes(int(t.unfinalized_object.Size)) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.unfinalized_object.Size, rd) + t.mockBucket.On("BucketType").Return(t.bucketType) + readerResponse, err := t.reader_unfinalized_object.ReadAt(t.ctx, make([]byte, origObjectSize), 0) + assert.NoError(t.T(), err) + assert.Equal(t.T(), int(origObjectSize), readerResponse.Size) +} + +func (t *fileCacheReaderTest) waitForDownloadJobToFinish(obj *gcs.MinObject) { + t.T().Helper() + + job := t.jobManager.GetJob(obj.Name, t.mockBucket.Name()) + if job != nil { + for job.GetStatus().Name == downloader.Downloading { + time.Sleep(10 * time.Millisecond) // Poll the job status. + } + } +} + +func (t *fileCacheReaderTest) Test_ReadAt_UnfinalizedObjectReadFromOffsetBeyondCachedSizeAfterSizeIncreasedShouldThrowFallbackError() { + t.skipForNonZonalBucket() + origObjectSize := t.unfinalized_object.Size + // First read, which may start a background download job. + t.fullyReadOriginalSizeOfUnfinalizedObject(origObjectSize) + // Wait for the background download job to complete to avoid a data race. + // This is needed to avoid the race condition on the size of t.unfinalized_object later on. + t.waitForDownloadJobToFinish(t.unfinalized_object) + + // Resize the object, and read beyond previously cached size and within new object size. + cachedObjectSize := int64(origObjectSize) + objectSizeIncrease := uint64(10) + newObjectSize := origObjectSize + objectSizeIncrease + t.unfinalized_object.Size = newObjectSize + readerResponse, err := t.reader_unfinalized_object.ReadAt(t.ctx, make([]byte, objectSizeIncrease), cachedObjectSize) + + assert.Error(t.T(), err) + assert.Zero(t.T(), readerResponse.Size) + assert.ErrorIs(t.T(), err, FallbackToAnotherReader) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_UnfinalizedObjectReadFromOffsetBeyondObjectSizeAfterSizeIncreasedShouldThrowEOFError() { + t.skipForNonZonalBucket() + origObjectSize := t.unfinalized_object.Size + // First read, which may start a background download job. + t.fullyReadOriginalSizeOfUnfinalizedObject(origObjectSize) + // Wait for the background download job to complete to avoid a data race. + // This is needed to avoid the race condition on the size of t.unfinalized_object later on. + t.waitForDownloadJobToFinish(t.unfinalized_object) + + // Resize the object, and read beyond new object size. + objectSizeIncrease := uint64(10) + newObjectSize := origObjectSize + objectSizeIncrease + t.unfinalized_object.Size = newObjectSize + readerResponse, err := t.reader_unfinalized_object.ReadAt(t.ctx, make([]byte, 1), int64(newObjectSize)) + + assert.Error(t.T(), err) + assert.Zero(t.T(), readerResponse.Size) + assert.ErrorIs(t.T(), err, io.EOF) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_UnfinalizedObjectReadFromOffsetBelowCachedSizeAndReadBeyondCachedSizeWithIncreasedObjectSizeShouldThrowFallbackError() { + t.skipForNonZonalBucket() + origObjectSize := t.unfinalized_object.Size + // First read, which may start a background download job. + t.fullyReadOriginalSizeOfUnfinalizedObject(origObjectSize) + // Wait for the background download job to complete to avoid a data race. + // This is needed to avoid the race condition on the size of t.unfinalized_object later on. + t.waitForDownloadJobToFinish(t.unfinalized_object) + + // Resize the object, and read from below previously cached size and within new object size. + cachedObjectSize := int64(origObjectSize) + objectSizeIncrease := uint64(10) + newObjectSize := origObjectSize + objectSizeIncrease + t.unfinalized_object.Size = newObjectSize + readerResponse, err := t.reader_unfinalized_object.ReadAt(t.ctx, make([]byte, cachedObjectSize+1), cachedObjectSize/2) + + assert.Error(t.T(), err) + assert.Zero(t.T(), readerResponse.Size) + assert.ErrorIs(t.T(), err, FallbackToAnotherReader) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_UnfinalizedObjectReadFromOffsetBelowCachedSizeAndReadBeyondObjectSizeWithIncreasedObjectSizeShouldThrowFallbackError() { + t.skipForNonZonalBucket() + origObjectSize := t.unfinalized_object.Size + // First read, which may start a background download job. + t.fullyReadOriginalSizeOfUnfinalizedObject(origObjectSize) + // Wait for the background download job to complete to avoid a data race. + // This is needed to avoid the race condition on the size of t.unfinalized_object later on. + t.waitForDownloadJobToFinish(t.unfinalized_object) + + // Resize the object, and read from below cached size and beyond this new object size. + cachedObjectSize := int64(origObjectSize) + objectSizeIncrease := uint64(10) + newObjectSize := origObjectSize + objectSizeIncrease + t.unfinalized_object.Size = newObjectSize + readerResponse, err := t.reader_unfinalized_object.ReadAt(t.ctx, make([]byte, newObjectSize), cachedObjectSize/2) + + assert.Error(t.T(), err) + assert.Zero(t.T(), readerResponse.Size) + assert.ErrorIs(t.T(), err, FallbackToAnotherReader) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_UnfinalizedObjectReadFromOffsetBelowCachedSizeAndReadBeyondCachedSizeShouldNotThrowError() { + t.skipForNonZonalBucket() + origObjectSize := t.unfinalized_object.Size + t.fullyReadOriginalSizeOfUnfinalizedObject(origObjectSize) + + cachedObjectSize := int64(origObjectSize) + readerResponse, err := t.reader_unfinalized_object.ReadAt(t.ctx, make([]byte, cachedObjectSize+1), cachedObjectSize/2) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), int(cachedObjectSize-cachedObjectSize/2), readerResponse.Size) + t.mockBucket.AssertExpectations(t.T()) +} + +func (t *fileCacheReaderTest) Test_ReadAt_FinalizedObjectReadFromOffsetBelowCachedSizeAndReadBeyondCachedSizeShouldNotThrowError() { + t.mockBucket.On("Name").Return("test-bucket") + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) + rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockBucket.On("BucketType").Return(t.bucketType) + readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) + assert.NoError(t.T(), err) + assert.Equal(t.T(), int(t.object.Size), readerResponse.Size) + + readerResponse, err = t.reader.ReadAt(t.ctx, make([]byte, t.object.Size+1), int64(t.object.Size)/2) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), int(t.object.Size-t.object.Size/2), readerResponse.Size) + t.mockBucket.AssertExpectations(t.T()) +} + func (t *fileCacheReaderTest) Test_Destroy_NonNilCacheHandle() { testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") + t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) assert.NoError(t.T(), err) assert.Equal(t.T(), readerResponse.DataBuf, testContent) diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index 189e46cfcb..a92094d176 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -152,9 +152,13 @@ type RandomReaderTest struct { cacheDir string jobManager *downloader.JobManager cacheHandler *file.CacheHandler + bucketType gcs.BucketType } -func init() { RegisterTestSuite(&RandomReaderTest{}) } +func init() { + RegisterTestSuite(&RandomReaderTest{bucketType: gcs.BucketType{}}) + RegisterTestSuite(&RandomReaderTest{bucketType: gcs.BucketType{Zonal: true, Hierarchical: true}}) +} var _ SetUpInterface = &RandomReaderTest{} var _ TearDownInterface = &RandomReaderTest{} @@ -232,7 +236,7 @@ func (t *RandomReaderTest) NoExistingReader() { // The bucket should be called to set up a new reader. ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). WillOnce(Return(nil, errors.New(""))) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) buf := make([]byte, 1) _, err := t.rr.ReadAt(buf, 0) @@ -266,7 +270,7 @@ func (t *RandomReaderTest) ExistingReader_ReadAtOffsetAfterTheReaderPosition() { func (t *RandomReaderTest) NewReaderReturnsError() { ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). WillOnce(Return(nil, errors.New("taco"))) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) buf := make([]byte, 1) _, err := t.rr.ReadAt(buf, 0) @@ -282,7 +286,7 @@ func (t *RandomReaderTest) ReaderFails() { ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). WillOnce(Return(rc, nil)) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) // Call buf := make([]byte, 3) @@ -438,7 +442,7 @@ func (t *RandomReaderTest) UpgradesReadsToObjectSize() { Any(), AllOf(rangeStartIs(1), rangeLimitIs(objectSize))). WillOnce(Return(rc, nil)) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) // Call through. buf := make([]byte, readSize) @@ -480,7 +484,7 @@ func (t *RandomReaderTest) UpgradeReadsToAverageSize() { rangeStartIs(start), rangeLimitIs(start+expectedBytesToRead), )).WillOnce(Return(rc, nil)) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) // Call through. buf := make([]byte, readSize) @@ -514,7 +518,7 @@ func (t *RandomReaderTest) UpgradesSequentialReads_ExistingReader() { Any(), AllOf(rangeStartIs(1), rangeLimitIs(1+sequentialReadSizeInBytes))). WillOnce(Return(rc, nil)) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) // Call through. buf := make([]byte, readSize) @@ -549,7 +553,7 @@ func (t *RandomReaderTest) UpgradesSequentialReads_NoExistingReader() { Any(), AllOf(rangeStartIs(1), rangeLimitIs(1+readSize))). WillOnce(Return(rc, nil)) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) // Call through. buf := make([]byte, readSize) @@ -572,6 +576,7 @@ func (t *RandomReaderTest) Test_ReadAt_SequentialFullObject() { rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) buf := make([]byte, objectSize) objectData, err := t.rr.ReadAt(buf, 0) ExpectFalse(objectData.CacheHit) @@ -592,6 +597,7 @@ func (t *RandomReaderTest) Test_ReadAt_SequentialRangeRead() { rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) start := 0 end := 10 // not included AssertLt(end, objectSize) @@ -612,6 +618,7 @@ func (t *RandomReaderTest) Test_ReadAt_SequentialSubsequentReadOffsetLessThanRea rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) start1 := 0 end1 := util.MiB // not included AssertLt(end1, objectSize) @@ -643,7 +650,7 @@ func (t *RandomReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCache rc := &fake.FakeReader{ReadCloser: getReadCloser(testContent[start:])} t.mockNewReaderWithHandleCallForTestBucket(uint64(start), objectSize, rc) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) - ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) buf := make([]byte, end-start) objectData, err := t.rr.ReadAt(buf, int64(start)) ExpectFalse(objectData.CacheHit) @@ -674,7 +681,7 @@ func (t *RandomReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCache // Mock for download job's NewReader call t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd1) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) buf := make([]byte, end-start) objectData, err := t.rr.ReadAt(buf, int64(start)) @@ -695,7 +702,7 @@ func (t *RandomReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffsetMor // Mock for download job's NewReader call t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) start1 := 0 end1 := util.MiB // not included AssertLt(end1, objectSize) @@ -728,7 +735,7 @@ func (t *RandomReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffsetLes rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) start1 := 0 end1 := util.MiB // not included AssertLt(end1, objectSize) @@ -766,7 +773,7 @@ func (t *RandomReaderTest) Test_ReadAt_CacheMissDueToInvalidJob() { rc1 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rc1) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) buf := make([]byte, objectSize) objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) @@ -800,7 +807,7 @@ func (t *RandomReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInvali rd1 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd1) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) buf := make([]byte, objectSize) objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) @@ -840,7 +847,7 @@ func (t *RandomReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInvali rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) buf := make([]byte, objectSize) objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) @@ -876,6 +883,7 @@ func (t *RandomReaderTest) Test_ReadAt_IfCacheFileGetsDeleted() { rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) buf := make([]byte, objectSize) objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) @@ -907,6 +915,7 @@ func (t *RandomReaderTest) Test_ReadAt_IfCacheFileGetsDeletedWithCacheHandleOpen rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) buf := make([]byte, objectSize) objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) @@ -940,7 +949,7 @@ func (t *RandomReaderTest) Test_ReadAt_FailedJobRestartAndCacheHit() { ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). WillOnce(Return(nil, errors.New(""))).WillRepeatedly(Return(rc, nil)) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(gcs.BucketType{})) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) buf := make([]byte, objectSize) objectData, err := t.rr.ReadAt(buf, 0) AssertEq(nil, err) @@ -976,6 +985,7 @@ func (t *RandomReaderTest) Test_tryReadingFromFileCache_CacheHit() { rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillRepeatedly(Return(t.bucketType)) buf := make([]byte, objectSize) // First read will be a cache miss. _, cacheHit, err := t.rr.wrapped.tryReadingFromFileCache(t.rr.ctx, buf, 0) @@ -1012,6 +1022,7 @@ func (t *RandomReaderTest) Test_ReadAt_OffsetEqualToObjectSize() { rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) start1 := 0 end1 := util.MiB // equal to objectSize // First call from offset 0 - objectSize @@ -1048,6 +1059,7 @@ func (t *RandomReaderTest) Test_Destroy_NonNilCacheHandle() { rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} t.mockNewReaderWithHandleCallForTestBucket(0, objectSize, rd) ExpectCall(t.bucket, "Name")().WillRepeatedly(Return("test")) + ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) buf := make([]byte, objectSize) _, cacheHit, err := t.rr.wrapped.tryReadingFromFileCache(t.rr.ctx, buf, 0) AssertFalse(cacheHit) diff --git a/internal/gcsx/read_manager/read_manager_test.go b/internal/gcsx/read_manager/read_manager_test.go index ade63177bb..af12d2166b 100644 --- a/internal/gcsx/read_manager/read_manager_test.go +++ b/internal/gcsx/read_manager/read_manager_test.go @@ -99,10 +99,15 @@ type readManagerTest struct { mockBucket *storage.TestifyMockBucket readManager *ReadManager ctx context.Context + bucketType gcs.BucketType } -func TestReadManagerTestSuite(t *testing.T) { - suite.Run(t, new(readManagerTest)) +func TestNonZonalBucketReadManagerTestSuite(t *testing.T) { + suite.Run(t, &readManagerTest{bucketType: gcs.BucketType{}}) +} + +func TestZonalBucketReadManagerTestSuite(t *testing.T) { + suite.Run(t, &readManagerTest{bucketType: gcs.BucketType{Zonal: true, Hierarchical: true}}) } func (t *readManagerTest) SetupTest() { @@ -183,7 +188,7 @@ func (t *readManagerTest) Test_ReadAt_InvalidOffset() { func (t *readManagerTest) Test_ReadAt_NoExistingReader() { // The bucket should be called to set up a new reader. t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(nil, errors.New("network error")) - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(t.bucketType).Times(2) t.mockBucket.On("Name").Return("test-bucket") _, err := t.readAt(0, 1) @@ -197,7 +202,7 @@ func (t *readManagerTest) Test_ReadAt_ReaderFailsWithTimeout() { r := iotest.OneByteReader(iotest.TimeoutReader(strings.NewReader("xxx"))) rc := &fake.FakeReader{ReadCloser: io.NopCloser(r)} t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(rc, nil).Once() - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(t.bucketType).Times(1) _, err := t.readAt(0, 3) @@ -208,7 +213,7 @@ func (t *readManagerTest) Test_ReadAt_ReaderFailsWithTimeout() { func (t *readManagerTest) Test_ReadAt_FileClobbered() { t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(nil, &gcs.NotFoundError{}) - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(t.bucketType).Times(1) t.mockBucket.On("Name").Return("test-bucket") _, err := t.readAt(1, 3) @@ -228,6 +233,7 @@ func (t *readManagerTest) Test_ReadAt_FullObjectFromCache() { // Mock the reader that returns full object data t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, fakeReader) t.mockBucket.On("Name").Return("test-bucket").Maybe() + t.mockBucket.On("BucketType").Return(t.bucketType) // Act: First read (expected to be served via GCS, populating the cache) firstResp, err := t.readAt(0, int64(objectSize)) diff --git a/internal/storage/gcs/object.go b/internal/storage/gcs/object.go index 5bddb2387e..095b88734b 100644 --- a/internal/storage/gcs/object.go +++ b/internal/storage/gcs/object.go @@ -113,3 +113,7 @@ type ExtendedObjectAttributes struct { func (mo MinObject) HasContentEncodingGzip() bool { return mo.ContentEncoding == ContentEncodingGzip } + +func (mo MinObject) IsUnfinalized() bool { + return mo.Finalized.IsZero() +} diff --git a/internal/storage/gcs/object_test.go b/internal/storage/gcs/object_test.go index a5ed7ab87f..8e7f32995b 100644 --- a/internal/storage/gcs/object_test.go +++ b/internal/storage/gcs/object_test.go @@ -16,6 +16,7 @@ package gcs import ( "testing" + "time" . "github.com/jacobsa/ogletest" ) @@ -52,3 +53,15 @@ func (t *ObjectTest) HasContentEncodingGzipNegative() { AssertFalse(mo.HasContentEncodingGzip()) } } + +func (t *ObjectTest) IsFinalized() { + mo := MinObject{Finalized: time.Date(2025, time.June, 19, 18, 23, 30, 0, time.UTC)} + + AssertFalse(mo.IsUnfinalized()) +} + +func (t *ObjectTest) IsNotFinalized() { + mo := MinObject{} + + AssertTrue(mo.IsUnfinalized()) +} From 2efd133ec2334b908cfccc2bc1c9ea8553bcdf3a Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 2 Jul 2025 12:57:49 +0530 Subject: [PATCH 0529/1298] Update go-viper/mapstructure to 2.3.0 (#3462) Needed for b/429007684. --- go.mod | 4 ++-- go.sum | 65 +++++----------------------------------------------------- 2 files changed, 7 insertions(+), 62 deletions(-) diff --git a/go.mod b/go.mod index bdda772884..c2cbecd552 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/googlecloudplatform/gcsfuse/v3 go 1.24.0 require ( + cloud.google.com/go/auth v0.16.1 cloud.google.com/go/compute/metadata v0.7.0 cloud.google.com/go/iam v1.5.2 cloud.google.com/go/profiler v0.4.2 @@ -11,7 +12,7 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.28.0 github.com/fsouza/fake-gcs-server v1.52.2 - github.com/go-viper/mapstructure/v2 v2.2.1 + github.com/go-viper/mapstructure/v2 v2.3.0 github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.2 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec @@ -55,7 +56,6 @@ require ( require ( cel.dev/expr v0.24.0 // indirect cloud.google.com/go v0.121.2 // indirect - cloud.google.com/go/auth v0.16.1 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/longrunning v0.6.7 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect diff --git a/go.sum b/go.sum index 17b2ce23d9..bf27842090 100644 --- a/go.sum +++ b/go.sum @@ -1,25 +1,18 @@ -cel.dev/expr v0.22.0 h1:+hFFhLPmquBImfs1BiN2PZmkr5ASse2ZOuaxIs9e4R8= -cel.dev/expr v0.22.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.121.0 h1:pgfwva8nGw7vivjZiRfrmglGWiCJBP+0OmDpenG/Fwg= -cloud.google.com/go v0.121.0/go.mod h1:rS7Kytwheu/y9buoDmu5EIpMMCI4Mb8ND4aeN4Vwj7Q= cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg= cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/compute v1.38.0 h1:MilCLYQW2m7Dku8hRIIKo4r0oKastlD74sSu16riYKs= -cloud.google.com/go/compute v1.38.0/go.mod h1:oAFNIuXOmXbK/ssXm3z4nZB8ckPdjltJ7xhHCdbWFZM= cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= -cloud.google.com/go/kms v1.21.2 h1:c/PRUSMNQ8zXrc1sdAUnsenWWaNXN+PzTXfXOcSFdoE= -cloud.google.com/go/kms v1.21.2/go.mod h1:8wkMtHV/9Z8mLXEXr1GK7xPSBdi6knuLXIhqjuWcI6w= cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk= +cloud.google.com/go/kms v1.22.0/go.mod h1:U7mf8Sva5jpOb4bxYZdtw/9zsbIjrklYwPcvMk34AL8= cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc= cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA= cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE= @@ -32,30 +25,19 @@ cloud.google.com/go/pubsub v1.49.0 h1:5054IkbslnrMCgA2MAEPcsN3Ky+AyMpEZcii/DoySP cloud.google.com/go/pubsub v1.49.0/go.mod h1:K1FswTWP+C1tI/nfi3HQecoVeFvL4HUOB1tdaNXKhUY= cloud.google.com/go/secretmanager v1.14.7 h1:VkscIRzj7GcmZyO4z9y1EH7Xf81PcoiAo7MtlD+0O80= cloud.google.com/go/secretmanager v1.14.7/go.mod h1:uRuB4F6NTFbg0vLQ6HsT7PSsfbY7FqHbtJP1J94qxGc= -cloud.google.com/go/storage v1.54.0 h1:Du3XEyliAiftfyW0bwfdppm2MMLdpVAfiIg4T2nAI+0= -cloud.google.com/go/storage v1.54.0/go.mod h1:hIi9Boe8cHxTyaeqh7KMMwKg088VblFK46C2x/BWaZE= cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0= cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.28.0 h1:VaFXBL0NJpiFBtw4aVJpKHeKULVTcHpD+/G0ibZkcBw= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.28.0/go.mod h1:JXkPazkEc/dZTHzOlzv2vT1DlpWSTbSLmu/1KY6Ly0I= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0 h1:QFgWzcdmJlgEAwJz/zePYVJQxfoJGRtgIqZfIUFg5oQ= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0/go.mod h1:ayYHuYU7iNcNtEs1K9k6D/Bju7u1VEHMQm5qQ1n3GtM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0 h1:Jtr816GUk6+I2ox9L/v+VcOwN6IyGOEDTSNHfD6m9sY= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0/go.mod h1:E05RN++yLx9W4fXPtX978OLo9P0+fBacauUdET1BckA= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.28.0 h1:RC78FvsZ8rLRLgVQuw1jMJ8d6t38QgOv3hDoUVGD50U= github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.28.0/go.mod h1:03+QlJ+6zSrBaVaZ9K87fzUyKBDcAh0X1n1Vxq3XAjc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.52.0 h1:0l8ynskVvq1dvIn5vJbFMf/a/3TqFpRmCMrruFbzlvk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.52.0/go.mod h1:f/ad5NuHnYz8AOZGuR0cY+l36oSCstdxD73YlIchr6I= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0 h1:wbMd4eG/fOhsCa6+IP8uEDvWF5vl7rNoUWmP5f72Tbs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0/go.mod h1:gdIm9TxRk5soClCwuB0FtdXsbqtw0aqPwBEurK9tPkw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -65,8 +47,6 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -92,29 +72,21 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsouza/fake-gcs-server v1.52.2 h1:j6ne83nqHrlX5EEor7WWVIKdBsztGtwJ1J2mL+k+iio= github.com/fsouza/fake-gcs-server v1.52.2/go.mod h1:47HKyIkz6oLTes1R8vEaHLwXfzYsGfmDUk1ViHHAUsA= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= -github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= -github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= -github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -145,8 +117,6 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= -github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4= github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= @@ -202,8 +172,6 @@ github.com/minio/minio-go/v7 v7.0.86 h1:DcgQ0AUjLJzRH6y/HrxiZ8CXarA70PAIufXHodP4 github.com/minio/minio-go/v7 v7.0.86/go.mod h1:VbfO4hYwUu3Of9WqGLBZ8vl3Hxnxo4ngxK4hzQDf4x4= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/xattr v0.4.10 h1:Qe0mtiNFHQZ296vRgUjRCoPHPqH7VdTOrZx3g0T+pGA= @@ -227,18 +195,12 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= @@ -269,25 +231,18 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.35.0 h1:bGvFt68+KTiAKFlacHW6AhA56GF2rS0bdD3aJYEnmzA= -go.opentelemetry.io/contrib/detectors/gcp v1.35.0/go.mod h1:qGWP8/+ILwMRIUf9uIVLloR1uo5ZYAslM4O6OqUi1DA= go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel/exporters/prometheus v0.58.0 h1:CJAxWKFIqdBennqxJyOgnt5LqkeFRT+Mz3Yjz3hL+h8= go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0 h1:PB3Zrjs1sG1GBX51SXyTSoOTqcDglmsk7nT6tkKPb/k= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.35.0/go.mod h1:U2R3XyVPzn0WX7wOIypPuptulsMcPDPs/oiSVOMVnHY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY= go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= @@ -345,8 +300,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.234.0 h1:d3sAmYq3E9gdr2mpmiWGbm9pHsA/KJmyiLkwKfHBqU4= -google.golang.org/api v0.234.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo= google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -354,16 +307,10 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78= -google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk= google.golang.org/genproto v0.0.0-20250528174236-200df99c418a h1:KXuwdBmgjb4T3l4ZzXhP6HxxFKXD9FcK5/8qfJI4WwU= google.golang.org/genproto v0.0.0-20250528174236-200df99c418a/go.mod h1:Nlk93rrS2X7rV8hiC2gh2A/AJspZhElz9Oh2KGsjLEY= -google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2 h1:vPV0tzlsK6EzEDHNNH5sa7Hs9bd7iXR7B1tSiPepkV0= -google.golang.org/genproto/googleapis/api v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:pKLAc5OolXC3ViWGI62vvC0n10CpwAtRcTNCFwTKBEw= google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ= google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= @@ -371,8 +318,6 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= From ce9f8eb39ecb83578ff902a3b0f2e8d772ff608d Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 2 Jul 2025 14:11:16 +0530 Subject: [PATCH 0530/1298] Add metric for retries caused by gcs errors. (#3439) * Add metric for retries caused by gcs errors. * rebased * Update internal/storage/storageutil/custom_retry.go Co-authored-by: Kislay Kishore * Resolved comments * Divided test into retryable and non-retryable tests * Arranged legacy_main_test.go according to AAA. * Updated attribute name --------- Co-authored-by: Kislay Kishore --- cmd/legacy_main.go | 5 +- cmd/legacy_main_test.go | 24 +++--- common/noop_metrics.go | 1 + common/otel_metrics.go | 25 +++++- common/telemetry.go | 1 + internal/storage/storage_handle.go | 10 ++- internal/storage/storageutil/client.go | 3 + internal/storage/storageutil/custom_retry.go | 23 ++++++ .../storage/storageutil/custom_retry_test.go | 81 +++++++++++++++++++ 9 files changed, 152 insertions(+), 21 deletions(-) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index e91cc2be4a..4303de1e80 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -117,7 +117,7 @@ func getConfigForUserAgent(mountConfig *cfg.Config) string { } return fmt.Sprintf("%s:%s:%s:%s", isFileCacheEnabled, isFileCacheForRangeReadEnabled, isParallelDownloadsEnabled, areStreamingWritesEnabled) } -func createStorageHandle(newConfig *cfg.Config, userAgent string) (storageHandle storage.StorageHandle, err error) { +func createStorageHandle(newConfig *cfg.Config, userAgent string, metricHandle common.MetricHandle) (storageHandle storage.StorageHandle, err error) { storageClientConfig := storageutil.StorageClientConfig{ ClientProtocol: newConfig.GcsConnection.ClientProtocol, MaxConnsPerHost: int(newConfig.GcsConnection.MaxConnsPerHost), @@ -136,6 +136,7 @@ func createStorageHandle(newConfig *cfg.Config, userAgent string) (storageHandle GrpcConnPoolSize: int(newConfig.GcsConnection.GrpcConnPoolSize), EnableHNS: newConfig.EnableHns, ReadStallRetryConfig: newConfig.GcsRetries.ReadStall, + MetricHandle: metricHandle, } logger.Infof("UserAgent = %s\n", storageClientConfig.UserAgent) storageHandle, err = storage.NewStorageHandle(context.Background(), storageClientConfig, newConfig.GcsConnection.BillingProject) @@ -164,7 +165,7 @@ func mountWithArgs(bucketName string, mountPoint string, newConfig *cfg.Config, if bucketName != canned.FakeBucketName { userAgent := getUserAgent(newConfig.AppName, getConfigForUserAgent(newConfig)) logger.Info("Creating Storage handle...") - storageHandle, err = createStorageHandle(newConfig, userAgent) + storageHandle, err = createStorageHandle(newConfig, userAgent, metricHandle) if err != nil { err = fmt.Errorf("failed to create storage handle using createStorageHandle: %w", err) return diff --git a/cmd/legacy_main_test.go b/cmd/legacy_main_test.go index f89bcac212..93f26e19ac 100644 --- a/cmd/legacy_main_test.go +++ b/cmd/legacy_main_test.go @@ -41,10 +41,10 @@ type MainTest struct { func (t *MainTest) TestCreateStorageHandle() { newConfig := &cfg.Config{ GcsConnection: cfg.GcsConnectionConfig{ClientProtocol: cfg.HTTP1}, - GcsAuth: cfg.GcsAuthConfig{KeyFile: "testdata/test_creds.json"}} + GcsAuth: cfg.GcsAuthConfig{KeyFile: "testdata/test_creds.json"}, + } - userAgent := "AppName" - storageHandle, err := createStorageHandle(newConfig, userAgent) + storageHandle, err := createStorageHandle(newConfig, "AppName", common.NewNoopMetrics()) assert.Equal(t.T(), nil, err) assert.NotEqual(t.T(), nil, storageHandle) @@ -56,8 +56,7 @@ func (t *MainTest) TestCreateStorageHandle_WithClientProtocolAsGRPC() { GcsAuth: cfg.GcsAuthConfig{KeyFile: "testdata/test_creds.json"}, } - userAgent := "AppName" - storageHandle, err := createStorageHandle(newConfig, userAgent) + storageHandle, err := createStorageHandle(newConfig, "AppName", common.NewNoopMetrics()) assert.Equal(t.T(), nil, err) assert.NotEqual(t.T(), nil, storageHandle) @@ -66,25 +65,28 @@ func (t *MainTest) TestCreateStorageHandle_WithClientProtocolAsGRPC() { func (t *MainTest) TestGetUserAgentWhenMetadataImageTypeEnvVarIsSet() { os.Setenv("GCSFUSE_METADATA_IMAGE_TYPE", "DLVM") defer os.Unsetenv("GCSFUSE_METADATA_IMAGE_TYPE") - mountConfig := &cfg.Config{} + userAgent := getUserAgent("AppName", getConfigForUserAgent(mountConfig)) - expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s AppName (GPN:gcsfuse-DLVM) (Cfg:0:0:0:0)", common.GetVersion())) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s AppName (GPN:gcsfuse-DLVM) (Cfg:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } func (t *MainTest) TestGetUserAgentWhenMetadataImageTypeEnvVarIsNotSet() { mountConfig := &cfg.Config{} + userAgent := getUserAgent("AppName", getConfigForUserAgent(mountConfig)) - expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } func (t *MainTest) TestGetUserAgentConfigWithNoFileCache() { mountConfig := &cfg.Config{} + userAgent := getUserAgent("AppName", getConfigForUserAgent(mountConfig)) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } @@ -208,11 +210,11 @@ func (t *MainTest) TestGetUserAgentConfig() { func (t *MainTest) TestGetUserAgentWhenMetadataImageTypeEnvVarSetAndAppNameNotSet() { os.Setenv("GCSFUSE_METADATA_IMAGE_TYPE", "DLVM") defer os.Unsetenv("GCSFUSE_METADATA_IMAGE_TYPE") - mountConfig := &cfg.Config{} + userAgent := getUserAgent("", getConfigForUserAgent(mountConfig)) - expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-DLVM) (Cfg:0:0:0:0)", common.GetVersion())) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-DLVM) (Cfg:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } @@ -223,11 +225,11 @@ func (t *MainTest) TestCallListRecursiveOnExistingDirectory() { t.T().Fatalf("Failed to set up test. error = %v", err) } defer os.RemoveAll(rootdir) + _, err = os.CreateTemp(rootdir, "abc-*.txt") if err != nil { t.T().Fatalf("Failed to set up test. error = %v", err) } - err = callListRecursive(rootdir) assert.Nil(t.T(), err) diff --git a/common/noop_metrics.go b/common/noop_metrics.go index b574248811..1e48303a8b 100644 --- a/common/noop_metrics.go +++ b/common/noop_metrics.go @@ -32,6 +32,7 @@ func (*noopMetrics) GCSRequestCount(_ context.Context, _ int64, _ string) func (*noopMetrics) GCSRequestLatency(_ context.Context, _ time.Duration, _ string) {} func (*noopMetrics) GCSReadCount(_ context.Context, _ int64, _ string) {} func (*noopMetrics) GCSDownloadBytesCount(_ context.Context, _ int64, _ string) {} +func (*noopMetrics) GCSRetryCount(_ context.Context, _ int64, _ string) {} func (*noopMetrics) OpsCount(_ context.Context, _ int64, _ string) {} func (*noopMetrics) OpsLatency(_ context.Context, _ time.Duration, _ string) {} diff --git a/common/otel_metrics.go b/common/otel_metrics.go index 4c5b6e018f..267b079359 100644 --- a/common/otel_metrics.go +++ b/common/otel_metrics.go @@ -44,11 +44,14 @@ var ( readTypeKey = attribute.Key("read_type") // cacheHitKey specifies whether the read operation from file cache resulted in a cache-hit or miss. cacheHitKey = attribute.Key("cache_hit") + // retryErrCategoryKey specifies the category of the error that triggered a retry. + retryErrCategoryKey = attribute.Key("retry_error_category") fsOpsOptionCache, readTypeOptionCache, ioMethodOptionCache, gcsMethodOptionCache, + retryErrCategoryOptionCache, cacheHitOptionCache, cacheHitReadTypeOptionCache, fsOpsErrorCategoryOptionCache sync.Map @@ -105,6 +108,13 @@ func gcsMethodAttrOption(gcsMethod string) metric.MeasurementOption { }) } +func retryErrCategoryAttrOption(retryErrCategory string) metric.MeasurementOption { + return loadOrStoreAttrOption(&retryErrCategoryOptionCache, retryErrCategory, + func() attribute.Set { + return attribute.NewSet(retryErrCategoryKey.String(retryErrCategory)) + }) +} + func cacheHitReadTypeAttrOption(attr CacheHitReadType) metric.MeasurementOption { return loadOrStoreAttrOption(&cacheHitReadTypeOptionCache, attr, func() attribute.Set { return attribute.NewSet(cacheHitKey.String(attr.CacheHit), readTypeKey.String(attr.ReadType)) @@ -123,6 +133,7 @@ type otelMetrics struct { gcsRequestCount metric.Int64Counter gcsRequestLatency metric.Float64Histogram gcsDownloadBytesCount metric.Int64Counter + gcsRetryCount metric.Int64Counter fileCacheReadCount metric.Int64Counter fileCacheReadBytesCount metric.Int64Counter @@ -153,6 +164,10 @@ func (o *otelMetrics) GCSDownloadBytesCount(ctx context.Context, inc int64, read o.gcsDownloadBytesCount.Add(ctx, inc, readTypeAttrOption(readType)) } +func (o *otelMetrics) GCSRetryCount(ctx context.Context, inc int64, retryErrCategory string) { + o.gcsRetryCount.Add(ctx, inc, retryErrCategoryAttrOption(retryErrCategory)) +} + func (o *otelMetrics) OpsCount(ctx context.Context, inc int64, fsOp string) { o.fsOpsCount.Add(ctx, inc, fsOpsAttrOption(fsOp)) } @@ -200,18 +215,19 @@ func NewOTelMetrics() (MetricHandle, error) { gcsReaderCount, err7 := gcsMeter.Int64Counter("gcs/reader_count", metric.WithDescription("The cumulative number of GCS object readers opened or closed.")) gcsRequestCount, err8 := gcsMeter.Int64Counter("gcs/request_count", metric.WithDescription("The cumulative number of GCS requests processed along with the GCS method.")) gcsRequestLatency, err9 := gcsMeter.Float64Histogram("gcs/request_latencies", metric.WithDescription("The cumulative distribution of the GCS request latencies."), metric.WithUnit("ms")) + gcsRetryCount, err10 := gcsMeter.Int64Counter("gcs/retry_count", metric.WithDescription("The cumulative number of retry requests made to GCS.")) - fileCacheReadCount, err10 := fileCacheMeter.Int64Counter("file_cache/read_count", + fileCacheReadCount, err11 := fileCacheMeter.Int64Counter("file_cache/read_count", metric.WithDescription("Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false")) - fileCacheReadBytesCount, err11 := fileCacheMeter.Int64Counter("file_cache/read_bytes_count", + fileCacheReadBytesCount, err12 := fileCacheMeter.Int64Counter("file_cache/read_bytes_count", metric.WithDescription("The cumulative number of bytes read from file cache along with read type - Sequential/Random"), metric.WithUnit("By")) - fileCacheReadLatency, err12 := fileCacheMeter.Float64Histogram("file_cache/read_latencies", + fileCacheReadLatency, err13 := fileCacheMeter.Float64Histogram("file_cache/read_latencies", metric.WithDescription("The cumulative distribution of the file cache read latencies along with cache hit - true/false"), metric.WithUnit("us"), defaultLatencyDistribution) - if err := errors.Join(err1, err2, err3, err4, err5, err6, err7, err8, err9, err10, err11, err12); err != nil { + if err := errors.Join(err1, err2, err3, err4, err5, err6, err7, err8, err9, err10, err11, err12, err13); err != nil { return nil, err } @@ -225,6 +241,7 @@ func NewOTelMetrics() (MetricHandle, error) { gcsRequestCount: gcsRequestCount, gcsRequestLatency: gcsRequestLatency, gcsDownloadBytesCount: gcsDownloadBytesCount, + gcsRetryCount: gcsRetryCount, fileCacheReadCount: fileCacheReadCount, fileCacheReadBytesCount: fileCacheReadBytesCount, fileCacheReadLatency: fileCacheReadLatency, diff --git a/common/telemetry.go b/common/telemetry.go index 2a59025b71..749ff793ec 100644 --- a/common/telemetry.go +++ b/common/telemetry.go @@ -77,6 +77,7 @@ type GCSMetricHandle interface { GCSRequestLatency(ctx context.Context, latency time.Duration, gcsMethod string) GCSReadCount(ctx context.Context, inc int64, readType string) GCSDownloadBytesCount(ctx context.Context, inc int64, readType string) + GCSRetryCount(ctx context.Context, inc int64, gcsRetryErr string) } type OpsMetricHandle interface { diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index 0f08456b67..fe4031c494 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -111,7 +111,7 @@ func createClientOptionForGRPCClient(clientConfig *storageutil.StorageClientConf return } -func setRetryConfig(sc *storage.Client, clientConfig *storageutil.StorageClientConfig) { +func setRetryConfig(ctx context.Context, sc *storage.Client, clientConfig *storageutil.StorageClientConfig) { if sc == nil || clientConfig == nil { logger.Fatal("setRetryConfig: Empty storage client or clientConfig") return @@ -129,7 +129,9 @@ func setRetryConfig(sc *storage.Client, clientConfig *storageutil.StorageClientC Multiplier: clientConfig.RetryMultiplier, }), storage.WithPolicy(storage.RetryAlways), - storage.WithErrorFunc(storageutil.ShouldRetry)} + storage.WithErrorFunc(func(err error) bool { + return storageutil.ShouldRetryWithMonitoring(ctx, err, clientConfig.MetricHandle) + })} sc.SetRetry(retryOpts...) @@ -156,7 +158,7 @@ func createGRPCClientHandle(ctx context.Context, clientConfig *storageutil.Stora if err != nil { err = fmt.Errorf("NewGRPCClient: %w", err) } else { - setRetryConfig(sc, clientConfig) + setRetryConfig(ctx, sc, clientConfig) } // Unset the environment variable, since it's used only while creation of grpc client. @@ -222,7 +224,7 @@ func createHTTPClientHandle(ctx context.Context, clientConfig *storageutil.Stora err = fmt.Errorf("go http storage client creation failed: %w", err) return } - setRetryConfig(sc, clientConfig) + setRetryConfig(ctx, sc, clientConfig) return } diff --git a/internal/storage/storageutil/client.go b/internal/storage/storageutil/client.go index 16917579eb..a6ed6d6eb0 100644 --- a/internal/storage/storageutil/client.go +++ b/internal/storage/storageutil/client.go @@ -22,6 +22,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/auth" "golang.org/x/net/context" "golang.org/x/oauth2" @@ -57,6 +58,8 @@ type StorageClientConfig struct { EnableHNS bool ReadStallRetryConfig cfg.ReadStallGcsRetriesConfig + + MetricHandle common.MetricHandle } func CreateHttpClient(storageClientConfig *StorageClientConfig) (httpClient *http.Client, err error) { diff --git a/internal/storage/storageutil/custom_retry.go b/internal/storage/storageutil/custom_retry.go index 956159b088..0c3d83d115 100644 --- a/internal/storage/storageutil/custom_retry.go +++ b/internal/storage/storageutil/custom_retry.go @@ -15,7 +15,11 @@ package storageutil import ( + "context" + "errors" + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "google.golang.org/api/googleapi" "google.golang.org/grpc/codes" @@ -56,3 +60,22 @@ func ShouldRetry(err error) (b bool) { } return } + +func ShouldRetryWithMonitoring(ctx context.Context, err error, metricHandle common.MetricHandle) bool { + if err == nil { + return false + } + + retry := ShouldRetry(err) + if !retry { + return false + } + // Record metrics + val := "OTHER_ERRORS" + if errors.Is(err, context.DeadlineExceeded) { + val = "STALLED_READ_REQUEST" + } + + metricHandle.GCSRetryCount(ctx, 1, val) + return retry +} diff --git a/internal/storage/storageutil/custom_retry_test.go b/internal/storage/storageutil/custom_retry_test.go index dfbb3973a3..cc5b617f2a 100644 --- a/internal/storage/storageutil/custom_retry_test.go +++ b/internal/storage/storageutil/custom_retry_test.go @@ -15,12 +15,14 @@ package storageutil import ( + "context" "errors" "io" "net" "net/url" "testing" + "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/stretchr/testify/assert" "google.golang.org/api/googleapi" "google.golang.org/grpc/codes" @@ -146,3 +148,82 @@ func TestShouldRetryReturnsTrueForUnauthenticatedGrpcErrors(t *testing.T) { }) } } + +type fakeMetricHandle struct { + common.MetricHandle + + gcsRetryCountCalled bool + gcsRetryCountInc int64 + gcsRetryCountVal string +} + +func (m *fakeMetricHandle) GCSRetryCount(ctx context.Context, inc int64, val string) { + m.gcsRetryCountCalled = true + m.gcsRetryCountInc = inc + m.gcsRetryCountVal = val +} + +func TestShouldRetryWithMonitoringForNonRetryableErrors(t *testing.T) { + testCases := []struct { + name string + err error + }{ + { + name: "nil error", + err: nil, + }, + { + name: "non-retryable error", + err: &googleapi.Error{Code: 400}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + fakeMetrics := &fakeMetricHandle{ + MetricHandle: common.NewNoopMetrics(), + } + + shouldRetry := ShouldRetryWithMonitoring(context.Background(), tc.err, fakeMetrics) + + assert.False(t, shouldRetry) + assert.False(t, fakeMetrics.gcsRetryCountCalled) + }) + } +} + +func TestShouldRetryWithMonitoringForRetryableErrors(t *testing.T) { + retryableErr := &googleapi.Error{Code: 429} + + testCases := []struct { + name string + err error + expectedMetricCategory string + }{ + { + name: "retryable error, DeadlineExceeded", + err: context.DeadlineExceeded, + expectedMetricCategory: "STALLED_READ_REQUEST", + }, + { + name: "retryable error, not DeadlineExceeded", + err: retryableErr, + expectedMetricCategory: "OTHER_ERRORS", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + fakeMetrics := &fakeMetricHandle{ + MetricHandle: common.NewNoopMetrics(), + } + + shouldRetry := ShouldRetryWithMonitoring(context.Background(), tc.err, fakeMetrics) + + assert.True(t, shouldRetry) + assert.True(t, fakeMetrics.gcsRetryCountCalled) + assert.Equal(t, int64(1), fakeMetrics.gcsRetryCountInc) + assert.Equal(t, tc.expectedMetricCategory, fakeMetrics.gcsRetryCountVal) + }) + } +} From 7ab8ba883325a874fe988964cd3e6455f33a423c Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 2 Jul 2025 14:13:34 +0530 Subject: [PATCH 0531/1298] Install presubmit to verify go.mod is tidy (#3463) * Install presubmit to verify go.mod is tidy * Run `go mod tidy` as part of build. --- .github/workflows/ci.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42cf1b1278..11d197be24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - name: CodeGen run: go generate ./... - name: Formatting diff - run: go fmt ./... && git diff --exit-code --name-only + run: go fmt ./... && go mod tidy && git diff --exit-code --name-only linux-tests: strategy: diff --git a/Makefile b/Makefile index 78d60b4978..e349a14730 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ imports: generate goimports -w . fmt: imports - go fmt ./... + go mod tidy && go fmt ./... vet: fmt go vet ./... From e1c05a86ec7289bf0bad6de76f887ccdcedcf637 Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Thu, 3 Jul 2025 10:43:50 +0530 Subject: [PATCH 0532/1298] Update Jacobsa FUSE version and add initial support for ReadDirPlus (#3467) --- go.mod | 2 +- go.sum | 4 ++-- internal/fs/fs.go | 8 ++++++++ internal/fs/wrappers/error_mapping.go | 9 +++++++++ internal/fs/wrappers/monitoring.go | 4 ++++ internal/fs/wrappers/tracing.go | 4 ++++ internal/fs/wrappers/tracing_test.go | 4 ++++ 7 files changed, 32 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c2cbecd552..a6d3740954 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.2 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec - github.com/jacobsa/fuse v0.0.0-20250506064942-d10bfe4fb08e + github.com/jacobsa/fuse v0.0.0-20250702080931-3e9d24d5e3ff github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 diff --git a/go.sum b/go.sum index bf27842090..c490d55044 100644 --- a/go.sum +++ b/go.sum @@ -138,8 +138,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= -github.com/jacobsa/fuse v0.0.0-20250506064942-d10bfe4fb08e h1:l3GV2yC45OcDO/ErTSS/LnLQdytjUSmmBFcQCjWSUDY= -github.com/jacobsa/fuse v0.0.0-20250506064942-d10bfe4fb08e/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= +github.com/jacobsa/fuse v0.0.0-20250702080931-3e9d24d5e3ff h1:QamXUXyWL9hTh55v6lFJdJOJN9bjX9i2Ml4zHZQq//M= +github.com/jacobsa/fuse v0.0.0-20250702080931-3e9d24d5e3ff/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 7ea661bee0..96a6f19359 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2507,6 +2507,14 @@ func (fs *fileSystem) ReadDir( return } +// LOCKS_EXCLUDED(fs.mu) +func (fs *fileSystem) ReadDirPlus( + ctx context.Context, + op *fuseops.ReadDirPlusOp) (err error) { + // TODO: Implement ReadDirPlus to fetch directory entries with attributes. + return syscall.ENOSYS +} + // LOCKS_EXCLUDED(fs.mu) func (fs *fileSystem) ReleaseDirHandle( ctx context.Context, diff --git a/internal/fs/wrappers/error_mapping.go b/internal/fs/wrappers/error_mapping.go index b010fab6ff..16eaf6ca64 100644 --- a/internal/fs/wrappers/error_mapping.go +++ b/internal/fs/wrappers/error_mapping.go @@ -280,6 +280,15 @@ func (em *errorMapping) ReadDir( return em.mapError("ReadDir", err) } +func (em *errorMapping) ReadDirPlus( + ctx context.Context, + op *fuseops.ReadDirPlusOp) error { + defer em.handlePanic() + + err := em.wrapped.ReadDirPlus(ctx, op) + return em.mapError("ReadDirPlus", err) +} + func (em *errorMapping) ReleaseDirHandle( ctx context.Context, op *fuseops.ReleaseDirHandleOp) error { diff --git a/internal/fs/wrappers/monitoring.go b/internal/fs/wrappers/monitoring.go index 614b7bc279..0f8a5e7edd 100644 --- a/internal/fs/wrappers/monitoring.go +++ b/internal/fs/wrappers/monitoring.go @@ -330,6 +330,10 @@ func (fs *monitoring) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) error return fs.invokeWrapped(ctx, "ReadDir", func(ctx context.Context) error { return fs.wrapped.ReadDir(ctx, op) }) } +func (fs *monitoring) ReadDirPlus(ctx context.Context, op *fuseops.ReadDirPlusOp) error { + return fs.invokeWrapped(ctx, "ReadDirPlus", func(ctx context.Context) error { return fs.wrapped.ReadDirPlus(ctx, op) }) +} + func (fs *monitoring) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) error { return fs.invokeWrapped(ctx, "ReleaseDirHandle", func(ctx context.Context) error { return fs.wrapped.ReleaseDirHandle(ctx, op) }) } diff --git a/internal/fs/wrappers/tracing.go b/internal/fs/wrappers/tracing.go index 73e169e97a..5ef16af61c 100644 --- a/internal/fs/wrappers/tracing.go +++ b/internal/fs/wrappers/tracing.go @@ -119,6 +119,10 @@ func (fs *tracing) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) error { return fs.invokeWrapped(ctx, "ReadDir", func(ctx context.Context) error { return fs.wrapped.ReadDir(ctx, op) }) } +func (fs *tracing) ReadDirPlus(ctx context.Context, op *fuseops.ReadDirPlusOp) error { + return fs.invokeWrapped(ctx, "ReadDirPlus", func(ctx context.Context) error { return fs.wrapped.ReadDirPlus(ctx, op) }) +} + func (fs *tracing) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) error { return fs.invokeWrapped(ctx, "ReleaseDirHandle", func(ctx context.Context) error { return fs.wrapped.ReleaseDirHandle(ctx, op) }) } diff --git a/internal/fs/wrappers/tracing_test.go b/internal/fs/wrappers/tracing_test.go index f98a259bf7..b621111032 100644 --- a/internal/fs/wrappers/tracing_test.go +++ b/internal/fs/wrappers/tracing_test.go @@ -103,6 +103,10 @@ func (d dummyFS) ReadDir(_ context.Context, _ *fuseops.ReadDirOp) error { return nil } +func (d dummyFS) ReadDirPlus(_ context.Context, _ *fuseops.ReadDirPlusOp) error { + return nil +} + func (d dummyFS) ReleaseDirHandle(_ context.Context, _ *fuseops.ReleaseDirHandleOp) error { return nil } From f1f5fca9d25ce619e12d6149000a97a50e284c6e Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Mon, 7 Jul 2025 12:28:53 +0530 Subject: [PATCH 0533/1298] Make PR review reminder default with Auto applying label on new/open/repopened PR(s). (#3474) * Make PR nudge default * minor fix * add new line at the end --- .github/workflows/auto-pr-reminder.yml | 30 ++++++++++++++++++++++++++ .github/workflows/pr-reminder.yml | 4 ++-- 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/auto-pr-reminder.yml diff --git a/.github/workflows/auto-pr-reminder.yml b/.github/workflows/auto-pr-reminder.yml new file mode 100644 index 0000000000..aadfc07b25 --- /dev/null +++ b/.github/workflows/auto-pr-reminder.yml @@ -0,0 +1,30 @@ +name: Auto Add Label on Ready for Review or Reopen PR + +on: + pull_request: + types: + - opened + - reopened + - ready_for_review + branches: + - master + +jobs: + add-label: + # This condition ensures the label is only added if the PR is not a draft. + # When a PR is reopened, it is never in a draft state. + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Add 'remind-reviewers' label + uses: actions/github-script@v6 + with: + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['remind-reviewers'] + }) diff --git a/.github/workflows/pr-reminder.yml b/.github/workflows/pr-reminder.yml index 465ba93d4a..451cf164a4 100644 --- a/.github/workflows/pr-reminder.yml +++ b/.github/workflows/pr-reminder.yml @@ -4,8 +4,8 @@ on: # Allow manual runs. workflow_dispatch: schedule: - # Runs at 9:30 UTC, which is 2:30 PM IST weekdays. - - cron: '30 9 * * 1-5' + # Runs every hour from 9AM to 5PM IST weekdays. + - cron: '30 3-11 * * 1-5' jobs: remind: From 033f97e9ac94d5dadcbff422358fc12a207c3ac0 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 7 Jul 2025 18:07:58 +0530 Subject: [PATCH 0534/1298] Block Pool: Release Block Flow Improvement for Buffered Write (#3475) * Block Pool: Release Block Flow Improvement for Buffered Write * addressing minor comments --- internal/block/block_pool.go | 15 +++++++++++++++ .../bufferedwrites/buffered_write_handler.go | 2 +- internal/bufferedwrites/upload_handler.go | 16 ++++++++-------- .../bufferedwrites/upload_handler_test.go | 19 +++++++++++-------- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/internal/block/block_pool.go b/internal/block/block_pool.go index 7ba258ecc6..6ce74cc107 100644 --- a/internal/block/block_pool.go +++ b/internal/block/block_pool.go @@ -113,6 +113,15 @@ func (bp *BlockPool) FreeBlocksChannel() chan Block { return bp.freeBlocksCh } +// Release puts the block back into the free blocks channel for reuse. +func (bp *BlockPool) Release(b Block) { + select { + case bp.freeBlocksCh <- b: + default: + panic("Block pool's free blocks channel is full, this should never happen") + } +} + // BlockSize returns the block size used by the blockPool. func (bp *BlockPool) BlockSize() int64 { return bp.blockSize @@ -138,3 +147,9 @@ func (bp *BlockPool) ClearFreeBlockChannel(releaseLastBlock bool) error { } } } + +// TotalFreeBlocks returns the total number of free blocks available in the pool. +// This is useful for testing and debugging purposes. +func (bp *BlockPool) TotalFreeBlocks() int { + return len(bp.freeBlocksCh) +} diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 277f1e0467..8b24e32a3d 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -117,7 +117,7 @@ func NewBWHandler(req *CreateBWHandlerRequest) (bwh BufferedWriteHandler, err er Object: req.Object, ObjectName: req.ObjectName, Bucket: req.Bucket, - FreeBlocksCh: bp.FreeBlocksChannel(), + BlockPool: bp, MaxBlocksPerFile: req.MaxBlocksPerFile, BlockSize: req.BlockSize, ChunkTransferTimeoutSecs: req.ChunkTransferTimeoutSecs, diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 2ef5e43e3b..2df49bc9c3 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -39,8 +39,8 @@ type UploadHandler struct { // Wait group for waiting for the uploader goroutine to finish. wg sync.WaitGroup - // Channel on which uploaded block will be posted for reuse. - freeBlocksCh chan block.Block + // Used to release the free (uploaded) block back to the pool. + blockPool *block.BlockPool // writer to resumable upload the blocks to GCS. writer gcs.Writer @@ -63,7 +63,7 @@ type CreateUploadHandlerRequest struct { Object *gcs.Object ObjectName string Bucket gcs.Bucket - FreeBlocksCh chan block.Block + BlockPool *block.BlockPool MaxBlocksPerFile int64 BlockSize int64 ChunkTransferTimeoutSecs int64 @@ -74,7 +74,7 @@ func newUploadHandler(req *CreateUploadHandlerRequest) *UploadHandler { uh := &UploadHandler{ uploadCh: make(chan block.Block, req.MaxBlocksPerFile), wg: sync.WaitGroup{}, - freeBlocksCh: req.FreeBlocksCh, + blockPool: req.BlockPool, bucket: req.Bucket, objectName: req.ObjectName, obj: req.Object, @@ -131,7 +131,7 @@ func (uh *UploadHandler) UploadError() (err error) { func (uh *UploadHandler) uploader() { for currBlock := range uh.uploadCh { if uh.UploadError() != nil { - uh.freeBlocksCh <- currBlock + uh.blockPool.Release(currBlock) uh.wg.Done() continue } @@ -147,8 +147,8 @@ func (uh *UploadHandler) uploader() { err = gcs.GetGCSError(err) uh.uploadError.Store(&err) } - // Put back the uploaded block on the freeBlocksChannel for re-use. - uh.freeBlocksCh <- currBlock + // Put back the uploaded block on the block pool for re-use. + uh.blockPool.Release(currBlock) uh.wg.Done() } } @@ -227,7 +227,7 @@ func (uh *UploadHandler) Destroy() { if !ok { return } - uh.freeBlocksCh <- currBlock + uh.blockPool.Release(currBlock) // Marking as wg.Done to ensure any waiters are unblocked. uh.wg.Done() default: diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index f22c5dc745..c2b74effe1 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -60,7 +60,7 @@ func (t *UploadHandlerTest) SetupTest() { Object: nil, ObjectName: "testObject", Bucket: t.mockBucket, - FreeBlocksCh: t.blockPool.FreeBlocksChannel(), + BlockPool: t.blockPool, MaxBlocksPerFile: maxBlocks, BlockSize: blockSize, ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, @@ -80,7 +80,7 @@ func (t *UploadHandlerTest) createUploadHandlerWithObjectOfGivenSize(size uint64 }, ObjectName: "testObject", Bucket: t.mockBucket, - FreeBlocksCh: t.blockPool.FreeBlocksChannel(), + BlockPool: t.blockPool, MaxBlocksPerFile: maxBlocks, BlockSize: blockSize, ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, @@ -182,11 +182,14 @@ func (t *UploadHandlerTest) TestMultipleBlockUpload() { require.NoError(t.T(), err) require.NotNil(t.T(), obj) assert.Equal(t.T(), mockObj, obj) - // The blocks should be available on the free channel for reuse. + // All the 5 blocks should be available on the free channel for reuse. + assert.Equal(t.T(), 5, t.uh.blockPool.TotalFreeBlocks()) for _, expect := range blocks { - got := <-t.uh.freeBlocksCh + got, err := t.uh.blockPool.Get() + require.NoError(t.T(), err) assert.Equal(t.T(), expect, got) } + assert.Equal(t.T(), 0, t.uh.blockPool.TotalFreeBlocks()) assertAllBlocksProcessed(t.T(), t.uh) } @@ -338,7 +341,7 @@ func (t *UploadHandlerTest) TestUploadSingleBlockThrowsErrorInCopy() { // Expect an error on upload due to error while copying content to GCS writer. assertUploadFailureError(t.T(), t.uh) assertAllBlocksProcessed(t.T(), t.uh) - assert.Equal(t.T(), 1, len(t.uh.freeBlocksCh)) + assert.Equal(t.T(), 1, t.uh.blockPool.TotalFreeBlocks()) } func (t *UploadHandlerTest) TestUploadMultipleBlocksThrowsErrorInCopy() { @@ -366,7 +369,7 @@ func (t *UploadHandlerTest) TestUploadMultipleBlocksThrowsErrorInCopy() { assertUploadFailureError(t.T(), t.uh) assertAllBlocksProcessed(t.T(), t.uh) - assert.Equal(t.T(), 4, len(t.uh.freeBlocksCh)) + assert.Equal(t.T(), 4, t.uh.blockPool.TotalFreeBlocks()) } func assertUploadFailureError(t *testing.T, handler *UploadHandler) { @@ -431,7 +434,7 @@ func (t *UploadHandlerTest) TestMultipleBlockAwaitBlocksUpload() { // AwaitBlocksUpload. t.uh.AwaitBlocksUpload() - assert.Equal(t.T(), 5, len(t.uh.freeBlocksCh)) + assert.Equal(t.T(), 5, t.uh.blockPool.TotalFreeBlocks()) assert.Equal(t.T(), 0, len(t.uh.uploadCh)) assertAllBlocksProcessed(t.T(), t.uh) } @@ -539,7 +542,7 @@ func (t *UploadHandlerTest) TestDestroy() { t.uh.Destroy() assertAllBlocksProcessed(t.T(), t.uh) - assert.Equal(t.T(), 5, len(t.uh.freeBlocksCh)) + assert.Equal(t.T(), 5, t.uh.blockPool.TotalFreeBlocks()) assert.Equal(t.T(), 0, len(t.uh.uploadCh)) // Check if uploadCh is closed. select { From c3b7baedf4814e0d2ef4ea1047008474b03a722e Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 8 Jul 2025 12:26:08 +0530 Subject: [PATCH 0535/1298] upgrade gcloud version (#3483) --- tools/cd_scripts/e2e_test.sh | 12 ++++++++++++ tools/cd_scripts/install_test.sh | 14 +++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index f2d06bc4de..59d9efe15c 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -18,6 +18,15 @@ set -x # Exit immediately if a command exits with a non-zero status. set -e +# Upgrade gcloud +echo "Upgrade gcloud version" +gcloud version +wget -O gcloud.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz -q +sudo tar xzf gcloud.tar.gz && sudo cp -r google-cloud-sdk /usr/local && sudo rm -r google-cloud-sdk +sudo /usr/local/google-cloud-sdk/install.sh +export PATH=/usr/local/google-cloud-sdk/bin:$PATH +gcloud version && rm gcloud.tar.gz + # Extract the metadata parameters passed, for which we need the zone of the GCE VM # on which the tests are supposed to run. ZONE=$(curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/zone) @@ -58,6 +67,9 @@ set -e # Print commands and their arguments as they are executed. set -x +# Since we are now operating as the starterscriptuser, we need to set the environment variable for this user again. +export PATH=/usr/local/google-cloud-sdk/bin:$PATH + # Export the RUN_ON_ZB_ONLY variable so that it is available in the environment of the 'starterscriptuser' user. # Since we are running the subsequent script as 'starterscriptuser' using sudo, the environment of 'starterscriptuser' # would not automatically have access to the environment variables set by the original user (i.e. $RUN_ON_ZB_ONLY). diff --git a/tools/cd_scripts/install_test.sh b/tools/cd_scripts/install_test.sh index 2e86fe0cf0..506fd84601 100755 --- a/tools/cd_scripts/install_test.sh +++ b/tools/cd_scripts/install_test.sh @@ -16,8 +16,16 @@ # Print commands and their arguments as they are executed. set -x +echo "Upgrade gcloud version" +gcloud version +wget -O gcloud.tar.gz https://dl.google.com/dl/cloudsdk/channels/rapid/google-cloud-sdk.tar.gz -q +sudo tar xzf gcloud.tar.gz && sudo cp -r google-cloud-sdk /usr/local && sudo rm -r google-cloud-sdk +sudo /usr/local/google-cloud-sdk/install.sh +export PATH=/usr/local/google-cloud-sdk/bin:$PATH +gcloud version && rm gcloud.tar.gz + #details.txt file contains the release version and commit hash of the current release. -gsutil cp gs://gcsfuse-release-packages/version-detail/details.txt . +gcloud storage cp gs://gcsfuse-release-packages/version-detail/details.txt . # Writing VM instance name to details.txt (Format: release-test-) vm_instance_name=$(curl http://metadata.google.internal/computeMetadata/v1/instance/name -H "Metadata-Flavor: Google") # first line of details.txt contains the release version in the format MAJOR.MINOR.PATCH @@ -133,7 +141,7 @@ if grep -q Failure ~/logs.txt; then echo "Test failed" &>> ~/logs.txt ; else touch success.txt - gsutil cp success.txt gs://gcsfuse-release-packages/v$(sed -n 1p details.txt)/installation-test/$(sed -n 3p details.txt)/ ; + gcloud storage cp success.txt gs://gcsfuse-release-packages/v$(sed -n 1p details.txt)/installation-test/$(sed -n 3p details.txt)/ ; fi -gsutil cp ~/logs.txt gs://gcsfuse-release-packages/v$(sed -n 1p details.txt)/installation-test/$(sed -n 3p details.txt)/ +gcloud storage cp ~/logs.txt gs://gcsfuse-release-packages/v$(sed -n 1p details.txt)/installation-test/$(sed -n 3p details.txt)/ From 5fd3dcc8228f8ce31b4b61fd6127b8e0599057c7 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 8 Jul 2025 12:37:23 +0530 Subject: [PATCH 0536/1298] Block: Adding ReadAt method in block interface and memoryBlock (#3479) * Adding more tests * Block: Adding ReadAt method in the existing block * removing unnecessary code * review comments. --- internal/block/block.go | 20 ++++++++++++ internal/block/block_test.go | 62 +++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/internal/block/block.go b/internal/block/block.go index f3e81fa7f8..5d25e5aa17 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -38,6 +38,10 @@ type Block interface { Reader() io.Reader Deallocate() error + + // Follows io.ReaderAt interface. + // Here, off is relative to the start of the block. + ReadAt(p []byte, off int64) (n int, err error) } // TODO: check if we need offset or just storing end is sufficient. We might need @@ -109,3 +113,19 @@ func createBlock(blockSize int64) (Block, error) { } return &mb, nil } + +// ReadAt reads data from the block at the specified offset. +// The offset is relative to the start of the block. +// It returns the number of bytes read and an error if any. +func (m *memoryBlock) ReadAt(p []byte, off int64) (n int, err error) { + if off < 0 || off >= m.Size() { + return 0, fmt.Errorf("offset %d is out of bounds for block size %d", off, m.Size()) + } + + n = copy(p, m.buffer[m.offset.start+off:m.offset.end]) + + if n < len(p) { + return n, io.EOF + } + return n, nil +} diff --git a/internal/block/block_test.go b/internal/block/block_test.go index 3b7d1673ec..77d256142b 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -141,6 +141,66 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockDeAllocate() { err = mb.Deallocate() + assert.Nil(testSuite.T(), err) + assert.Nil(testSuite.T(), mb.(*memoryBlock).buffer) +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockReadAtSuccess() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + _, err = mb.Write(content) require.Nil(testSuite.T(), err) - require.Nil(testSuite.T(), mb.(*memoryBlock).buffer) + readBuffer := make([]byte, 5) + + n, err := mb.ReadAt(readBuffer, 6) // Read "world" + + assert.Nil(testSuite.T(), err) + assert.Equal(testSuite.T(), 5, n) + assert.Equal(testSuite.T(), []byte("world"), readBuffer) +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockReadAtBeyondBlockSize() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + _, err = mb.Write(content) + require.Nil(testSuite.T(), err) + readBuffer := make([]byte, 5) + + n, err := mb.ReadAt(readBuffer, 15) // Read beyond the block size + + assert.NotNil(testSuite.T(), err) + assert.NotErrorIs(testSuite.T(), err, io.EOF) + assert.Equal(testSuite.T(), 0, n) +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockReadAtWithNegativeOffset() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + _, err = mb.Write(content) + require.Nil(testSuite.T(), err) + readBuffer := make([]byte, 5) + + n, err := mb.ReadAt(readBuffer, -1) // Negative offset + + assert.NotNil(testSuite.T(), err) + assert.NotErrorIs(testSuite.T(), err, io.EOF) + assert.Equal(testSuite.T(), 0, n) +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockReadAtEOF() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + _, err = mb.Write(content) + require.Nil(testSuite.T(), err) + readBuffer := make([]byte, 15) + + n, err := mb.ReadAt(readBuffer, 6) // Read "world" + + assert.Equal(testSuite.T(), io.EOF, err) + assert.Equal(testSuite.T(), 5, n) + assert.Equal(testSuite.T(), []byte("world"), readBuffer[0:n]) } From 0d349805d4005f6c8e8a3c5a391390245d2b10a9 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 8 Jul 2025 13:33:10 +0530 Subject: [PATCH 0537/1298] Upgrade net dependency (#3482) * upgrade net dependecy * lint fix --- go.mod | 8 ++++---- go.sum | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index a6d3740954..d9d2f3778f 100644 --- a/go.mod +++ b/go.mod @@ -40,11 +40,11 @@ require ( go.opentelemetry.io/otel/sdk v1.36.0 go.opentelemetry.io/otel/sdk/metric v1.36.0 go.opentelemetry.io/otel/trace v1.36.0 - golang.org/x/net v0.40.0 + golang.org/x/net v0.41.0 golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.14.0 + golang.org/x/sync v0.15.0 golang.org/x/sys v0.33.0 - golang.org/x/text v0.25.0 + golang.org/x/text v0.26.0 golang.org/x/time v0.11.0 google.golang.org/api v0.235.0 google.golang.org/grpc v1.72.2 @@ -100,7 +100,7 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.38.0 // indirect + golang.org/x/crypto v0.39.0 // indirect google.golang.org/genproto v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect diff --git a/go.sum b/go.sum index c490d55044..0dfb56f535 100644 --- a/go.sum +++ b/go.sum @@ -259,8 +259,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -271,16 +271,16 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -290,8 +290,8 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= From c8a9c123c6208c942b382d3dfeb3623ee311ba86 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 8 Jul 2025 16:07:43 +0530 Subject: [PATCH 0538/1298] Enable read of unfinalized objects while being written via streaming (#3405) **What this change does**: - This pull request enables reading from GCS objects while they are being written to via streaming. - This change is particularly relevant for zonal buckets. - The changes primarily involves modifying the conditions under which a read is permitted and ensuring data is flushed to GCS before a read occurs. - The associated unit and integration tests have been updated to validate this new behavior. ### Link to the issue in case of a bug fix. [b/425828097](http://b/425828097) [b/410698332](http://b/410698332) [b/422355580](http://b/422355580) * support reading object even when that object has streaming-write enabled * enable e2e read test for unfinalized-object * disallow reading from finalized objects when upload in progresss * enable read-checks for zonal bucket objects in streaming_writes package * sync on bwh write before read for zonal bucket objects * self-review comments in gargnitin/support-unfinalized-read/read-cached/v2 * address some review comments * fix a legacy unit test * address Mohit's review comments * empty commit * put back a necessary error message * address Mohits comments * address Ashmeen review comment * Address review comments * empty commit --- internal/fs/fs.go | 14 ++++++++++---- internal/fs/inode/file.go | 7 +++---- internal/fs/inode/file_streaming_writes_test.go | 5 ++--- internal/fs/inode/file_test.go | 17 +++++++++++------ .../common_streaming_writes_suite_test.go | 12 +++--------- .../unfinalized_object/unfinalized_read_test.go | 13 ++++++------- 6 files changed, 35 insertions(+), 33 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 96a6f19359..6a4bed8af5 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2587,10 +2587,16 @@ func (fs *fileSystem) ReadFile( fh.Lock() fh.Inode().Lock() defer fh.Unlock() - // TODO(b/417136852): Remove bucket type check when we start leaving zonal bucket objects unfinalized. - // Flush Pending streaming writes file for regional bucket and issue read within same inode lock. - if fh.Inode().IsUsingBWH() && !fh.Inode().Bucket().BucketType().Zonal { - err = fs.flushFile(ctx, fh.Inode()) + if fh.Inode().IsUsingBWH() { + // Flush Pending streaming writes and issue read within same inode lock. + // TODO(b/417136852): Remove bucket type check and call only flushFile + // when we start leaving zonal bucket objects unfinalized. + if !fh.Inode().Bucket().BucketType().Zonal { + err = fs.flushFile(ctx, fh.Inode()) + } else { + // Flush but don't finalize, in zonal bucket. + err = fs.syncFile(ctx, fh.Inode()) + } if err != nil { fh.Inode().Unlock() return err diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 23c33ba4a0..2232f0acc0 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -20,7 +20,6 @@ import ( "io" "strconv" "strings" - "syscall" "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" @@ -394,7 +393,8 @@ func (f *FileInode) Source() *gcs.MinObject { func (f *FileInode) SourceGenerationIsAuthoritative() bool { // Source generation is authoritative if: // 1. No pending writes exists on the inode (both content and bwh are nil). - return f.content == nil && f.bwh == nil + // 2. The bucket is zonal and there are no pending writes in the temporary file. + return (f.content == nil && f.bwh == nil) || (f.bucket.BucketType().Zonal && f.content == nil) } // Equivalent to the generation returned by f.Source(). @@ -551,9 +551,8 @@ func (f *FileInode) Read( ctx context.Context, dst []byte, offset int64) (n int, err error) { - // It is not nil when streaming writes are enabled and bucket type is Zonal. if f.bwh != nil { - err = fmt.Errorf("cannot read a file when upload in progress: %w", syscall.ENOTSUP) + err = fmt.Errorf("unexpected read call for %q when streaming write is in progress for it", f.Name().LocalName()) return } diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 09c926e7b7..c4b2a962e6 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -223,13 +223,12 @@ func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritative assert.True(t.T(), t.in.SourceGenerationIsAuthoritative()) } -func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritativeReturnsFalseAfterWriteForZonalBuckets() { +func (t *FileStreamingWritesZonalBucketTest) TestSourceGenerationIsAuthoritativeReturnsTrueAfterWriteForZonalBuckets() { t.createBufferedWriteHandler() gcsSynced, err := t.in.Write(t.ctx, []byte("taco"), 0, util.Write) assert.NoError(t.T(), err) assert.False(t.T(), gcsSynced) - - assert.False(t.T(), t.in.SourceGenerationIsAuthoritative()) + assert.True(t.T(), t.in.SourceGenerationIsAuthoritative()) } func (t *FileStreamingWritesZonalBucketTest) TestSyncPendingBufferedWritesForZonalBucketsPromotesInodeToNonLocal() { diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 097957c1df..0b39ab5ce3 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -22,7 +22,6 @@ import ( "os" "strconv" "strings" - "syscall" "testing" "time" @@ -1679,14 +1678,20 @@ func (t *FileTest) TestReadFileWhenStreamingWritesAreEnabled() { assert.False(t.T(), gcsSynced) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) } - - data := make([]byte, 10) + data := make([]byte, len("hi")) + // Flush is required before reading an object for which BWH is open. + assert.NoError(t.T(), t.in.Flush(context.Background())) n, err := t.in.Read(t.ctx, data, 0) - assert.Equal(t.T(), 0, n) - require.Error(t.T(), err) - assert.ErrorIs(t.T(), err, syscall.ENOTSUP) + if tc.performWrite { + assert.Equal(t.T(), len(data), n) + require.NoError(t.T(), err) + } else { + assert.Equal(t.T(), 0, n) + require.Error(t.T(), err) + assert.ErrorIs(t.T(), err, io.EOF) + } }) } } diff --git a/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go b/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go index 16feb98e48..5c0d39a185 100644 --- a/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go +++ b/tools/integration_tests/streaming_writes/common_streaming_writes_suite_test.go @@ -19,7 +19,6 @@ import ( "slices" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" "github.com/stretchr/testify/assert" @@ -53,12 +52,7 @@ func (t *StreamingWritesSuite) TearDownSuite() { func (t *StreamingWritesSuite) validateReadCall(fh *os.File, content string) { readContent := make([]byte, len(content)) n, err := fh.ReadAt(readContent, 0) - // TODO(b/417136852): Fix validation once zb reads start working. - if setup.IsZonalBucketRun() && !t.fallbackToDiskCase { - operations.ValidateEOPNOTSUPPError(t.T(), err) - } else { - require.NoError(t.T(), err) - assert.Equal(t.T(), len(content), n) - assert.Equal(t.T(), content, string(readContent)) - } + require.NoError(t.T(), err) + assert.Equal(t.T(), len(content), n) + assert.Equal(t.T(), content, string(readContent)) } diff --git a/tools/integration_tests/unfinalized_object/unfinalized_read_test.go b/tools/integration_tests/unfinalized_object/unfinalized_read_test.go index 288f8eea1c..fddfae10dc 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_read_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_read_test.go @@ -18,7 +18,6 @@ import ( "context" "log" "path" - "syscall" "testing" "cloud.google.com/go/storage" @@ -55,19 +54,19 @@ func (t *unfinalizedObjectReads) TeardownTest() {} // Test scenarios //////////////////////////////////////////////////////////////////////// -func (t *unfinalizedObjectReads) TestUnfinalizedObjectsCantBeRead() { +func (t *unfinalizedObjectReads) TestUnfinalizedObjectsCanBeRead() { var size int = operations.MiB + writtenContent := setup.GenerateRandomString(size) // Create un-finalized object via same mount. fh := operations.CreateFile(path.Join(t.testDirPath, t.fileName), setup.FilePermission_0600, t.T()) - operations.WriteWithoutClose(fh, setup.GenerateRandomString(size), t.T()) + operations.WriteWithoutClose(fh, writtenContent, t.T()) defer operations.CloseFileShouldNotThrowError(t.T(), fh) // Read un-finalized object. - content, err := operations.ReadFileSequentially(path.Join(t.testDirPath, t.fileName), util.MiB) + readContent, err := operations.ReadFileSequentially(path.Join(t.testDirPath, t.fileName), util.MiB) - require.Error(t.T(), err) - assert.ErrorContains(t.T(), err, syscall.ENOTSUP.Error()) - assert.Empty(t.T(), content, "expected empty content, but got size: %v", len(content)) + require.NoError(t.T(), err) + assert.Equal(t.T(), writtenContent, string(readContent)) } //////////////////////////////////////////////////////////////////////// From e94b00a9ee68ba0abd46730ff0690c9442828ac4 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 9 Jul 2025 10:15:35 +0530 Subject: [PATCH 0539/1298] Block: Adding Getter/Setter for the AbsStartOffset in the block (#3480) * Block: Adding getter/setter for abs start offset * minor test update * Review comments --- internal/block/block.go | 40 ++++++++++++++++++++++++-- internal/block/block_test.go | 56 ++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+), 2 deletions(-) diff --git a/internal/block/block.go b/internal/block/block.go index 5d25e5aa17..6f39d9e823 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -42,6 +42,16 @@ type Block interface { // Follows io.ReaderAt interface. // Here, off is relative to the start of the block. ReadAt(p []byte, off int64) (n int, err error) + + // AbsStartOff returns the absolute start offset of the block. + // Panics if the absolute start offset is not set. + AbsStartOff() int64 + + // SetAbsStartOff sets the absolute start offset of the block. + // This should be called only once just after getting the block from the pool. + // It returns an error if the startOff is negative or if it is already set. + // TODO(princer): check if a way to set it as part of constructor. + SetAbsStartOff(startOff int64) error } // TODO: check if we need offset or just storing end is sufficient. We might need @@ -54,6 +64,9 @@ type memoryBlock struct { Block buffer []byte offset offset + + // Stores the absolute start offset of the block-segment in the file. + absStartOff int64 } func (m *memoryBlock) Reuse() { @@ -61,6 +74,7 @@ func (m *memoryBlock) Reuse() { m.offset.end = 0 m.offset.start = 0 + m.absStartOff = -1 } func (m *memoryBlock) Size() int64 { @@ -108,8 +122,9 @@ func createBlock(blockSize int64) (Block, error) { } mb := memoryBlock{ - buffer: addr, - offset: offset{0, 0}, + buffer: addr, + offset: offset{0, 0}, + absStartOff: -1, } return &mb, nil } @@ -129,3 +144,24 @@ func (m *memoryBlock) ReadAt(p []byte, off int64) (n int, err error) { } return n, nil } + +func (m *memoryBlock) AbsStartOff() int64 { + if m.absStartOff < 0 { + panic("AbsStartOff is not set, it should be set before calling this method.") + } + return m.absStartOff +} + +func (m *memoryBlock) SetAbsStartOff(startOff int64) error { + if startOff < 0 { + return fmt.Errorf("startOff cannot be negative, got %d", startOff) + } + + // If absStartOff is already set, then return an error. + if m.absStartOff >= 0 { + return fmt.Errorf("AbsStartOff is already set, it should be set only once.") + } + + m.absStartOff = startOff + return nil +} diff --git a/internal/block/block_test.go b/internal/block/block_test.go index 77d256142b..2dcb754a58 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -99,6 +99,8 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockReuse() { require.Nil(testSuite.T(), err) require.Equal(testSuite.T(), content, output) require.Equal(testSuite.T(), int64(2), mb.Size()) + err = mb.SetAbsStartOff(23) + require.Nil(testSuite.T(), err) mb.Reuse() @@ -106,6 +108,9 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockReuse() { assert.Nil(testSuite.T(), err) assert.Empty(testSuite.T(), output) assert.Equal(testSuite.T(), int64(0), mb.Size()) + assert.Panics(testSuite.T(), func() { + _ = mb.AbsStartOff() + }) } // Other cases for Size are covered as part of write tests. @@ -204,3 +209,54 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockReadAtEOF() { assert.Equal(testSuite.T(), 5, n) assert.Equal(testSuite.T(), []byte("world"), readBuffer[0:n]) } + +func (testSuite *MemoryBlockTest) TestMemoryBlockAbsStartOffsetPanicsOnEmptyBlock() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + + // The absolute start offset should be -1 initially. + assert.Panics(testSuite.T(), func() { + _ = mb.AbsStartOff() + }) +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockAbsStartOffsetValid() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + + // Set the absolute start offset to a valid value. + mb.(*memoryBlock).absStartOff = 100 + + // The absolute start offset should return the set value. + assert.Equal(testSuite.T(), int64(100), mb.AbsStartOff()) +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockSetAbsStartOffsetInvalid() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + + err = mb.SetAbsStartOff(-23) + + assert.Error(testSuite.T(), err) +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockSetAbsStartOffsetValid() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + + err = mb.SetAbsStartOff(23) + + assert.NoError(testSuite.T(), err) + assert.Equal(testSuite.T(), int64(23), mb.AbsStartOff()) +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockSetAbsStartOffsetTwiceInvalid() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + err = mb.SetAbsStartOff(23) + require.Nil(testSuite.T(), err) + + err = mb.SetAbsStartOff(42) + + assert.Error(testSuite.T(), err) +} From b53f4ecbe6fc60d5d174b187b40deead36921dde Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Wed, 9 Jul 2025 11:14:44 +0530 Subject: [PATCH 0540/1298] Add boolean clobberedCheck parameter to Attributes method of Inode interface (#3478) Add cloberredCheck parameter in Attributes method --- internal/fs/fs.go | 2 +- internal/fs/inode/base_dir.go | 2 +- internal/fs/inode/base_dir_test.go | 14 ++- internal/fs/inode/dir.go | 2 +- internal/fs/inode/dir_test.go | 14 ++- internal/fs/inode/file.go | 24 ++-- .../fs/inode/file_streaming_writes_test.go | 14 +-- internal/fs/inode/file_test.go | 105 ++++++++++++------ internal/fs/inode/inode.go | 5 +- internal/fs/inode/symlink.go | 2 +- internal/fs/inode/symlink_test.go | 40 +++++++ 11 files changed, 164 insertions(+), 60 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 6a4bed8af5..9e190dd222 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1348,7 +1348,7 @@ func (fs *fileSystem) getAttributes( expiration time.Time, err error) { // Call through. - attr, err = in.Attributes(ctx) + attr, err = in.Attributes(ctx, true) if err != nil { return } diff --git a/internal/fs/inode/base_dir.go b/internal/fs/inode/base_dir.go index 562cb47cb7..2d0d5a6678 100644 --- a/internal/fs/inode/base_dir.go +++ b/internal/fs/inode/base_dir.go @@ -147,7 +147,7 @@ func (d *baseDirInode) Destroy() (err error) { // LOCKS_REQUIRED(d) func (d *baseDirInode) Attributes( - ctx context.Context) (attrs fuseops.InodeAttributes, err error) { + ctx context.Context, clobberedCheck bool) (attrs fuseops.InodeAttributes, err error) { // Set up basic attributes. attrs = d.attrs attrs.Nlink = 1 diff --git a/internal/fs/inode/base_dir_test.go b/internal/fs/inode/base_dir_test.go index 4904c5e8e5..f10f559da1 100644 --- a/internal/fs/inode/base_dir_test.go +++ b/internal/fs/inode/base_dir_test.go @@ -152,8 +152,18 @@ func (t *BaseDirTest) LookupCount() { ExpectTrue(t.in.DecrementLookupCount(1)) } -func (t *BaseDirTest) Attributes() { - attrs, err := t.in.Attributes(t.ctx) +func (t *BaseDirTest) Attributes_ClobberedCheckTrue() { + attrs, err := t.in.Attributes(t.ctx, true) + + AssertEq(nil, err) + ExpectEq(uid, attrs.Uid) + ExpectEq(gid, attrs.Gid) + ExpectEq(dirMode|os.ModeDir, attrs.Mode) +} + +func (t *BaseDirTest) Attributes_ClobberedCheckFalse() { + attrs, err := t.in.Attributes(t.ctx, false) + AssertEq(nil, err) ExpectEq(uid, attrs.Uid) ExpectEq(gid, attrs.Gid) diff --git a/internal/fs/inode/dir.go b/internal/fs/inode/dir.go index df9993fd8d..c4c0d43894 100644 --- a/internal/fs/inode/dir.go +++ b/internal/fs/inode/dir.go @@ -512,7 +512,7 @@ func (d *dirInode) Destroy() (err error) { // LOCKS_REQUIRED(d) func (d *dirInode) Attributes( - ctx context.Context) (attrs fuseops.InodeAttributes, err error) { + ctx context.Context, clobberedCheck bool) (attrs fuseops.InodeAttributes, err error) { // Set up basic attributes. attrs = d.attrs attrs.Nlink = 1 diff --git a/internal/fs/inode/dir_test.go b/internal/fs/inode/dir_test.go index f0bf6dd1b7..69fb78301c 100644 --- a/internal/fs/inode/dir_test.go +++ b/internal/fs/inode/dir_test.go @@ -239,8 +239,18 @@ func (t *DirTest) LookupCount() { ExpectTrue(t.in.DecrementLookupCount(1)) } -func (t *DirTest) Attributes() { - attrs, err := t.in.Attributes(t.ctx) +func (t *DirTest) Attributes_WithClobberedCheckTrue() { + attrs, err := t.in.Attributes(t.ctx, true) + + AssertEq(nil, err) + ExpectEq(uid, attrs.Uid) + ExpectEq(gid, attrs.Gid) + ExpectEq(dirMode|os.ModeDir, attrs.Mode) +} + +func (t *DirTest) Attributes_WithClobberedCheckFalse() { + attrs, err := t.in.Attributes(t.ctx, false) + AssertEq(nil, err) ExpectEq(uid, attrs.Uid) ExpectEq(gid, attrs.Gid) diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 2232f0acc0..c1492285ad 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -469,9 +469,8 @@ func (f *FileInode) Destroy() (err error) { // LOCKS_REQUIRED(f.mu) func (f *FileInode) Attributes( - ctx context.Context) (attrs fuseops.InodeAttributes, err error) { + ctx context.Context, clobberedCheck bool) (attrs fuseops.InodeAttributes, err error) { attrs = f.attrs - // Obtain default information from the source object. attrs.Mtime = f.src.Updated attrs.Size = f.src.Size @@ -519,18 +518,25 @@ func (f *FileInode) Attributes( attrs.Atime = attrs.Mtime attrs.Ctime = attrs.Mtime - // If the object has been clobbered, we reflect that as the inode being - // unlinked. - _, clobbered, err := f.clobbered(ctx, false, false) - if err != nil { - err = fmt.Errorf("clobbered: %w", err) - return + if clobberedCheck { + // If the object has been clobbered, we reflect that as the inode being + // unlinked. + var clobbered bool + _, clobbered, err = f.clobbered(ctx, false, false) + if err != nil { + err = fmt.Errorf("clobbered: %w", err) + return + } + if clobbered { + attrs.Nlink = 0 + return + } } attrs.Nlink = 1 // For local files, also checking if file is unlinked locally. - if clobbered || (f.IsLocal() && f.IsUnlinked()) { + if f.IsLocal() && f.IsUnlinked() { attrs.Nlink = 0 } diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index c4b2a962e6..734e9a8b8f 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -345,7 +345,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF assert.False(t.T(), gcsSynced) require.NotNil(t.T(), t.in.bwh) // validate attributes. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.Nil(t.T(), err) assert.WithinDuration(t.T(), attrs.Mtime, createTime, 0) assert.Equal(t.T(), uint64(4), attrs.Size) @@ -360,7 +360,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWritesToLocalFileFallBackToTempF assert.Nil(t.T(), t.in.bwh) assert.NotNil(t.T(), t.in.content) // The inode should agree about the new mtime and size. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.Nil(t.T(), err) assert.Equal(t.T(), uint64(len(tc.expectedContent)), attrs.Size) assert.WithinDuration(t.T(), attrs.Mtime, mtime, 0) @@ -388,7 +388,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { assert.Nil(t.T(), t.in.bwh) assert.NotNil(t.T(), t.in.content) // validate attributes. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.Nil(t.T(), err) assert.WithinDuration(t.T(), attrs.Mtime, createTime, 0) assert.Equal(t.T(), uint64(10), attrs.Size) @@ -402,7 +402,7 @@ func (t *FileStreamingWritesTest) TestOutOfOrderWriteFollowedByOrderedWrite() { // Ensure bwh not re-created. assert.Nil(t.T(), t.in.bwh) // The inode should agree about the new mtime and size. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.Nil(t.T(), err) assert.Equal(t.T(), uint64(len("hello\x00taco")), attrs.Size) assert.WithinDuration(t.T(), attrs.Mtime, mtime, 0) @@ -523,7 +523,7 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndFlush() { // Verify that fileInode is no more local assert.False(t.T(), t.in.IsLocal()) // Check attributes. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("tacos")), attrs.Size) assert.Equal(t.T(), t.clock.Now().UTC(), attrs.Mtime.UTC()) @@ -578,7 +578,7 @@ func (t *FileStreamingWritesTest) TestFlushEmptyFile() { // Verify that fileInode is no more local assert.False(t.T(), t.in.IsLocal()) // Check attributes. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) // For synced file, mtime is updated by SetInodeAttributes call. @@ -744,7 +744,7 @@ func (t *FileStreamingWritesTest) TestTruncateOnFileUsingTempFileDoesNotRecreate // Ensure bwh not re-created. assert.Nil(t.T(), t.in.bwh) // The inode should agree about the new size. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.Nil(t.T(), err) assert.Equal(t.T(), uint64(10), attrs.Size) // sync file and validate content diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 0b39ab5ce3..777f82999a 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -302,8 +302,43 @@ func (t *FileTest) TestSyncPendingBufferedWritesReturnsNilAndNoOpForNonStreaming assert.Equal(t.T(), t.initialContents, string(contents)) } +func (t *FileTest) TestAttributes_Clobbered_WithClobberCheckTrue() { + // Simulate a clobbered file by creating a new object with the same name, + // which will have a new generation. + _, err := storageutil.CreateObject( + t.ctx, + t.bucket, + t.in.Name().GcsObjectName(), + []byte("new clobbering content")) + require.NoError(t.T(), err) + + attrs, err := t.in.Attributes(t.ctx, true) + + require.NoError(t.T(), err) + // Since clobberCheck is true and the generation has changed, + // Nlink should be 0. + assert.Equal(t.T(), uint32(0), attrs.Nlink) +} + +func (t *FileTest) TestAttributes_Clobbered_WithClobberCheckFalse() { + // Simulate a clobbered file by creating a new object with the same name, + // which will have a new generation. + _, err := storageutil.CreateObject( + t.ctx, + t.bucket, + t.in.Name().GcsObjectName(), + []byte("new clobbering content")) + require.NoError(t.T(), err) + + attrs, err := t.in.Attributes(t.ctx, false) + + require.NoError(t.T(), err) + // Since clobberCheck is false, Nlink should be 1. + assert.Equal(t.T(), uint32(1), attrs.Nlink) +} + func (t *FileTest) TestInitialAttributes() { - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len(t.initialContents)), attrs.Size) @@ -328,7 +363,7 @@ func (t *FileTest) TestInitialAttributes_MtimeFromObjectMetadata_Gcsfuse() { t.createInode() // Ask it for its attributes. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) @@ -346,7 +381,7 @@ func (t *FileTest) TestInitialAttributes_MtimeFromObjectMetadata_Gsutil() { t.createInode() // Ask it for its attributes. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime.UTC(), mtime) @@ -367,7 +402,7 @@ func (t *FileTest) TestInitialAttributes_MtimeFromObjectMetadata_GcsfuseOutranks t.createInode() // Ask it for its attributes. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, canonicalMtime) @@ -454,7 +489,7 @@ func (t *FileTest) TestWrite() { assert.Equal(t.T(), "pacoburrito", string(buf[:n])) // Check attributes. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("pacoburrito")), attrs.Size) @@ -489,7 +524,7 @@ func (t *FileTest) TestTruncate() { assert.Equal(t.T(), "ta", string(buf[:n])) // Check attributes. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("ta")), attrs.Size) @@ -576,7 +611,7 @@ func (t *FileTest) TestWriteThenSync() { assert.Equal(t.T(), "paco", string(contents)) // Check attributes. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("paco")), attrs.Size) @@ -649,7 +684,7 @@ func (t *FileTest) TestWriteToLocalFileThenSync() { require.NoError(t.T(), err) assert.Equal(t.T(), "tacos", string(contents)) // Check attributes. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("tacos")), attrs.Size) assert.Equal(t.T(), attrs.Mtime, writeTime.UTC()) @@ -717,7 +752,7 @@ func (t *FileTest) TestSyncEmptyLocalFile() { require.NoError(t.T(), err) assert.Equal(t.T(), "", string(contents)) // Check attributes. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) }) @@ -793,7 +828,7 @@ func (t *FileTest) TestAppendThenSync() { assert.Equal(t.T(), "tacoburrito", string(contents)) // Check attributes. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(len("tacoburrito")), attrs.Size) @@ -897,7 +932,7 @@ func (t *FileTest) TestTruncateDownwardThenSync() { m.Metadata["gcsfuse_mtime"]) // Check attributes. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) @@ -970,7 +1005,7 @@ func (t *FileTest) TestTruncateUpwardThenFlush() { assert.Equal(t.T(), uint64(6), m.Size) // Check attributes. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(6), attrs.Size) @@ -988,7 +1023,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes err = t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) // Fetch the attributes and check if the file is empty. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) @@ -997,7 +1032,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileShouldUpdateLocalFileAttributes assert.Nil(t.T(), err) assert.False(t.T(), gcsSynced) // The inode should return the new size. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(6), attrs.Size) // Data shouldn't be updated to GCS. @@ -1020,7 +1055,7 @@ func (t *FileTest) TestTruncateDownwardForLocalFileShouldUpdateLocalFileAttribut assert.Nil(t.T(), err) assert.False(t.T(), gcsSynced) // Validate the new data is written correctly. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(7), attrs.Size) @@ -1029,7 +1064,7 @@ func (t *FileTest) TestTruncateDownwardForLocalFileShouldUpdateLocalFileAttribut assert.Nil(t.T(), err) assert.False(t.T(), gcsSynced) // The inode should return the new size. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) // Data shouldn't be updated to GCS. @@ -1059,7 +1094,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() t.createInodeWithLocalParam("test", true) t.in.config = &cfg.Config{Write: *getWriteConfig()} // Fetch the attributes and check if the file is empty. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) @@ -1070,7 +1105,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() assert.False(t.T(), gcsSynced) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) // Fetch the attributes and check if the file size reflects the write. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) } @@ -1081,7 +1116,7 @@ func (t *FileTest) TestTruncateUpwardForLocalFileWhenStreamingWritesAreEnabled() assert.Nil(t.T(), err) assert.False(t.T(), gcsSynced) // The inode should return the new size. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(10), attrs.Size) // Data shouldn't be updated to GCS. @@ -1113,7 +1148,7 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable t.in.config = &cfg.Config{Write: *getWriteConfig()} // Fetch the attributes and check if the file is empty. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) @@ -1124,7 +1159,7 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable assert.False(t.T(), gcsSynced) assert.Equal(t.T(), int64(2), t.in.bwh.WriteFileInfo().TotalSize) // Fetch the attributes and check if the file size reflects the write. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) } @@ -1135,7 +1170,7 @@ func (t *FileTest) TestTruncateUpwardForEmptyGCSFileWhenStreamingWritesAreEnable assert.Nil(t.T(), err) assert.False(t.T(), gcsSynced) // The inode should return the new size. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(10), attrs.Size) // Data shouldn't be updated to GCS. @@ -1184,7 +1219,7 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { } t.in.config = &cfg.Config{Write: *getWriteConfig()} // Fetch the attributes and check if the file is empty. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(0), attrs.Size) @@ -1194,7 +1229,7 @@ func (t *FileTest) TestTruncateDownwardWhenStreamingWritesAreEnabled() { assert.False(t.T(), gcsSynced) assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) // Fetch the attributes and check if the file size reflects the write. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(7), attrs.Size) gcsSynced, err = t.in.Truncate(t.ctx, tc.truncateSize) @@ -1305,7 +1340,7 @@ func (t *FileTest) TestSetMtime_ContentNotFaultedIn() { assert.Nil(t.T(), err) // The inode should agree about the new mtime. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) @@ -1336,7 +1371,7 @@ func (t *FileTest) TestSetMtime_ContentClean() { assert.Nil(t.T(), err) // The inode should agree about the new mtime. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) @@ -1368,7 +1403,7 @@ func (t *FileTest) TestSetMtime_ContentDirty() { assert.Nil(t.T(), err) // The inode should agree about the new mtime. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) @@ -1452,7 +1487,7 @@ func (t *FileTest) TestSetMtime_SourceObjectMetaGenerationChanged() { func (t *FileTest) TestSetMtimeForUnlinkedFileIsNoOp() { t.in.unlinked = true - beforeUpdateAttr, err := t.in.Attributes(t.ctx) + beforeUpdateAttr, err := t.in.Attributes(t.ctx, true) require.Nil(t.T(), err) mtime := beforeUpdateAttr.Mtime.UTC().Add(123 * time.Second) @@ -1460,7 +1495,7 @@ func (t *FileTest) TestSetMtimeForUnlinkedFileIsNoOp() { err = t.in.SetMtime(t.ctx, mtime) require.Nil(t.T(), err) - afterUpdateAttr, err := t.in.Attributes(t.ctx) + afterUpdateAttr, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.NotEqual(t.T(), mtime, afterUpdateAttr.Mtime) assert.Equal(t.T(), beforeUpdateAttr.Mtime, afterUpdateAttr.Mtime) @@ -1477,7 +1512,7 @@ func (t *FileTest) TestTestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() err = t.in.CreateEmptyTempFile(t.ctx) assert.Nil(t.T(), err) // Validate the attributes on an empty file. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) @@ -1487,7 +1522,7 @@ func (t *FileTest) TestTestSetMtimeForLocalFileShouldUpdateLocalFileAttributes() assert.Nil(t.T(), err) // The inode should agree about the new mtime. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) assert.Equal(t.T(), attrs.Ctime, mtime) @@ -1514,7 +1549,7 @@ func (t *FileTest) TestSetMtimeForLocalFileWhenStreamingWritesAreEnabled() { assert.Nil(t.T(), err) // The inode should agree about the new mtime. - attrs, err = t.in.Attributes(t.ctx) + attrs, err = t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) assert.Equal(t.T(), attrs.Ctime, mtime) @@ -1753,7 +1788,7 @@ func (t *FileTest) TestMultipleWritesToLocalFileWhenStreamingWritesAreEnabled() assert.False(t.T(), gcsSynced) assert.Equal(t.T(), int64(7), t.in.bwh.WriteFileInfo().TotalSize) // The inode should agree about the new mtime. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(7), attrs.Size) assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) @@ -1773,7 +1808,7 @@ func (t *FileTest) TestWriteToEmptyGCSFileWhenStreamingWritesAreEnabled() { writeFileInfo := t.in.bwh.WriteFileInfo() assert.Equal(t.T(), int64(2), writeFileInfo.TotalSize) // The inode should agree about the new mtime. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), uint64(2), attrs.Size) assert.WithinDuration(t.T(), attrs.Mtime, createTime, Delta) @@ -1809,7 +1844,7 @@ func (t *FileTest) TestSetMtimeOnEmptyGCSFileAfterWritesWhenStreamingWritesAreEn assert.Nil(t.T(), err) // The inode should agree about the new mtime. - attrs, err := t.in.Attributes(t.ctx) + attrs, err := t.in.Attributes(t.ctx, true) require.NoError(t.T(), err) assert.Equal(t.T(), attrs.Mtime, mtime) assert.Equal(t.T(), attrs.Ctime, mtime) diff --git a/internal/fs/inode/inode.go b/internal/fs/inode/inode.go index 59c58eecb1..6cba9c5f44 100644 --- a/internal/fs/inode/inode.go +++ b/internal/fs/inode/inode.go @@ -41,7 +41,10 @@ type Inode interface { IncrementLookupCount() // Return up to date attributes for this inode. - Attributes(ctx context.Context) (fuseops.InodeAttributes, error) + // The `clobberedCheck` parameter controls whether this function performs a + // remote check to see if the backing GCS object has been modified by another + // process. + Attributes(ctx context.Context, clobberedCheck bool) (fuseops.InodeAttributes, error) // Decrement the lookup count for the inode by the given amount. // diff --git a/internal/fs/inode/symlink.go b/internal/fs/inode/symlink.go index 0e7ca5e726..d8adbbac8f 100644 --- a/internal/fs/inode/symlink.go +++ b/internal/fs/inode/symlink.go @@ -140,7 +140,7 @@ func (s *SymlinkInode) Destroy() (err error) { } func (s *SymlinkInode) Attributes( - ctx context.Context) (attrs fuseops.InodeAttributes, err error) { + ctx context.Context, clobberedCheck bool) (attrs fuseops.InodeAttributes, err error) { attrs = s.attrs return } diff --git a/internal/fs/inode/symlink_test.go b/internal/fs/inode/symlink_test.go index 1c2a1c9e46..b80671d464 100644 --- a/internal/fs/inode/symlink_test.go +++ b/internal/fs/inode/symlink_test.go @@ -15,8 +15,12 @@ package inode_test import ( + "context" + "os" "testing" + "github.com/jacobsa/fuse/fuseops" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" . "github.com/jacobsa/ogletest" @@ -63,3 +67,39 @@ func (t *SymlinkTest) TestIsSymLinkWhenMetadataKeyIsNotPresent() { func (t *SymlinkTest) TestIsSymLinkForNilObject() { AssertEq(false, inode.IsSymlink(nil)) } + +func (t *SymlinkTest) TestAttributes() { + metadata := map[string]string{ + inode.SymlinkMetadataKey: "target", + } + m := &gcs.MinObject{ + Name: "test", + Metadata: metadata, + } + attrs := fuseops.InodeAttributes{ + Uid: 1001, + Gid: 1002, + Mode: 0777 | os.ModeSymlink, + } + name := inode.NewFileName(inode.NewRootName("some-bucket"), m.Name) + s := inode.NewSymlinkInode(fuseops.InodeID(42), name, m, attrs) + tests := []struct { + name string + clobberedCheck bool + }{ + {"WithClobberedCheckFalse", false}, + {"WithClobberedCheckTrue", true}, + } + + for _, tt := range tests { + // Call Attributes + extracted, err := s.Attributes(context.TODO(), tt.clobberedCheck) + + // Check expected values + AssertEq(nil, err) + ExpectEq(uint32(1), extracted.Nlink) + ExpectEq(attrs.Uid, extracted.Uid) + ExpectEq(attrs.Gid, extracted.Gid) + ExpectEq(attrs.Mode, extracted.Mode) + } +} From 3a24cd2cb7fd3514d75728171c3627bbdb9983a9 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 9 Jul 2025 22:58:58 +0530 Subject: [PATCH 0541/1298] Block: adding Cap() method (#3487) --- internal/block/block.go | 8 ++++++++ internal/block/block_test.go | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/internal/block/block.go b/internal/block/block.go index 6f39d9e823..4f924ff30c 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -30,6 +30,9 @@ type Block interface { // can be >= data_size. Size() int64 + // Cap returns the capacity of the block, kind of block-size. + Cap() int64 + // Write writes the given data to block. Write(bytes []byte) (n int, err error) @@ -80,6 +83,11 @@ func (m *memoryBlock) Reuse() { func (m *memoryBlock) Size() int64 { return m.offset.end - m.offset.start } + +func (m *memoryBlock) Cap() int64 { + return int64(cap(m.buffer)) +} + func (m *memoryBlock) Write(bytes []byte) (int, error) { if m.Size()+int64(len(bytes)) > int64(cap(m.buffer)) { return 0, fmt.Errorf("received data more than capacity of the block") diff --git a/internal/block/block_test.go b/internal/block/block_test.go index 2dcb754a58..9977a999b9 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -260,3 +260,21 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockSetAbsStartOffsetTwiceInvalid() assert.Error(testSuite.T(), err) } + +func (testSuite *MemoryBlockTest) TestMemoryBlockCap() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + + assert.Equal(testSuite.T(), int64(12), mb.Cap()) +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockCapAfterWrite() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hi") + n, err := mb.Write(content) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), 2, n) + + assert.Equal(testSuite.T(), int64(12), mb.Cap()) +} From a1226ae9ea855c5e52e5240fd3aa433a4767bc5f Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Thu, 10 Jul 2025 10:39:59 +0530 Subject: [PATCH 0542/1298] Add ReadEntryCores method to DirInode interface (#3469) * Add ReadEntryCores in DirInode interface --- internal/fs/inode/base_dir.go | 10 ++ internal/fs/inode/base_dir_test.go | 10 ++ internal/fs/inode/dir.go | 24 ++++- internal/fs/inode/dir_test.go | 145 +++++++++++++++++++++++++++++ 4 files changed, 187 insertions(+), 2 deletions(-) diff --git a/internal/fs/inode/base_dir.go b/internal/fs/inode/base_dir.go index 2d0d5a6678..2d6ff070b4 100644 --- a/internal/fs/inode/base_dir.go +++ b/internal/fs/inode/base_dir.go @@ -191,6 +191,16 @@ func (d *baseDirInode) ReadEntries( return nil, "", syscall.ENOTSUP } +// LOCKS_REQUIRED(d) +func (d *baseDirInode) ReadEntryCores(ctx context.Context, tok string) (cores map[Name]*Core, newTok string, err error) { + + // The subdirectories of the base directory should be all the accessible + // buckets. Although the user is allowed to visit each individual + // subdirectory, listing all the subdirectories (i.e. the buckets) can be + // very expensive and currently not supported. + return nil, "", syscall.ENOTSUP +} + //////////////////////////////////////////////////////////////////////// // Forbidden Public interface //////////////////////////////////////////////////////////////////////// diff --git a/internal/fs/inode/base_dir_test.go b/internal/fs/inode/base_dir_test.go index f10f559da1..86c38bd11e 100644 --- a/internal/fs/inode/base_dir_test.go +++ b/internal/fs/inode/base_dir_test.go @@ -17,6 +17,7 @@ package inode import ( "fmt" "os" + "syscall" "testing" "time" @@ -228,3 +229,12 @@ func (t *BaseDirTest) Test_ShouldInvalidateKernelListCache_TtlExpired() { AssertEq(true, t.in.ShouldInvalidateKernelListCache(ttl)) } + +func (t *BaseDirTest) TestReadEntryCores() { + cores, newTok, err := t.in.ReadEntryCores(t.ctx, "") + + // Should return ENOTSUP because listing is unsupported. + ExpectEq(nil, cores) + ExpectEq("", newTok) + ExpectEq(syscall.ENOTSUP, err) +} diff --git a/internal/fs/inode/dir.go b/internal/fs/inode/dir.go index c4c0d43894..35a6861684 100644 --- a/internal/fs/inode/dir.go +++ b/internal/fs/inode/dir.go @@ -91,6 +91,17 @@ type DirInode interface { ctx context.Context, tok string) (entries []fuseutil.Dirent, newTok string, err error) + // ReadEntryCores reads a batch of directory entries and returns them as a + // map of `inode.Core` objects along with a continuation token that can be + // used to pick up the read operation where it left off. + // Supply the empty token on the first call. + // + // At the end of the directory, the returned continuation token will be + // empty. Otherwise it will be non-empty. There is no guarantee about the + // number of entries returned; it may be zero even with a non-empty + // continuation token. + ReadEntryCores(ctx context.Context, tok string) (cores map[Name]*Core, newTok string, err error) + // Create an empty child file with the supplied (relative) name, failing with // *gcs.PreconditionError if a backing object already exists in GCS. // Return the full name of the child and the GCS object it backs up. @@ -775,9 +786,8 @@ func (d *dirInode) ReadEntries( ctx context.Context, tok string) (entries []fuseutil.Dirent, newTok string, err error) { var cores map[Name]*Core - cores, newTok, err = d.readObjects(ctx, tok) + cores, newTok, err = d.ReadEntryCores(ctx, tok) if err != nil { - err = fmt.Errorf("read objects: %w", err) return } @@ -797,6 +807,16 @@ func (d *dirInode) ReadEntries( entries = append(entries, entry) } + return +} + +func (d *dirInode) ReadEntryCores(ctx context.Context, tok string) (cores map[Name]*Core, newTok string, err error) { + cores, newTok, err = d.readObjects(ctx, tok) + if err != nil { + err = fmt.Errorf("read objects: %w", err) + return + } + d.prevDirListingTimeStamp = d.cacheClock.Now() return } diff --git a/internal/fs/inode/dir_test.go b/internal/fs/inode/dir_test.go index 69fb78301c..bc84dc234e 100644 --- a/internal/fs/inode/dir_test.go +++ b/internal/fs/inode/dir_test.go @@ -16,6 +16,7 @@ package inode import ( "errors" + "maps" "math" "os" "path" @@ -177,6 +178,26 @@ func (t *DirTest) readAllEntries() (entries []fuseutil.Dirent, err error) { return } +// Read all of the entry cores +func (t *DirTest) readAllEntryCores() (cores map[Name]*Core, err error) { + cores = make(map[Name]*Core) + tok := "" + for { + var fetchedCores map[Name]*Core + fetchedCores, tok, err = t.in.ReadEntryCores(t.ctx, tok) + if err != nil { + return + } + maps.Copy(cores, fetchedCores) + + if tok == "" { + break + } + } + + return +} + func (t *DirTest) setSymlinkTarget( objName string, target string) (err error) { @@ -216,6 +237,21 @@ func (t *DirTest) getLocalDirentKey(in Inode) string { return path.Base(in.Name().LocalName()) } +func (t *DirTest) validateCore(cores map[Name]*Core, entryName string, isDir bool, expectedType metadata.Type, expectedFullName string) { + var name Name + if isDir { + name = NewDirName(t.in.Name(), entryName) + } else { + name = NewFileName(t.in.Name(), entryName) + } + + core, ok := cores[name] + AssertTrue(ok, "entry for "+entryName+" not found") + ExpectEq(expectedFullName, core.FullName.GcsObjectName()) + ExpectEq(expectedType, core.Type()) + ExpectEq(expectedType, t.getTypeFromCache(entryName)) +} + //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// @@ -970,6 +1006,115 @@ func (t *DirTest) ReadEntries_TypeCaching() { AssertFalse(d.prevDirListingTimeStamp.IsZero()) } +func (t *DirTest) ReadEntryCores_Empty() { + d := t.in.(*dirInode) + AssertNe(nil, d) + AssertTrue(d.prevDirListingTimeStamp.IsZero()) + + cores, err := t.readAllEntryCores() + + AssertEq(nil, err) + ExpectEq(0, len(cores)) + // Make sure prevDirListingTimeStamp is initialized. + AssertFalse(d.prevDirListingTimeStamp.IsZero()) +} + +func (t *DirTest) ReadEntryCores_NonEmpty_ImplicitDirsDisabled() { + var err error + var cores map[Name]*Core + + // Set up contents. + backedDirEmptyName := path.Join(dirInodeName, "backed_dir_empty") + "/" + backedDirNonEmptyName := path.Join(dirInodeName, "backed_dir_nonempty") + "/" + backedDirNonEmptyFileName := path.Join(backedDirNonEmptyName, "blah") + testFileName := path.Join(dirInodeName, "file") + implicitDirObjName := path.Join(dirInodeName, "implicit_dir") + "/blah" + symlinkName := path.Join(dirInodeName, "symlink") + + objs := []string{ + backedDirEmptyName, + backedDirNonEmptyName, + backedDirNonEmptyFileName, + testFileName, + implicitDirObjName, + symlinkName, + } + + err = storageutil.CreateEmptyObjects(t.ctx, t.bucket, objs) + AssertEq(nil, err) + + // Set up the symlink target. + err = t.setSymlinkTarget(dirInodeName+"symlink", "blah") + AssertEq(nil, err) + + // Nil prevDirListingTimeStamp + d := t.in.(*dirInode) + AssertNe(nil, d) + AssertTrue(d.prevDirListingTimeStamp.IsZero()) + + // Read cores. + cores, err = t.readAllEntryCores() + + AssertEq(nil, err) + AssertEq(4, len(cores)) + t.validateCore(cores, "backed_dir_empty", true, metadata.ExplicitDirType, backedDirEmptyName) + t.validateCore(cores, "backed_dir_nonempty", true, metadata.ExplicitDirType, backedDirNonEmptyName) + t.validateCore(cores, "file", false, metadata.RegularFileType, testFileName) + t.validateCore(cores, "symlink", false, metadata.SymlinkType, symlinkName) + // Make sure prevDirListingTimeStamp is initialized. + AssertFalse(d.prevDirListingTimeStamp.IsZero()) +} + +func (t *DirTest) ReadEntryCores_NonEmpty_ImplicitDirsEnabled() { + var err error + var cores map[Name]*Core + + // Enable implicit dirs. + t.resetInode(true, false, true) + + // Set up contents. + backedDirEmptyName := path.Join(dirInodeName, "backed_dir_empty") + "/" + backedDirNonEmptyName := path.Join(dirInodeName, "backed_dir_nonempty") + "/" + backedDirNonEmptyFileName := path.Join(backedDirNonEmptyName, "blah") + testFileName := path.Join(dirInodeName, "file") + implicitDirObjName := path.Join(dirInodeName, "implicit_dir") + "/blah" + symlinkName := path.Join(dirInodeName, "symlink") + + objs := []string{ + backedDirEmptyName, + backedDirNonEmptyName, + backedDirNonEmptyFileName, + testFileName, + implicitDirObjName, + symlinkName, + } + + err = storageutil.CreateEmptyObjects(t.ctx, t.bucket, objs) + AssertEq(nil, err) + + // Set up the symlink target. + err = t.setSymlinkTarget(dirInodeName+"symlink", "blah") + AssertEq(nil, err) + + // Nil prevDirListingTimeStamp + d := t.in.(*dirInode) + AssertNe(nil, d) + AssertTrue(d.prevDirListingTimeStamp.IsZero()) + + // Read cores. + cores, err = t.readAllEntryCores() + + AssertEq(nil, err) + AssertEq(5, len(cores)) + t.validateCore(cores, "backed_dir_empty", true, metadata.ExplicitDirType, backedDirEmptyName) + t.validateCore(cores, "backed_dir_nonempty", true, metadata.ExplicitDirType, backedDirNonEmptyName) + t.validateCore(cores, "file", false, metadata.RegularFileType, testFileName) + t.validateCore(cores, "implicit_dir", true, metadata.ImplicitDirType, path.Join(dirInodeName, "implicit_dir")+"/") + t.validateCore(cores, "symlink", false, metadata.SymlinkType, symlinkName) + // Make sure prevDirListingTimeStamp is initialized. + AssertFalse(d.prevDirListingTimeStamp.IsZero()) +} + func (t *DirTest) CreateChildFile_DoesntExist() { const name = "qux" objName := path.Join(dirInodeName, name) From 1f9252ddee4383ffafc8e1a8c3aeba107b040611 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Thu, 10 Jul 2025 11:36:04 +0530 Subject: [PATCH 0543/1298] Block: Adding support for Notify & Await Ready (#3481) * Block: Adding support for Notify & Await Read * Fix lint error * minor test formatting change * Adding test canceled context * Review comments * minor doc change --- internal/block/block.go | 70 +++++++++++++++++++- internal/block/block_test.go | 122 +++++++++++++++++++++++++++++++++++ 2 files changed, 189 insertions(+), 3 deletions(-) diff --git a/internal/block/block.go b/internal/block/block.go index 4f924ff30c..9748ed0354 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -16,11 +16,22 @@ package block import ( "bytes" + "context" "fmt" "io" "syscall" ) +// BlockStatus represents the status of the block. +type BlockStatus int + +const ( + BlockStatusInProgress BlockStatus = iota // Download of this block is in progress + BlockStatusDownloaded // Download of this block is complete + BlockStatusDownloadFailed // Download of this block has failed + BlockStatusDownloadCancelled // Download of this block has been cancelled +) + // Block represents the buffer which holds the data. type Block interface { // Reuse resets the blocks for reuse. @@ -55,6 +66,17 @@ type Block interface { // It returns an error if the startOff is negative or if it is already set. // TODO(princer): check if a way to set it as part of constructor. SetAbsStartOff(startOff int64) error + + // AwaitReady waits for the block to be ready to consume. + // It returns the status of the block and an error if any. + AwaitReady(ctx context.Context) (BlockStatus, error) + + // NotifyReady is used by producer to mark the block as ready to consume. + // The value indicates the status of the block: + // - BlockStatusDownloaded: Download of this block is complete. + // - BlockStatusDownloadFailed: Download of this block has failed. + // - BlockStatusDownloadCancelled: Download of this block has been cancelled. + NotifyReady(val BlockStatus) } // TODO: check if we need offset or just storing end is sufficient. We might need @@ -68,6 +90,12 @@ type memoryBlock struct { buffer []byte offset offset + // Indicates if block is in progress, downloaded, download failed or download cancelled. + status BlockStatus + + // notification is a channel that notifies when the block is ready to consume. + notification chan BlockStatus + // Stores the absolute start offset of the block-segment in the file. absStartOff int64 } @@ -77,6 +105,8 @@ func (m *memoryBlock) Reuse() { m.offset.end = 0 m.offset.start = 0 + m.notification = make(chan BlockStatus, 1) + m.status = BlockStatusInProgress m.absStartOff = -1 } @@ -130,9 +160,11 @@ func createBlock(blockSize int64) (Block, error) { } mb := memoryBlock{ - buffer: addr, - offset: offset{0, 0}, - absStartOff: -1, + buffer: addr, + offset: offset{0, 0}, + notification: make(chan BlockStatus, 1), + status: BlockStatusInProgress, + absStartOff: -1, } return &mb, nil } @@ -173,3 +205,35 @@ func (m *memoryBlock) SetAbsStartOff(startOff int64) error { m.absStartOff = startOff return nil } + +// AwaitReady waits for the block to be ready to consume. +// It returns the status of the block and an error if any. +func (m *memoryBlock) AwaitReady(ctx context.Context) (BlockStatus, error) { + select { + case val, ok := <-m.notification: + if !ok { + return m.status, nil + } + + // Close the notification channel to prevent further notifications. + close(m.notification) + // Save the last status for subsequent AwaitReady calls. + m.status = val + + return m.status, nil + case <-ctx.Done(): + return 0, ctx.Err() + } +} + +// NotifyReady is used by the producer to mark the block as ready to consume. +// This should be called only once to notify the consumer. +// If called multiple times, it will panic - either because of writing to the +// closed channel or blocking due to writing over full notification channel. +func (m *memoryBlock) NotifyReady(val BlockStatus) { + select { + case m.notification <- val: + default: + panic("Expected to notify only once, but got multiple notifications.") + } +} diff --git a/internal/block/block_test.go b/internal/block/block_test.go index 9977a999b9..881e0361e9 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -15,8 +15,11 @@ package block import ( + "context" "io" + "sync" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -278,3 +281,122 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockCapAfterWrite() { assert.Equal(testSuite.T(), int64(12), mb.Cap()) } + +func (testSuite *MemoryBlockTest) TestAwaitReadyWaitIfNotNotify() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + ctx, cancel := context.WithTimeout(testSuite.T().Context(), 100*time.Millisecond) + defer cancel() + + _, err = mb.AwaitReady(ctx) + + assert.NotNil(testSuite.T(), err) + assert.EqualError(testSuite.T(), context.DeadlineExceeded, err.Error()) +} + +func (testSuite *MemoryBlockTest) TestAwaitReadyReturnsErrorOnContextCancellation() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + ctx, cancel := context.WithCancel(testSuite.T().Context()) + cancel() // Cancel the context immediately + + _, err = mb.AwaitReady(ctx) + + require.NotNil(testSuite.T(), err) + assert.EqualError(testSuite.T(), context.Canceled, err.Error()) +} + +func (testSuite *MemoryBlockTest) TestAwaitReadyNotifyVariants() { + tests := []struct { + name string + notifyStatus BlockStatus + wantStatus BlockStatus + }{ + { + name: "AfterNotifySuccess", + notifyStatus: BlockStatusDownloaded, + wantStatus: BlockStatusDownloaded, + }, + { + name: "AfterNotifyError", + notifyStatus: BlockStatusDownloadFailed, + wantStatus: BlockStatusDownloadFailed, + }, + { + name: "AfterNotifyCancelled", + notifyStatus: BlockStatusDownloadCancelled, + wantStatus: BlockStatusDownloadCancelled, + }, + } + + for _, tt := range tests { + testSuite.T().Run(tt.name, func(t *testing.T) { + mb, err := createBlock(12) + require.Nil(t, err) + go func() { + time.Sleep(time.Millisecond) + mb.NotifyReady(tt.notifyStatus) + }() + + status, err := mb.AwaitReady(context.Background()) + + require.Nil(t, err) + assert.Equal(t, tt.wantStatus, status) + }) + } +} + +func (testSuite *MemoryBlockTest) TestTwoNotifyReadyWithoutAwaitReady() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + + mb.NotifyReady(BlockStatusDownloaded) + // 2nd notify will lead to panic since it is not allowed to notify a block more than once. + assert.Panics(testSuite.T(), func() { + mb.NotifyReady(BlockStatusDownloaded) + }) +} + +func (testSuite *MemoryBlockTest) TestNotifyReadyAfterAwaitReady() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + ctx, cancel := context.WithTimeout(testSuite.T().Context(), 100*time.Millisecond) + defer cancel() + go func() { + mb.NotifyReady(BlockStatusDownloaded) + }() + status, err := mb.AwaitReady(ctx) + require.Nil(testSuite.T(), err) + assert.Equal(testSuite.T(), BlockStatusDownloaded, status) + + // 2nd notify will lead to panic since channel is closed after first await ready. + assert.Panics(testSuite.T(), func() { + mb.NotifyReady(BlockStatusDownloaded) + }) +} + +func (testSuite *MemoryBlockTest) TestSingleNotifyAndMultipleAwaitReady() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + go func() { + mb.NotifyReady(BlockStatusDownloaded) + }() + ctx, cancel := context.WithTimeout(testSuite.T().Context(), 5*time.Millisecond) + defer cancel() + var wg sync.WaitGroup + wg.Add(5) + + // Multiple goroutines waiting for the same block to be ready. + // They should all receive the same status once the block is notified. + for i := 0; i < 5; i++ { + go func() { + defer wg.Done() + + status, err := mb.AwaitReady(ctx) + + require.Nil(testSuite.T(), err) + assert.Equal(testSuite.T(), BlockStatusDownloaded, status) + }() + } + wg.Wait() +} From c982fa5926f0a9efbf30f00486826cae0b1e0bc8 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 10 Jul 2025 12:29:19 +0530 Subject: [PATCH 0544/1298] [Process Improvement] Standardize PR titles (#3486) * update pr title template * small fix * adding permisison check * replace standard template --- .github/pull_request_template.md | 7 +++++ .github/workflows/pr-title-check.yml | 42 ++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 .github/workflows/pr-title-check.yml diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 96b112a6cf..6107ec0ebf 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,10 @@ +**Please ensure your PR title follows the format:** +``` +type(scope): subject +``` +Example: +feat(api): add user login endpoint + ### Description ### Link to the issue in case of a bug fix. diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml new file mode 100644 index 0000000000..4511056475 --- /dev/null +++ b/.github/workflows/pr-title-check.yml @@ -0,0 +1,42 @@ +name: PR Title Check + +on: + pull_request_target: + types: + - opened + - edited + +permissions: + pull-requests: write + +jobs: + main: + name: Validate PR title + runs-on: ubuntu-latest + + steps: + - uses: amannn/action-semantic-pull-request@v5 + id: lint_pr_title + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: marocchino/sticky-pull-request-comment@v2 + if: always() && (steps.lint_pr_title.outputs.error_message != null) + with: + header: pr-title-lint-error + message: | + Hey there and thank you for opening this pull request! 👋🏼 + + We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted. + + Details: + + ``` + ${{ steps.lint_pr_title.outputs.error_message }} + ``` + + - if: ${{ steps.lint_pr_title.outputs.error_message == null }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: pr-title-lint-error + delete: true From 952dd38799314ae43d826d070a2e24992fbc7461 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:45:40 +0530 Subject: [PATCH 0545/1298] update troubleshooting doc (#3493) --- docs/troubleshooting.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 4701aa9199..ef95c407d9 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -13,7 +13,11 @@ Try mounting the gcsfuse with `--implicit-dirs` flag. Read the [semantics](https ### Mount failed with fusermount3 exit status 1 -It comes when the bucket is already mounted in a folder, and we try to mount it again. You need to unmount first and then remount. +- It can come when the bucket is already mounted in a folder, and we try to mount it again. You need to unmount first and then remount. +- It can also happen if you're trying to mount the bucket on a directory that has read-only permissions. Please provide write permissions to the directory and try mounting it again. You can use the below command to grant write permissions. + ``` + chmod 755 mount_point + ``` ### version GLIBC_x.yz not found From dc43d34157ba039e46a7e2e7864caf0a72ebf0ba Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 10 Jul 2025 15:15:18 +0530 Subject: [PATCH 0546/1298] Make --enable-read-stall-retry flag hidden (#3488) --- cfg/config.go | 4 ++++ cfg/params.yaml | 1 + 2 files changed, 5 insertions(+) diff --git a/cfg/config.go b/cfg/config.go index 0d9a7433c3..3ee22861c3 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -401,6 +401,10 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-read-stall-retry", "", true, "To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past.") + if err := flagSet.MarkHidden("enable-read-stall-retry"); err != nil { + return err + } + flagSet.BoolP("enable-streaming-writes", "", true, "Enables streaming uploads during write file operation.") flagSet.BoolP("experimental-enable-dentry-cache", "", false, "When enabled, it sets the Dentry cache entry timeout same as metadata-cache-ttl. This enables kernel to use cached entry to map the file paths to inodes, instead of making LookUpInode calls to GCSFuse.") diff --git a/cfg/params.yaml b/cfg/params.yaml index a8758cf1c2..f14ebd9b75 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -431,6 +431,7 @@ To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past. default: true + hide-flag: true - config-path: "gcs-retries.read-stall.initial-req-timeout" flag-name: "read-stall-initial-req-timeout" From 796ee60b67568f51bbde37794f018a53cd712ad2 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:51:54 +0530 Subject: [PATCH 0547/1298] ci: fix failure in list_large_dir e2e package for ZB (#3491) ### Description **What it does**: - Add precondition (DoesNotExist: true) in utility UploadGcsObject in storage.client in `tools/integration_tests/`, through an alternative utility, to allow list_large_dir package tests to pass, by allowing retries to work through idempotent write support. - Keep the change restricted to only this test package, and no other test packages. This is for two reasons. - Minimum code changes - Minimum impact to other packages **What it doesn't do and will be taken up in a follow-up PR**: - Add this precondition for all packages using `UploadGcsObject`, test and cleanup code. ### Link to the issue in case of a bug fix. [b/411002767](http://b/411002767) [b/410452983](http://b/410452983) * [temp] simulate retry failure by increasing number of goroutines to a crazy scale * pass DoesNotExist:true precondition in list_large_dir tests * Revert "[temp] simulate retry failure by increasing number of goroutines to a crazy scale" This reverts commit c2d649e3b0cd071df234b137e5064934f1044750. --- ...ist_dir_with_twelve_thousand_files_test.go | 4 ++-- .../util/client/storage_client.go | 20 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go index a16042bdbd..6bb66f3da3 100644 --- a/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go +++ b/tools/integration_tests/list_large_dir/list_dir_with_twelve_thousand_files_test.go @@ -150,7 +150,7 @@ func testdataUploadFilesToBucket(ctx context.Context, t *testing.T, storageClien if !ok { break } - client.CopyFileInBucket(ctx, storageClient, copyRequest.srcLocalFilePath, copyRequest.dstGCSObjectPath, bucketName) + client.CopyFileInBucketWithPreconditions(ctx, storageClient, copyRequest.srcLocalFilePath, copyRequest.dstGCSObjectPath, bucketName, &storage.Conditions{DoesNotExist: true}) } }() } @@ -222,7 +222,7 @@ func testdataCreateImplicitDir(t *testing.T, ctx context.Context, storageClient sem <- struct{}{} // acquire semaphore defer func() { <-sem }() // release semaphore - client.CopyFileInBucket(ctx, storageClient, testFile, destinationPath, bucketName) + client.CopyFileInBucketWithPreconditions(ctx, storageClient, testFile, destinationPath, bucketName, &storage.Conditions{DoesNotExist: true}) }(objectPath) } diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 613366850e..3875725ccd 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -311,11 +311,14 @@ func StatObject(ctx context.Context, client *storage.Client, object string) (*st return attrs, nil } -// UploadGcsObject uploads a local file to a specified GCS bucket and object. +// UploadGcsObjectWithPreconditions uploads a local file to a specified GCS bucket and object with given preconditions. // Handles gzip compression if requested. -func UploadGcsObject(ctx context.Context, client *storage.Client, localPath, bucketName, objectName string, uploadGzipEncoded bool) error { +func UploadGcsObjectWithPreconditions(ctx context.Context, client *storage.Client, localPath, bucketName, objectName string, uploadGzipEncoded bool, preconditions *storage.Conditions) error { // Create a writer to upload the object. obj := client.Bucket(bucketName).Object(objectName) + if preconditions != nil { + obj = obj.If(*preconditions) + } w, err := NewWriter(ctx, obj, client) if err != nil { return fmt.Errorf("failed to open writer for GCS object gs://%s/%s: %w", bucketName, objectName, err) @@ -381,6 +384,12 @@ func ClearCacheControlOnGcsObject(ctx context.Context, client *storage.Client, o return nil } +// UploadGcsObject uploads a local file to a specified GCS bucket and object without any preconditions. +// Handles gzip compression if requested. +func UploadGcsObject(ctx context.Context, client *storage.Client, localPath, bucketName, objectName string, uploadGzipEncoded bool) error { + return UploadGcsObjectWithPreconditions(ctx, client, localPath, bucketName, objectName, uploadGzipEncoded, nil) +} + func CopyFileInBucket(ctx context.Context, storageClient *storage.Client, srcfilePath, destFilePath, bucket string) { err := UploadGcsObject(ctx, storageClient, srcfilePath, bucket, destFilePath, false) if err != nil { @@ -388,6 +397,13 @@ func CopyFileInBucket(ctx context.Context, storageClient *storage.Client, srcfil } } +func CopyFileInBucketWithPreconditions(ctx context.Context, storageClient *storage.Client, srcfilePath, destFilePath, bucket string, preconditions *storage.Conditions) { + err := UploadGcsObjectWithPreconditions(ctx, storageClient, srcfilePath, bucket, destFilePath, false, preconditions) + if err != nil { + log.Fatalf("Error while copying file %q to GCS object \"gs://%s/%s\" : %v", srcfilePath, bucket, destFilePath, err) + } +} + func DeleteBucket(ctx context.Context, client *storage.Client, bucketName string) error { bucket := client.Bucket(bucketName) From 19db985b04d91ce4f15a0de51cfaf2f54301b56c Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Fri, 11 Jul 2025 10:48:12 +0530 Subject: [PATCH 0548/1298] fix(bash installation): Fix bash installation script to install gcc/make dependencies before installing bash. (#3496) * Add make and gcc dependencies of bash installation * revert empty line change --- perfmetrics/scripts/install_bash.sh | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/perfmetrics/scripts/install_bash.sh b/perfmetrics/scripts/install_bash.sh index 38fff18edb..eefd7a8ec7 100755 --- a/perfmetrics/scripts/install_bash.sh +++ b/perfmetrics/scripts/install_bash.sh @@ -29,6 +29,25 @@ fi BASH_VERSION="$1" INSTALL_DIR="/usr/local/" # Installation directory +# Function to install dependencies like gcc and make if not present +install_dependencies() { + if ! command -v gcc &>/dev/null || ! command -v make &>/dev/null; then + echo "GCC or make not found. Attempting to install build tools..." + if command -v apt-get &>/dev/null; then + sudo apt-get update && sudo apt-get install -y build-essential + elif command -v dnf &>/dev/null; then + sudo dnf install -y gcc make + elif command -v yum &>/dev/null; then + sudo yum install -y gcc make + else + echo "Error: Could not find a known package manager (apt, dnf, yum)." + echo "Please install gcc and make manually before running this script." + exit 1 + fi + echo "Build tools installed successfully." + fi +} + # Function to download, compile, and install Bash install_bash() { local temp_dir @@ -49,8 +68,11 @@ install_bash() { echo "Installing bash version ${BASH_VERSION} to ${INSTALL_DIR}bin/bash" INSTALLATION_LOG=$(mktemp /tmp/bash_install_log.XXXXXX) +# Installing dependencies before installing Bash +install_dependencies + if ! install_bash >"$INSTALLATION_LOG" 2>&1; then - echo "Bash version ${BASH_VERSION} installation failed." + echo "Error: Bash version ${BASH_VERSION} installation failed." cat "$INSTALLATION_LOG" rm -f "$INSTALLATION_LOG" exit 1 From 38339b291edeb230e39eb83bfb18409610565617 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Fri, 11 Jul 2025 11:38:46 +0530 Subject: [PATCH 0549/1298] refactor(block-pool): deleting the FreeBlocksChannel() method of block-pool (#3498) --- internal/block/block_pool.go | 5 ----- internal/block/block_pool_test.go | 12 ------------ .../bufferedwrites/buffered_write_handler_test.go | 12 ++++++------ 3 files changed, 6 insertions(+), 23 deletions(-) diff --git a/internal/block/block_pool.go b/internal/block/block_pool.go index 6ce74cc107..5998cc0e44 100644 --- a/internal/block/block_pool.go +++ b/internal/block/block_pool.go @@ -108,11 +108,6 @@ func (bp *BlockPool) canAllocateBlock() bool { return semAcquired } -// FreeBlocksChannel returns the freeBlocksCh being used by the block pool. -func (bp *BlockPool) FreeBlocksChannel() chan Block { - return bp.freeBlocksCh -} - // Release puts the block back into the free blocks channel for reuse. func (bp *BlockPool) Release(b Block) { select { diff --git a/internal/block/block_pool_test.go b/internal/block/block_pool_test.go index 75bd22ff06..b17c3eaa70 100644 --- a/internal/block/block_pool_test.go +++ b/internal/block/block_pool_test.go @@ -300,18 +300,6 @@ func (t *BlockPoolTest) validateGetBlockIsNotBlocked(bp *BlockPool) Block { } } -func (t *BlockPoolTest) TestFreeBlocksChannel() { - freeBlocksCh := make(chan Block) - bp := &BlockPool{ - freeBlocksCh: freeBlocksCh, - } - - ch := bp.FreeBlocksChannel() - - assert.NotNil(t.T(), ch) - assert.Equal(t.T(), freeBlocksCh, ch) -} - func (t *BlockPoolTest) TestCanAllocateBlock() { tests := []struct { name string diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 4ffc56d127..8e49c0b22b 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -227,7 +227,7 @@ func (testSuite *BufferedWriteTest) TestFlushWithNonNilCurrentBlock() { assert.NotNil(testSuite.T(), obj) assert.Equal(testSuite.T(), uint64(2), obj.Size) // Validate that all blocks have been freed up. - assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) + assert.Equal(testSuite.T(), 0, bwhImpl.uploadHandler.blockPool.TotalFreeBlocks()) } func (testSuite *BufferedWriteTest) TestFlushWithNilCurrentBlock() { @@ -293,7 +293,7 @@ func (testSuite *BufferedWriteTest) TestSync5InProgressBlocks() { assert.NoError(testSuite.T(), err) bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) - assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) + assert.Equal(testSuite.T(), 0, bwhImpl.uploadHandler.blockPool.TotalFreeBlocks()) assert.Nil(testSuite.T(), o) } @@ -341,7 +341,7 @@ func (testSuite *BufferedWriteTest) TestSyncPartialBlockTableDriven() { // Current block should also be uploaded. assert.Nil(testSuite.T(), bwhImpl.current) assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) - assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) + assert.Equal(testSuite.T(), 0, bwhImpl.uploadHandler.blockPool.TotalFreeBlocks()) // Read the object from back door. content, err := storageutil.ReadObject(context.Background(), bwhImpl.uploadHandler.bucket, bwhImpl.uploadHandler.objectName) if tc.bucketType.Zonal { @@ -452,7 +452,7 @@ func (testSuite *BufferedWriteTest) TestDestroyShouldClearFreeBlockChannel() { require.Nil(testSuite.T(), err) bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) - assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) + assert.Equal(testSuite.T(), 0, bwhImpl.uploadHandler.blockPool.TotalFreeBlocks()) assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) } @@ -462,7 +462,7 @@ func (testSuite *BufferedWriteTest) TestUnlinkBeforeWrite() { bwhImpl := testSuite.bwh.(*bufferedWriteHandlerImpl) assert.Nil(testSuite.T(), bwhImpl.uploadHandler.cancelFunc) assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) - assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) + assert.Equal(testSuite.T(), 0, bwhImpl.uploadHandler.blockPool.TotalFreeBlocks()) } func (testSuite *BufferedWriteTest) TestUnlinkAfterWrite() { @@ -481,7 +481,7 @@ func (testSuite *BufferedWriteTest) TestUnlinkAfterWrite() { assert.True(testSuite.T(), cancelCalled) assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) - assert.Equal(testSuite.T(), 0, len(bwhImpl.blockPool.FreeBlocksChannel())) + assert.Equal(testSuite.T(), 0, bwhImpl.uploadHandler.blockPool.TotalFreeBlocks()) } func (testSuite *BufferedWriteTest) TestReFlushAfterUploadFails() { From e573be55c674cc4c5ba9f3b93141616202c8a3ab Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Fri, 11 Jul 2025 12:53:27 +0530 Subject: [PATCH 0550/1298] feat(buffered-read): implement download task for worker pool (#3484) * Bufferedread: Implement Download Task * review comments * reverting unnecessary change --- internal/bufferedread/download_task.go | 108 +++++++++ internal/bufferedread/download_task_test.go | 232 ++++++++++++++++++++ 2 files changed, 340 insertions(+) create mode 100644 internal/bufferedread/download_task.go create mode 100644 internal/bufferedread/download_task_test.go diff --git a/internal/bufferedread/download_task.go b/internal/bufferedread/download_task.go new file mode 100644 index 0000000000..056cdbd092 --- /dev/null +++ b/internal/bufferedread/download_task.go @@ -0,0 +1,108 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bufferedread + +import ( + "context" + "errors" + "fmt" + "io" + "time" + + "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" +) + +type DownloadTask struct { + workerpool.Task + object *gcs.MinObject + bucket gcs.Bucket + + // block is the block to which the data will be downloaded. + block block.Block + + // ctx is the context for the download task. It is used to cancel the download. + ctx context.Context + + // Used for zonal bucket to bypass the auth & metadata checks. + readHandle []byte +} + +func NewDownloadTask(ctx context.Context, object *gcs.MinObject, bucket gcs.Bucket, block block.Block, readHandle []byte) *DownloadTask { + return &DownloadTask{ + ctx: ctx, + object: object, + bucket: bucket, + block: block, + readHandle: readHandle, + } +} + +// Execute implements the workerpool.Task interface. It downloads the data from +// the GCS object to the block. +// After completion, it notifies the block consumer about the status of the +// download task. The status can be one of the following: +// - BlockStatusDownloaded: The download was successful. +// - BlockStatusDownloadFailed: The download failed due to an error. +// - BlockStatusDownloadCancelled: The download was cancelled due to context cancellation. +func (p *DownloadTask) Execute() { + startOff := p.block.AbsStartOff() + blockId := startOff / p.block.Cap() + logger.Tracef("Download: <- block (%s, %v).", p.object.Name, blockId) + stime := time.Now() + var err error + defer func() { + if err == nil { + logger.Tracef("Download: -> block (%s, %v) completed in: %v.", p.object.Name, blockId, time.Since(stime)) + p.block.NotifyReady(block.BlockStatusDownloaded) + } else if errors.Is(err, context.Canceled) && p.ctx.Err() == context.Canceled { + logger.Tracef("Download: -> block (%s, %v) cancelled: %v.", p.object.Name, blockId, err) + p.block.NotifyReady(block.BlockStatusDownloadCancelled) + } else { + logger.Errorf("Download: -> block (%s, %v) failed: %v.", p.object.Name, blockId, err) + p.block.NotifyReady(block.BlockStatusDownloadFailed) + } + }() + + start := uint64(startOff) + end := start + uint64(p.block.Cap()) + if end > p.object.Size { + end = p.object.Size + } + newReader, err := p.bucket.NewReaderWithReadHandle( + p.ctx, + &gcs.ReadObjectRequest{ + Name: p.object.Name, + Generation: p.object.Generation, + Range: &gcs.ByteRange{ + Start: start, + Limit: end, + }, + ReadCompressed: p.object.HasContentEncodingGzip(), + ReadHandle: p.readHandle, + }) + if err != nil { + err = fmt.Errorf("while reader-creations: %w", err) + return + } + + _, err = io.CopyN(p.block, newReader, int64(end-start)) + if err != nil { + err = fmt.Errorf("while copying data: %w", err) + return + } +} diff --git a/internal/bufferedread/download_task_test.go b/internal/bufferedread/download_task_test.go new file mode 100644 index 0000000000..1d3873d032 --- /dev/null +++ b/internal/bufferedread/download_task_test.go @@ -0,0 +1,232 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bufferedread + +import ( + "bytes" + "context" + "errors" + "io" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "golang.org/x/sync/semaphore" +) + +const ( + testBlockSize = 500 +) + +type DownloadTaskTestSuite struct { + workerpool.Task + suite.Suite + object *gcs.MinObject + mockBucket *storage.TestifyMockBucket + blockPool *block.BlockPool +} + +func TestDownloadTaskTestSuite(t *testing.T) { + suite.Run(t, new(DownloadTaskTestSuite)) +} + +func (dts *DownloadTaskTestSuite) SetupTest() { + dts.object = &gcs.MinObject{ + Name: "test-object", + Size: 1024, + Generation: 1234567890, + } + dts.mockBucket = new(storage.TestifyMockBucket) + var err error + dts.blockPool, err = block.NewBlockPool(testBlockSize, 10, semaphore.NewWeighted(100)) + require.NoError(dts.T(), err, "Failed to create block pool") +} + +func getReadCloser(content []byte) io.ReadCloser { + r := bytes.NewReader(content) + rc := io.NopCloser(r) + return rc +} + +func (dts *DownloadTaskTestSuite) TestExecuteSuccess() { + downloadBlock, err := dts.blockPool.Get() + require.Nil(dts.T(), err) + err = downloadBlock.SetAbsStartOff(0) + require.Nil(dts.T(), err) + task := NewDownloadTask(context.Background(), dts.object, dts.mockBucket, downloadBlock, nil) + testContent := testutil.GenerateRandomBytes(testBlockSize) + rc := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} + readObjectRequest := &gcs.ReadObjectRequest{ + Name: dts.object.Name, + Generation: dts.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: uint64(testBlockSize), + }, + } + dts.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil).Times(1) + + task.Execute() + + assert.Equal(dts.T(), int64(len(testContent)), downloadBlock.Size()) + assert.Equal(dts.T(), int64(testBlockSize), downloadBlock.Cap()) + assert.NoError(dts.T(), err) + dts.mockBucket.AssertExpectations(dts.T()) + ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) + defer cancelFunc() + status, err := downloadBlock.AwaitReady(ctx) + assert.Equal(dts.T(), block.BlockStatusDownloaded, status) + assert.NoError(dts.T(), err) +} + +func (dts *DownloadTaskTestSuite) TestExecuteError() { + downloadBlock, err := dts.blockPool.Get() + require.Nil(dts.T(), err) + err = downloadBlock.SetAbsStartOff(0) + require.Nil(dts.T(), err) + task := NewDownloadTask(context.Background(), dts.object, dts.mockBucket, downloadBlock, nil) + readObjectRequest := &gcs.ReadObjectRequest{ + Name: dts.object.Name, + Generation: dts.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: uint64(testBlockSize), + }, + } + expectedError := errors.New("read error") + dts.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(nil, expectedError).Times(1) + + task.Execute() + + assert.Error(dts.T(), expectedError) + dts.mockBucket.AssertExpectations(dts.T()) + ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) + defer cancelFunc() + status, err := downloadBlock.AwaitReady(ctx) + assert.Equal(dts.T(), block.BlockStatusDownloadFailed, status) + assert.NoError(dts.T(), err) +} + +func (dts *DownloadTaskTestSuite) TestExecuteContextDeadlineExceededByServerTreatedAsFailed() { + downloadBlock, err := dts.blockPool.Get() + require.Nil(dts.T(), err) + err = downloadBlock.SetAbsStartOff(0) + require.Nil(dts.T(), err) + taskCtx, taskCancelFunc := context.WithTimeout(context.Background(), 1*time.Millisecond) + defer taskCancelFunc() // Ensure the context is cancelled after the test. + task := NewDownloadTask(taskCtx, dts.object, dts.mockBucket, downloadBlock, nil) + readObjectRequest := &gcs.ReadObjectRequest{ + Name: dts.object.Name, + Generation: dts.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: uint64(testBlockSize), + }, + } + dts.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(nil, context.DeadlineExceeded).Times(1) + + task.Execute() + + assert.Error(dts.T(), context.DeadlineExceeded) + dts.mockBucket.AssertExpectations(dts.T()) + ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) + defer cancelFunc() + status, err := downloadBlock.AwaitReady(ctx) + assert.Equal(dts.T(), block.BlockStatusDownloadFailed, status) + assert.NoError(dts.T(), err) +} + +func (dts *DownloadTaskTestSuite) TestExecuteContextCancelledWhileReaderCreation() { + downloadBlock, err := dts.blockPool.Get() + require.Nil(dts.T(), err) + err = downloadBlock.SetAbsStartOff(0) + require.Nil(dts.T(), err) + taskCtx, taskCancelFunc := context.WithCancel(context.TODO()) + task := NewDownloadTask(taskCtx, dts.object, dts.mockBucket, downloadBlock, nil) + rc := &fake.FakeReader{ReadCloser: getReadCloser(nil)} // No content since context is cancelled + readObjectRequest := &gcs.ReadObjectRequest{ + Name: dts.object.Name, + Generation: dts.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: uint64(testBlockSize), + }, + } + dts.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, context.Canceled).Times(1) + taskCancelFunc() // Ensure client side cancellation. + + task.Execute() + + assert.Error(dts.T(), context.Canceled) + dts.mockBucket.AssertExpectations(dts.T()) + ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) + defer cancelFunc() + status, err := downloadBlock.AwaitReady(ctx) + assert.Equal(dts.T(), block.BlockStatusDownloadCancelled, status) + assert.NoError(dts.T(), err) +} + +// ctxCancelledReader is a mock reader that simulates a context cancellation error while reading. +type ctxCancelledReader struct { + io.Reader + io.Closer +} + +func (r *ctxCancelledReader) Read(p []byte) (n int, err error) { + return 0, context.Canceled +} + +func (r *ctxCancelledReader) Close() error { + return nil +} + +func (dts *DownloadTaskTestSuite) TestExecuteContextCancelledWhileReadingFromReader() { + downloadBlock, err := dts.blockPool.Get() + require.Nil(dts.T(), err) + err = downloadBlock.SetAbsStartOff(0) + require.Nil(dts.T(), err) + taskCtx, taskCancelFunc := context.WithCancel(context.TODO()) + task := NewDownloadTask(taskCtx, dts.object, dts.mockBucket, downloadBlock, nil) + rc := &fake.FakeReader{ReadCloser: new(ctxCancelledReader)} + readObjectRequest := &gcs.ReadObjectRequest{ + Name: dts.object.Name, + Generation: dts.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: uint64(testBlockSize), + }, + } + dts.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil).Times(1) + taskCancelFunc() // Ensure client side cancellation. + + task.Execute() + + assert.Error(dts.T(), context.Canceled) + dts.mockBucket.AssertExpectations(dts.T()) + ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) + defer cancelFunc() + status, err := downloadBlock.AwaitReady(ctx) + assert.Equal(dts.T(), block.BlockStatusDownloadCancelled, status) + assert.NoError(dts.T(), err) +} From 6b0b02c61fcb216a6987df7fa0ded249d56357cc Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 11 Jul 2025 20:30:32 +0530 Subject: [PATCH 0551/1298] ci: Adding minimum threshold for micro benchmark (#3494) * adding minimum thresold for micro benchmark * updating logic --- .../gcp_ubuntu/micro_benchmarks/build.sh | 35 ++++++-- .../scripts/micro_benchmarks/helper.py | 82 ++++++++++++++++++- .../micro_benchmarks/read_single_thread.py | 6 +- .../micro_benchmarks/write_single_thread.py | 6 +- 4 files changed, 120 insertions(+), 9 deletions(-) diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh index 2c710ed4c1..c9e6a6b87f 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh @@ -32,15 +32,38 @@ python3 -m venv venv source venv/bin/activate pip install -r requirements.txt +# Temporarily allow script to continue after command failure +set +e + echo "Running Python scripts for hns bucket..." + FILE_SIZE_READ_GB=15 -LOG_FILE=${KOKORO_ARTIFACTS_DIR}/gcsfuse-logs-single-threaded-read-${FILE_SIZE_READ_GB}gb-test.txt -GCSFUSE_READ_FLAGS="--log-file $LOG_FILE" -python3 read_single_thread.py --bucket single-threaded-tests --gcsfuse-config "$GCSFUSE_READ_FLAGS" --total-files 10 --file-size-gb ${FILE_SIZE_READ_GB} +READ_LOG_FILE="${KOKORO_ARTIFACTS_DIR}/gcsfuse-logs-single-threaded-read-${FILE_SIZE_READ_GB}gb-test.txt" +GCSFUSE_READ_FLAGS="--log-file $READ_LOG_FILE" +python3 read_single_thread.py --bucket single-threaded-tests --gcsfuse-config "$GCSFUSE_READ_FLAGS" --total-files 10 --file-size-gb "$FILE_SIZE_READ_GB" +exit_read_code=$? FILE_SIZE_WRITE_GB=15 -LOG_FILE=${KOKORO_ARTIFACTS_DIR}/gcsfuse-logs-single-threaded-write-${FILE_SIZE_WRITE_GB}gb-test.txt -GCSFUSE_WRITE_FLAGS="--log-file $LOG_FILE" -python3 write_single_thread.py --bucket single-threaded-tests --gcsfuse-config "$GCSFUSE_WRITE_FLAGS" --total-files 1 --file-size-gb ${FILE_SIZE_WRITE_GB} +WRITE_LOG_FILE="${KOKORO_ARTIFACTS_DIR}/gcsfuse-logs-single-threaded-write-${FILE_SIZE_WRITE_GB}gb-test.txt" +GCSFUSE_WRITE_FLAGS="--log-file $WRITE_LOG_FILE" +python3 write_single_thread.py --bucket single-threaded-tests --gcsfuse-config "$GCSFUSE_WRITE_FLAGS" --total-files 1 --file-size-gb "$FILE_SIZE_WRITE_GB" +exit_write_code=$? deactivate + +# Re-enable strict mode +set -e + +# Final result +if [[ $exit_read_code -ne 0 ]]; then + echo "Read benchmark failed with exit code $exit_read_code" + exit $exit_read_code +fi + +if [[ $exit_write_code -ne 0 ]]; then + echo "Write benchmark failed with exit code $exit_write_code" + exit $exit_write_code +fi + +echo "Benchmarks completed successfully." +exit 0 diff --git a/perfmetrics/scripts/micro_benchmarks/helper.py b/perfmetrics/scripts/micro_benchmarks/helper.py index 7bb2563ba0..2158d08569 100644 --- a/perfmetrics/scripts/micro_benchmarks/helper.py +++ b/perfmetrics/scripts/micro_benchmarks/helper.py @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from datetime import datetime +import sys +from datetime import datetime, timedelta from google.cloud import bigquery import os import subprocess @@ -124,3 +125,82 @@ def log_to_bigquery(start_time_sec: float, duration_sec: float, total_bytes: int client.load_table_from_dataframe(df, table_ref).result() print("Successfully logged data to BigQuery.") + + +def get_last_n_days_bandwidth_entries( + client: bigquery.Client, + table_ref: bigquery.TableReference, + workload_type: str, + days: int = 3 +) -> list[float]: + """ + Fetches bandwidth measurements (in Mbps) for a given workload type + from the last 'n' days of records in the specified BigQuery table. + + Args: + client (bigquery.Client): Authenticated BigQuery client instance. + table_ref (bigquery.TableReference): Reference to the BigQuery table. + workload_type (str): Type of workload to filter for (e.g., "read" or "write"). + days (int): Number of past days to look back from the current time. + + Returns: + list[float]: A list of bandwidth values (in Mbps). Returns an empty list if no data is found or an error occurs. + """ + full_table_name = f"`{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}`" + time_ago = datetime.now() - timedelta(days=days) + time_ago_str = time_ago.strftime('%Y-%m-%d %H:%M:%S') + + query = f""" + SELECT bandwidth_mbps + FROM {full_table_name} + WHERE start_time >= TIMESTAMP('{time_ago_str}') + AND workload_type = '{workload_type}' + ORDER BY start_time DESC + """ + + bandwidths = [] + try: + query_job = client.query(query) + rows = query_job.result() + for row in rows: + bandwidths.append(row.bandwidth_mbps) + except Exception as e: + print(f"Error fetching bandwidth entries for the past {days} days: {e}") + + return bandwidths + + +def check_and_alert_bandwidth(bandwidth_threshold_mbps: float, workload_type: str) -> None: + """ + Validates current bandwidth performance for a workload by comparing it against + a historical average from the past 3 days. If the average bandwidth is below + the defined threshold, the function prints a warning and exits with status code 1. + + Args: + bandwidth_threshold_mbps (float): Minimum acceptable bandwidth (in Mbps). + workload_type (str): The type of workload being evaluated (e.g., "read" or "write"). + """ + client = bigquery.Client(project=PROJECT_ID) + table_ref = client.dataset(DATASET_ID).table(TABLE_ID) + + print("\n--- Bandwidth Validation: Comparing Against Last 3 Days Average ---") + last_three_days_bandwidths = get_last_n_days_bandwidth_entries( + client, table_ref, workload_type, days=3 + ) + + if last_three_days_bandwidths: + avg_past_bandwidth = sum(last_three_days_bandwidths) / len(last_three_days_bandwidths) + print(f"Workload Type : {workload_type}") + print(f"3-Day Average : {avg_past_bandwidth:.2f} Mbps") + print(f"Configured Threshold: {bandwidth_threshold_mbps:.2f} Mbps") + + if avg_past_bandwidth < bandwidth_threshold_mbps: + print("FAILURE: 3-day average bandwidth is below the threshold.") + print("\n----------------------------------\n") + sys.exit(1) # Fail the Kokoro build + else: + print("Bandwidth is within acceptable range.") + else: + print(f"No recent data available for '{workload_type}' workload in the last 3 days.") + + print("\n----------------------------------\n") diff --git a/perfmetrics/scripts/micro_benchmarks/read_single_thread.py b/perfmetrics/scripts/micro_benchmarks/read_single_thread.py index 887527f4f1..cdb2246771 100644 --- a/perfmetrics/scripts/micro_benchmarks/read_single_thread.py +++ b/perfmetrics/scripts/micro_benchmarks/read_single_thread.py @@ -139,12 +139,16 @@ def main(): # Log to BigQuery helper.log_to_bigquery( - start_time_sec=start, + start_time_sec=start, duration_sec=duration, total_bytes=total_bytes, gcsfuse_config=args.gcsfuse_config, workload_type=workflow_type, ) + # TODO: Remove this once alerts are configured. + # 160 Mbps is the minimum threshold based on the 3-runs average bandwidth + helper.check_and_alert_bandwidth(160, workflow_type) + if __name__ == "__main__": main() diff --git a/perfmetrics/scripts/micro_benchmarks/write_single_thread.py b/perfmetrics/scripts/micro_benchmarks/write_single_thread.py index 7319a1c3fe..100aafc146 100644 --- a/perfmetrics/scripts/micro_benchmarks/write_single_thread.py +++ b/perfmetrics/scripts/micro_benchmarks/write_single_thread.py @@ -125,12 +125,16 @@ def main(): helper.unmount_gcs_directory(MOUNT_DIR) helper.log_to_bigquery( - start_time_sec=start, + start_time_sec=start, duration_sec=duration, total_bytes=total_bytes, gcsfuse_config=args.gcsfuse_config, workload_type=workflow_type, ) + # TODO: Remove this once alerts are configured. + # 80 Mbps is the minimum threshold based on the 3-runs average bandwidth + helper.check_and_alert_bandwidth(80, workflow_type) + if __name__ == "__main__": main() From 22fd69a17726d6b9f596558e3e71ef5428eb9970 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 14 Jul 2025 13:58:36 +0530 Subject: [PATCH 0552/1298] ci: Upgrade to python311 in micro benchmark script (#3506) * upgrade to python 3.11 * upgrade python version --- .../gcp_ubuntu/micro_benchmarks/build.sh | 3 + perfmetrics/scripts/upgrade_python3.sh | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100755 perfmetrics/scripts/upgrade_python3.sh diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh index c9e6a6b87f..56437d050d 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh @@ -27,6 +27,9 @@ commitId=$(git log --before='yesterday 23:59:59' --max-count=1 --pretty=%H) cd "./perfmetrics/scripts/micro_benchmarks" +echo "Upgrade python3 version" +./perfmetrics/scripts/upgrade_python3.sh + echo "Installing dependencies..." python3 -m venv venv source venv/bin/activate diff --git a/perfmetrics/scripts/upgrade_python3.sh b/perfmetrics/scripts/upgrade_python3.sh new file mode 100755 index 0000000000..ab02fcd108 --- /dev/null +++ b/perfmetrics/scripts/upgrade_python3.sh @@ -0,0 +1,63 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +#!/bin/bash + +set -e + +PYTHON_VERSION=3.11.9 +INSTALL_PREFIX="/usr/local" + +# Install dependencies silently +sudo apt update > /dev/null +sudo apt install -y \ + build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev \ + libssl-dev libreadline-dev libffi-dev curl libsqlite3-dev \ + libbz2-dev liblzma-dev tk-dev uuid-dev wget > /dev/null + +# Download Python source silently +cd /usr/src +sudo wget -q https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz +sudo tar -xf Python-${PYTHON_VERSION}.tgz > /dev/null +cd Python-${PYTHON_VERSION} + +# Configure silently +sudo ./configure --enable-optimizations --prefix=$INSTALL_PREFIX > /dev/null + +# Build silently +sudo make -j"$(nproc)" > /dev/null + +# Install silently +sudo make altinstall > /dev/null + +# Install pip silently +sudo $INSTALL_PREFIX/bin/python3.11 -m ensurepip > /dev/null +sudo $INSTALL_PREFIX/bin/python3.11 -m pip install --upgrade pip > /dev/null + +# Remove old alternatives silently +sudo update-alternatives --remove-all python3 > /dev/null 2>&1 || true +sudo update-alternatives --remove-all pip3 > /dev/null 2>&1 || true + +# Register Python 3.11 as alternative silently +sudo update-alternatives --install /usr/bin/python3 python3 $INSTALL_PREFIX/bin/python3.11 1 > /dev/null +sudo update-alternatives --install /usr/bin/pip3 pip3 $INSTALL_PREFIX/bin/pip3.11 1 > /dev/null + +# Set Python 3.11 as default silently +sudo update-alternatives --set python3 $INSTALL_PREFIX/bin/python3.11 > /dev/null +sudo update-alternatives --set pip3 $INSTALL_PREFIX/bin/pip3.11 > /dev/null + +# Final version check (visible) +python3 --version +pip3 --version From b4df5d060a199eb1779a489bc76b93c3ba496953 Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Mon, 14 Jul 2025 10:58:25 +0000 Subject: [PATCH 0553/1298] Dir Handle changes to support Readdirplus operation (#3490) * Dir Handle Changes --- internal/fs/handle/dir_handle.go | 264 +++++++++++++++++++++----- internal/fs/handle/dir_handle_test.go | 166 ++++++++++++++++ internal/fs/inode/dir.go | 2 + 3 files changed, 386 insertions(+), 46 deletions(-) diff --git a/internal/fs/handle/dir_handle.go b/internal/fs/handle/dir_handle.go index e4c6b42257..c3f484620b 100644 --- a/internal/fs/handle/dir_handle.go +++ b/internal/fs/handle/dir_handle.go @@ -15,8 +15,10 @@ package handle import ( + "cmp" "fmt" - "sort" + "maps" + "slices" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" @@ -26,6 +28,30 @@ import ( "golang.org/x/net/context" ) +// DirEntry is a generic interface for directory entries that expose +// name, type, and offset. It is implemented by fuseutil.Dirent and fuseutil.DirentPlus. +type DirEntry interface { + EntryName() string + SetName(name string) + EntryType() fuseutil.DirentType + SetOffset(offset fuseops.DirOffset) +} + +type dirent fuseutil.Dirent +type direntPlus fuseutil.DirentPlus + +// For Dirent +func (d *dirent) EntryName() string { return d.Name } +func (d *dirent) SetName(name string) { d.Name = name } +func (d *dirent) EntryType() fuseutil.DirentType { return d.Type } +func (d *dirent) SetOffset(offset fuseops.DirOffset) { d.Offset = offset } + +// For DirentPlus +func (dp *direntPlus) EntryName() string { return dp.Dirent.Name } +func (dp *direntPlus) SetName(name string) { dp.Dirent.Name = name } +func (dp *direntPlus) EntryType() fuseutil.DirentType { return dp.Dirent.Type } +func (dp *direntPlus) SetOffset(offset fuseops.DirOffset) { dp.Dirent.Offset = offset } + // DirHandle is the state required for reading from directories. type DirHandle struct { ///////////////////////// @@ -48,12 +74,26 @@ type DirHandle struct { // GUARDED_BY(Mu) entries []fuseutil.Dirent + // All entries in the directory along with their attributes. Populated the first time we need one. + // + // INVARIANT: For each i, entriesPlus[i+1].Offset == entriesPlus[i].Offset + 1 + // + // GUARDED_BY(Mu) + entriesPlus []fuseutil.DirentPlus + // Has entries yet been populated? // // INVARIANT: If !entriesValid, then len(entries) == 0 // // GUARDED_BY(Mu) entriesValid bool + + // Has entriesPlus yet been populated? + // + // INVARIANT: If !entriesPlusValid, then len(entriesPlus) == 0 + // + // GUARDED_BY(Mu) + entriesPlusValid bool } // NewDirHandle creates a directory handle that obtains listings from the supplied inode. @@ -76,13 +116,6 @@ func NewDirHandle( // Helpers //////////////////////////////////////////////////////////////////////// -// Directory entries, sorted by name. -type sortedDirents []fuseutil.Dirent - -func (p sortedDirents) Len() int { return len(p) } -func (p sortedDirents) Less(i, j int) bool { return p[i].Name < p[j].Name } -func (p sortedDirents) Swap(i, j int) { p[i], p[j] = p[j], p[i] } - func (dh *DirHandle) checkInvariants() { // INVARIANT: For each i, entries[i+1].Offset == entries[i].Offset + 1 for i := 0; i < len(dh.entries)-1; i++ { @@ -99,6 +132,28 @@ func (dh *DirHandle) checkInvariants() { if !dh.entriesValid && len(dh.entries) != 0 { panic("Unexpected non-empty entries slice") } + + // INVARIANT: For each i, entriesPlus[i+1].Dirent.Offset == entriesPlus[i].Dirent.Offset + 1 + for i := 0; i < len(dh.entriesPlus)-1; i++ { + if !(dh.entriesPlus[i+1].Dirent.Offset == dh.entriesPlus[i].Dirent.Offset+1) { + panic( + fmt.Sprintf( + "Unexpected offset sequence: %v, %v", + dh.entriesPlus[i].Dirent.Offset, + dh.entriesPlus[i+1].Dirent.Offset)) + } + } + + // INVARIANT: If !entriesPlusValid, then len(entriesPlus) == 0 + if !dh.entriesPlusValid && len(dh.entriesPlus) != 0 { + panic("Unexpected non-empty entries slice") + } +} + +// compareEntriesByName provides a comparison function for sorting directory entries +// by name. +func compareEntriesByName[T DirEntry](a, b T) int { + return cmp.Compare(a.EntryName(), b.EntryName()) } // Resolve name conflicts between file objects and directory objects (e.g. the @@ -106,37 +161,37 @@ func (dh *DirHandle) checkInvariants() { // GCS object names, to conflicting file names. // // Input must be sorted by name. -func fixConflictingNames(entries []fuseutil.Dirent, localEntries map[string]fuseutil.Dirent) (output []fuseutil.Dirent, err error) { +func fixConflictingNames[T DirEntry](entries []T, localEntries map[string]T) (output []T, err error) { // Sanity check. - if !sort.IsSorted(sortedDirents(entries)) { + if !slices.IsSortedFunc(entries, compareEntriesByName) { err = fmt.Errorf("expected sorted input") return } // Examine each adjacent pair of names. for i := range entries { - e := &entries[i] + e := entries[i] // Find the previous entry. if i == 0 { - output = append(output, *e) + output = append(output, e) continue } - prev := &output[len(output)-1] + prev := output[len(output)-1] // Does the pair have matching names? - if e.Name != prev.Name { - output = append(output, *e) + if e.EntryName() != prev.EntryName() { + output = append(output, e) continue } // We expect exactly one to be a directory. - eIsDir := e.Type == fuseutil.DT_Directory - prevIsDir := prev.Type == fuseutil.DT_Directory + eIsDir := e.EntryType() == fuseutil.DT_Directory + prevIsDir := prev.EntryType() == fuseutil.DT_Directory if eIsDir == prevIsDir { - if _, ok := localEntries[e.Name]; ok && !eIsDir { + if _, ok := localEntries[e.EntryName()]; ok && !eIsDir { // We have found same entry in GCS and local file entries, i.e, the // entry is uploaded to GCS but not yet deleted from local entries. // Do not return the duplicate entry as part of list response. @@ -144,26 +199,69 @@ func fixConflictingNames(entries []fuseutil.Dirent, localEntries map[string]fuse } else { err = fmt.Errorf( "weird dirent type pair for name %q: %v, %v", - e.Name, - e.Type, - prev.Type) + e.EntryName(), + e.EntryType(), + prev.EntryType()) return } } // Repair whichever is not the directory. if eIsDir { - prev.Name += inode.ConflictingFileNameSuffix + prev.SetName(prev.EntryName() + inode.ConflictingFileNameSuffix) } else { - e.Name += inode.ConflictingFileNameSuffix + e.SetName(e.EntryName() + inode.ConflictingFileNameSuffix) } - output = append(output, *e) + output = append(output, e) } return } +// sortAndResolveEntries is a generic helper function that takes a list of +// directory entries, sorts them, resolves name conflicts, and sets their +// offsets. +// +// This generic function supports both fuseutil.Dirent and fuseutil.DirentPlus +// by wrapping/ unwrapping them into DirEntry interface-compatible types. +func sortAndResolveEntries[Entry any, WrappedEntry DirEntry](entries []Entry, localEntries map[string]Entry, wrap func(Entry) WrappedEntry, unwrap func(WrappedEntry) Entry) ([]Entry, error) { + // Wrap and append local file entry (not synced to GCS). + wrappedLocalEntries := make(map[string]WrappedEntry) + for name, localEntry := range localEntries { + wrappedLocalEntries[name] = wrap(localEntry) + entries = append(entries, localEntry) + } + wrappedEntries := make([]WrappedEntry, 0, len(entries)) + for _, entry := range entries { + wrappedEntries = append(wrappedEntries, wrap(entry)) + } + + // Ensure that the entries are sorted, for use in fixConflictingNames + // below. + slices.SortFunc(wrappedEntries, compareEntriesByName) + + // Fix name conflicts. + // When a local file is synced to GCS but not removed from the local file map, + // the entries list will have two duplicate entries. + // To handle this scenario, we are removing the duplicate entry before + // returning the response to kernel. + fixedEntries, err := fixConflictingNames(wrappedEntries, wrappedLocalEntries) + if err != nil { + err = fmt.Errorf("fixConflictingNames: %w", err) + return nil, err + } + + // Fix up offset fields and unwrap. + finalEntries := make([]Entry, 0, len(fixedEntries)) + for i, fe := range fixedEntries { + fe.SetOffset(fuseops.DirOffset(i) + 1) + finalEntries = append(finalEntries, unwrap(fe)) + } + + return finalEntries, nil +} + // Read all entries for the directory, fix up conflicting names, and fill in // offset fields. // @@ -194,29 +292,10 @@ func readAllEntries( } } - // Append local file entries (not synced to GCS). - for _, localEntry := range localEntries { - entries = append(entries, localEntry) - } - - // Ensure that the entries are sorted, for use in fixConflictingNames - // below. - sort.Sort(sortedDirents(entries)) - - // Fix name conflicts. - // When a local file is synced to GCS but not removed from the local file map, - // the entries list will have two duplicate entries. - // To handle this scenario, we are removing the duplicate entry before - // returning the response to kernel. - entries, err = fixConflictingNames(entries, localEntries) + // Sort, resolve conflicts, and set offsets. + entries, err = sortAndResolveEntries(entries, localEntries, func(e fuseutil.Dirent) *dirent { d := dirent(e); return &d }, func(w *dirent) fuseutil.Dirent { return fuseutil.Dirent(*w) }) if err != nil { - err = fmt.Errorf("fixConflictingNames: %w", err) - return - } - - // Fix up offset fields. - for i := 0; i < len(entries); i++ { - entries[i].Offset = fuseops.DirOffset(i) + 1 + return nil, err } // Return a bogus inode ID for each entry, but not the root inode ID. @@ -239,6 +318,33 @@ func readAllEntries( return } +// readAllEntryCores retrieves all directory entry cores for the given inode, +// handling pagination and accumulating the results. +// LOCKS_REQUIRED(in) +func readAllEntryCores(ctx context.Context, in inode.DirInode) (cores map[inode.Name]*inode.Core, err error) { + // Read entries from GCS. + // Read one batch at a time. + var tok string + cores = make(map[inode.Name]*inode.Core) + for { + // Read a batch from GCS + var batch map[inode.Name]*inode.Core + batch, tok, err = in.ReadEntryCores(ctx, tok) + if err != nil { + return + } + // Accumulate. + maps.Copy(cores, batch) + + // Are we done? + if tok == "" { + break + } + } + + return +} + // LOCKS_REQUIRED(dh.Mu) // LOCKS_EXCLUDED(dh.in) func (dh *DirHandle) ensureEntries(ctx context.Context, localFileEntries map[string]fuseutil.Dirent) (err error) { @@ -311,3 +417,69 @@ func (dh *DirHandle) ReadDir( return } + +// FetchEntryCores retrieves the core inode data for all entries within the directory from GCS. +// +// Special case: If the request offset is zero, it assumes the directory is being read from the +// beginning and resets the cached list of entries. +// +// LOCKS_REQUIRED(dh.Mu) +// LOCKS_EXCLUDED(dh.in) +func (dh *DirHandle) FetchEntryCores(ctx context.Context, op *fuseops.ReadDirPlusOp) (cores map[inode.Name]*inode.Core, err error) { + // If the request is for offset zero, we assume that either this is the first + // call or rewinddir has been called. Reset state. + if op.Offset == 0 { + dh.entriesPlus = nil + dh.entriesPlusValid = false + } + + // Do we need to read entries from GCS? + if !dh.entriesPlusValid { + dh.in.Lock() + cores, err = readAllEntryCores(ctx, dh.in) + if err != nil { + dh.in.Unlock() + return + } + dh.in.Unlock() + } + + return +} + +// ReadDirPlus populates the FUSE response buffer using a pre-processed list +// of directory entries. +// LOCKS_REQUIRED(dh.Mu) +func (dh *DirHandle) ReadDirPlus(op *fuseops.ReadDirPlusOp, entries []fuseutil.DirentPlus, localEntries map[string]fuseutil.DirentPlus) (err error) { + // If entriesPlus has not been populated yet, populate it. + if !dh.entriesPlusValid { + // Sort, resolve conflicts, and set offsets. + entries, err = sortAndResolveEntries(entries, localEntries, func(e fuseutil.DirentPlus) *direntPlus { dp := direntPlus(e); return &dp }, func(w *direntPlus) fuseutil.DirentPlus { return fuseutil.DirentPlus(*w) }) + if err != nil { + return + } + // Update state. + dh.entriesPlus = entries + dh.entriesPlusValid = true + } + + // Is the offset past the end of what we have buffered? If so, this must be + // an invalid seekdir according to posix. + index := int(op.Offset) + if index > len(dh.entriesPlus) { + err = fuse.EINVAL + return + } + + //We copy out entries until we run out of entries or space. + for i := index; i < len(dh.entriesPlus); i++ { + n := fuseutil.WriteDirentPlus(op.Dst[op.BytesRead:], dh.entriesPlus[i]) + if n == 0 { + break + } + + op.BytesRead += n + } + + return +} diff --git a/internal/fs/handle/dir_handle_test.go b/internal/fs/handle/dir_handle_test.go index f24b37b4f0..a96580c79b 100644 --- a/internal/fs/handle/dir_handle_test.go +++ b/internal/fs/handle/dir_handle_test.go @@ -17,9 +17,12 @@ package handle import ( "context" "math" + "path" "testing" "time" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" @@ -116,6 +119,44 @@ func (t *DirHandleTest) validateEntry(entry fuseutil.Dirent, name string, filety AssertEq(filetype, entry.Type) } +func (t *DirHandleTest) createTestDirentPlus(name string, dtype fuseutil.DirentType, childInodeID fuseops.InodeID, size uint64) fuseutil.DirentPlus { + attrs := fuseops.InodeAttributes{ + Size: size, + Mode: 0777, + Nlink: 1, + Uid: 123, + Gid: 456, + } + if dtype != fuseutil.DT_Directory { + attrs.Mode = 0666 + } + + return fuseutil.DirentPlus{ + Dirent: fuseutil.Dirent{ + Name: name, + Type: dtype, + }, + Entry: fuseops.ChildInodeEntry{ + Child: childInodeID, + Attributes: attrs, + }, + } +} + +func (t *DirHandleTest) validateEntryPlus(entry fuseutil.DirentPlus, expectedName string, expectedType fuseutil.DirentType, expectedChildInodeID fuseops.InodeID) { + AssertEq(expectedName, entry.Dirent.Name) + AssertEq(expectedType, entry.Dirent.Type) + AssertEq(expectedChildInodeID, entry.Entry.Child) +} + +func (t *DirHandleTest) validateCore(core *inode.Core, expectedName string, expectedType metadata.Type, expectedMinObjectName string) { + AssertNe(nil, core) + AssertNe(nil, core.MinObject) + AssertEq(expectedName, path.Base(core.FullName.LocalName())) + AssertEq(expectedMinObjectName, core.MinObject.Name) + AssertEq(expectedType, core.Type()) +} + //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// @@ -278,3 +319,128 @@ func (t *DirHandleTest) EnsureEntriesWithOneLocalFile() { AssertEq(1, len(t.dh.entries)) t.validateEntry(t.dh.entries[0], localFileName1, fuseutil.DT_File) } + +func (t *DirHandleTest) ReadAllEntryCoresWithNoEntry() { + cores, err := readAllEntryCores(t.ctx, t.dh.in) + + AssertEq(nil, err) + AssertEq(0, len(cores)) +} + +func (t *DirHandleTest) ReadAllEntryCoresReturnsAllEntryCores() { + // Setup GCS objects + _, err := storageutil.CreateObject(t.ctx, t.bucket, "testDir/gcsObject1", nil) + AssertEq(nil, err) + _, err = storageutil.CreateObject(t.ctx, t.bucket, "testDir/gcsObject2", nil) + AssertEq(nil, err) + + // read all entry cores + cores, err := readAllEntryCores(t.ctx, t.dh.in) + + // validations + AssertEq(nil, err) + AssertEq(2, len(cores)) + entry1, ok := cores[inode.NewFileName(t.dh.in.Name(), "gcsObject1")] + AssertTrue(ok, "Core for gcsObject1 not found") + t.validateCore(entry1, "gcsObject1", metadata.RegularFileType, "testDir/gcsObject1") + entry2, ok := cores[inode.NewFileName(t.dh.in.Name(), "gcsObject2")] + AssertTrue(ok, "Core for gcsObject2 not found") + t.validateCore(entry2, "gcsObject2", metadata.RegularFileType, "testDir/gcsObject2") +} + +func (t *DirHandleTest) FetchEntryCoresFetchesCores() { + _, err := storageutil.CreateObject(t.ctx, t.bucket, "testDir/testFile", nil) + AssertEq(nil, err) + op := &fuseops.ReadDirPlusOp{ + ReadDirOp: fuseops.ReadDirOp{Offset: 0}, + } + t.dh.entriesPlusValid = false + + cores, err := t.dh.FetchEntryCores(t.ctx, op) + + AssertEq(nil, err) + AssertEq(1, len(cores)) + entry, ok := cores[inode.NewFileName(t.dh.in.Name(), "testFile")] + AssertTrue(ok, "Core for gcsFile1 not found") + t.validateCore(entry, "testFile", metadata.RegularFileType, "testDir/testFile") +} + +func (t *DirHandleTest) FetchEntryCoresNonZeroOffsetNoFetchIfCacheValid() { + t.dh.entriesPlus = []fuseutil.DirentPlus{{}} + t.dh.entriesPlusValid = true + op := &fuseops.ReadDirPlusOp{ + ReadDirOp: fuseops.ReadDirOp{Offset: 1}, + } + + cores, err := t.dh.FetchEntryCores(t.ctx, op) + + AssertEq(nil, err) + AssertEq(nil, cores) + AssertTrue(t.dh.entriesPlusValid) +} + +func (t *DirHandleTest) FetchEntryCoresNonZeroOffsetFetchesIfCacheInvalid() { + t.dh.entriesPlusValid = false + _, err := storageutil.CreateObject(t.ctx, t.bucket, "testDir/fetchThis", nil) + AssertEq(nil, err) + op := &fuseops.ReadDirPlusOp{ + ReadDirOp: fuseops.ReadDirOp{Offset: 1}, + } + + cores, err := t.dh.FetchEntryCores(t.ctx, op) + + AssertEq(nil, err) + AssertEq(1, len(cores)) + entry, ok := cores[inode.NewFileName(t.dh.in.Name(), "fetchThis")] + AssertTrue(ok, "Core for fetchThis not found") + t.validateCore(entry, "fetchThis", metadata.RegularFileType, "testDir/fetchThis") +} + +func (t *DirHandleTest) ReadDirPlusResponseForNoFile() { + op := &fuseops.ReadDirPlusOp{ + ReadDirOp: fuseops.ReadDirOp{Dst: make([]byte, 1024)}, + } + var gcsEntries []fuseutil.DirentPlus + localFileEntries := make(map[string]fuseutil.DirentPlus) + + err := t.dh.ReadDirPlus(op, gcsEntries, localFileEntries) + + AssertEq(nil, err) + AssertEq(0, op.BytesRead) + AssertTrue(t.dh.entriesPlusValid) + AssertEq(0, len(t.dh.entriesPlus)) +} + +func (t *DirHandleTest) ReadDirPlusSameNameLocalAndGCSFile() { + op := &fuseops.ReadDirPlusOp{ + ReadDirOp: fuseops.ReadDirOp{Dst: make([]byte, 1024)}, + } + gcsFile := t.createTestDirentPlus("sameName", fuseutil.DT_File, 1001, 10) + localFile := t.createTestDirentPlus("sameName", fuseutil.DT_File, 1002, 0) + gcsEntriesPlus := []fuseutil.DirentPlus{gcsFile} + localFileEntriesPlus := map[string]fuseutil.DirentPlus{"sameName": localFile} + + err := t.dh.ReadDirPlus(op, gcsEntriesPlus, localFileEntriesPlus) + + AssertEq(nil, err) + AssertEq(1, len(t.dh.entriesPlus)) + t.validateEntryPlus(t.dh.entriesPlus[0], "sameName", fuseutil.DT_File, 1001) +} + +func (t *DirHandleTest) ReadDirPlusSameNameLocalFileAndGCSDirectory() { + op := &fuseops.ReadDirPlusOp{ + ReadDirOp: fuseops.ReadDirOp{Dst: make([]byte, 1024)}, + } + gcsDir := t.createTestDirentPlus("sameName", fuseutil.DT_Directory, 1001, 0) + gcsEntriesPlus := []fuseutil.DirentPlus{gcsDir} + localFile := t.createTestDirentPlus("sameName", fuseutil.DT_File, 2001, 20) + localFileEntriesPlus := map[string]fuseutil.DirentPlus{"sameName": localFile} + + err := t.dh.ReadDirPlus(op, gcsEntriesPlus, localFileEntriesPlus) + + AssertEq(nil, err) + AssertEq(2, len(t.dh.entriesPlus)) + t.validateEntryPlus(t.dh.entriesPlus[0], "sameName", fuseutil.DT_Directory, 1001) + t.validateEntryPlus(t.dh.entriesPlus[1], "sameName"+inode.ConflictingFileNameSuffix, fuseutil.DT_File, 2001) + AssertEq(t.dh.entriesPlus[1].Dirent.Offset, t.dh.entriesPlus[0].Dirent.Offset+1) +} diff --git a/internal/fs/inode/dir.go b/internal/fs/inode/dir.go index 35a6861684..eb5202114b 100644 --- a/internal/fs/inode/dir.go +++ b/internal/fs/inode/dir.go @@ -782,6 +782,7 @@ func (d *dirInode) readObjects( return } +// LOCKS_REQUIRED(d) func (d *dirInode) ReadEntries( ctx context.Context, tok string) (entries []fuseutil.Dirent, newTok string, err error) { @@ -810,6 +811,7 @@ func (d *dirInode) ReadEntries( return } +// LOCKS_REQUIRED(d) func (d *dirInode) ReadEntryCores(ctx context.Context, tok string) (cores map[Name]*Core, newTok string, err error) { cores, newTok, err = d.readObjects(ctx, tok) if err != nil { From bb28c11b229d5c2706cbb0abb9eb8634363b3799 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Mon, 14 Jul 2025 18:19:04 +0530 Subject: [PATCH 0554/1298] remove dependabot from review reminders (#3507) --- .github/workflows/auto-pr-reminder.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/auto-pr-reminder.yml b/.github/workflows/auto-pr-reminder.yml index aadfc07b25..593a1c21fe 100644 --- a/.github/workflows/auto-pr-reminder.yml +++ b/.github/workflows/auto-pr-reminder.yml @@ -22,6 +22,20 @@ jobs: uses: actions/github-script@v6 with: script: | + // List of authors to exclude from reminders. + const excludedAuthors = [ + 'dependabot[bot]', + ]; + + const author = context.payload.pull_request.user.login; + console.log(`Pull Request author is: ${author}`); + + // Check if the author is in the exclusion list. + if (excludedAuthors.includes(author)) { + console.log(`Author '${author}' is in the exclusion list. Skipping label addition.`); + return; + } + github.rest.issues.addLabels({ issue_number: context.issue.number, owner: context.repo.owner, From 2d5e08b631ae932e4fd821cd3a201e13783b4b80 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 15 Jul 2025 09:45:30 +0530 Subject: [PATCH 0555/1298] swap the execution (#3516) --- .../continuous_test/gcp_ubuntu/micro_benchmarks/build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh index 56437d050d..52aa81e130 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh @@ -25,11 +25,11 @@ echo "Building and installing gcsfuse" commitId=$(git log --before='yesterday 23:59:59' --max-count=1 --pretty=%H) ./perfmetrics/scripts/build_and_install_gcsfuse.sh $commitId -cd "./perfmetrics/scripts/micro_benchmarks" - echo "Upgrade python3 version" ./perfmetrics/scripts/upgrade_python3.sh +cd "./perfmetrics/scripts/micro_benchmarks" + echo "Installing dependencies..." python3 -m venv venv source venv/bin/activate From be7f5529b30101cf621214da5b727a4bd545626d Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 15 Jul 2025 10:42:14 +0530 Subject: [PATCH 0556/1298] Minor refactoring (#3515) Reduce the scope of OTel meters to local. --- common/otel_metrics.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/common/otel_metrics.go b/common/otel_metrics.go index 267b079359..d0e39290b2 100644 --- a/common/otel_metrics.go +++ b/common/otel_metrics.go @@ -27,10 +27,6 @@ import ( ) var ( - fsOpsMeter = otel.Meter("fs_op") - gcsMeter = otel.Meter("gcs") - fileCacheMeter = otel.Meter("file_cache") - // Attribute Keys // ioMethodKey specifies the I/O method attribute (e.g., opened, closed). ioMethodKey = attribute.Key("io_method") @@ -193,6 +189,9 @@ func (o *otelMetrics) FileCacheReadLatency(ctx context.Context, latency time.Dur } func NewOTelMetrics() (MetricHandle, error) { + fsOpsMeter := otel.Meter("fs_op") + gcsMeter := otel.Meter("gcs") + fileCacheMeter := otel.Meter("file_cache") fsOpsCount, err1 := fsOpsMeter.Int64Counter("fs/ops_count", metric.WithDescription("The cumulative number of ops processed by the file system.")) fsOpsLatency, err2 := fsOpsMeter.Float64Histogram("fs/ops_latency", metric.WithDescription("The cumulative distribution of file system operation latencies"), metric.WithUnit("us"), defaultLatencyDistribution) From e558c45cff29be6edcda3f6a232fa5360d4ed70d Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 15 Jul 2025 11:50:42 +0530 Subject: [PATCH 0557/1298] * Use Int64Histogram instead of Float64Histogram (#3471) * Use bool instead of string to represent cache-hit. Reducing overheads of casting integer to float and converting bool to string. There is no meaningful CPU/memory impact however. --- common/noop_metrics.go | 2 +- common/otel_metrics.go | 26 +++++++++++++------------- common/telemetry.go | 4 ++-- internal/gcsx/file_cache_reader.go | 5 ++--- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/common/noop_metrics.go b/common/noop_metrics.go index 1e48303a8b..ac69062697 100644 --- a/common/noop_metrics.go +++ b/common/noop_metrics.go @@ -40,4 +40,4 @@ func (*noopMetrics) OpsErrorCount(_ context.Context, _ int64, _ FSOpsErrorCatego func (*noopMetrics) FileCacheReadCount(_ context.Context, _ int64, attrs CacheHitReadType) {} func (*noopMetrics) FileCacheReadBytesCount(_ context.Context, _ int64, _ string) {} -func (*noopMetrics) FileCacheReadLatency(_ context.Context, _ time.Duration, _ string) {} +func (*noopMetrics) FileCacheReadLatency(_ context.Context, _ time.Duration, _ bool) {} diff --git a/common/otel_metrics.go b/common/otel_metrics.go index d0e39290b2..97d724fd67 100644 --- a/common/otel_metrics.go +++ b/common/otel_metrics.go @@ -90,10 +90,10 @@ func getFsOpsErrorCategoryAttributeOption(attr FSOpsErrorCategory) metric.Measur }) } -func cacheHitAttrOption(cacheHit string) metric.MeasurementOption { +func cacheHitAttrOption(cacheHit bool) metric.MeasurementOption { return loadOrStoreAttrOption(&cacheHitOptionCache, cacheHit, func() attribute.Set { - return attribute.NewSet(cacheHitKey.String(cacheHit)) + return attribute.NewSet(cacheHitKey.Bool(cacheHit)) }) } @@ -113,7 +113,7 @@ func retryErrCategoryAttrOption(retryErrCategory string) metric.MeasurementOptio func cacheHitReadTypeAttrOption(attr CacheHitReadType) metric.MeasurementOption { return loadOrStoreAttrOption(&cacheHitReadTypeOptionCache, attr, func() attribute.Set { - return attribute.NewSet(cacheHitKey.String(attr.CacheHit), readTypeKey.String(attr.ReadType)) + return attribute.NewSet(cacheHitKey.Bool(attr.CacheHit), readTypeKey.String(attr.ReadType)) }) } @@ -121,19 +121,19 @@ func cacheHitReadTypeAttrOption(attr CacheHitReadType) metric.MeasurementOption type otelMetrics struct { fsOpsCount metric.Int64Counter fsOpsErrorCount metric.Int64Counter - fsOpsLatency metric.Float64Histogram + fsOpsLatency metric.Int64Histogram gcsReadCount metric.Int64Counter gcsReadBytesCountAtomic *atomic.Int64 gcsReaderCount metric.Int64Counter gcsRequestCount metric.Int64Counter - gcsRequestLatency metric.Float64Histogram + gcsRequestLatency metric.Int64Histogram gcsDownloadBytesCount metric.Int64Counter gcsRetryCount metric.Int64Counter fileCacheReadCount metric.Int64Counter fileCacheReadBytesCount metric.Int64Counter - fileCacheReadLatency metric.Float64Histogram + fileCacheReadLatency metric.Int64Histogram } func (o *otelMetrics) GCSReadBytesCount(_ context.Context, inc int64) { @@ -149,7 +149,7 @@ func (o *otelMetrics) GCSRequestCount(ctx context.Context, inc int64, gcsMethod } func (o *otelMetrics) GCSRequestLatency(ctx context.Context, latency time.Duration, gcsMethod string) { - o.gcsRequestLatency.Record(ctx, float64(latency.Milliseconds()), gcsMethodAttrOption(gcsMethod)) + o.gcsRequestLatency.Record(ctx, latency.Milliseconds(), gcsMethodAttrOption(gcsMethod)) } func (o *otelMetrics) GCSReadCount(ctx context.Context, inc int64, readType string) { @@ -169,7 +169,7 @@ func (o *otelMetrics) OpsCount(ctx context.Context, inc int64, fsOp string) { } func (o *otelMetrics) OpsLatency(ctx context.Context, latency time.Duration, fsOp string) { - o.fsOpsLatency.Record(ctx, float64(latency.Microseconds()), fsOpsAttrOption(fsOp)) + o.fsOpsLatency.Record(ctx, latency.Microseconds(), fsOpsAttrOption(fsOp)) } func (o *otelMetrics) OpsErrorCount(ctx context.Context, inc int64, attrs FSOpsErrorCategory) { @@ -184,8 +184,8 @@ func (o *otelMetrics) FileCacheReadBytesCount(ctx context.Context, inc int64, re o.fileCacheReadBytesCount.Add(ctx, inc, readTypeAttrOption(readType)) } -func (o *otelMetrics) FileCacheReadLatency(ctx context.Context, latency time.Duration, cacheHit string) { - o.fileCacheReadLatency.Record(ctx, float64(latency.Microseconds()), cacheHitAttrOption(cacheHit)) +func (o *otelMetrics) FileCacheReadLatency(ctx context.Context, latency time.Duration, cacheHit bool) { + o.fileCacheReadLatency.Record(ctx, latency.Microseconds(), cacheHitAttrOption(cacheHit)) } func NewOTelMetrics() (MetricHandle, error) { @@ -193,7 +193,7 @@ func NewOTelMetrics() (MetricHandle, error) { gcsMeter := otel.Meter("gcs") fileCacheMeter := otel.Meter("file_cache") fsOpsCount, err1 := fsOpsMeter.Int64Counter("fs/ops_count", metric.WithDescription("The cumulative number of ops processed by the file system.")) - fsOpsLatency, err2 := fsOpsMeter.Float64Histogram("fs/ops_latency", metric.WithDescription("The cumulative distribution of file system operation latencies"), metric.WithUnit("us"), + fsOpsLatency, err2 := fsOpsMeter.Int64Histogram("fs/ops_latency", metric.WithDescription("The cumulative distribution of file system operation latencies"), metric.WithUnit("us"), defaultLatencyDistribution) fsOpsErrorCount, err3 := fsOpsMeter.Int64Counter("fs/ops_error_count", metric.WithDescription("The cumulative number of errors generated by file system operations")) @@ -213,7 +213,7 @@ func NewOTelMetrics() (MetricHandle, error) { })) gcsReaderCount, err7 := gcsMeter.Int64Counter("gcs/reader_count", metric.WithDescription("The cumulative number of GCS object readers opened or closed.")) gcsRequestCount, err8 := gcsMeter.Int64Counter("gcs/request_count", metric.WithDescription("The cumulative number of GCS requests processed along with the GCS method.")) - gcsRequestLatency, err9 := gcsMeter.Float64Histogram("gcs/request_latencies", metric.WithDescription("The cumulative distribution of the GCS request latencies."), metric.WithUnit("ms")) + gcsRequestLatency, err9 := gcsMeter.Int64Histogram("gcs/request_latencies", metric.WithDescription("The cumulative distribution of the GCS request latencies."), metric.WithUnit("ms")) gcsRetryCount, err10 := gcsMeter.Int64Counter("gcs/retry_count", metric.WithDescription("The cumulative number of retry requests made to GCS.")) fileCacheReadCount, err11 := fileCacheMeter.Int64Counter("file_cache/read_count", @@ -221,7 +221,7 @@ func NewOTelMetrics() (MetricHandle, error) { fileCacheReadBytesCount, err12 := fileCacheMeter.Int64Counter("file_cache/read_bytes_count", metric.WithDescription("The cumulative number of bytes read from file cache along with read type - Sequential/Random"), metric.WithUnit("By")) - fileCacheReadLatency, err13 := fileCacheMeter.Float64Histogram("file_cache/read_latencies", + fileCacheReadLatency, err13 := fileCacheMeter.Int64Histogram("file_cache/read_latencies", metric.WithDescription("The cumulative distribution of the file cache read latencies along with cache hit - true/false"), metric.WithUnit("us"), defaultLatencyDistribution) diff --git a/common/telemetry.go b/common/telemetry.go index 749ff793ec..f956b4fb22 100644 --- a/common/telemetry.go +++ b/common/telemetry.go @@ -37,7 +37,7 @@ const ( // Pair of CacheHit and ReadType attributes type CacheHitReadType struct { - CacheHit string + CacheHit bool ReadType string } @@ -89,7 +89,7 @@ type OpsMetricHandle interface { type FileCacheMetricHandle interface { FileCacheReadCount(ctx context.Context, inc int64, attrs CacheHitReadType) FileCacheReadBytesCount(ctx context.Context, inc int64, readType string) - FileCacheReadLatency(ctx context.Context, latency time.Duration, cacheHit string) + FileCacheReadLatency(ctx context.Context, latency time.Duration, cacheHit bool) } type MetricHandle interface { GCSMetricHandle diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index cc1fb3afce..11c5bf3499 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -19,7 +19,6 @@ import ( "errors" "fmt" "io" - "strconv" "time" "github.com/google/uuid" @@ -210,11 +209,11 @@ func (fc *FileCacheReader) ReadAt(ctx context.Context, p []byte, offset int64) ( func captureFileCacheMetrics(ctx context.Context, metricHandle common.MetricHandle, readType string, readDataSize int, cacheHit bool, readLatency time.Duration) { metricHandle.FileCacheReadCount(ctx, 1, common.CacheHitReadType{ ReadType: readType, - CacheHit: strconv.FormatBool(cacheHit), + CacheHit: cacheHit, }) metricHandle.FileCacheReadBytesCount(ctx, int64(readDataSize), readType) - metricHandle.FileCacheReadLatency(ctx, readLatency, strconv.FormatBool(cacheHit)) + metricHandle.FileCacheReadLatency(ctx, readLatency, cacheHit) } func (fc *FileCacheReader) Destroy() { From dbd174d30b2e52cd04c64f092a9b715ec8928d2e Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 15 Jul 2025 12:10:22 +0530 Subject: [PATCH 0558/1298] ci: Add missing dependency for micro benchmarks failing on kokoro machine (#3518) * fix dependecy * update exit code script --- .../gcp_ubuntu/micro_benchmarks/build.sh | 10 ++++++++-- perfmetrics/scripts/micro_benchmarks/requirements.txt | 1 + 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh index 52aa81e130..f7007f9441 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh @@ -58,14 +58,20 @@ deactivate set -e # Final result +exit_code=0 if [[ $exit_read_code -ne 0 ]]; then echo "Read benchmark failed with exit code $exit_read_code" - exit $exit_read_code + exit_code=$exit_read_code fi if [[ $exit_write_code -ne 0 ]]; then echo "Write benchmark failed with exit code $exit_write_code" - exit $exit_write_code + exit_code=$exit_write_code +fi + +if [[ $exit_code != 0 ]]; then + echo "Benchmarks failed." + exit $exit_code fi echo "Benchmarks completed successfully." diff --git a/perfmetrics/scripts/micro_benchmarks/requirements.txt b/perfmetrics/scripts/micro_benchmarks/requirements.txt index d09a1d5a71..943ee4726c 100644 --- a/perfmetrics/scripts/micro_benchmarks/requirements.txt +++ b/perfmetrics/scripts/micro_benchmarks/requirements.txt @@ -4,3 +4,4 @@ google-cloud-storage pyarrow pandas_gbq google-crc32c +psutil From f610386821f8805833eaad9cc5037d16471ca0f2 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 15 Jul 2025 12:39:38 +0530 Subject: [PATCH 0559/1298] docs(troubleshooting): Update troubleshooting doc for unsupported objects (#3505) * Update troubleshooting doc for unsupported objects This adds a more clear identifier error log, diagnosis and mitigation for unsupported GCS objects in a gcsfuse mounted bucket. * fix typo * fix the formatting * fix punctuation * fix typo --- docs/troubleshooting.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index ef95c407d9..021e5744b9 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -105,9 +105,15 @@ Unable to mount with the following error `daemonize.Run: readFromProcess: sub-pr Fuse package is not installed. It may throw this error. Run the following command to install fuse
sudo apt-get install fuse for Debian/Ubuntu
sudo yum install fuse for RHEL/CentOs/Rocky
-### ls: reading directory \/\: Input/output error +### Encountered unsupported prefixes during listing, or ls: reading directory \/\: Input/output error +Unable to list unsupported objects in a mounted bucket, i.e. objects which have names with `//` in them or have names starting with `/` e.g. `gs:////A` or `gs:///A//B` etc. Such objects can be listed by the following command: `gcloud storage ls gs:///**//**`. -Find out if you have any object(s) with name/prefix having `//` in it or starting with `/`, in the mounted GCS bucket (use `gsutil ls gs:///` to find out). If yes, move/rename such objects to name/prefix not having `//` or starting with `/` (e.g. for object/prefix `A//B`, use `gsutil -m mv -r gs:///A//* gs:///A/`), or delete it (use `gsutil -m rm -r A//`). Refer [semantics](semantics.md#unsupported-object-names) for more details. +This error can be mitigated in one of the following two ways. + +* Move/Rename such objects e.g. for objects of names like `A//B`, use `gcloud storage mv gs:///A//* gs:///A/`), Or +* Delete such objects e.g. for objects of names like `A//B`, use `gcloud storage rm gs:///A//**`. + +Refer [semantics](semantics.md#unsupported-object-names) for more details. ### Experiencing hang while executing "ls" on a directory containing large number of files/directories. From 0c326b9a52bbd80f854b90c6fee6acd333e6c65e Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 15 Jul 2025 12:50:47 +0530 Subject: [PATCH 0560/1298] Pass grpc log env variables to gcsfuse daemon (#3517) Pass environment variables GRPC_GO_LOG_VERBOSITY_LEVEL and GRPC_GO_LOG_SEVERITY_LEVEL to gcsfuse daemon for background run. This is needed for debugging errors coming from grpc. Context: http://b/412688930#comment51 --- cmd/legacy_main.go | 3 ++- cmd/legacy_main_test.go | 28 +++++++++++++++++++++------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 4303de1e80..829a1ce794 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -284,7 +284,8 @@ func forwardedEnvVars() []string { // also be included to know for which hosts the use of proxies // should be ignored. // Forward GCE_METADATA_HOST, GCE_METADATA_ROOT, GCE_METADATA_IP as these are used for mocked metadata services. - for _, envvar := range []string{"GOOGLE_APPLICATION_CREDENTIALS", "no_proxy", "GCE_METADATA_HOST", "GCE_METADATA_ROOT", "GCE_METADATA_IP"} { + // Forward GRPC_GO_LOG_VERBOSITY_LEVEL and GRPC_GO_LOG_SEVERITY_LEVEL as these are used to enable grpc debug logs. + for _, envvar := range []string{"GOOGLE_APPLICATION_CREDENTIALS", "no_proxy", "GCE_METADATA_HOST", "GCE_METADATA_ROOT", "GCE_METADATA_IP", "GRPC_GO_LOG_VERBOSITY_LEVEL", "GRPC_GO_LOG_SEVERITY_LEVEL"} { if envval, ok := os.LookupEnv(envvar); ok { env = append(env, fmt.Sprintf("%s=%s", envvar, envval)) fmt.Fprintf( diff --git a/cmd/legacy_main_test.go b/cmd/legacy_main_test.go index 93f26e19ac..3d903168b9 100644 --- a/cmd/legacy_main_test.go +++ b/cmd/legacy_main_test.go @@ -266,29 +266,43 @@ func (t *MainTest) TestIsDynamicMount() { func (t *MainTest) TestForwardedEnvVars() { for _, input := range []struct { - inputEnvVars map[string]string - expectedForwardedEnvVars []string + inputEnvVars map[string]string + expectedForwardedEnvVars []string + unexpectedForwardedEnvVarNames []string }{{ inputEnvVars: map[string]string{"GCE_METADATA_HOST": "www.metadata-host.com", "GCE_METADATA_ROOT": "metadata-root", "GCE_METADATA_IP": "99.100.101.102"}, expectedForwardedEnvVars: []string{"GCE_METADATA_HOST=www.metadata-host.com", "GCE_METADATA_ROOT=metadata-root", "GCE_METADATA_IP=99.100.101.102"}, }, { - inputEnvVars: map[string]string{"https_proxy": "https-proxy-123", "http_proxy": "http-proxy-123", "no_proxy": "no-proxy-123"}, - expectedForwardedEnvVars: []string{"https_proxy=https-proxy-123", "no_proxy=no-proxy-123"}, + inputEnvVars: map[string]string{"https_proxy": "https-proxy-123", "http_proxy": "http-proxy-123", "no_proxy": "no-proxy-123"}, + expectedForwardedEnvVars: []string{"https_proxy=https-proxy-123", "no_proxy=no-proxy-123"}, + unexpectedForwardedEnvVarNames: []string{"http_proxy"}, }, { - inputEnvVars: map[string]string{"http_proxy": "http-proxy-123", "no_proxy": "no-proxy-123"}, - expectedForwardedEnvVars: []string{"http_proxy=http-proxy-123", "no_proxy=no-proxy-123"}, + inputEnvVars: map[string]string{"http_proxy": "http-proxy-123", "no_proxy": "no-proxy-123"}, + expectedForwardedEnvVars: []string{"http_proxy=http-proxy-123", "no_proxy=no-proxy-123"}, + unexpectedForwardedEnvVarNames: []string{"https_proxy"}, }, { inputEnvVars: map[string]string{"GOOGLE_APPLICATION_CREDENTIALS": "goog-app-cred"}, expectedForwardedEnvVars: []string{"GOOGLE_APPLICATION_CREDENTIALS=goog-app-cred"}, }, { - expectedForwardedEnvVars: []string{"GCSFUSE_IN_BACKGROUND_MODE=true"}, + expectedForwardedEnvVars: []string{"GCSFUSE_IN_BACKGROUND_MODE=true"}, + unexpectedForwardedEnvVarNames: []string{"GRPC_GO_LOG_VERBOSITY_LEVEL", "GRPC_GO_LOG_SEVERITY_LEVEL", "GCE_METADATA_HOST", "GCE_METADATA_IP", "GCE_METADATA_ROOT", "http_proxy", "https_proxy", "no_proxy", "GOOGLE_APPLICATION_CREDENTIALS"}, + }, { + inputEnvVars: map[string]string{"GRPC_GO_LOG_VERBOSITY_LEVEL": "99", "GRPC_GO_LOG_SEVERITY_LEVEL": "INFO"}, + expectedForwardedEnvVars: []string{"GRPC_GO_LOG_VERBOSITY_LEVEL=99", "GRPC_GO_LOG_SEVERITY_LEVEL=INFO"}, }, } { for envvar, envval := range input.inputEnvVars { os.Setenv(envvar, envval) } + forwardedEnvVars := forwardedEnvVars() + assert.Subset(t.T(), forwardedEnvVars, input.expectedForwardedEnvVars) + for _, forwardedEnvVar := range forwardedEnvVars { + forwardedEnvVarName, _, ok := strings.Cut(forwardedEnvVar, "=") + assert.True(t.T(), ok) + assert.NotContains(t.T(), input.unexpectedForwardedEnvVarNames, forwardedEnvVarName) + } assert.Contains(t.T(), forwardedEnvVars, fmt.Sprintf("PATH=%s", os.Getenv("PATH"))) for envvar := range input.inputEnvVars { os.Unsetenv(envvar) From a1f8a64e0e23583ec52ea15193202868b01b4118 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 15 Jul 2025 14:11:09 +0530 Subject: [PATCH 0561/1298] Fix issue with formatting (#3520) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e349a14730..ba452cf41d 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ generate: go generate ./... imports: generate - goimports -w . + fmt: imports go mod tidy && go fmt ./... From fc515bdb3416850c663a2afc1b1e5271d7f32df6 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 15 Jul 2025 17:04:04 +0530 Subject: [PATCH 0562/1298] test(metrics): Add unit-tests for metrics. (#3521) * Add tests for metrics. --- common/otel_metrics.go | 2 +- common/otel_metrics_test.go | 877 ++++++++++++++++++++++++++++++++++++ 2 files changed, 878 insertions(+), 1 deletion(-) create mode 100644 common/otel_metrics_test.go diff --git a/common/otel_metrics.go b/common/otel_metrics.go index 97d724fd67..1b2bd0afd8 100644 --- a/common/otel_metrics.go +++ b/common/otel_metrics.go @@ -188,7 +188,7 @@ func (o *otelMetrics) FileCacheReadLatency(ctx context.Context, latency time.Dur o.fileCacheReadLatency.Record(ctx, latency.Microseconds(), cacheHitAttrOption(cacheHit)) } -func NewOTelMetrics() (MetricHandle, error) { +func NewOTelMetrics() (*otelMetrics, error) { fsOpsMeter := otel.Meter("fs_op") gcsMeter := otel.Meter("gcs") fileCacheMeter := otel.Meter("file_cache") diff --git a/common/otel_metrics_test.go b/common/otel_metrics_test.go new file mode 100644 index 0000000000..585713abf1 --- /dev/null +++ b/common/otel_metrics_test.go @@ -0,0 +1,877 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" +) + +func waitForMetricsProcessing() { + // Currently the metrics processing is synchronous, so there is no waiting. + // This will change as we will move to asynchronous metrics. +} + +func setupOTel(t *testing.T) (*otelMetrics, *metric.ManualReader) { + t.Helper() + reader := metric.NewManualReader() + provider := metric.NewMeterProvider(metric.WithReader(reader)) + otel.SetMeterProvider(provider) + + m, err := NewOTelMetrics() + require.NoError(t, err) + return m, reader +} + +// gatherHistogramMetrics collects all histogram metrics from the reader. +// It returns a map where the key is the metric name, and the value is another map. +// The inner map's key is the set of attributes, +// and the value is the HistogramDataPoint. +func gatherHistogramMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[attribute.Set]metricdata.HistogramDataPoint[int64] { + t.Helper() + var rm metricdata.ResourceMetrics + err := rd.Collect(ctx, &rm) + require.NoError(t, err) + + results := make(map[string]map[attribute.Set]metricdata.HistogramDataPoint[int64]) + + for _, sm := range rm.ScopeMetrics { + for _, m := range sm.Metrics { + // We are interested in Histogram[int64]. + hist, ok := m.Data.(metricdata.Histogram[int64]) + if !ok { + continue + } + + metricMap := make(map[attribute.Set]metricdata.HistogramDataPoint[int64]) + for _, dp := range hist.DataPoints { + if dp.Count == 0 { + continue + } + + metricMap[dp.Attributes] = dp + } + + if len(metricMap) > 0 { + results[m.Name] = metricMap + } + } + } + + return results +} + +// gatherNonZeroCounterMetrics collects all non-zero counter metrics from the reader. +// It returns a map where the key is the metric name, and the value is another map. +// The inner map's key is the set of attributes, +// and the value is the counter's value. +func gatherNonZeroCounterMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[attribute.Set]int64 { + t.Helper() + var rm metricdata.ResourceMetrics + err := rd.Collect(ctx, &rm) + require.NoError(t, err) + + results := make(map[string]map[attribute.Set]int64) + + for _, sm := range rm.ScopeMetrics { + for _, m := range sm.Metrics { + // We are interested in Sum[int64] which corresponds to int_counter. + sum, ok := m.Data.(metricdata.Sum[int64]) + if !ok { + continue + } + + metricMap := make(map[attribute.Set]int64) + for _, dp := range sum.DataPoints { + if dp.Value == 0 { + continue + } + + // The attribute.Set can be used as a map key directly. + metricMap[dp.Attributes] = dp.Value + } + + if len(metricMap) > 0 { + results[m.Name] = metricMap + } + } + } + + return results +} + +func TestFsOpsCount(t *testing.T) { + fsOps := []string{ + "StatFS", "LookUpInode", "GetInodeAttributes", "SetInodeAttributes", "ForgetInode", + "BatchForget", "MkDir", "MkNode", "CreateFile", "CreateLink", "CreateSymlink", + "Rename", "RmDir", "Unlink", "OpenDir", "ReadDir", "ReleaseDirHandle", + "OpenFile", "ReadFile", "WriteFile", "SyncFile", "FlushFile", "ReleaseFileHandle", + "ReadSymlink", "RemoveXattr", "GetXattr", "ListXattr", "SetXattr", "Fallocate", "SyncFS", + } + + for _, op := range fsOps { + t.Run(op, func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + m.OpsCount(context.TODO(), 3, op) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + opsCount, ok := metrics["fs/ops_count"] + expected := map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", op)): 3, + } + assert.True(t, ok, "fs/ops_count metric not found") + assert.Equal(t, expected, opsCount) + }) + } + +} + +func TestMultipleFSOpsSummed(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + m.OpsCount(context.TODO(), 5, "BatchForget") + m.OpsCount(context.TODO(), 2, "CreateFile") + m.OpsCount(context.TODO(), 3, "BatchForget") + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + opsCount, ok := metrics["fs/ops_count"] + assert.True(t, ok, "fs/ops_count metric not found") + expected := map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "BatchForget")): 8, + attribute.NewSet(attribute.String("fs_op", "CreateFile")): 2, + } + assert.Equal(t, expected, opsCount) + +} + +func TestFsOpsErrorCount(t *testing.T) { + fsOps := []string{ + "StatFS", "LookUpInode", "GetInodeAttributes", "SetInodeAttributes", "ForgetInode", + "BatchForget", "MkDir", "MkNode", "CreateFile", "CreateLink", "CreateSymlink", + "Rename", "RmDir", "Unlink", "OpenDir", "ReadDir", "ReleaseDirHandle", + "OpenFile", "ReadFile", "WriteFile", "SyncFile", "FlushFile", "ReleaseFileHandle", + "ReadSymlink", "RemoveXattr", "GetXattr", "ListXattr", "SetXattr", "Fallocate", "SyncFS", + } + fsErrorCategories := []string{ + "DEVICE_ERROR", "DIR_NOT_EMPTY", "FILE_EXISTS", "FILE_DIR_ERROR", "NOT_IMPLEMENTED", + "IO_ERROR", "INTERRUPT_ERROR", "INVALID_ARGUMENT", "INVALID_OPERATION", "MISC_ERROR", + "NETWORK_ERROR", "NO_FILE_OR_DIR", "NOT_A_DIR", "PERM_ERROR", + "PROCESS_RESOURCE_MGMT_ERROR", "TOO_MANY_OPEN_FILES", + } + + for _, op := range fsOps { + for _, category := range fsErrorCategories { + op, category := op, category + t.Run(fmt.Sprintf("%s_%s", op, category), func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + m.OpsErrorCount(context.TODO(), 5, FSOpsErrorCategory{ErrorCategory: category, FSOps: op}) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + opsErrorCount, ok := metrics["fs/ops_error_count"] + require.True(t, ok, "fs/ops_error_count metric not found") + expectedKey := attribute.NewSet( + attribute.String("fs_error_category", category), + attribute.String("fs_op", op)) + expected := map[attribute.Set]int64{ + expectedKey: 5, + } + assert.Equal(t, expected, opsErrorCount) + }) + } + } + +} + +func TestFsOpsErrorCountSummed(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + m.OpsErrorCount(context.TODO(), 5, FSOpsErrorCategory{ErrorCategory: "IO_ERROR", FSOps: "ReadFile"}) + m.OpsErrorCount(context.TODO(), 3, FSOpsErrorCategory{ErrorCategory: "IO_ERROR", FSOps: "ReadFile"}) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + opsErrorCount, ok := metrics["fs/ops_error_count"] + assert.True(t, ok, "fs/ops_error_count metric not found") + expectedKey := attribute.NewSet( + attribute.String("fs_error_category", "IO_ERROR"), + attribute.String("fs_op", "ReadFile"), + ) + assert.Equal(t, map[attribute.Set]int64{expectedKey: 8}, opsErrorCount) +} + +func TestFsOpsErrorCountDifferentErrors(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + m.OpsErrorCount(context.TODO(), 5, FSOpsErrorCategory{ErrorCategory: "IO_ERROR", FSOps: "ReadFile"}) + m.OpsErrorCount(context.TODO(), 2, FSOpsErrorCategory{ErrorCategory: "NETWORK_ERROR", FSOps: "WriteFile"}) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + opsErrorCount, ok := metrics["fs/ops_error_count"] + assert.True(t, ok, "fs/ops_error_count metric not found") + expected := map[attribute.Set]int64{ + attribute.NewSet( + attribute.String("fs_error_category", "IO_ERROR"), + attribute.String("fs_op", "ReadFile"), + ): 5, + attribute.NewSet( + attribute.String("fs_error_category", "NETWORK_ERROR"), + attribute.String("fs_op", "WriteFile"), + ): 2, + } + assert.Equal(t, expected, opsErrorCount) +} + +func TestFsOpsErrorCountDifferentErrorsSummed(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + m.OpsErrorCount(context.TODO(), 5, FSOpsErrorCategory{ErrorCategory: "IO_ERROR", FSOps: "ReadFile"}) + m.OpsErrorCount(context.TODO(), 2, FSOpsErrorCategory{ErrorCategory: "NETWORK_ERROR", FSOps: "WriteFile"}) + m.OpsErrorCount(context.TODO(), 3, FSOpsErrorCategory{ErrorCategory: "IO_ERROR", FSOps: "ReadFile"}) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + opsErrorCount, ok := metrics["fs/ops_error_count"] + assert.True(t, ok, "fs/ops_error_count metric not found") + expected := map[attribute.Set]int64{ + attribute.NewSet( + attribute.String("fs_error_category", "IO_ERROR"), + attribute.String("fs_op", "ReadFile"), + ): 8, + attribute.NewSet( + attribute.String("fs_error_category", "NETWORK_ERROR"), + attribute.String("fs_op", "WriteFile"), + ): 2, + } + assert.Equal(t, expected, opsErrorCount) +} + +func TestFsOpsLatency(t *testing.T) { + fsOps := []string{ + "StatFS", "LookUpInode", "GetInodeAttributes", "SetInodeAttributes", "ForgetInode", + "BatchForget", "MkDir", "MkNode", "CreateFile", "CreateLink", "CreateSymlink", + "Rename", "RmDir", "Unlink", "OpenDir", "ReadDir", "ReleaseDirHandle", + "OpenFile", "ReadFile", "WriteFile", "SyncFile", "FlushFile", "ReleaseFileHandle", + "ReadSymlink", "RemoveXattr", "GetXattr", "ListXattr", "SetXattr", "Fallocate", "SyncFS", + } + + for _, op := range fsOps { + op := op + t.Run(op, func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + latency := 123 * time.Microsecond + + m.OpsLatency(ctx, latency, op) + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + opsLatency, ok := metrics["fs/ops_latency"] + require.True(t, ok, "fs/ops_latency metric not found") + expectedKey := attribute.NewSet(attribute.String("fs_op", op)) + dp, ok := opsLatency[expectedKey] + require.True(t, ok, "DataPoint not found for key: %s", expectedKey) + assert.Equal(t, uint64(1), dp.Count) + assert.Equal(t, latency.Microseconds(), dp.Sum) + }) + } +} + +func TestFsOpsLatencySummed(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + latency1 := 100 * time.Microsecond + latency2 := 200 * time.Microsecond + + m.OpsLatency(ctx, latency1, "ReadFile") + m.OpsLatency(ctx, latency2, "ReadFile") + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + opsLatency, ok := metrics["fs/ops_latency"] + require.True(t, ok, "fs/ops_latency metric not found") + dp, ok := opsLatency[attribute.NewSet(attribute.String("fs_op", "ReadFile"))] + require.True(t, ok, "DataPoint not found for key: fs_op=ReadFile") + assert.Equal(t, uint64(2), dp.Count) + assert.Equal(t, latency1.Microseconds()+latency2.Microseconds(), dp.Sum) +} + +func TestGcsReadCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "Sequential", + f: func(m *otelMetrics) { + m.GCSReadCount(context.TODO(), 5, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Sequential")): 5, + }, + }, + { + name: "Random", + f: func(m *otelMetrics) { + m.GCSReadCount(context.TODO(), 3, "Random") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Random")): 3, + }, + }, + { + name: "Parallel", + f: func(m *otelMetrics) { + m.GCSReadCount(context.TODO(), 2, "Parallel") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Parallel")): 2, + }, + }, + { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GCSReadCount(context.TODO(), 5, "Sequential") + m.GCSReadCount(context.TODO(), 2, "Random") + m.GCSReadCount(context.TODO(), 3, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Sequential")): 8, + attribute.NewSet(attribute.String("read_type", "Random")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + readCount, ok := metrics["gcs/read_count"] + assert.True(t, ok, "gcs/read_count metric not found") + assert.Equal(t, tc.expected, readCount) + }) + } +} + +func TestGcsDownloadBytesCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "Sequential", + f: func(m *otelMetrics) { + m.GCSDownloadBytesCount(context.TODO(), 500, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Sequential")): 500, + }, + }, + { + name: "Random", + f: func(m *otelMetrics) { + m.GCSDownloadBytesCount(context.TODO(), 300, "Random") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Random")): 300, + }, + }, + { + name: "Parallel", + f: func(m *otelMetrics) { + m.GCSDownloadBytesCount(context.TODO(), 200, "Parallel") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Parallel")): 200, + }, + }, + { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GCSDownloadBytesCount(context.TODO(), 500, "Sequential") + m.GCSDownloadBytesCount(context.TODO(), 200, "Random") + m.GCSDownloadBytesCount(context.TODO(), 300, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Sequential")): 800, + attribute.NewSet(attribute.String("read_type", "Random")): 200, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + downloadBytes, ok := metrics["gcs/download_bytes_count"] + assert.True(t, ok, "gcs/download_bytes_count metric not found") + assert.Equal(t, tc.expected, downloadBytes) + }) + } +} + +func TestGcsReadBytesCount(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + m.GCSReadBytesCount(context.TODO(), 1024) + m.GCSReadBytesCount(context.TODO(), 2048) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + readBytes, ok := metrics["gcs/read_bytes_count"] + require.True(t, ok, "gcs/read_bytes_count metric not found") + assert.Equal(t, map[attribute.Set]int64{attribute.NewSet(): 3072}, readBytes) +} + +func TestGcsReaderCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "opened", + f: func(m *otelMetrics) { + m.GCSReaderCount(context.TODO(), 5, "opened") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("io_method", "opened")): 5, + }, + }, + { + name: "closed", + f: func(m *otelMetrics) { + m.GCSReaderCount(context.TODO(), 3, "closed") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("io_method", "closed")): 3, + }, + }, + { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GCSReaderCount(context.TODO(), 5, "opened") + m.GCSReaderCount(context.TODO(), 2, "closed") + m.GCSReaderCount(context.TODO(), 3, "opened") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("io_method", "opened")): 8, + attribute.NewSet(attribute.String("io_method", "closed")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + readerCount, ok := metrics["gcs/reader_count"] + assert.True(t, ok, "gcs/reader_count metric not found") + assert.Equal(t, tc.expected, readerCount) + }) + } +} + +func TestGcsRequestCount(t *testing.T) { + gcsMethods := []string{ + "MultiRangeDownloader::Add", "ComposeObjects", "CreateFolder", "CreateObjectChunkWriter", + "DeleteFolder", "DeleteObject", "FinalizeUpload", "GetFolder", "ListObjects", + "MoveObject", "NewReader", "RenameFolder", "StatObject", "UpdateObject", + } + + for _, method := range gcsMethods { + method := method + t.Run(method, func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + m.GCSRequestCount(context.TODO(), 5, method) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + requestCount, ok := metrics["gcs/request_count"] + require.True(t, ok, "gcs/request_count metric not found") + expectedKey := attribute.NewSet(attribute.String("gcs_method", method)) + expected := map[attribute.Set]int64{ + expectedKey: 5, + } + assert.Equal(t, expected, requestCount) + }) + } +} + +func TestGcsRequestCountSummed(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + m.GCSRequestCount(context.TODO(), 5, "StatObject") + m.GCSRequestCount(context.TODO(), 3, "StatObject") + m.GCSRequestCount(context.TODO(), 2, "ListObjects") + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + requestCount, ok := metrics["gcs/request_count"] + assert.True(t, ok, "gcs/request_count metric not found") + assert.Equal(t, map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "StatObject")): 8, + attribute.NewSet(attribute.String("gcs_method", "ListObjects")): 2, + }, requestCount) +} + +func TestGcsRequestLatencies(t *testing.T) { + gcsMethods := []string{ + "MultiRangeDownloader::Add", "ComposeObjects", "CreateFolder", "CreateObjectChunkWriter", + "DeleteFolder", "DeleteObject", "FinalizeUpload", "GetFolder", "ListObjects", + "MoveObject", "NewReader", "RenameFolder", "StatObject", "UpdateObject", + } + + for _, method := range gcsMethods { + method := method + t.Run(method, func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + latency := 123 * time.Millisecond + + m.GCSRequestLatency(ctx, latency, method) + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + requestLatencies, ok := metrics["gcs/request_latencies"] + require.True(t, ok, "gcs/request_latencies metric not found") + expectedKey := attribute.NewSet(attribute.String("gcs_method", method)) + dp, ok := requestLatencies[expectedKey] + require.True(t, ok, "DataPoint not found for key: %s", expectedKey) + assert.Equal(t, uint64(1), dp.Count) + assert.Equal(t, latency.Milliseconds(), dp.Sum) + }) + } +} + +func TestGcsRequestLatenciesSummed(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + latency1 := 100 * time.Millisecond + latency2 := 200 * time.Millisecond + + m.GCSRequestLatency(ctx, latency1, "StatObject") + m.GCSRequestLatency(ctx, latency2, "StatObject") + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + requestLatencies, ok := metrics["gcs/request_latencies"] + require.True(t, ok, "gcs/request_latencies metric not found") + dp, ok := requestLatencies[attribute.NewSet(attribute.String("gcs_method", "StatObject"))] + require.True(t, ok, "DataPoint not found for key: gcs_method=StatObject") + assert.Equal(t, uint64(2), dp.Count) + assert.Equal(t, latency1.Milliseconds()+latency2.Milliseconds(), dp.Sum) +} + +func TestGcsRetryCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "STALLED_READ_REQUEST", + f: func(m *otelMetrics) { + m.GCSRetryCount(context.TODO(), 5, "STALLED_READ_REQUEST") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST")): 5, + }, + }, + { + name: "OTHER_ERRORS", + f: func(m *otelMetrics) { + m.GCSRetryCount(context.TODO(), 3, "OTHER_ERRORS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS")): 3, + }, + }, + { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GCSRetryCount(context.TODO(), 5, "STALLED_READ_REQUEST") + m.GCSRetryCount(context.TODO(), 2, "OTHER_ERRORS") + m.GCSRetryCount(context.TODO(), 3, "STALLED_READ_REQUEST") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST")): 8, + attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + retryCount, ok := metrics["gcs/retry_count"] + assert.True(t, ok, "gcs/retry_count metric not found") + assert.Equal(t, tc.expected, retryCount) + }) + } +} + +func TestFileCacheReadCountNew(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "cache_hit_true_sequential", + f: func(m *otelMetrics) { + m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: true, ReadType: "Sequential"}) + }, + expected: map[attribute.Set]int64{ + attribute.NewSet( + attribute.Bool("cache_hit", true), + attribute.String("read_type", "Sequential")): 5, + }, + }, + { + name: "cache_hit_true_random", + f: func(m *otelMetrics) { + m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: true, ReadType: "Random"}) + }, + expected: map[attribute.Set]int64{ + attribute.NewSet( + attribute.Bool("cache_hit", true), + attribute.String("read_type", "Random")): 5, + }, + }, + { + name: "cache_hit_true_parallel", + f: func(m *otelMetrics) { + m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: true, ReadType: "Parallel"}) + }, + expected: map[attribute.Set]int64{ + attribute.NewSet( + attribute.Bool("cache_hit", true), + attribute.String("read_type", "Parallel")): 5, + }, + }, + { + name: "cache_hit_false_sequential", + f: func(m *otelMetrics) { + m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: false, ReadType: "Sequential"}) + }, + expected: map[attribute.Set]int64{ + attribute.NewSet( + attribute.Bool("cache_hit", false), + attribute.String("read_type", "Sequential")): 5, + }, + }, + { + name: "cache_hit_false_random", + f: func(m *otelMetrics) { + m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: false, ReadType: "Random"}) + }, + expected: map[attribute.Set]int64{ + attribute.NewSet( + attribute.Bool("cache_hit", false), + attribute.String("read_type", "Random")): 5, + }, + }, + { + name: "cache_hit_false_parallel", + f: func(m *otelMetrics) { + m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: false, ReadType: "Parallel"}) + }, + expected: map[attribute.Set]int64{ + attribute.NewSet( + attribute.Bool("cache_hit", false), + attribute.String("read_type", "Parallel")): 5, + }, + }, + { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: true, ReadType: "Sequential"}) + m.FileCacheReadCount(context.TODO(), 2, CacheHitReadType{CacheHit: false, ReadType: "Random"}) + m.FileCacheReadCount(context.TODO(), 3, CacheHitReadType{CacheHit: true, ReadType: "Sequential"}) + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Random")): 2, + attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Sequential")): 8, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + readCount, ok := metrics["file_cache/read_count"] + assert.True(t, ok, "file_cache/read_count metric not found") + assert.Equal(t, tc.expected, readCount) + }) + } +} + +func TestFileCacheReadBytesCountNew(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "Sequential", + f: func(m *otelMetrics) { + m.FileCacheReadBytesCount(context.TODO(), 500, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Sequential")): 500, + }, + }, + { + name: "Random", + f: func(m *otelMetrics) { + m.FileCacheReadBytesCount(context.TODO(), 300, "Random") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Random")): 300, + }, + }, + { + name: "Parallel", + f: func(m *otelMetrics) { + m.FileCacheReadBytesCount(context.TODO(), 200, "Parallel") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Parallel")): 200, + }, + }, + { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.FileCacheReadBytesCount(context.TODO(), 500, "Sequential") + m.FileCacheReadBytesCount(context.TODO(), 200, "Random") + m.FileCacheReadBytesCount(context.TODO(), 300, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Random")): 200, + attribute.NewSet(attribute.String("read_type", "Sequential")): 800, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + readBytesCount, ok := metrics["file_cache/read_bytes_count"] + assert.True(t, ok, "file_cache/read_bytes_count metric not found") + assert.Equal(t, tc.expected, readBytesCount) + }) + } +} + +func TestFileCacheReadLatencies(t *testing.T) { + tests := []struct { + name string + cacheHit bool + latencies []time.Duration + }{ + { + name: "cache_hit_true", + cacheHit: true, + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + }, + { + name: "cache_hit_false", + cacheHit: false, + latencies: []time.Duration{300 * time.Microsecond, 400 * time.Microsecond}, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + m, rd := setupOTel(t) + var totalLatency time.Duration + + for _, latency := range tc.latencies { + m.FileCacheReadLatency(ctx, latency, tc.cacheHit) + totalLatency += latency + } + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + readLatencies, ok := metrics["file_cache/read_latencies"] + require.True(t, ok, "file_cache/read_latencies metric not found") + expectedKey := attribute.NewSet(attribute.Bool("cache_hit", tc.cacheHit)) + dp, ok := readLatencies[expectedKey] + require.True(t, ok, "DataPoint not found for key: %s", expectedKey) + assert.Equal(t, uint64(len(tc.latencies)), dp.Count) + assert.Equal(t, totalLatency.Microseconds(), dp.Sum) + }) + } +} From 28ec6331e69dea6d93feaaf3ac63716560e748f7 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 15 Jul 2025 22:17:53 +0530 Subject: [PATCH 0563/1298] feat(buffered-read): passing error for failed state via notification channel (#3502) * feat(buffered-read): passing error for failed state via notification channel * review comments * minor review comments --- internal/block/block.go | 26 +++++++++++++------- internal/block/block_test.go | 27 +++++++++++---------- internal/bufferedread/download_task.go | 6 ++--- internal/bufferedread/download_task_test.go | 17 +++++++------ 4 files changed, 43 insertions(+), 33 deletions(-) diff --git a/internal/block/block.go b/internal/block/block.go index 9748ed0354..44dd5a4bcf 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -22,14 +22,22 @@ import ( "syscall" ) -// BlockStatus represents the status of the block. -type BlockStatus int +// BlockStatus represents the status of a block. +// It contains the state of the block and an error +// that may have occurred during the block's operation. +type BlockStatus struct { + State BlockState + Err error +} + +// BlockState represents the state of the block. +type BlockState int const ( - BlockStatusInProgress BlockStatus = iota // Download of this block is in progress - BlockStatusDownloaded // Download of this block is complete - BlockStatusDownloadFailed // Download of this block has failed - BlockStatusDownloadCancelled // Download of this block has been cancelled + BlockStateInProgress BlockState = iota // Download of this block is in progress + BlockStateDownloaded // Download of this block is complete + BlockStateDownloadFailed // Download of this block has failed + BlockStateDownloadCancelled // Download of this block has been cancelled ) // Block represents the buffer which holds the data. @@ -106,7 +114,7 @@ func (m *memoryBlock) Reuse() { m.offset.end = 0 m.offset.start = 0 m.notification = make(chan BlockStatus, 1) - m.status = BlockStatusInProgress + m.status = BlockStatus{State: BlockStateInProgress} m.absStartOff = -1 } @@ -163,7 +171,7 @@ func createBlock(blockSize int64) (Block, error) { buffer: addr, offset: offset{0, 0}, notification: make(chan BlockStatus, 1), - status: BlockStatusInProgress, + status: BlockStatus{State: BlockStateInProgress}, absStartOff: -1, } return &mb, nil @@ -222,7 +230,7 @@ func (m *memoryBlock) AwaitReady(ctx context.Context) (BlockStatus, error) { return m.status, nil case <-ctx.Done(): - return 0, ctx.Err() + return BlockStatus{State: BlockStateInProgress}, ctx.Err() } } diff --git a/internal/block/block_test.go b/internal/block/block_test.go index 881e0361e9..e7577afe54 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -16,6 +16,7 @@ package block import ( "context" + "fmt" "io" "sync" "testing" @@ -314,18 +315,18 @@ func (testSuite *MemoryBlockTest) TestAwaitReadyNotifyVariants() { }{ { name: "AfterNotifySuccess", - notifyStatus: BlockStatusDownloaded, - wantStatus: BlockStatusDownloaded, + notifyStatus: BlockStatus{State: BlockStateDownloaded}, + wantStatus: BlockStatus{State: BlockStateDownloaded}, }, { name: "AfterNotifyError", - notifyStatus: BlockStatusDownloadFailed, - wantStatus: BlockStatusDownloadFailed, + notifyStatus: BlockStatus{State: BlockStateDownloadFailed, Err: fmt.Errorf("download failed")}, + wantStatus: BlockStatus{State: BlockStateDownloadFailed, Err: fmt.Errorf("download failed")}, }, { name: "AfterNotifyCancelled", - notifyStatus: BlockStatusDownloadCancelled, - wantStatus: BlockStatusDownloadCancelled, + notifyStatus: BlockStatus{State: BlockStateDownloadCancelled}, + wantStatus: BlockStatus{State: BlockStateDownloadCancelled}, }, } @@ -350,10 +351,10 @@ func (testSuite *MemoryBlockTest) TestTwoNotifyReadyWithoutAwaitReady() { mb, err := createBlock(12) require.Nil(testSuite.T(), err) - mb.NotifyReady(BlockStatusDownloaded) + mb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) // 2nd notify will lead to panic since it is not allowed to notify a block more than once. assert.Panics(testSuite.T(), func() { - mb.NotifyReady(BlockStatusDownloaded) + mb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) }) } @@ -363,15 +364,15 @@ func (testSuite *MemoryBlockTest) TestNotifyReadyAfterAwaitReady() { ctx, cancel := context.WithTimeout(testSuite.T().Context(), 100*time.Millisecond) defer cancel() go func() { - mb.NotifyReady(BlockStatusDownloaded) + mb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) }() status, err := mb.AwaitReady(ctx) require.Nil(testSuite.T(), err) - assert.Equal(testSuite.T(), BlockStatusDownloaded, status) + assert.Equal(testSuite.T(), BlockStatus{State: BlockStateDownloaded}, status) // 2nd notify will lead to panic since channel is closed after first await ready. assert.Panics(testSuite.T(), func() { - mb.NotifyReady(BlockStatusDownloaded) + mb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) }) } @@ -379,7 +380,7 @@ func (testSuite *MemoryBlockTest) TestSingleNotifyAndMultipleAwaitReady() { mb, err := createBlock(12) require.Nil(testSuite.T(), err) go func() { - mb.NotifyReady(BlockStatusDownloaded) + mb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) }() ctx, cancel := context.WithTimeout(testSuite.T().Context(), 5*time.Millisecond) defer cancel() @@ -395,7 +396,7 @@ func (testSuite *MemoryBlockTest) TestSingleNotifyAndMultipleAwaitReady() { status, err := mb.AwaitReady(ctx) require.Nil(testSuite.T(), err) - assert.Equal(testSuite.T(), BlockStatusDownloaded, status) + assert.Equal(testSuite.T(), BlockStatus{State: BlockStateDownloaded}, status) }() } wg.Wait() diff --git a/internal/bufferedread/download_task.go b/internal/bufferedread/download_task.go index 056cdbd092..223a378def 100644 --- a/internal/bufferedread/download_task.go +++ b/internal/bufferedread/download_task.go @@ -68,13 +68,13 @@ func (p *DownloadTask) Execute() { defer func() { if err == nil { logger.Tracef("Download: -> block (%s, %v) completed in: %v.", p.object.Name, blockId, time.Since(stime)) - p.block.NotifyReady(block.BlockStatusDownloaded) + p.block.NotifyReady(block.BlockStatus{State: block.BlockStateDownloaded}) } else if errors.Is(err, context.Canceled) && p.ctx.Err() == context.Canceled { logger.Tracef("Download: -> block (%s, %v) cancelled: %v.", p.object.Name, blockId, err) - p.block.NotifyReady(block.BlockStatusDownloadCancelled) + p.block.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadCancelled}) } else { logger.Errorf("Download: -> block (%s, %v) failed: %v.", p.object.Name, blockId, err) - p.block.NotifyReady(block.BlockStatusDownloadFailed) + p.block.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadFailed, Err: err}) } }() diff --git a/internal/bufferedread/download_task_test.go b/internal/bufferedread/download_task_test.go index 1d3873d032..6485522c32 100644 --- a/internal/bufferedread/download_task_test.go +++ b/internal/bufferedread/download_task_test.go @@ -96,7 +96,7 @@ func (dts *DownloadTaskTestSuite) TestExecuteSuccess() { ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) defer cancelFunc() status, err := downloadBlock.AwaitReady(ctx) - assert.Equal(dts.T(), block.BlockStatusDownloaded, status) + assert.Equal(dts.T(), block.BlockStatus{State: block.BlockStateDownloaded}, status) assert.NoError(dts.T(), err) } @@ -114,17 +114,16 @@ func (dts *DownloadTaskTestSuite) TestExecuteError() { Limit: uint64(testBlockSize), }, } - expectedError := errors.New("read error") - dts.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(nil, expectedError).Times(1) + dts.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(nil, errors.New("read error")).Times(1) task.Execute() - assert.Error(dts.T(), expectedError) dts.mockBucket.AssertExpectations(dts.T()) ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) defer cancelFunc() status, err := downloadBlock.AwaitReady(ctx) - assert.Equal(dts.T(), block.BlockStatusDownloadFailed, status) + assert.Equal(dts.T(), block.BlockStateDownloadFailed, status.State) + assert.NotNil(dts.T(), status.Err) assert.NoError(dts.T(), err) } @@ -153,8 +152,9 @@ func (dts *DownloadTaskTestSuite) TestExecuteContextDeadlineExceededByServerTrea ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) defer cancelFunc() status, err := downloadBlock.AwaitReady(ctx) - assert.Equal(dts.T(), block.BlockStatusDownloadFailed, status) assert.NoError(dts.T(), err) + assert.Equal(dts.T(), block.BlockStateDownloadFailed, status.State) + assert.NotNil(dts.T(), status.Err) } func (dts *DownloadTaskTestSuite) TestExecuteContextCancelledWhileReaderCreation() { @@ -183,8 +183,9 @@ func (dts *DownloadTaskTestSuite) TestExecuteContextCancelledWhileReaderCreation ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) defer cancelFunc() status, err := downloadBlock.AwaitReady(ctx) - assert.Equal(dts.T(), block.BlockStatusDownloadCancelled, status) assert.NoError(dts.T(), err) + assert.Equal(dts.T(), block.BlockStateDownloadCancelled, status.State) + assert.NoError(dts.T(), status.Err) } // ctxCancelledReader is a mock reader that simulates a context cancellation error while reading. @@ -227,6 +228,6 @@ func (dts *DownloadTaskTestSuite) TestExecuteContextCancelledWhileReadingFromRea ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) defer cancelFunc() status, err := downloadBlock.AwaitReady(ctx) - assert.Equal(dts.T(), block.BlockStatusDownloadCancelled, status) + assert.Equal(dts.T(), block.BlockStatus{State: block.BlockStateDownloadCancelled}, status) assert.NoError(dts.T(), err) } From 39d54db7b11073d281763e3577305b172c1f0c9f Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Wed, 16 Jul 2025 05:46:26 +0000 Subject: [PATCH 0564/1298] Integrate readdirplus with flag (#3527) --- cmd/mount.go | 3 +++ cmd/mount_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/cmd/mount.go b/cmd/mount.go index 3192becb3f..67ea2307a7 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -172,6 +172,9 @@ func getFuseMountConfig(fsName string, newConfig *cfg.Config) *fuse.MountConfig EnableParallelDirOps: !(newConfig.FileSystem.DisableParallelDirops), // We disable write-back cache when streaming writes are enabled. DisableWritebackCaching: newConfig.Write.EnableStreamingWrites, + // Enables ReadDirPlus, allowing the kernel to retrieve directory entries and their + // attributes in a single operation. + EnableReaddirplus: newConfig.FileSystem.ExperimentalEnableReaddirplus, } // GCSFuse to Jacobsa Fuse Log Level mapping: diff --git a/cmd/mount_test.go b/cmd/mount_test.go index d993501190..ac74b6bb97 100644 --- a/cmd/mount_test.go +++ b/cmd/mount_test.go @@ -114,3 +114,37 @@ func TestGetFuseMountConfig_LoggerInitializationInFuse(t *testing.T) { assert.Equal(t, tc.shouldInitializeTrace, fuseMountCfg.DebugLogger != nil) } } + +func TestGetFuseMountConfig_EnableReaddirplus(t *testing.T) { + testCases := []struct { + name string + enableReaddirplus bool + expectedValue bool + }{ + { + name: "ExperimentalEnableReaddirplusFlagFalse", + enableReaddirplus: false, + expectedValue: false, + }, + { + name: "ExperimentalEnableReaddirplusFlagTrue", + enableReaddirplus: true, + expectedValue: true, + }, + } + + fsName := "mybucket" + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + newConfig := &cfg.Config{ + FileSystem: cfg.FileSystemConfig{ + ExperimentalEnableReaddirplus: tc.enableReaddirplus, + }, + } + + fuseMountCfg := getFuseMountConfig(fsName, newConfig) + + assert.Equal(t, tc.expectedValue, fuseMountCfg.EnableReaddirplus) + }) + } +} From 905d463aa6e4275455f0246b3dbdd17a7623bd6f Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:47:40 +0530 Subject: [PATCH 0565/1298] fix kokoro failure (#3524) --- .../gcp_ubuntu/micro_benchmarks/build.sh | 9 ++-- perfmetrics/scripts/upgrade_python3.sh | 50 ++++++------------- 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh index f7007f9441..d76ce8dfab 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh @@ -25,13 +25,16 @@ echo "Building and installing gcsfuse" commitId=$(git log --before='yesterday 23:59:59' --max-count=1 --pretty=%H) ./perfmetrics/scripts/build_and_install_gcsfuse.sh $commitId -echo "Upgrade python3 version" +echo "Upgrading Python3 version" ./perfmetrics/scripts/upgrade_python3.sh +# Path to locally installed upgraded Python +PYTHON_BIN="$HOME/.local/python-3.11.9/bin/python3.11" + cd "./perfmetrics/scripts/micro_benchmarks" -echo "Installing dependencies..." -python3 -m venv venv +echo "Installing dependencies using upgraded Python..." +"$PYTHON_BIN" -m venv venv source venv/bin/activate pip install -r requirements.txt diff --git a/perfmetrics/scripts/upgrade_python3.sh b/perfmetrics/scripts/upgrade_python3.sh index ab02fcd108..8c6f9359f3 100755 --- a/perfmetrics/scripts/upgrade_python3.sh +++ b/perfmetrics/scripts/upgrade_python3.sh @@ -12,52 +12,34 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - #!/bin/bash set -e PYTHON_VERSION=3.11.9 -INSTALL_PREFIX="/usr/local" +INSTALL_PREFIX="$HOME/.local/python-$PYTHON_VERSION" -# Install dependencies silently -sudo apt update > /dev/null -sudo apt install -y \ +echo "Installing dependencies for building Python..." +sudo apt-get update -y > /dev/null +sudo apt-get install -y \ build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev \ libssl-dev libreadline-dev libffi-dev curl libsqlite3-dev \ libbz2-dev liblzma-dev tk-dev uuid-dev wget > /dev/null -# Download Python source silently -cd /usr/src -sudo wget -q https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz -sudo tar -xf Python-${PYTHON_VERSION}.tgz > /dev/null +# Download and build Python locally +cd /tmp +wget -q https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz +tar -xf Python-${PYTHON_VERSION}.tgz cd Python-${PYTHON_VERSION} -# Configure silently -sudo ./configure --enable-optimizations --prefix=$INSTALL_PREFIX > /dev/null - -# Build silently -sudo make -j"$(nproc)" > /dev/null - -# Install silently -sudo make altinstall > /dev/null - -# Install pip silently -sudo $INSTALL_PREFIX/bin/python3.11 -m ensurepip > /dev/null -sudo $INSTALL_PREFIX/bin/python3.11 -m pip install --upgrade pip > /dev/null - -# Remove old alternatives silently -sudo update-alternatives --remove-all python3 > /dev/null 2>&1 || true -sudo update-alternatives --remove-all pip3 > /dev/null 2>&1 || true +echo "Configuring Python build for local install..." +./configure --enable-optimizations --prefix="$INSTALL_PREFIX" > /dev/null -# Register Python 3.11 as alternative silently -sudo update-alternatives --install /usr/bin/python3 python3 $INSTALL_PREFIX/bin/python3.11 1 > /dev/null -sudo update-alternatives --install /usr/bin/pip3 pip3 $INSTALL_PREFIX/bin/pip3.11 1 > /dev/null +echo "Building Python $PYTHON_VERSION..." +make -j"$(nproc)" > /dev/null -# Set Python 3.11 as default silently -sudo update-alternatives --set python3 $INSTALL_PREFIX/bin/python3.11 > /dev/null -sudo update-alternatives --set pip3 $INSTALL_PREFIX/bin/pip3.11 > /dev/null +echo "Installing Python $PYTHON_VERSION locally at $INSTALL_PREFIX..." +make altinstall > /dev/null -# Final version check (visible) -python3 --version -pip3 --version +echo "Python $PYTHON_VERSION installed at $INSTALL_PREFIX/bin/python3.11" +"$INSTALL_PREFIX/bin/python3.11" --version From 3cd31a64a06947daf003e4b4d2d8ff87e0e0f7ad Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 16 Jul 2025 12:06:40 +0530 Subject: [PATCH 0566/1298] feat(Random Reader Refactoring): Enable new reader implementation by default (#3402) * enable the flag * enable composite tests * test perf against zonal bucket * remove test changes * move increasing seek count after existing reader read fails * rebase * add err equals nil in recent changes --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/root_test.go | 9 +++++++-- internal/fs/fs_test.go | 1 + internal/gcsx/file_cache_reader.go | 1 + 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 3ee22861c3..77ce2e9a92 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -391,7 +391,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.BoolP("enable-new-reader", "", false, "Enables support for new reader implementation.") + flagSet.BoolP("enable-new-reader", "", true, "Enables support for new reader implementation.") if err := flagSet.MarkHidden("enable-new-reader"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index f14ebd9b75..2c9143cab0 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -103,7 +103,7 @@ flag-name: "enable-new-reader" type: "bool" usage: "Enables support for new reader implementation." - default: false + default: true hide-flag: true - config-path: "file-cache.cache-file-for-range-read" diff --git a/cmd/root_test.go b/cmd/root_test.go index 59ab5f3f5e..7869a07f30 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1092,10 +1092,15 @@ func TestArgsParsing_EnableNewReaderFlag(t *testing.T) { expectedEnableNewReader bool }{ { - name: "normal", - args: []string{"gcsfuse", "--enable-new-reader=true", "abc", "pqr"}, + name: "default", + args: []string{"gcsfuse", "abc", "pqr"}, expectedEnableNewReader: true, }, + { + name: "normal", + args: []string{"gcsfuse", "--enable-new-reader=false", "abc", "pqr"}, + expectedEnableNewReader: false, + }, } for _, tc := range tests { diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index c88cd499bd..5312d48a91 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -166,6 +166,7 @@ func (t *fsTest) SetUpTestSuite() { FileSystem: cfg.FileSystemConfig{ PreconditionErrors: false, }, + EnableNewReader: true, } } t.serverCfg.MetricHandle = common.NewNoopMetrics() diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index 11c5bf3499..9c372044b7 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -142,6 +142,7 @@ func (fc *FileCacheReader) tryReadingFromFileCache(ctx context.Context, p []byte err = nil return 0, false, nil case errors.Is(err, cacheUtil.ErrFileExcludedFromCacheByRegex): + err = nil return 0, false, nil default: err = fmt.Errorf("tryReadingFromFileCache: GetCacheHandle failed: %w", err) From 73b39339d5b9c95063529dcb6e8085e2bb6fadee Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Wed, 16 Jul 2025 07:55:35 +0000 Subject: [PATCH 0567/1298] feat(Readdirplus): Implement ReadDirPlus operation (#3510) * Implement ReadDirPlus --- internal/fs/fs.go | 164 +++++++++++++++++++++++++++- internal/fs/read_dir_plus_test.go | 176 ++++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+), 5 deletions(-) create mode 100644 internal/fs/read_dir_plus_test.go diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 9e190dd222..e5d0f8f75c 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -28,6 +28,8 @@ import ( "syscall" "time" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "golang.org/x/sync/semaphore" "github.com/googlecloudplatform/gcsfuse/v3/cfg" @@ -1439,6 +1441,117 @@ func (fs *fileSystem) invalidateChildFileCacheIfExist(parentInode inode.DirInode return nil } +// coreToDirentPlus creates a fuseutil.DirentPlus entry from an inode core. +// LOCKS_EXCLUDED(fs.mu) +func (fs *fileSystem) coreToDirentPlus( + ctx context.Context, + fullName inode.Name, + core inode.Core) (entry *fuseutil.DirentPlus, err error) { + + // Look up or create the inode for the core. + child := fs.lookUpOrCreateInodeIfNotStale(core) + if child == nil { + return nil, fmt.Errorf("coreToDirentPlus: stale record for %s", path.Base(fullName.LocalName())) + } + defer child.Unlock() + + // Extract the child's attributes. + attributes, err := child.Attributes(ctx, false) + if err != nil { + // The inode is valid, but we couldn't get attributes. + return nil, fmt.Errorf("coreToDirentPlus: unable to fetch attributes for %s: %w", path.Base(fullName.LocalName()), err) + } + + expiration := time.Now().Add(fs.inodeAttributeCacheTTL) + entry = &fuseutil.DirentPlus{ + Dirent: fuseutil.Dirent{ + Name: path.Base(fullName.LocalName()), + Type: fuseutil.DT_Unknown, + Inode: child.ID(), + }, + Entry: fuseops.ChildInodeEntry{ + Child: child.ID(), + Attributes: attributes, + AttributesExpiration: expiration, + }, + } + + // Set the directory entry type based on the core type. + switch core.Type() { + case metadata.SymlinkType: + entry.Dirent.Type = fuseutil.DT_Link + case metadata.RegularFileType: + entry.Dirent.Type = fuseutil.DT_File + case metadata.ImplicitDirType, metadata.ExplicitDirType: + entry.Dirent.Type = fuseutil.DT_Directory + } + + return entry, nil +} + +// LocalFileEntries lists the local files (file that is not yet present on GCS) present in the directory. +// For each entry, only the Dirent field is populated; the ChildInodeEntry field is not set. +// LOCKS_REQUIRED(fs.mu) +func (fs *fileSystem) localFileEntriesPlus(parent inode.Name) (localEntriesPlus map[string]fuseutil.DirentPlus) { + localEntriesPlus = make(map[string]fuseutil.DirentPlus) + + for localInodeName, in := range fs.localFileInodes { + // It is possible that the local file inode has been unlinked, but + // still present in localFileInodes map because of open file handle. + // So, if the inode has been unlinked, skip the entry. + file, ok := in.(*inode.FileInode) + if ok && file.IsUnlinked() { + continue + } + if localInodeName.IsDirectChildOf(parent) { + entry := fuseutil.DirentPlus{ + Dirent: fuseutil.Dirent{ + Name: path.Base(localInodeName.LocalName()), + Type: fuseutil.DT_File, + Inode: in.ID(), + }, + } + localEntriesPlus[entry.Dirent.Name] = entry + } + } + return +} + +// lookupAndFetchAttributesForLocalFileEntriesPlus performs a lookup for each local file entry, +// fetches its attributes, and updates the corresponding DirentPlus.Entry field. +// LOCKS_EXCLUDED(fs.mu) +func (fs *fileSystem) lookupAndFetchAttributesForLocalFileEntriesPlus(parentName inode.DirInode, localFileEntriesPlus map[string]fuseutil.DirentPlus) (err error) { + for localEntryName, localEntryPlus := range localFileEntriesPlus { + // Lookup the child inode under the parent directory. + child, err := fs.lookUpLocalFileInode(parentName, localEntryName) + if err != nil { + return fmt.Errorf("lookupAndFetchAttributesForLocalFileEntriesPlus: while looking up local file %q: %w", localEntryName, err) + } + if child == nil { + // This indicates a potential race condition where the file was removed after being listed. + return fmt.Errorf("lookupAndFetchAttributesForLocalFileEntriesPlus: local file %q disappeared", localEntryName) + } + // Fetch attributes from the child inode. + attrs, err := child.Attributes(context.Background(), false) + if err != nil { + child.Unlock() + return fmt.Errorf("lookupAndFetchAttributesForLocalFileEntriesPlus: unable to fetch attributes for %s: %w", localEntryName, err) + } + // Unlock the inode after retrieving its attributes. + child.Unlock() + + expiration := time.Now().Add(fs.inodeAttributeCacheTTL) + childInodeEntry := fuseops.ChildInodeEntry{ + Child: child.ID(), + Attributes: attrs, + AttributesExpiration: expiration, + } + localEntryPlus.Entry = childInodeEntry + localFileEntriesPlus[localEntryName] = localEntryPlus + } + return +} + //////////////////////////////////////////////////////////////////////// // fuse.FileSystem methods //////////////////////////////////////////////////////////////////////// @@ -2508,11 +2621,52 @@ func (fs *fileSystem) ReadDir( } // LOCKS_EXCLUDED(fs.mu) -func (fs *fileSystem) ReadDirPlus( - ctx context.Context, - op *fuseops.ReadDirPlusOp) (err error) { - // TODO: Implement ReadDirPlus to fetch directory entries with attributes. - return syscall.ENOSYS +func (fs *fileSystem) ReadDirPlus(ctx context.Context, op *fuseops.ReadDirPlusOp) (err error) { + if fs.newConfig.FileSystem.IgnoreInterrupts { + // When ignore interrupts config is set, we are creating a new context not + // cancellable by parent context. + ctx = context.Background() + } + // Find the handle. + fs.mu.Lock() + dh := fs.handles[op.Handle].(*handle.DirHandle) + in := fs.dirInodeOrDie(op.Inode) + // Fetch local file entries beforehand for passing it to directory handle as + // we need fs lock to fetch local file entries. + localFileEntriesPlus := fs.localFileEntriesPlus(in.Name()) + // Unlock fs lock and fetch attributes for local file entries as it requires inode lock. + fs.mu.Unlock() + + err = fs.lookupAndFetchAttributesForLocalFileEntriesPlus(in, localFileEntriesPlus) + if err != nil { + return err + } + + dh.Mu.Lock() + defer dh.Mu.Unlock() + // Serve the request. + var cores map[inode.Name]*inode.Core + cores, err = dh.FetchEntryCores(ctx, op) + if err != nil { + return fmt.Errorf("FetchDirCores: %w", err) + } + // dh.mu lock is not required during iteration over cores, but was acquired earlier + // for code readability and to use a common defer unlock pattern. Holding the + // lock here has no performance overhead. + var entriesPlus []fuseutil.DirentPlus + for fullName, core := range cores { + entry, err := fs.coreToDirentPlus(ctx, fullName, *core) + if err != nil { + return err + } + entriesPlus = append(entriesPlus, *entry) + } + + if err := dh.ReadDirPlus(op, entriesPlus, localFileEntriesPlus); err != nil { + return err + } + + return nil } // LOCKS_EXCLUDED(fs.mu) diff --git a/internal/fs/read_dir_plus_test.go b/internal/fs/read_dir_plus_test.go new file mode 100644 index 0000000000..d659b7ebff --- /dev/null +++ b/internal/fs/read_dir_plus_test.go @@ -0,0 +1,176 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// A collection of tests to check Readdirplus operation. +package fs_test + +import ( + "os" + "path" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/jacobsa/fuse/fusetesting" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" +) + +//////////////////////////////////////////////////////////////////////// +// ReadDirPlusTest +//////////////////////////////////////////////////////////////////////// + +type ReadDirPlusTest struct { + suite.Suite + fsTest +} + +func TestReadDirPlusTestSuite(t *testing.T) { + suite.Run(t, new(ReadDirPlusTest)) +} + +func (t *ReadDirPlusTest) SetupSuite() { + t.mountCfg.EnableReaddirplus = true + t.serverCfg.ImplicitDirectories = true + t.fsTest.SetUpTestSuite() +} + +func (t *ReadDirPlusTest) TearDownTest() { + t.fsTest.TearDown() +} + +func (t *ReadDirPlusTest) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} + +func (t *ReadDirPlusTest) TestEmptyDirectory() { + entries, err := fusetesting.ReadDirPlusPicky(mntDir) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), 0, len(entries)) +} + +func (t *ReadDirPlusTest) TestDirectoryWithVariousEntryTypes() { + // Set up contents. + assert.Nil(t.T(), t.createObjects( + map[string]string{ + "file.txt": "taco", + "dir/": "", + "dir/baz": "burrito", + "implicit/file.txt": "content", + })) + // Set up a symlink. + err := os.Symlink("file.txt", path.Join(mntDir, "symlink_to_file")) + assert.Nil(t.T(), err) + expectedEntries := []string{ + "dir", + "file.txt", + "implicit", + "symlink_to_file", + } + + // Read the directory. + entries, err := fusetesting.ReadDirPlusPicky(mntDir) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), len(expectedEntries), len(entries)) + // Check entries. + for i, entry := range entries { + assert.Equal(t.T(), expectedEntries[i], entry.Name()) + switch entry.Name() { + case "file.txt": + assert.False(t.T(), entry.IsDir()) + assert.Equal(t.T(), int64(len("taco")), entry.Size()) + assert.Equal(t.T(), filePerms, entry.Mode()) + case "dir": + assert.True(t.T(), entry.IsDir()) + assert.Equal(t.T(), dirPerms|os.ModeDir, entry.Mode()) + case "implicit": + assert.True(t.T(), entry.IsDir()) + assert.Equal(t.T(), dirPerms|os.ModeDir, entry.Mode()) + case "symlink_to_file": + assert.False(t.T(), entry.IsDir()) + assert.Equal(t.T(), os.ModeSymlink, entry.Mode()&os.ModeType) + default: + assert.FailNow(t.T(), "unexpected entry: %s", entry.Name()) + } + } +} + +//////////////////////////////////////////////////////////////////////// +// LocalFileEntriesReadDirPlusTest +//////////////////////////////////////////////////////////////////////// + +type LocalFileEntriesReadDirPlusTest struct { + suite.Suite + fsTest +} + +func TestLocalFileEntriesReadDirPlusTestSuite(t *testing.T) { + suite.Run(t, new(LocalFileEntriesReadDirPlusTest)) +} + +func (t *LocalFileEntriesReadDirPlusTest) SetupSuite() { + t.mountCfg.EnableReaddirplus = true + t.serverCfg.NewConfig = &cfg.Config{ + Write: cfg.WriteConfig{ + CreateEmptyFile: false, + }} + t.fsTest.SetUpTestSuite() +} + +func (t *LocalFileEntriesReadDirPlusTest) TearDownTest() { + t.fsTest.TearDown() +} + +func (t *LocalFileEntriesReadDirPlusTest) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} + +func (t *LocalFileEntriesReadDirPlusTest) TestDirectoryWithLocalFile() { + // Create a local file that is not yet synced to GCS. + f, err := os.Create(path.Join(mntDir, "local_file")) + assert.Nil(t.T(), err) + defer f.Close() + + // Read the directory. + entries, err := fusetesting.ReadDirPlusPicky(mntDir) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), 1, len(entries)) + // Check the entry for the local file. + entry := entries[0] + assert.Equal(t.T(), "local_file", entry.Name()) + assert.False(t.T(), entry.IsDir()) + assert.Equal(t.T(), filePerms, entry.Mode()) +} + +func (t *LocalFileEntriesReadDirPlusTest) TestDirWithOneLocalAndOneGCSEntry() { + // Create a remote object on GCS + assert.Nil(t.T(), t.createObjects(map[string]string{"gcs_file": "content"})) + // Create a local-only file + f, err := os.Create(path.Join(mntDir, "local_file")) + assert.Nil(t.T(), err) + defer f.Close() + + // Read the directory + entries, err := fusetesting.ReadDirPlusPicky(mntDir) + + assert.Nil(t.T(), err) + assert.Equal(t.T(), 2, len(entries)) + assert.Equal(t.T(), "gcs_file", entries[0].Name()) + assert.False(t.T(), entries[0].IsDir()) + assert.Equal(t.T(), int64(len("content")), entries[0].Size()) + assert.Equal(t.T(), "local_file", entries[1].Name()) + assert.False(t.T(), entries[1].IsDir()) +} From 93ca472825b749ce6cdf17703aa0b4da414f6b9d Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Wed, 16 Jul 2025 11:03:48 +0000 Subject: [PATCH 0568/1298] Update jacobsa fuse version (#3531) --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d9d2f3778f..5cdc6ee12a 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.2 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec - github.com/jacobsa/fuse v0.0.0-20250702080931-3e9d24d5e3ff + github.com/jacobsa/fuse v0.0.0-20250715095703-8f8de1982c06 github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 diff --git a/go.sum b/go.sum index 0dfb56f535..307909bbbc 100644 --- a/go.sum +++ b/go.sum @@ -138,8 +138,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= -github.com/jacobsa/fuse v0.0.0-20250702080931-3e9d24d5e3ff h1:QamXUXyWL9hTh55v6lFJdJOJN9bjX9i2Ml4zHZQq//M= -github.com/jacobsa/fuse v0.0.0-20250702080931-3e9d24d5e3ff/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= +github.com/jacobsa/fuse v0.0.0-20250715095703-8f8de1982c06 h1:QslSjXa4yR2iAZ8YxSKMKpXLblO1t2zbN/pskXGVzew= +github.com/jacobsa/fuse v0.0.0-20250715095703-8f8de1982c06/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= From 5123c98fbc406d68d1efd8ec06eb660881e53eb1 Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Wed, 16 Jul 2025 13:16:29 +0000 Subject: [PATCH 0569/1298] feat(entry-timeout): Set EntryExpiration for dentry caching (#3529) * Add entry timeout --- internal/fs/fs.go | 25 +++++++++++------- internal/fs/read_dir_plus_test.go | 44 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 10 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index e5d0f8f75c..eec03c9204 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1443,11 +1443,7 @@ func (fs *fileSystem) invalidateChildFileCacheIfExist(parentInode inode.DirInode // coreToDirentPlus creates a fuseutil.DirentPlus entry from an inode core. // LOCKS_EXCLUDED(fs.mu) -func (fs *fileSystem) coreToDirentPlus( - ctx context.Context, - fullName inode.Name, - core inode.Core) (entry *fuseutil.DirentPlus, err error) { - +func (fs *fileSystem) coreToDirentPlus(ctx context.Context, fullName inode.Name, core inode.Core) (entryPlus *fuseutil.DirentPlus, err error) { // Look up or create the inode for the core. child := fs.lookUpOrCreateInodeIfNotStale(core) if child == nil { @@ -1463,7 +1459,7 @@ func (fs *fileSystem) coreToDirentPlus( } expiration := time.Now().Add(fs.inodeAttributeCacheTTL) - entry = &fuseutil.DirentPlus{ + entryPlus = &fuseutil.DirentPlus{ Dirent: fuseutil.Dirent{ Name: path.Base(fullName.LocalName()), Type: fuseutil.DT_Unknown, @@ -1475,18 +1471,21 @@ func (fs *fileSystem) coreToDirentPlus( AttributesExpiration: expiration, }, } + if fs.newConfig.FileSystem.ExperimentalEnableDentryCache { + entryPlus.Entry.EntryExpiration = expiration + } // Set the directory entry type based on the core type. switch core.Type() { case metadata.SymlinkType: - entry.Dirent.Type = fuseutil.DT_Link + entryPlus.Dirent.Type = fuseutil.DT_Link case metadata.RegularFileType: - entry.Dirent.Type = fuseutil.DT_File + entryPlus.Dirent.Type = fuseutil.DT_File case metadata.ImplicitDirType, metadata.ExplicitDirType: - entry.Dirent.Type = fuseutil.DT_Directory + entryPlus.Dirent.Type = fuseutil.DT_Directory } - return entry, nil + return entryPlus, nil } // LocalFileEntries lists the local files (file that is not yet present on GCS) present in the directory. @@ -1546,6 +1545,9 @@ func (fs *fileSystem) lookupAndFetchAttributesForLocalFileEntriesPlus(parentName Attributes: attrs, AttributesExpiration: expiration, } + if fs.newConfig.FileSystem.ExperimentalEnableDentryCache { + childInodeEntry.EntryExpiration = expiration + } localEntryPlus.Entry = childInodeEntry localFileEntriesPlus[localEntryName] = localEntryPlus } @@ -1611,6 +1613,9 @@ func (fs *fileSystem) LookUpInode( e := &op.Entry e.Child = child.ID() e.Attributes, e.AttributesExpiration, err = fs.getAttributes(ctx, child) + if fs.newConfig.FileSystem.ExperimentalEnableDentryCache { + e.EntryExpiration = e.AttributesExpiration + } if err != nil { return err diff --git a/internal/fs/read_dir_plus_test.go b/internal/fs/read_dir_plus_test.go index d659b7ebff..2dd37f3e36 100644 --- a/internal/fs/read_dir_plus_test.go +++ b/internal/fs/read_dir_plus_test.go @@ -19,6 +19,7 @@ import ( "os" "path" "testing" + "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/jacobsa/fuse/fusetesting" @@ -42,6 +43,12 @@ func TestReadDirPlusTestSuite(t *testing.T) { func (t *ReadDirPlusTest) SetupSuite() { t.mountCfg.EnableReaddirplus = true t.serverCfg.ImplicitDirectories = true + t.serverCfg.InodeAttributeCacheTTL = 500 * time.Millisecond + t.serverCfg.NewConfig = &cfg.Config{ + FileSystem: cfg.FileSystemConfig{ + ExperimentalEnableDentryCache: true, + }, + } t.fsTest.SetUpTestSuite() } @@ -107,6 +114,39 @@ func (t *ReadDirPlusTest) TestDirectoryWithVariousEntryTypes() { } } +// Test that stat after Readdirplus return the same attributes as Readdirplus even if data on GCS has changed +func (t *ReadDirPlusTest) TestStatAfterReaddirplus() { + // Set up contents. + testFileName := "file.txt" + filePath := path.Join(mntDir, testFileName) + initialContent := generateRandomString(10) + updatedContent := generateRandomString(5) + assert.Nil(t.T(), t.createObjects(map[string]string{testFileName: initialContent})) + + // Read the directory with Readdirplus. + _, _ = fusetesting.ReadDirPlusPicky(mntDir) + // Modify the file content in GCS. + assert.Nil(t.T(), t.createObjects(map[string]string{testFileName: updatedContent})) + // Stat the file before entry expires in cache. + // This should return the same attributes as Readdirplus. + fileInfo, err := os.Stat(filePath) + + // Check that the stat returns the old attributes. + assert.Nil(t.T(), err) + assert.Equal(t.T(), testFileName, fileInfo.Name()) + assert.Equal(t.T(), int64(len(initialContent)), fileInfo.Size()) + // Check stat after cache expiry. + // Wait for a duration longer than the metadata cache TTL. + time.Sleep(time.Second) + // Stat the file again. + // This should return the updated attributes. + fileInfo, err = os.Stat(filePath) + // Check that the stat returns the updated attributes. + assert.Nil(t.T(), err) + assert.Equal(t.T(), testFileName, fileInfo.Name()) + assert.Equal(t.T(), int64(len(updatedContent)), fileInfo.Size()) +} + //////////////////////////////////////////////////////////////////////// // LocalFileEntriesReadDirPlusTest //////////////////////////////////////////////////////////////////////// @@ -122,7 +162,11 @@ func TestLocalFileEntriesReadDirPlusTestSuite(t *testing.T) { func (t *LocalFileEntriesReadDirPlusTest) SetupSuite() { t.mountCfg.EnableReaddirplus = true + t.serverCfg.InodeAttributeCacheTTL = 60 * time.Second t.serverCfg.NewConfig = &cfg.Config{ + FileSystem: cfg.FileSystemConfig{ + ExperimentalEnableDentryCache: true, + }, Write: cfg.WriteConfig{ CreateEmptyFile: false, }} From 9d277836e1a16fb208a2f649bf68a78c682828aa Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Thu, 17 Jul 2025 23:00:03 +0530 Subject: [PATCH 0570/1298] fix: improve logging while unmount external mount (#3540) * bug(): improve logging while unmount external mount * minor improvement * fixing formatting test. * minor review comment --- cmd/legacy_main.go | 5 +++++ go.mod | 2 +- go.sum | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 829a1ce794..f6b467af9d 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -20,6 +20,7 @@ package cmd import ( + "errors" "fmt" "io/fs" "os" @@ -76,6 +77,10 @@ func registerTerminatingSignalHandler(mountPoint string, c *cfg.Config) { err := fuse.Unmount(mountPoint) if err != nil { + if errors.Is(err, fuse.ErrExternallyManagedMountPoint) { + logger.Infof("Mount point %s is externally managed; gcsfuse will not unmount it.", mountPoint) + return + } logger.Errorf("Failed to unmount in response to %s: %v", sigName, err) } else { logger.Infof("Successfully unmounted in response to %s.", sigName) diff --git a/go.mod b/go.mod index 5cdc6ee12a..16ae999665 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,7 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.2 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec - github.com/jacobsa/fuse v0.0.0-20250715095703-8f8de1982c06 + github.com/jacobsa/fuse v0.0.0-20250717091043-15503eb5696d github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 diff --git a/go.sum b/go.sum index 307909bbbc..3a011ed2e7 100644 --- a/go.sum +++ b/go.sum @@ -138,8 +138,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= -github.com/jacobsa/fuse v0.0.0-20250715095703-8f8de1982c06 h1:QslSjXa4yR2iAZ8YxSKMKpXLblO1t2zbN/pskXGVzew= -github.com/jacobsa/fuse v0.0.0-20250715095703-8f8de1982c06/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= +github.com/jacobsa/fuse v0.0.0-20250717091043-15503eb5696d h1:e1rS3W2isplX97AJ/MsbTJrVohWGzqXx6nvOx3RTikk= +github.com/jacobsa/fuse v0.0.0-20250717091043-15503eb5696d/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= From b83002736bb89d769c41f55c3f7352d0e8c96993 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 18 Jul 2025 14:47:44 +0530 Subject: [PATCH 0571/1298] feat(Migrate Auth TPC): Adding function to get token source from url (#3532) * adding token source function and unit tests * review comment from gemini * fix return type according to go style * assert to require --- internal/auth/{proxy.go => token_source.go} | 16 ++-- internal/auth/token_source_test.go | 88 +++++++++++++++++++++ 2 files changed, 98 insertions(+), 6 deletions(-) rename internal/auth/{proxy.go => token_source.go} (88%) create mode 100644 internal/auth/token_source_test.go diff --git a/internal/auth/proxy.go b/internal/auth/token_source.go similarity index 88% rename from internal/auth/proxy.go rename to internal/auth/token_source.go index 9818bad28e..9020f4b4d6 100644 --- a/internal/auth/proxy.go +++ b/internal/auth/token_source.go @@ -36,7 +36,7 @@ func newProxyTokenSource( u, err := url.Parse(endpoint) if err != nil { err = fmt.Errorf("newProxyTokenSource cannot parse endpoint %s: %w", endpoint, err) - return + return nil, err } client := &http.Client{} @@ -71,13 +71,13 @@ func (ts proxyTokenSource) Token() (token *oauth2.Token, err error) { resp, err := ts.client.Get(ts.endpoint) if err != nil { err = fmt.Errorf("proxyTokenSource cannot fetch token: %w", err) - return + return nil, err } body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) if err != nil { err = fmt.Errorf("proxyTokenSource cannot load body: %w", err) - return + return nil, err } if c := resp.StatusCode; c < 200 || c >= 300 { @@ -85,15 +85,19 @@ func (ts proxyTokenSource) Token() (token *oauth2.Token, err error) { Response: resp, Body: body, } - return + return nil, err } token = &oauth2.Token{} err = json.Unmarshal(body, token) if err != nil { err = fmt.Errorf("proxyTokenSource cannot decode body: %w", err) - return + return nil, err } - return + return token, nil +} + +func NewTokenSourceFromURL(ctx context.Context, tokenUrl string, reuseTokenFromUrl bool) (tokenSrc oauth2.TokenSource, err error) { + return newProxyTokenSource(ctx, tokenUrl, reuseTokenFromUrl) } diff --git a/internal/auth/token_source_test.go b/internal/auth/token_source_test.go new file mode 100644 index 0000000000..df649ce99a --- /dev/null +++ b/internal/auth/token_source_test.go @@ -0,0 +1,88 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +import ( + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/oauth2" +) + +func Test_NewTokenSourceFromURL_Success(t *testing.T) { + // Create fake token server. + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + token := oauth2.Token{ + AccessToken: "test-access-token", + TokenType: "Bearer", + } + require.NoError(t, json.NewEncoder(w).Encode(token)) + })) + defer server.Close() + + ts, err := NewTokenSourceFromURL(context.Background(), server.URL, false) + + assert.NoError(t, err) + assert.NotNil(t, ts) + // Fetch token + token, err := ts.Token() + assert.NoError(t, err) + assert.Equal(t, "test-access-token", token.AccessToken) +} + +func Test_NewTokenSourceFromURL_InvalidURL(t *testing.T) { + ts, err := NewTokenSourceFromURL(context.Background(), ":", false) // invalid URL + + assert.Error(t, err) + assert.Nil(t, ts) +} + +func TestProxyTokenSource_TokenFetch_ServerError(t *testing.T) { + // Simulate HTTP 500 error. + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "server error", http.StatusInternalServerError) + })) + defer server.Close() + ts, err := NewTokenSourceFromURL(context.Background(), server.URL, false) + require.NoError(t, err) + + token, err := ts.Token() + + assert.Error(t, err) + assert.Contains(t, err.Error(), "server error") + assert.Nil(t, token) +} + +func TestProxyTokenSource_TokenFetch_InvalidJSON(t *testing.T) { + // Simulate invalid JSON. + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := w.Write([]byte("not-json")) + require.NoError(t, err) + })) + defer server.Close() + ts, err := NewTokenSourceFromURL(context.Background(), server.URL, false) + require.NoError(t, err) + + token, err := ts.Token() + + assert.Error(t, err) + assert.Contains(t, err.Error(), "decode body") + assert.Nil(t, token) +} From 1cb93ca1f7e3513c166349a0b89ef1a3a0f4e057 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Fri, 18 Jul 2025 16:27:04 +0530 Subject: [PATCH 0572/1298] ci(read unfinalized objects): Add new e2e tests for reads of unfinalized objects while being appended (#3473) * Add initial test for unfinalized appends * Add initial two append test * fix test for mountedDir * test unfinalized_appends package * test mount failure * test changes * stop kokoro machine from getting killed on completion/failure * fix test package * fix tests * revert temporary changes * fix review comments * clean up cache dir after test completes * fix review comments and add helper for opening files in different mode * swap primary/secondary mount * Fix review comments * fix review comments * fix review comments * fix formatting * fix review comments --------- Co-authored-by: Nitin Garg --- .../improved_run_e2e_tests.sh | 2 +- .../rapid_appends/rapid_appends_test.go | 139 ++++++++++++++++++ .../rapid_appends/setup_test.go | 135 +++++++++++++++++ .../unfinalized_object_operations_test.go | 12 +- .../util/client/gcs_helper.go | 11 +- .../util/operations/file_operations.go | 8 + 6 files changed, 296 insertions(+), 11 deletions(-) create mode 100644 tools/integration_tests/rapid_appends/rapid_appends_test.go create mode 100644 tools/integration_tests/rapid_appends/setup_test.go diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh index 240a8fb3b6..61938ece3e 100755 --- a/tools/integration_tests/improved_run_e2e_tests.sh +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -221,7 +221,7 @@ TEST_PACKAGES_COMMON=( # Test packages for regional buckets. TEST_PACKAGES_FOR_RB=("${TEST_PACKAGES_COMMON[@]}" "inactive_stream_timeout" "cloud_profiler") # Test packages for zonal buckets. -TEST_PACKAGES_FOR_ZB=("${TEST_PACKAGES_COMMON[@]}" "unfinalized_object") +TEST_PACKAGES_FOR_ZB=("${TEST_PACKAGES_COMMON[@]}" "unfinalized_object" "rapid_appends") # Test packages for TPC buckets. TEST_PACKAGES_FOR_TPC=("operations") diff --git a/tools/integration_tests/rapid_appends/rapid_appends_test.go b/tools/integration_tests/rapid_appends/rapid_appends_test.go new file mode 100644 index 0000000000..b950220b8d --- /dev/null +++ b/tools/integration_tests/rapid_appends/rapid_appends_test.go @@ -0,0 +1,139 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rapid_appends + +import ( + "log" + "os" + "path" + "syscall" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +const numAppends = 3 // Number of appends to perform on test file. +const appendSize = 10 // Size in bytes for each append. +const unfinalizedObjectSize = 10 // Size in bytes of initial unfinalized Object. + +// ////////////////////////////////////////////////////////////////////// +// Boilerplate +// ////////////////////////////////////////////////////////////////////// + +// TODO: Split the suite in two suites single mount and multi-mount. +type RapidAppendsSuite struct { + suite.Suite + fileName string + fileContent string +} + +// ////////////////////////////////////////////////////////////////////// +// Helpers +// ////////////////////////////////////////////////////////////////////// + +func (t *RapidAppendsSuite) SetupSuite() { + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) + secondaryMntTestDirPath = setup.SetupTestDirectory(testDirName) +} + +func (t *RapidAppendsSuite) TearDownSuite() { + setup.UnmountGCSFuse(secondaryMntRootDir) + if t.T().Failed() { + log.Println("Secondary mount log file:") + setup.SaveGCSFuseLogFileInCaseOfFailure(t.T()) + log.Println("Primary mount log file:") + setup.SetLogFile(primaryMntLogFilePath) + setup.SaveGCSFuseLogFileInCaseOfFailure(t.T()) + } +} + +func (t *RapidAppendsSuite) SetupSubTest() { + t.fileName = fileNamePrefix + setup.GenerateRandomString(5) + // Create unfinalized object. + t.fileContent = setup.GenerateRandomString(unfinalizedObjectSize) + client.CreateUnfinalizedObject(ctx, t.T(), storageClient, path.Join(testDirName, t.fileName), t.fileContent) +} + +func (t *RapidAppendsSuite) TearDownSubTest() { + err := os.Remove(path.Join(primaryMntTestDirPath, t.fileName)) + require.NoError(t.T(), err) +} + +// appendToFile appends "appendContent" to the given file. +func (t *RapidAppendsSuite) appendToFile(file *os.File, appendContent string) { + t.T().Helper() + n, err := file.WriteString(appendContent) + assert.NoError(t.T(), err) + assert.Equal(t.T(), len(appendContent), n) + t.fileContent += appendContent +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *RapidAppendsSuite) TestAppendsAndRead() { + testCases := []struct { + name string + readMountPath string + syncNeeded bool + }{ + { + name: "reading_seq_from_same_mount", + readMountPath: primaryMntTestDirPath, + syncNeeded: false, // Sync is not required when reading from the same mount. + }, + { + name: "reading_seq_from_different_mount", + readMountPath: secondaryMntTestDirPath, + syncNeeded: true, // Sync is required for writes to be visible on another mount. + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + // Open the file for appending on the primary mount. + appendFileHandle := operations.OpenFileInMode(t.T(), path.Join(primaryMntTestDirPath, t.fileName), os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + defer operations.CloseFileShouldNotThrowError(t.T(), appendFileHandle) + readPath := path.Join(tc.readMountPath, t.fileName) + for range numAppends { + t.appendToFile(appendFileHandle, setup.GenerateRandomString(appendSize)) + // Sync the file if the test case requires it. + if tc.syncNeeded { + operations.SyncFile(appendFileHandle, t.T()) + } + + gotContent, err := operations.ReadFile(readPath) + + require.NoError(t.T(), err) + assert.Equal(t.T(), t.fileContent, string(gotContent)) + } + }) + } +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestRapidAppendsSuite(t *testing.T) { + rapidAppendsSuite := new(RapidAppendsSuite) + suite.Run(t, rapidAppendsSuite) +} diff --git a/tools/integration_tests/rapid_appends/setup_test.go b/tools/integration_tests/rapid_appends/setup_test.go new file mode 100644 index 0000000000..322f7cf768 --- /dev/null +++ b/tools/integration_tests/rapid_appends/setup_test.go @@ -0,0 +1,135 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rapid_appends + +import ( + "context" + "log" + "os" + "path" + "testing" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" +) + +const ( + testDirName = "RapidAppendsTest" + fileNamePrefix = "rapid-append-file-" +) + +var ( + // Flags for mount options for primaryMntRootDir + flags []string + // Mount function to be used for the mounting. + mountFunc func([]string) error + + // Globals for primary mount which is used to append content to files. + // Other Root directory which is mounted by gcsfuse for multi-mount scenarios. + primaryMntRootDir string + // Stores test directory path in the mounted path for primaryMntRootDir. + primaryMntTestDirPath string + // Stores log file path for the mount primaryMntRootDir. + primaryMntLogFilePath string + + // Globals for secondary mount which is used to verify reads on existing unfinalized objects. + // Root directory which is mounted by gcsfuse. + secondaryMntRootDir string + // Stores test directory path in the mounted path for secondaryMntRootDir. + secondaryMntTestDirPath string + // Stores log file path for the mount secondaryMntRootDir. + secondaryMntLogFilePath string + + // Clients to create the object in GCS. + storageClient *storage.Client + ctx context.Context +) + +//////////////////////////////////////////////////////////////////////// +// TestMain +//////////////////////////////////////////////////////////////////////// + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + if !setup.IsZonalBucketRun() { + log.Fatalf("This package is not supposed to be run with Regional Buckets.") + } + // TODO(b/431926259): Add support for mountedDir tests as this + // package has multi-mount scenario tests and currently we only + // pass single mountedDir to test package. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + log.Fatalf("This package doesn't support --mountedDirectory option currently.") + } + ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() + + // Set up test directory for primary mount. + setup.SetUpTestDirForTestBucketFlag() + primaryMntRootDir = setup.MntDir() + primaryMntLogFilePath = setup.LogFile() + // TODO(b/432179045): `--write-global-max-blocks=-1` is needed right now because of a bug in global semaphore release. + // Remove this flag once bug is fixed. + primaryMountFlags := []string{"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0", "--write-global-max-blocks=-1"} + err := static_mounting.MountGcsfuseWithStaticMounting(primaryMountFlags) + if err != nil { + log.Fatalf("Unable to mount primary mount: %v", err) + } + // Setup Package Test Directory for primary mount. + primaryMntTestDirPath = setup.SetupTestDirectory(testDirName) + defer setup.UnmountGCSFuse(primaryMntRootDir) + + // Set up test directory for secondary mount. + setup.SetUpTestDirForTestBucketFlag() + secondaryMntRootDir = setup.MntDir() + secondaryMntLogFilePath = setup.LogFile() + rapidAppendsCacheDir, err := os.MkdirTemp("", "rapid_appends_cache_dir_*") + if err != nil { + log.Fatalf("Failed to create cache dir for rapid append tests: %v", err) + } + defer func() { + err := os.RemoveAll(rapidAppendsCacheDir) + if err != nil { + log.Fatalf("Error while cleaning up cache dir %q: %v", rapidAppendsCacheDir, err) + } + }() + // Define flag set for secondary mount to run the tests. + flagsSet := [][]string{ + {"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0"}, + {"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0", "--file-cache-max-size-mb=-1", "--cache-dir=" + rapidAppendsCacheDir}, + } + + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + + var successCode int + for i := range flagsSet { + log.Printf("Running tests with flags: %v", flagsSet[i]) + flags = flagsSet[i] + successCode = m.Run() + if successCode != 0 { + break + } + } + setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) + os.Exit(successCode) +} diff --git a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go index 9e9ecaf875..798158e1c5 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go @@ -55,8 +55,8 @@ func (t *unfinalizedObjectOperations) TeardownTest() {} //////////////////////////////////////////////////////////////////////// func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCreatedOutsideOfMountReports0Size() { - var size int64 = operations.MiB - writer := client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), size) + size := operations.MiB + writer := client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), setup.GenerateRandomString(size)) statRes, err := operations.StatFile(path.Join(t.testDirPath, t.fileName)) @@ -96,8 +96,8 @@ func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCreatedFromSameMountR func (t *unfinalizedObjectOperations) TestOverWritingUnfinalizedObjectsReturnsESTALE() { // TODO(b/411333280): Enable the test once flush on unfinalized Object is fixed. t.T().Skip("Skipping the test due to b/411333280") - var size int64 = operations.MiB - _ = client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), size) + size := operations.MiB + _ = client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), setup.GenerateRandomString(size)) fh := operations.OpenFile(path.Join(t.testDirPath, t.fileName), t.T()) // Overwrite unfinalized object. @@ -127,8 +127,8 @@ func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCanBeRenamedIfCreated } func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCantBeRenamedIfCreatedFromDifferentMount() { - var size int64 = operations.MiB - _ = client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), size) + size := operations.MiB + _ = client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), setup.GenerateRandomString(size)) // Overwrite unfinalized object. err := operations.RenameFile(path.Join(t.testDirPath, t.fileName), path.Join(t.testDirPath, "New"+t.fileName)) diff --git a/tools/integration_tests/util/client/gcs_helper.go b/tools/integration_tests/util/client/gcs_helper.go index a1c0174a64..515bd14f3b 100644 --- a/tools/integration_tests/util/client/gcs_helper.go +++ b/tools/integration_tests/util/client/gcs_helper.go @@ -166,17 +166,20 @@ func GetCRCFromGCS(objectPath string, ctx context.Context, storageClient *storag return attr.CRC32C, nil } -func CreateUnfinalizedObject(ctx context.Context, t *testing.T, client *storage.Client, object string, size int64) *storage.Writer { +// This method creates an Unfinalized Object with given content using appendable writer +// and performs a flush with Zonal Bucket Flush API for content to be available for read +// and returns the writer. +func CreateUnfinalizedObject(ctx context.Context, t *testing.T, client *storage.Client, object, content string) *storage.Writer { writer, err := AppendableWriter(ctx, client, object, storage.Conditions{}) require.NoError(t, err) - bytesWritten, err := writer.Write([]byte(setup.GenerateRandomString(int(size)))) + bytesWritten, err := writer.Write([]byte(content)) require.NoError(t, err) - assert.EqualValues(t, size, bytesWritten) + assert.EqualValues(t, len(content), bytesWritten) flushOffset, err := writer.Flush() require.NoError(t, err) - assert.Equal(t, size, flushOffset) + assert.Equal(t, int64(len(content)), flushOffset) return writer } diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index e9ca638256..444057f706 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -363,6 +363,14 @@ func OpenFileAsReadonly(filepath string) (*os.File, error) { return f, nil } +// Open file in mode opens the given file in the flag mode provided. +func OpenFileInMode(t *testing.T, filepath string, flag int) *os.File { + t.Helper() + fh, err := os.OpenFile(filepath, flag, FilePermission_0600) + require.NoError(t, err) + return fh +} + func readBytesFromFile(f *os.File, numBytesToRead int, b []byte) error { numBytesRead, err := f.Read(b) if err != nil { From 16cb9878fb721973df5a3071db3856159b511322 Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Fri, 18 Jul 2025 13:16:34 +0000 Subject: [PATCH 0573/1298] feat(Readdirplus): Add e2e test for Readdirplus (#3533) * Add e2e test for Readdirplus --- .../improved_run_e2e_tests.sh | 1 + .../readdirplus_with_dentry_cache_test.go | 106 +++++++++++++ .../readdirplus_without_dentry_cache_test.go | 106 +++++++++++++ .../readdirplus/setup_test.go | 149 ++++++++++++++++++ tools/integration_tests/run_e2e_tests.sh | 1 + .../run_tests_mounted_directory.sh | 21 +++ 6 files changed, 384 insertions(+) create mode 100644 tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go create mode 100644 tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go create mode 100644 tools/integration_tests/readdirplus/setup_test.go diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh index 61938ece3e..4410bb7fbf 100755 --- a/tools/integration_tests/improved_run_e2e_tests.sh +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -216,6 +216,7 @@ TEST_PACKAGES_COMMON=( "negative_stat_cache" "stale_handle" "release_version" + "readdirplus" ) # Test packages for regional buckets. diff --git a/tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go b/tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go new file mode 100644 index 0000000000..0a71bf4adc --- /dev/null +++ b/tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go @@ -0,0 +1,106 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package readdirplus + +import ( + "log" + "os" + "path" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/jacobsa/fuse/fusetesting" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type readdirplusWithDentryCacheTest struct { + flags []string +} + +func (s *readdirplusWithDentryCacheTest) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(t, s.flags, testDirName) +} + +func (s *readdirplusWithDentryCacheTest) Teardown(t *testing.T) { + if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory + setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) + setup.UnmountGCSFuseAndDeleteLogFile(rootDir) + } +} + +func (s *readdirplusWithDentryCacheTest) TestReaddirplusWithDentryCache(t *testing.T) { + // Create directory structure + // testBucket/target_dir/ -- Dir + // testBucket/target_dir/file -- File + // testBucket/target_dir/emptySubDirectory -- Dir + // testBucket/target_dir/subDirectory -- Dir + // testBucket/target_dir/subDirectory/file1 -- File + targetDir := path.Join(testDirPath, targetDirName) + operations.CreateDirectory(targetDir, t) + // Create a file in the target directory. + f1 := operations.CreateFile(path.Join(targetDir, "file"), setup.FilePermission_0600, t) + operations.CloseFileShouldNotThrowError(t, f1) + // Create an empty subdirectory + operations.CreateDirectory(path.Join(targetDir, "emptySubDirectory"), t) + // Create a subdirectory with file + operations.CreateDirectoryWithNFiles(1, path.Join(targetDir, "subDirectory"), "file", t) + + // Call Readdirplus to list the directory. + startTime := time.Now() + entries, err := fusetesting.ReadDirPlusPicky(targetDir) + endTime := time.Now() + + require.NoError(t, err, "ReadDirPlusPicky failed") + expectedEntries := []struct { + name string + isDir bool + mode os.FileMode + }{ + {name: "emptySubDirectory", isDir: true, mode: os.ModeDir | 0755}, + {name: "file", isDir: false, mode: 0644}, + {name: "subDirectory", isDir: true, mode: os.ModeDir | 0755}, + } + // Verify the entries. + assert.Equal(t, len(expectedEntries), len(entries), "Number of entries mismatch") + for i, expected := range expectedEntries { + entry := entries[i] + assert.Equal(t, expected.name, entry.Name(), "Name mismatch for entry %d", i) + assert.Equal(t, expected.isDir, entry.IsDir(), "IsDir mismatch for entry %s", entry.Name()) + assert.Equal(t, expected.mode, entry.Mode(), "Mode mismatch for entry %s", entry.Name()) + } + // Dentry cache is enabled, so LookUpInode should also not be called. + // This applies even to the parent directory, as its inode is cached during + // the test setup phase when the directory structure is created. + validateLogsForReaddirplus(t, setup.LogFile(), true, startTime, endTime) +} + +func TestReaddirplusWithDentryCacheTest(t *testing.T) { + ts := &readdirplusWithDentryCacheTest{} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + // Setup flags and run tests. + ts.flags = []string{"--implicit-dirs", "--experimental-enable-readdirplus", "--experimental-enable-dentry-cache"} + log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) +} diff --git a/tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go b/tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go new file mode 100644 index 0000000000..ea44509fe6 --- /dev/null +++ b/tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go @@ -0,0 +1,106 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package readdirplus + +import ( + "log" + "os" + "path" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/jacobsa/fuse/fusetesting" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type readdirplusWithoutDentryCacheTest struct { + flags []string +} + +func (s *readdirplusWithoutDentryCacheTest) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(t, s.flags, testDirName) +} + +func (s *readdirplusWithoutDentryCacheTest) Teardown(t *testing.T) { + if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory + setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) + setup.UnmountGCSFuseAndDeleteLogFile(rootDir) + } +} + +func (s *readdirplusWithoutDentryCacheTest) TestReaddirplusWithoutDentryCache(t *testing.T) { + // Create directory structure + // testBucket/target_dir/ -- Dir + // testBucket/target_dir/file -- File + // testBucket/target_dir/emptySubDirectory -- Dir + // testBucket/target_dir/subDirectory -- Dir + // testBucket/target_dir/subDirectory/file1 -- File + targetDir := path.Join(testDirPath, targetDirName) + operations.CreateDirectory(targetDir, t) + // Create a file in the target directory. + f1 := operations.CreateFile(path.Join(targetDir, "file"), setup.FilePermission_0600, t) + operations.CloseFileShouldNotThrowError(t, f1) + // Create an empty subdirectory + operations.CreateDirectory(path.Join(targetDir, "emptySubDirectory"), t) + // Create a subdirectory with file + operations.CreateDirectoryWithNFiles(1, path.Join(targetDir, "subDirectory"), "file", t) + + // Call Readdirplus to list the directory. + startTime := time.Now() + entries, err := fusetesting.ReadDirPlusPicky(targetDir) + endTime := time.Now() + + require.NoError(t, err, "ReadDirPlusPicky failed") + expectedEntries := []struct { + name string + isDir bool + mode os.FileMode + }{ + {name: "emptySubDirectory", isDir: true, mode: os.ModeDir | 0755}, + {name: "file", isDir: false, mode: 0644}, + {name: "subDirectory", isDir: true, mode: os.ModeDir | 0755}, + } + // Verify the entries. + assert.Equal(t, len(expectedEntries), len(entries), "Number of entries mismatch") + for i, expected := range expectedEntries { + entry := entries[i] + assert.Equal(t, expected.name, entry.Name(), "Name mismatch for entry %d", i) + assert.Equal(t, expected.isDir, entry.IsDir(), "IsDir mismatch for entry %s", entry.Name()) + assert.Equal(t, expected.mode, entry.Mode(), "Mode mismatch for entry %s", entry.Name()) + } + // Validate logs to check that ReadDirPlus was called and ReadDir was not. + // Dentry cache is not enabled, so LookUpInode should be called for + // parent directory as well as for all the entries. + validateLogsForReaddirplus(t, setup.LogFile(), false, startTime, endTime) +} + +func TestReaddirplusWithoutDentryCacheTest(t *testing.T) { + ts := &readdirplusWithoutDentryCacheTest{} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + // Run tests. + ts.flags = []string{"--implicit-dirs", "--experimental-enable-readdirplus"} + log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) +} diff --git a/tools/integration_tests/readdirplus/setup_test.go b/tools/integration_tests/readdirplus/setup_test.go new file mode 100644 index 0000000000..c5783eeedc --- /dev/null +++ b/tools/integration_tests/readdirplus/setup_test.go @@ -0,0 +1,149 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Provides integration tests for Readdirplus +package readdirplus + +import ( + "context" + "io" + "log" + "os" + "strings" + "testing" + "time" + + "cloud.google.com/go/storage" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/require" +) + +const ( + testDirName = "dirForReaddirplusTest" + targetDirName = "target_dir" + logFileNameForMountedDirectoryTests = "/tmp/readdirplus_logs/log.json" +) + +var ( + storageClient *storage.Client + ctx context.Context + testDirPath string + mountFunc func([]string) error + // mount directory is where our tests run. + mountDir string + // root directory is the directory to be unmounted. + rootDir string +) + +func loadLogLines(reader io.Reader) ([]string, error) { + content, err := io.ReadAll(reader) + if err != nil { + return nil, err + } + return strings.Split(string(content), "\n"), nil +} + +// validateLogsForReaddirplus checks that ReadDirPlus was called and ReadDir was not. +// It also checks that LookUpInode is not called when dentry cache is enabled. +func validateLogsForReaddirplus(t *testing.T, logFile string, dentryCacheEnabled bool, startTime, endTime time.Time) { + t.Helper() + + logForReadDirPlus := "ReadDirPlus (" + logForReadDir := "ReadDir (" + logForLookUpInode := "LookUpInode (" + + file, err := os.Open(logFile) + require.NoError(t, err, "Failed to open log file") + defer file.Close() + + logLines, err := loadLogLines(file) + require.NoError(t, err, "Failed to read log file") + + foundReadDirPlus := false + foundReadDir := false + foundLookUpInode := false + for _, line := range logLines { + logEntry, err := read_logs.ParseJsonLogLineIntoLogEntryStruct(line) // Assuming read_logs can parse general log lines too or a more generic parser is available. + // If parsing fails, it might be a non-JSON line or a different structured log. + // For this specific message, we expect it to be in the "Message" field of a structured log. + + if err == nil && logEntry != nil { + // Check if the log entry's timestamp is within the expected window. + if (logEntry.Timestamp.After(startTime) || logEntry.Timestamp.Equal(startTime)) && + (logEntry.Timestamp.Before(endTime) || logEntry.Timestamp.Equal(endTime)) { + if strings.Contains(logEntry.Message, logForReadDirPlus) { + foundReadDirPlus = true + } + if strings.Contains(logEntry.Message, logForReadDir) { + foundReadDir = true + } + if strings.Contains(logEntry.Message, logForLookUpInode) { + foundLookUpInode = true + } + } + } + } + + require.True(t, foundReadDirPlus, "ReadDirPlus not called") + require.False(t, foundReadDir, "ReadDir called unexpectedly") + if dentryCacheEnabled { + require.False(t, foundLookUpInode, "LookUpInode called unexpectedly") + } else { + require.True(t, foundLookUpInode, "LookUpInode not called") + } +} + +func mountGCSFuseAndSetupTestDir(t *testing.T, flags []string, testDirName string) { + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) + setup.SetMntDir(mountDir) + testDirPath = setup.SetupTestDirectory(testDirName) +} + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + + // Create common storage client to be used in test. + ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() + + if setup.MountedDirectory() != "" { + mountDir = setup.MountedDirectory() + setup.SetLogFile(logFileNameForMountedDirectoryTests) + // Run tests for mounted directory if the flag is set. + os.Exit(m.Run()) + } + // Else run tests for testBucket. + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + + // Save mount and root directory variables. + mountDir, rootDir = setup.MntDir(), setup.MntDir() + + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() + + os.Exit(successCode) +} diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index f6234e752d..4fe3a6e16a 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -119,6 +119,7 @@ TEST_DIR_PARALLEL=( "inactive_stream_timeout" "cloud_profiler" "release_version" + "readdirplus" ) # These tests never become parallel as it is changing bucket permissions. diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index d0de122d55..4127724ee3 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -658,3 +658,24 @@ random_profile_label="test" gcsfuse --enable-cloud-profiling --profiling-goroutines --profiling-cpu --profiling-heap --profiling-allocated-heap --profiling-mutex --profiling-label $random_profile_label $TEST_BUCKET_NAME $MOUNT_DIR GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/cloud_profiler/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR --profile_label=$random_profile_label sudo umount $MOUNT_DIR + +# Test package: readdirplus +# Readdirplus test with dentry cache enabled (--experimental-enable-dentry-cache=true) +test_case="TestReaddirplusWithDentryCacheTest/TestReaddirplusWithDentryCache" +log_dir="/tmp/readdirplus_logs" +mkdir -p $log_dir +log_file="$log_dir/log.json" +gcsfuse --implicit-dirs --experimental-enable-readdirplus --experimental-enable-dentry-cache --log-file $log_file --log-severity=trace --log-format=json "$TEST_BUCKET_NAME" "$MOUNT_DIR" +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readdirplus/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run $test_case +sudo umount "$MOUNT_DIR" +rm -rf $log_dir + +# Readdirplus test with dentry cache disabled (--experimental-enable-dentry-cache=false) +test_case="TestReaddirplusWithoutDentryCacheTest/TestReaddirplusWithoutDentryCache" +log_dir="/tmp/readdirplus_logs" +mkdir -p $log_dir +log_file="$log_dir/log.json" +gcsfuse --implicit-dirs --experimental-enable-readdirplus --log-file $log_file --log-severity=trace --log-format=json "$TEST_BUCKET_NAME" "$MOUNT_DIR" +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readdirplus/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run $test_case +sudo umount "$MOUNT_DIR" +rm -rf $log_dir From eae4188f6746b8bbed2a3d7b4e6fb47c7b6c5c1a Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 21 Jul 2025 10:46:00 +0530 Subject: [PATCH 0574/1298] ci(debugging): Save gcsfuse logs for failed mount_timeout package (#3528) * Save gcsfuse logs for failed mount_timeout package 1. It improves the error log messages for mount_timeout package. 2. It saves the generated log file to the gcs bucket. This is needed for debugging by enabling grpc logs. 3. It also generates gcsfuse info logs. * address gemini code review comment --- .../gcsfuse_mount_timeout_test.go | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go index e9f0d238c7..0621c10630 100644 --- a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go +++ b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go @@ -19,6 +19,7 @@ import ( "math" "os" "path" + "path/filepath" "testing" "time" @@ -160,28 +161,36 @@ func (testSuite *ZBMountTimeoutTest) TearDownTest() { // mountOrTimeout mounts the bucket with the given client protocol. If the time taken // exceeds the expected for the particular test case , an error is thrown and test will fail. -func (testSuite *MountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientProtocol string, expectedMountTime time.Duration) error { +func (testSuite *MountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientProtocol string, expectedMountTime time.Duration) (err error) { minMountTime := time.Duration(math.MaxInt64) + logFile := setup.LogFile() + defer func() { + if err != nil { + setup.SaveLogFileAsArtifact(logFile, setup.GCSFuseLogFilePrefix+filepath.Base(logFile)) + } + }() // Iterating 10 times to account for randomness in time taken to mount. + args := []string{"--client-protocol", clientProtocol, "--log-file=" + logFile, bucketName, testSuite.dir} for i := 0; i < iterations; i++ { - args := []string{"--client-protocol", clientProtocol, bucketName, testSuite.dir} start := time.Now() - if err := mounting.MountGcsfuse(testSuite.gcsfusePath, args); err != nil { + if err = mounting.MountGcsfuse(testSuite.gcsfusePath, args); err != nil { + err = fmt.Errorf("mount failed for bucket %q (client protocol: %s) on attempt#%v: %w", bucketName, clientProtocol, i, err) return err } mountTime := time.Since(start) minMountTime = time.Duration(math.Min(float64(minMountTime), float64(mountTime))) - if err := util.Unmount(mountDir); err != nil { - err = fmt.Errorf("Warning: unmount failed: %v\n", err) + if err = util.Unmount(mountDir); err != nil { + err = fmt.Errorf("unmount failed for bucket %q on attempt#%v: %w", bucketName, i, err) return err } } if minMountTime > expectedMountTime { - return fmt.Errorf("[Client Protocol: %s] Mounting failed due to timeout (exceeding %f seconds). Time taken for mounting %s: %f sec", clientProtocol, expectedMountTime.Seconds(), bucketName, minMountTime.Seconds()) + err = fmt.Errorf("mount took too long for bucket %q (client protocol: %s). expected: %v, actual minimum mount time: %v", bucketName, clientProtocol, expectedMountTime, minMountTime) + return err } return nil } From 8d6d35587665ea933c5311f2c89d00fdc2b7f18a Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 21 Jul 2025 11:12:05 +0530 Subject: [PATCH 0575/1298] Refactor metrics code into a separate package. (#3550) --- cmd/legacy_main.go | 11 +-- cmd/legacy_main_test.go | 5 +- cmd/mount.go | 4 +- common/util.go | 18 +++++ common/util_test.go | 66 ++++++++++++++++++ internal/cache/file/cache_handle_test.go | 10 +-- internal/cache/file/cache_handler_test.go | 4 +- internal/cache/file/downloader/downloader.go | 6 +- .../cache/file/downloader/downloader_test.go | 4 +- .../downloader/jm_parallel_downloads_test.go | 6 +- internal/cache/file/downloader/job.go | 8 +-- internal/cache/file/downloader/job_test.go | 4 +- .../cache/file/downloader/job_testify_test.go | 4 +- .../file/downloader/parallel_downloads_job.go | 4 +- internal/fs/caching_test.go | 4 +- internal/fs/fs.go | 6 +- internal/fs/fs_test.go | 6 +- internal/fs/handle/file.go | 6 +- internal/fs/handle/file_test.go | 14 ++-- internal/fs/hns_bucket_test.go | 4 +- internal/fs/implicit_dirs_test.go | 4 +- internal/fs/implicit_dirs_with_cache_test.go | 4 +- internal/fs/inode/base_dir.go | 6 +- internal/fs/inode/base_dir_test.go | 6 +- .../kernel_list_cache_inifinite_ttl_test.go | 3 +- internal/fs/kernel_list_cache_test.go | 4 +- .../fs/kernel_list_cache_zero_ttl_test.go | 4 +- internal/fs/local_file_test.go | 4 +- internal/fs/parallel_dirops_test.go | 4 +- internal/fs/read_cache_test.go | 4 +- internal/fs/type_cache_test.go | 4 +- internal/fs/wrappers/monitoring.go | 10 +-- internal/fs/zonal_bucket_test.go | 4 +- internal/gcsx/bucket_manager.go | 6 +- internal/gcsx/bucket_manager_test.go | 10 +-- internal/gcsx/client_readers/gcs_reader.go | 10 +-- .../gcsx/client_readers/gcs_reader_test.go | 22 +++--- .../gcsx/client_readers/multi_range_reader.go | 6 +- .../client_readers/multi_range_reader_test.go | 4 +- internal/gcsx/client_readers/range_reader.go | 10 +-- .../gcsx/client_readers/range_reader_test.go | 8 +-- internal/gcsx/file_cache_reader.go | 14 ++-- internal/gcsx/file_cache_reader_test.go | 8 +-- .../gcsx/multi_range_downloader_wrapper.go | 4 +- .../multi_range_downloader_wrapper_test.go | 14 ++-- internal/gcsx/random_reader.go | 20 +++--- internal/gcsx/random_reader_stretchr_test.go | 32 ++++----- internal/gcsx/random_reader_test.go | 8 +-- internal/gcsx/read_manager/read_manager.go | 4 +- .../gcsx/read_manager/read_manager_test.go | 6 +- internal/monitor/bucket.go | 16 ++--- internal/storage/storageutil/client.go | 4 +- internal/storage/storageutil/custom_retry.go | 4 +- .../storage/storageutil/custom_retry_test.go | 8 +-- metrics/constants.go | 21 ++++++ {common => metrics}/noop_metrics.go | 2 +- {common => metrics}/otel_metrics.go | 2 +- {common => metrics}/otel_metrics_test.go | 2 +- {common => metrics}/telemetry.go | 27 +------- {common => metrics}/telemetry_test.go | 69 +------------------ 60 files changed, 308 insertions(+), 288 deletions(-) create mode 100644 metrics/constants.go rename {common => metrics}/noop_metrics.go (99%) rename {common => metrics}/otel_metrics.go (99%) rename {common => metrics}/otel_metrics_test.go (99%) rename {common => metrics}/telemetry.go (83%) rename {common => metrics}/telemetry_test.go (56%) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index f6b467af9d..80beb682d7 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -29,6 +29,7 @@ import ( "path/filepath" "strings" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/sys/unix" "github.com/googlecloudplatform/gcsfuse/v3/cfg" @@ -122,7 +123,7 @@ func getConfigForUserAgent(mountConfig *cfg.Config) string { } return fmt.Sprintf("%s:%s:%s:%s", isFileCacheEnabled, isFileCacheForRangeReadEnabled, isParallelDownloadsEnabled, areStreamingWritesEnabled) } -func createStorageHandle(newConfig *cfg.Config, userAgent string, metricHandle common.MetricHandle) (storageHandle storage.StorageHandle, err error) { +func createStorageHandle(newConfig *cfg.Config, userAgent string, metricHandle metrics.MetricHandle) (storageHandle storage.StorageHandle, err error) { storageClientConfig := storageutil.StorageClientConfig{ ClientProtocol: newConfig.GcsConnection.ClientProtocol, MaxConnsPerHost: int(newConfig.GcsConnection.MaxConnsPerHost), @@ -153,7 +154,7 @@ func createStorageHandle(newConfig *cfg.Config, userAgent string, metricHandle c //////////////////////////////////////////////////////////////////////// // Mount the file system according to arguments in the supplied context. -func mountWithArgs(bucketName string, mountPoint string, newConfig *cfg.Config, metricHandle common.MetricHandle) (mfs *fuse.MountedFileSystem, err error) { +func mountWithArgs(bucketName string, mountPoint string, newConfig *cfg.Config, metricHandle metrics.MetricHandle) (mfs *fuse.MountedFileSystem, err error) { // Enable invariant checking if requested. if newConfig.Debug.ExitOnInvariantViolation { locker.EnableInvariantsCheck() @@ -392,11 +393,11 @@ func Mount(newConfig *cfg.Config, bucketName, mountPoint string) (err error) { ctx := context.Background() var metricExporterShutdownFn common.ShutdownFn - metricHandle := common.NewNoopMetrics() + metricHandle := metrics.NewNoopMetrics() if cfg.IsMetricsEnabled(&newConfig.Metrics) { metricExporterShutdownFn = monitor.SetupOTelMetricExporters(ctx, newConfig) - if metricHandle, err = common.NewOTelMetrics(); err != nil { - metricHandle = common.NewNoopMetrics() + if metricHandle, err = metrics.NewOTelMetrics(); err != nil { + metricHandle = metrics.NewNoopMetrics() } } shutdownTracingFn := monitor.SetupTracing(ctx, newConfig) diff --git a/cmd/legacy_main_test.go b/cmd/legacy_main_test.go index 3d903168b9..5717c98fa8 100644 --- a/cmd/legacy_main_test.go +++ b/cmd/legacy_main_test.go @@ -22,6 +22,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" ) @@ -44,7 +45,7 @@ func (t *MainTest) TestCreateStorageHandle() { GcsAuth: cfg.GcsAuthConfig{KeyFile: "testdata/test_creds.json"}, } - storageHandle, err := createStorageHandle(newConfig, "AppName", common.NewNoopMetrics()) + storageHandle, err := createStorageHandle(newConfig, "AppName", metrics.NewNoopMetrics()) assert.Equal(t.T(), nil, err) assert.NotEqual(t.T(), nil, storageHandle) @@ -56,7 +57,7 @@ func (t *MainTest) TestCreateStorageHandle_WithClientProtocolAsGRPC() { GcsAuth: cfg.GcsAuthConfig{KeyFile: "testdata/test_creds.json"}, } - storageHandle, err := createStorageHandle(newConfig, "AppName", common.NewNoopMetrics()) + storageHandle, err := createStorageHandle(newConfig, "AppName", metrics.NewNoopMetrics()) assert.Equal(t.T(), nil, err) assert.NotEqual(t.T(), nil, storageHandle) diff --git a/cmd/mount.go b/cmd/mount.go index 67ea2307a7..f06b1b3164 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -20,9 +20,9 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/mount" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/net/context" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs" @@ -42,7 +42,7 @@ func mountWithStorageHandle( mountPoint string, newConfig *cfg.Config, storageHandle storage.StorageHandle, - metricHandle common.MetricHandle) (mfs *fuse.MountedFileSystem, err error) { + metricHandle metrics.MetricHandle) (mfs *fuse.MountedFileSystem, err error) { // Sanity check: make sure the temporary directory exists and is writable // currently. This gives a better user experience than harder to debug EIO // errors when reading files in the future. diff --git a/common/util.go b/common/util.go index 9f23f95ffd..e17b0d440f 100644 --- a/common/util.go +++ b/common/util.go @@ -15,11 +15,29 @@ package common import ( + "context" + "errors" "os/exec" "regexp" "strings" ) +type ShutdownFn func(ctx context.Context) error + +// JoinShutdownFunc combines the provided shutdown functions into a single function. +func JoinShutdownFunc(shutdownFns ...ShutdownFn) ShutdownFn { + return func(ctx context.Context) error { + var err error + for _, fn := range shutdownFns { + if fn == nil { + continue + } + err = errors.Join(err, fn(ctx)) + } + return err + } +} + // GetKernelVersion returns the kernel version. func GetKernelVersion() (string, error) { cmd := exec.Command("uname", "-r") diff --git a/common/util_test.go b/common/util_test.go index 31fc2c35b7..95bd0a037e 100644 --- a/common/util_test.go +++ b/common/util_test.go @@ -14,6 +14,8 @@ package common import ( + "context" + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -80,3 +82,67 @@ func TestIsKLCacheEvictionUnSupported(t *testing.T) { }) } } + +func TestJoinShutdownFunc(t *testing.T) { + t.Parallel() + tests := []struct { + name string + fns []ShutdownFn + expectedErrs []string + }{ + { + name: "normal", + fns: []ShutdownFn{func(_ context.Context) error { return nil }}, + expectedErrs: nil, + }, + { + name: "one_err", + fns: []ShutdownFn{func(_ context.Context) error { return fmt.Errorf("err") }}, + expectedErrs: []string{"err"}, + }, + { + name: "two_err", + fns: []ShutdownFn{ + func(_ context.Context) error { return fmt.Errorf("err1") }, + func(_ context.Context) error { return fmt.Errorf("err2") }, + }, + expectedErrs: []string{"err1", "err2"}, + }, + { + name: "two_err_one_normal", + fns: []ShutdownFn{ + func(_ context.Context) error { return fmt.Errorf("err1") }, + func(_ context.Context) error { return nil }, + func(_ context.Context) error { return fmt.Errorf("err2") }, + }, + expectedErrs: []string{"err1", "err2"}, + }, + { + name: "nil", + fns: []ShutdownFn{ + func(_ context.Context) error { return fmt.Errorf("err1") }, + nil, + func(_ context.Context) error { return fmt.Errorf("err2") }, + }, + expectedErrs: []string{"err1", "err2"}, + }, + } + + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + err := JoinShutdownFunc(tc.fns...)(context.Background()) + + if len(tc.expectedErrs) == 0 { + assert.NoError(t, err) + } else { + require.Error(t, err) + for _, e := range tc.expectedErrs { + assert.ErrorContains(t, err, e) + } + } + }) + } +} diff --git a/internal/cache/file/cache_handle_test.go b/internal/cache/file/cache_handle_test.go index 24cde3f75a..8bb0bedd6c 100644 --- a/internal/cache/file/cache_handle_test.go +++ b/internal/cache/file/cache_handle_test.go @@ -29,7 +29,6 @@ import ( "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" @@ -38,6 +37,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -155,7 +155,7 @@ func (cht *cacheHandleTest) SetupTest() { func() {}, fileCacheConfig, semaphore.NewWeighted(math.MaxInt64), - common.NewNoopMetrics(), + metrics.NewNoopMetrics(), ) cht.cacheHandle = NewCacheHandle(readLocalFileHandle, fileDownloadJob, cht.cache, false, 0) @@ -858,7 +858,7 @@ func (cht *cacheHandleTest) Test_SequentialRead_Parallel_Download_True() { func() {}, fileCacheConfig, semaphore.NewWeighted(math.MaxInt64), - common.NewNoopMetrics(), + metrics.NewNoopMetrics(), ) cht.cacheHandle.fileDownloadJob = fileDownloadJob @@ -893,7 +893,7 @@ func (cht *cacheHandleTest) Test_RandomRead_Parallel_Download_True() { func() {}, fileCacheConfig, semaphore.NewWeighted(math.MaxInt64), - common.NewNoopMetrics(), + metrics.NewNoopMetrics(), ) cht.cacheHandle.fileDownloadJob = fileDownloadJob @@ -928,7 +928,7 @@ func (cht *cacheHandleTest) Test_RandomRead_CacheForRangeReadFalse_And_ParallelD func() {}, fileCacheConfig, semaphore.NewWeighted(math.MaxInt64), - common.NewNoopMetrics(), + metrics.NewNoopMetrics(), ) // Since, it's a random read, download job will not start. diff --git a/internal/cache/file/cache_handler_test.go b/internal/cache/file/cache_handler_test.go index 76fd98c291..169efde0a4 100644 --- a/internal/cache/file/cache_handler_test.go +++ b/internal/cache/file/cache_handler_test.go @@ -27,7 +27,6 @@ import ( "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" @@ -36,6 +35,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -85,7 +85,7 @@ func initializeCacheHandlerTestArgs(t *testing.T, fileCacheConfig *cfg.FileCache // Job manager jobManager := downloader.NewJobManager(cache, util.DefaultFilePerm, - util.DefaultDirPerm, cacheDir, DefaultSequentialReadSizeMb, fileCacheConfig, common.NewNoopMetrics()) + util.DefaultDirPerm, cacheDir, DefaultSequentialReadSizeMb, fileCacheConfig, metrics.NewNoopMetrics()) // Mocked cached handler object. cacheHandler := NewCacheHandler(cache, jobManager, cacheDir, util.DefaultFilePerm, util.DefaultDirPerm, fileCacheConfig.ExperimentalExcludeRegex) diff --git a/internal/cache/file/downloader/downloader.go b/internal/cache/file/downloader/downloader.go index f79d4a6529..91be9bf916 100644 --- a/internal/cache/file/downloader/downloader.go +++ b/internal/cache/file/downloader/downloader.go @@ -19,12 +19,12 @@ import ( "os" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/sync/semaphore" ) @@ -61,12 +61,12 @@ type JobManager struct { jobs map[string]*Job mu locker.Locker maxParallelismSem *semaphore.Weighted - metricHandle common.MetricHandle + metricHandle metrics.MetricHandle } func NewJobManager(fileInfoCache *lru.Cache, filePerm os.FileMode, dirPerm os.FileMode, cacheDir string, sequentialReadSizeMb int32, c *cfg.FileCacheConfig, - metricHandle common.MetricHandle) (jm *JobManager) { + metricHandle metrics.MetricHandle) (jm *JobManager) { maxParallelDownloads := int64(math.MaxInt64) if c.MaxParallelDownloads > 0 { maxParallelDownloads = c.MaxParallelDownloads diff --git a/internal/cache/file/downloader/downloader_test.go b/internal/cache/file/downloader/downloader_test.go index 8f519170f7..c90748931a 100644 --- a/internal/cache/file/downloader/downloader_test.go +++ b/internal/cache/file/downloader/downloader_test.go @@ -24,7 +24,6 @@ import ( "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" @@ -32,6 +31,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" . "github.com/jacobsa/ogletest" "github.com/stretchr/testify/mock" @@ -70,7 +70,7 @@ func (dt *downloaderTest) setupHelper() { ExpectEq(nil, err) dt.initJobTest(DefaultObjectName, []byte("taco"), DefaultSequentialReadSizeMb, CacheMaxSize, func() {}) - dt.jm = NewJobManager(dt.cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, DefaultSequentialReadSizeMb, dt.defaultFileCacheConfig, common.NewNoopMetrics()) + dt.jm = NewJobManager(dt.cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, DefaultSequentialReadSizeMb, dt.defaultFileCacheConfig, metrics.NewNoopMetrics()) } func (dt *downloaderTest) SetUp(*TestInfo) { diff --git a/internal/cache/file/downloader/jm_parallel_downloads_test.go b/internal/cache/file/downloader/jm_parallel_downloads_test.go index 55cdd28de0..a9f60d2dd9 100644 --- a/internal/cache/file/downloader/jm_parallel_downloads_test.go +++ b/internal/cache/file/downloader/jm_parallel_downloads_test.go @@ -24,13 +24,13 @@ import ( "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -156,7 +156,7 @@ func TestParallelDownloads(t *testing.T) { WriteBufferSize: 4 * 1024 * 1024, EnableODirect: tc.enableODirect, } - jm := NewJobManager(cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, 2, fileCacheConfig, common.NewNoopMetrics()) + jm := NewJobManager(cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, 2, fileCacheConfig, metrics.NewNoopMetrics()) job := jm.CreateJobIfNotExists(&minObj, bucket) subscriberC := job.subscribe(tc.subscribedOffset) @@ -199,7 +199,7 @@ func TestMultipleConcurrentDownloads(t *testing.T) { MaxParallelDownloads: 2, WriteBufferSize: 4 * 1024 * 1024, } - jm := NewJobManager(cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, 2, fileCacheConfig, common.NewNoopMetrics()) + jm := NewJobManager(cache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, 2, fileCacheConfig, metrics.NewNoopMetrics()) job1 := jm.CreateJobIfNotExists(&minObj1, bucket) job2 := jm.CreateJobIfNotExists(&minObj2, bucket) s1 := job1.subscribe(10 * util.MiB) diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index 7487bae699..dc7cec7914 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -25,13 +25,13 @@ import ( "syscall" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" cacheutil "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/net/context" "golang.org/x/sync/semaphore" ) @@ -96,7 +96,7 @@ type Job struct { // downloaded when parallel download is enabled. rangeChan chan data.ObjectRange - metricsHandle common.MetricHandle + metricsHandle metrics.MetricHandle } // JobStatus represents the status of job. @@ -122,7 +122,7 @@ func NewJob( removeJobCallback func(), fileCacheConfig *cfg.FileCacheConfig, maxParallelismSem *semaphore.Weighted, - metricHandle common.MetricHandle, + metricHandle metrics.MetricHandle, ) (job *Job) { job = &Job{ object: object, @@ -326,7 +326,7 @@ func (job *Job) downloadObjectToFile(cacheFile *os.File) (err error) { if newReader != nil { readHandle = newReader.ReadHandle() } - common.CaptureGCSReadMetrics(job.cancelCtx, job.metricsHandle, common.ReadTypeSequential, newReaderLimit-start) + metrics.CaptureGCSReadMetrics(job.cancelCtx, job.metricsHandle, metrics.ReadTypeSequential, newReaderLimit-start) } maxRead := min(ReadChunkSize, newReaderLimit-start) diff --git a/internal/cache/file/downloader/job_test.go b/internal/cache/file/downloader/job_test.go index 36512657cf..32e36b7f7e 100644 --- a/internal/cache/file/downloader/job_test.go +++ b/internal/cache/file/downloader/job_test.go @@ -28,13 +28,13 @@ import ( "sync/atomic" "time" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" . "github.com/jacobsa/ogletest" "golang.org/x/sync/semaphore" ) @@ -63,7 +63,7 @@ func (dt *downloaderTest) initJobTest(objectName string, objectContent []byte, s } dt.cache = lru.NewCache(lruCacheSize) - dt.job = NewJob(&dt.object, dt.bucket, dt.cache, sequentialReadSize, dt.fileSpec, removeCallback, dt.defaultFileCacheConfig, semaphore.NewWeighted(math.MaxInt64), common.NewNoopMetrics()) + dt.job = NewJob(&dt.object, dt.bucket, dt.cache, sequentialReadSize, dt.fileSpec, removeCallback, dt.defaultFileCacheConfig, semaphore.NewWeighted(math.MaxInt64), metrics.NewNoopMetrics()) fileInfoKey := data.FileInfoKey{ BucketName: storage.TestBucketName, ObjectName: objectName, diff --git a/internal/cache/file/downloader/job_testify_test.go b/internal/cache/file/downloader/job_testify_test.go index 7ce082b950..7adcd7df6c 100644 --- a/internal/cache/file/downloader/job_testify_test.go +++ b/internal/cache/file/downloader/job_testify_test.go @@ -22,8 +22,8 @@ import ( "strings" "testing" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/net/context" "golang.org/x/sync/semaphore" @@ -65,7 +65,7 @@ func (t *JobTestifyTest) initReadCacheTestifyTest(objectName string, objectConte DirPerm: util.DefaultDirPerm, } t.cache = lru.NewCache(lruCacheSize) - t.job = NewJob(&t.object, t.mockBucket, t.cache, sequentialReadSize, t.fileSpec, removeCallback, t.defaultFileCacheConfig, semaphore.NewWeighted(math.MaxInt64), common.NewNoopMetrics()) + t.job = NewJob(&t.object, t.mockBucket, t.cache, sequentialReadSize, t.fileSpec, removeCallback, t.defaultFileCacheConfig, semaphore.NewWeighted(math.MaxInt64), metrics.NewNoopMetrics()) fileInfoKey := data.FileInfoKey{ BucketName: storage.TestBucketName, ObjectName: objectName, diff --git a/internal/cache/file/downloader/parallel_downloads_job.go b/internal/cache/file/downloader/parallel_downloads_job.go index e74d3f4560..6f03b60095 100644 --- a/internal/cache/file/downloader/parallel_downloads_job.go +++ b/internal/cache/file/downloader/parallel_downloads_job.go @@ -21,11 +21,11 @@ import ( "io" "os" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/data" cacheutil "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/sync/errgroup" ) @@ -60,7 +60,7 @@ func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, e } }() - common.CaptureGCSReadMetrics(ctx, job.metricsHandle, common.ReadTypeParallel, end-start) + metrics.CaptureGCSReadMetrics(ctx, job.metricsHandle, metrics.ReadTypeParallel, end-start) // Use standard copy function if O_DIRECT is disabled and memory aligned // buffer otherwise. diff --git a/internal/fs/caching_test.go b/internal/fs/caching_test.go index 406cce4821..3e212048e7 100644 --- a/internal/fs/caching_test.go +++ b/internal/fs/caching_test.go @@ -21,7 +21,6 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" @@ -29,6 +28,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" @@ -479,7 +479,7 @@ func (t *MultiBucketMountCachingTest) SetUpTestSuite() { // Enable directory type caching. t.serverCfg.DirTypeCacheTTL = ttl - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() // Call through. t.fsTest.SetUpTestSuite() diff --git a/internal/fs/fs.go b/internal/fs/fs.go index eec03c9204..8b3508b3e1 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -29,11 +29,11 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/sync/semaphore" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" @@ -130,7 +130,7 @@ type ServerConfig struct { // NewConfig has all the config specified by the user using config-file or CLI flags. NewConfig *cfg.Config - MetricHandle common.MetricHandle + MetricHandle metrics.MetricHandle } // Create a fuse file system server according to the supplied configuration. @@ -487,7 +487,7 @@ type fileSystem struct { // random file access. cacheFileForRangeRead bool - metricHandle common.MetricHandle + metricHandle metrics.MetricHandle enableAtomicRenameObject bool diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index 5312d48a91..a6fc062282 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -30,7 +30,6 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" @@ -39,6 +38,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/ogletest" @@ -169,7 +169,7 @@ func (t *fsTest) SetUpTestSuite() { EnableNewReader: true, } } - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() // Set up ownership. t.serverCfg.Uid, t.serverCfg.Gid, err = perms.MyUserAndGroup() @@ -372,7 +372,7 @@ func (bm *fakeBucketManager) ShutDown() {} func (bm *fakeBucketManager) SetUpBucket( ctx context.Context, - name string, isMultibucketMount bool, _ common.MetricHandle) (sb gcsx.SyncerBucket, err error) { + name string, isMultibucketMount bool, _ metrics.MetricHandle) (sb gcsx.SyncerBucket, err error) { bucket, ok := bm.buckets[name] if ok { sb = gcsx.NewSyncerBucket( diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 2e3b02a64c..4e5d35663c 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -20,13 +20,13 @@ import ( "io" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx/read_manager" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/syncutil" "golang.org/x/net/context" ) @@ -59,7 +59,7 @@ type FileHandle struct { // cacheFileForRangeRead is also valid for cache workflow, if true, object content // will be downloaded for random reads as well too. cacheFileForRangeRead bool - metricHandle common.MetricHandle + metricHandle metrics.MetricHandle // openMode is used to store the mode in which the file is opened. openMode util.OpenMode @@ -68,7 +68,7 @@ type FileHandle struct { } // LOCKS_REQUIRED(fh.inode.mu) -func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle, openMode util.OpenMode, rc *cfg.ReadConfig) (fh *FileHandle) { +func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle metrics.MetricHandle, openMode util.OpenMode, rc *cfg.ReadConfig) (fh *FileHandle) { fh = &FileHandle{ inode: inode, fileCacheHandler: fileCacheHandler, diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 226e1825a8..8ad4855607 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -24,7 +24,6 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" @@ -32,6 +31,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/timeutil" "github.com/stretchr/testify/assert" @@ -167,7 +167,7 @@ func (t *fileTest) Test_Read_Success() { expectedData := []byte("hello from reader") parent := createDirInode(&t.bucket, &t.clock, "parentRoot") in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, "test_obj_reader", expectedData, false) - fh := NewFileHandle(in, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) buf := make([]byte, len(expectedData)) fh.inode.Lock() @@ -183,7 +183,7 @@ func (t *fileTest) Test_ReadWithReadManager_Success() { expectedData := []byte("hello from readManager") parent := createDirInode(&t.bucket, &t.clock, "parentRoot") in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, "test_obj_readManager", expectedData, false) - fh := NewFileHandle(in, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) buf := make([]byte, len(expectedData)) fh.inode.Lock() @@ -215,7 +215,7 @@ func (t *fileTest) Test_ReadWithReadManager_ErrorScenarios() { t.SetupTest() parent := createDirInode(&t.bucket, &t.clock, "parentRoot") testInode := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, []byte("data"), false) - fh := NewFileHandle(testInode, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) fh.inode.Lock() mockRM := new(read_manager.MockReadManager) mockRM.On("ReadAt", t.ctx, dst, int64(0)).Return(gcsx.ReaderResponse{}, tc.returnErr) @@ -253,7 +253,7 @@ func (t *fileTest) Test_Read_ErrorScenarios() { t.SetupTest() parent := createDirInode(&t.bucket, &t.clock, "parentRoot") testInode := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, []byte("data"), false) - fh := NewFileHandle(testInode, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) fh.inode.Lock() mockReader := new(gcsx.MockRandomReader) mockReader.On("ReadAt", t.ctx, dst, int64(0)).Return(gcsx.ObjectData{}, tc.returnErr) @@ -278,7 +278,7 @@ func (t *fileTest) Test_ReadWithReadManager_FallbackToInode() { object := gcs.MinObject{Name: "test_obj", Generation: 0} parent := createDirInode(&t.bucket, &t.clock, "parentRoot") in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, objectData, true) - fh := NewFileHandle(in, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) fh.inode.Lock() mockRM := new(read_manager.MockReadManager) mockRM.On("Destroy").Return() @@ -300,7 +300,7 @@ func (t *fileTest) Test_Read_FallbackToInode() { object := gcs.MinObject{Name: "test_obj", Generation: 0} parent := createDirInode(&t.bucket, &t.clock, "parentRoot") in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, objectData, true) - fh := NewFileHandle(in, nil, false, common.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) fh.inode.Lock() mockR := new(gcsx.MockRandomReader) mockR.On("Destroy").Return() diff --git a/internal/fs/hns_bucket_test.go b/internal/fs/hns_bucket_test.go index b5cfffd680..50aa20494b 100644 --- a/internal/fs/hns_bucket_test.go +++ b/internal/fs/hns_bucket_test.go @@ -24,12 +24,12 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/caching" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/timeutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -65,7 +65,7 @@ func (t *HNSBucketTests) SetupSuite() { EnableHns: true, EnableAtomicRenameObject: true, } - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() bucketType = gcs.BucketType{Hierarchical: true} t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/implicit_dirs_test.go b/internal/fs/implicit_dirs_test.go index 8e58a9defc..1bfb7cec14 100644 --- a/internal/fs/implicit_dirs_test.go +++ b/internal/fs/implicit_dirs_test.go @@ -24,8 +24,8 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/fuse/fusetesting" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" @@ -46,7 +46,7 @@ func init() { func (t *ImplicitDirsTest) SetUpTestSuite() { t.serverCfg.ImplicitDirectories = true - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/implicit_dirs_with_cache_test.go b/internal/fs/implicit_dirs_with_cache_test.go index b3d8cdcb56..8d010ad393 100644 --- a/internal/fs/implicit_dirs_with_cache_test.go +++ b/internal/fs/implicit_dirs_with_cache_test.go @@ -24,7 +24,7 @@ import ( "path" "time" - "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" ) @@ -44,7 +44,7 @@ func init() { func (t *ImplicitDirsWithCacheTest) SetUpTestSuite() { t.serverCfg.ImplicitDirectories = true t.serverCfg.DirTypeCacheTTL = time.Minute * 3 - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/inode/base_dir.go b/internal/fs/inode/base_dir.go index 2d6ff070b4..d3a173f304 100644 --- a/internal/fs/inode/base_dir.go +++ b/internal/fs/inode/base_dir.go @@ -18,8 +18,8 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/locker" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/jacobsa/fuse" @@ -62,7 +62,7 @@ type baseDirInode struct { // GUARDED_BY(mu) buckets map[string]gcsx.SyncerBucket - metricHandle common.MetricHandle + metricHandle metrics.MetricHandle } // NewBaseDirInode returns a baseDirInode that acts as the directory of @@ -72,7 +72,7 @@ func NewBaseDirInode( name Name, attrs fuseops.InodeAttributes, bm gcsx.BucketManager, - metricHandle common.MetricHandle) (d DirInode) { + metricHandle metrics.MetricHandle) (d DirInode) { typed := &baseDirInode{ id: id, name: NewRootName(""), diff --git a/internal/fs/inode/base_dir_test.go b/internal/fs/inode/base_dir_test.go index 86c38bd11e..4e1979f92e 100644 --- a/internal/fs/inode/base_dir_test.go +++ b/internal/fs/inode/base_dir_test.go @@ -21,10 +21,10 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/net/context" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" @@ -93,7 +93,7 @@ type fakeBucketManager struct { func (bm *fakeBucketManager) SetUpBucket( ctx context.Context, - name string, isMultibucketMount bool, _ common.MetricHandle) (sb gcsx.SyncerBucket, err error) { + name string, isMultibucketMount bool, _ metrics.MetricHandle) (sb gcsx.SyncerBucket, err error) { bm.setupTimes++ var ok bool @@ -125,7 +125,7 @@ func (t *BaseDirTest) resetInode() { Mode: dirMode, }, t.bm, - common.NewNoopMetrics()) + metrics.NewNoopMetrics()) t.in.Lock() } diff --git a/internal/fs/kernel_list_cache_inifinite_ttl_test.go b/internal/fs/kernel_list_cache_inifinite_ttl_test.go index d8effb5ae7..3994942f7f 100644 --- a/internal/fs/kernel_list_cache_inifinite_ttl_test.go +++ b/internal/fs/kernel_list_cache_inifinite_ttl_test.go @@ -25,6 +25,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -58,7 +59,7 @@ func (t *KernelListCacheTestWithInfiniteTtl) SetupSuite() { }, } t.serverCfg.RenameDirLimit = 10 - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/kernel_list_cache_test.go b/internal/fs/kernel_list_cache_test.go index bc320d5c80..cf35e789a6 100644 --- a/internal/fs/kernel_list_cache_test.go +++ b/internal/fs/kernel_list_cache_test.go @@ -31,8 +31,8 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -115,7 +115,7 @@ func (t *KernelListCacheTestWithPositiveTtl) SetupSuite() { }, } t.serverCfg.RenameDirLimit = 10 - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/kernel_list_cache_zero_ttl_test.go b/internal/fs/kernel_list_cache_zero_ttl_test.go index c40d6e3f98..a4077eb6fc 100644 --- a/internal/fs/kernel_list_cache_zero_ttl_test.go +++ b/internal/fs/kernel_list_cache_zero_ttl_test.go @@ -22,7 +22,7 @@ import ( "testing" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -45,7 +45,7 @@ func (t *KernelListCacheTestWithZeroTtl) SetupSuite() { }, } t.serverCfg.RenameDirLimit = 10 - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/local_file_test.go b/internal/fs/local_file_test.go index c3bc3d2f02..5bdc390be1 100644 --- a/internal/fs/local_file_test.go +++ b/internal/fs/local_file_test.go @@ -30,10 +30,10 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/fuse/fusetesting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -876,7 +876,7 @@ func (t *LocalFileTest) TestStatFailsOnNewFileAfterDeletion() { }, Logging: cfg.DefaultLoggingConfig(), } - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() filePath := path.Join(mntDir, "test.txt") f1, err := os.Create(filePath) require.NoError(t.T(), err) diff --git a/internal/fs/parallel_dirops_test.go b/internal/fs/parallel_dirops_test.go index f8d4416a48..03349576c1 100644 --- a/internal/fs/parallel_dirops_test.go +++ b/internal/fs/parallel_dirops_test.go @@ -24,7 +24,7 @@ import ( "testing" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -48,7 +48,7 @@ func (t *ParallelDiropsTest) SetupSuite() { DisableParallelDirops: false, }} t.serverCfg.RenameDirLimit = 10 - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/read_cache_test.go b/internal/fs/read_cache_test.go index 5f1e860ffc..b445412fc3 100644 --- a/internal/fs/read_cache_test.go +++ b/internal/fs/read_cache_test.go @@ -26,9 +26,9 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" . "github.com/jacobsa/ogletest" ) @@ -76,7 +76,7 @@ func (t *FileCacheTest) SetUpTestSuite() { }, CacheDir: cfg.ResolvedPath(CacheDir), } - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() t.fsTest.SetUpTestSuite() } diff --git a/internal/fs/type_cache_test.go b/internal/fs/type_cache_test.go index 1fafe2f50d..e712a57269 100644 --- a/internal/fs/type_cache_test.go +++ b/internal/fs/type_cache_test.go @@ -27,12 +27,12 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" gcsfusefs "github.com/googlecloudplatform/gcsfuse/v3/internal/fs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" @@ -79,7 +79,7 @@ func (t *typeCacheTestCommon) SetUpTestSuite() { TtlSecs: ttlInSeconds, }, } - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() // Fill server-cfg from mount-config. func(newConfig *cfg.Config, serverCfg *gcsfusefs.ServerConfig) { diff --git a/internal/fs/wrappers/monitoring.go b/internal/fs/wrappers/monitoring.go index 0f8a5e7edd..ec047a8276 100644 --- a/internal/fs/wrappers/monitoring.go +++ b/internal/fs/wrappers/monitoring.go @@ -20,7 +20,7 @@ import ( "syscall" "time" - "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/fuse/fuseutil" ) @@ -225,13 +225,13 @@ func categorize(err error) string { } // Records file system operation count, failed operation count and the operation latency. -func recordOp(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time, fsErr error) { +func recordOp(ctx context.Context, metricHandle metrics.MetricHandle, method string, start time.Time, fsErr error) { metricHandle.OpsCount(ctx, 1, method) // Recording opErrorCount. if fsErr != nil { errCategory := categorize(fsErr) - metricHandle.OpsErrorCount(ctx, 1, common.FSOpsErrorCategory{ + metricHandle.OpsErrorCount(ctx, 1, metrics.FSOpsErrorCategory{ FSOps: method, ErrorCategory: errCategory, }) @@ -241,7 +241,7 @@ func recordOp(ctx context.Context, metricHandle common.MetricHandle, method stri // WithMonitoring takes a FileSystem, returns a FileSystem with monitoring // on the counts of requests per API. -func WithMonitoring(fs fuseutil.FileSystem, metricHandle common.MetricHandle) fuseutil.FileSystem { +func WithMonitoring(fs fuseutil.FileSystem, metricHandle metrics.MetricHandle) fuseutil.FileSystem { return &monitoring{ wrapped: fs, metricHandle: metricHandle, @@ -250,7 +250,7 @@ func WithMonitoring(fs fuseutil.FileSystem, metricHandle common.MetricHandle) fu type monitoring struct { wrapped fuseutil.FileSystem - metricHandle common.MetricHandle + metricHandle metrics.MetricHandle } func (fs *monitoring) Destroy() { diff --git a/internal/fs/zonal_bucket_test.go b/internal/fs/zonal_bucket_test.go index 59441a4c1d..21ee775582 100644 --- a/internal/fs/zonal_bucket_test.go +++ b/internal/fs/zonal_bucket_test.go @@ -17,8 +17,8 @@ package fs_test import ( "testing" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -32,7 +32,7 @@ func TestZonalBucketTests(t *testing.T) { suite.Run(t, new(ZonalBucketTests)) } func (t *ZonalBucketTests) SetupSuite() { t.serverCfg.ImplicitDirectories = false - t.serverCfg.MetricHandle = common.NewNoopMetrics() + t.serverCfg.MetricHandle = metrics.NewNoopMetrics() bucketType = gcs.BucketType{Zonal: true} t.fsTest.SetUpTestSuite() } diff --git a/internal/gcsx/bucket_manager.go b/internal/gcsx/bucket_manager.go index e271bc2d66..052fa5b4a6 100644 --- a/internal/gcsx/bucket_manager.go +++ b/internal/gcsx/bucket_manager.go @@ -21,7 +21,6 @@ import ( "path" "time" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" "github.com/googlecloudplatform/gcsfuse/v3/internal/canned" @@ -31,6 +30,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/caching" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/timeutil" ) @@ -72,7 +72,7 @@ type BucketConfig struct { type BucketManager interface { SetUpBucket( ctx context.Context, - name string, isMultibucketMount bool, metricHandle common.MetricHandle) (b SyncerBucket, err error) + name string, isMultibucketMount bool, metricHandle metrics.MetricHandle) (b SyncerBucket, err error) // Shuts down the bucket manager and its buckets ShutDown() @@ -161,7 +161,7 @@ func (bm *bucketManager) SetUpBucket( ctx context.Context, name string, isMultibucketMount bool, - metricHandle common.MetricHandle, + metricHandle metrics.MetricHandle, ) (sb SyncerBucket, err error) { var b gcs.Bucket // Set up the appropriate backing bucket. diff --git a/internal/gcsx/bucket_manager_test.go b/internal/gcsx/bucket_manager_test.go index 934bd518f4..4436f263a4 100644 --- a/internal/gcsx/bucket_manager_test.go +++ b/internal/gcsx/bucket_manager_test.go @@ -22,9 +22,9 @@ import ( "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" . "github.com/jacobsa/ogletest" "github.com/stretchr/testify/mock" ) @@ -107,7 +107,7 @@ func (t *BucketManagerTest) TestSetUpBucketMethod() { bm.config = bucketConfig bm.gcCtx = ctx - bucket, err := bm.SetUpBucket(context.Background(), TestBucketName, false, common.NewNoopMetrics()) + bucket, err := bm.SetUpBucket(context.Background(), TestBucketName, false, metrics.NewNoopMetrics()) ExpectNe(nil, bucket.Syncer) ExpectEq(nil, err) @@ -131,7 +131,7 @@ func (t *BucketManagerTest) TestSetUpBucketMethod_IsMultiBucketMountTrue() { bm.config = bucketConfig bm.gcCtx = ctx - bucket, err := bm.SetUpBucket(context.Background(), TestBucketName, true, common.NewNoopMetrics()) + bucket, err := bm.SetUpBucket(context.Background(), TestBucketName, true, metrics.NewNoopMetrics()) ExpectNe(nil, bucket.Syncer) ExpectEq(nil, err) @@ -155,7 +155,7 @@ func (t *BucketManagerTest) TestSetUpBucketMethodWhenBucketDoesNotExist() { bm.config = bucketConfig bm.gcCtx = ctx - bucket, err := bm.SetUpBucket(context.Background(), invalidBucketName, false, common.NewNoopMetrics()) + bucket, err := bm.SetUpBucket(context.Background(), invalidBucketName, false, metrics.NewNoopMetrics()) AssertNe(nil, err) ExpectTrue(strings.Contains(err.Error(), "error in iterating through objects: storage: bucket doesn't exist")) @@ -180,7 +180,7 @@ func (t *BucketManagerTest) TestSetUpBucketMethodWhenBucketDoesNotExist_IsMultiB bm.config = bucketConfig bm.gcCtx = ctx - bucket, err := bm.SetUpBucket(context.Background(), invalidBucketName, true, common.NewNoopMetrics()) + bucket, err := bm.SetUpBucket(context.Background(), invalidBucketName, true, metrics.NewNoopMetrics()) AssertNe(nil, err) ExpectTrue(strings.Contains(err.Error(), "error in iterating through objects: storage: bucket doesn't exist")) diff --git a/internal/gcsx/client_readers/gcs_reader.go b/internal/gcsx/client_readers/gcs_reader.go index 2e111a6ac7..596b817c97 100644 --- a/internal/gcsx/client_readers/gcs_reader.go +++ b/internal/gcsx/client_readers/gcs_reader.go @@ -21,9 +21,9 @@ import ( "io" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" ) // ReaderType represents different types of go-sdk gcs readers. @@ -76,7 +76,7 @@ type GCSReader struct { } type GCSReaderConfig struct { - MetricHandle common.MetricHandle + MetricHandle metrics.MetricHandle MrdWrapper *gcsx.MultiRangeDownloaderWrapper SequentialReadSizeMb int32 ReadConfig *cfg.ReadConfig @@ -89,7 +89,7 @@ func NewGCSReader(obj *gcs.MinObject, bucket gcs.Bucket, config *GCSReaderConfig sequentialReadSizeMb: config.SequentialReadSizeMb, rangeReader: NewRangeReader(obj, bucket, config.ReadConfig, config.MetricHandle), mrr: NewMultiRangeReader(obj, config.MetricHandle, config.MrdWrapper), - readType: common.ReadTypeSequential, + readType: metrics.ReadTypeSequential, } } @@ -149,7 +149,7 @@ func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.R // readerType specifies the go-sdk interface to use for reads. func (gr *GCSReader) readerType(start int64, end int64, bucketType gcs.BucketType) ReaderType { bytesToBeRead := end - start - if gr.readType == common.ReadTypeRandom && bytesToBeRead < maxReadSize && bucketType.Zonal { + if gr.readType == metrics.ReadTypeRandom && bytesToBeRead < maxReadSize && bucketType.Zonal { return MultiRangeReaderType } return RangeReaderType @@ -177,7 +177,7 @@ func (gr *GCSReader) getReadInfo(start int64, size int64) (int64, error) { func (gr *GCSReader) determineEnd(start int64) int64 { end := int64(gr.object.Size) if gr.seeks >= minSeeksForRandom { - gr.readType = common.ReadTypeRandom + gr.readType = metrics.ReadTypeRandom averageReadBytes := gr.totalReadBytes / gr.seeks if averageReadBytes < maxReadSize { randomReadSize := int64(((averageReadBytes / MB) + 1) * MB) diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go index afc79ffa88..04428db87a 100644 --- a/internal/gcsx/client_readers/gcs_reader_test.go +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -22,13 +22,13 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" testUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -75,7 +75,7 @@ func (t *gcsReaderTest) SetupTest() { } t.mockBucket = new(storage.TestifyMockBucket) t.gcsReader = NewGCSReader(t.object, t.mockBucket, &GCSReaderConfig{ - MetricHandle: common.NewNoopMetrics(), + MetricHandle: metrics.NewNoopMetrics(), MrdWrapper: nil, SequentialReadSizeMb: sequentialReadSizeInMb, ReadConfig: nil, @@ -99,7 +99,7 @@ func (t *gcsReaderTest) Test_NewGCSReader() { } gcsReader := NewGCSReader(object, t.mockBucket, &GCSReaderConfig{ - MetricHandle: common.NewNoopMetrics(), + MetricHandle: metrics.NewNoopMetrics(), MrdWrapper: nil, SequentialReadSizeMb: 200, ReadConfig: nil, @@ -107,7 +107,7 @@ func (t *gcsReaderTest) Test_NewGCSReader() { assert.Equal(t.T(), object, gcsReader.object) assert.Equal(t.T(), t.mockBucket, gcsReader.bucket) - assert.Equal(t.T(), common.ReadTypeSequential, gcsReader.readType) + assert.Equal(t.T(), metrics.ReadTypeSequential, gcsReader.readType) } func (t *gcsReaderTest) Test_ReadAt_InvalidOffset() { @@ -290,7 +290,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential}, + expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -298,7 +298,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential}, + expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -306,7 +306,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeRandom, common.ReadTypeRandom, common.ReadTypeRandom}, + expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, { @@ -314,7 +314,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeRandom, common.ReadTypeRandom, common.ReadTypeRandom}, + expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, } @@ -325,7 +325,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { require.Equal(t.T(), len(tc.readRanges), len(tc.expectedReadTypes), "Test Parameter Error: readRanges and expectedReadTypes should have same length") t.gcsReader.mrr.isMRDInUse = false t.gcsReader.seeks = 0 - t.gcsReader.rangeReader.readType = common.ReadTypeSequential + t.gcsReader.rangeReader.readType = metrics.ReadTypeSequential t.gcsReader.expectedOffset = 0 t.object.Size = uint64(tc.dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) @@ -608,7 +608,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateZonalRandomReads() { t.gcsReader.rangeReader.reader = nil t.gcsReader.mrr.isMRDInUse = false t.gcsReader.seeks = 0 - t.gcsReader.rangeReader.readType = common.ReadTypeSequential + t.gcsReader.rangeReader.readType = metrics.ReadTypeSequential t.gcsReader.expectedOffset = 0 t.gcsReader.totalReadBytes = 0 t.object.Size = 20 * MiB @@ -640,7 +640,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateZonalRandomReads() { assert.NoError(t.T(), err) assert.Equal(t.T(), uint64(seeks), t.gcsReader.seeks) - assert.Equal(t.T(), common.ReadTypeRandom, t.gcsReader.readType) + assert.Equal(t.T(), metrics.ReadTypeRandom, t.gcsReader.readType) assert.Equal(t.T(), int64(readRange[1]), t.gcsReader.expectedOffset) } } diff --git a/internal/gcsx/client_readers/multi_range_reader.go b/internal/gcsx/client_readers/multi_range_reader.go index 813e477468..c96822b307 100644 --- a/internal/gcsx/client_readers/multi_range_reader.go +++ b/internal/gcsx/client_readers/multi_range_reader.go @@ -20,10 +20,10 @@ import ( "io" "time" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" ) // TimeoutForMultiRangeRead is the timeout value for multi-range read operations. @@ -42,10 +42,10 @@ type MultiRangeReader struct { // boolean variable to determine if MRD is being used or not. isMRDInUse bool - metricHandle common.MetricHandle + metricHandle metrics.MetricHandle } -func NewMultiRangeReader(object *gcs.MinObject, metricHandle common.MetricHandle, mrdWrapper *gcsx.MultiRangeDownloaderWrapper) *MultiRangeReader { +func NewMultiRangeReader(object *gcs.MinObject, metricHandle metrics.MetricHandle, mrdWrapper *gcsx.MultiRangeDownloaderWrapper) *MultiRangeReader { return &MultiRangeReader{ object: object, metricHandle: metricHandle, diff --git a/internal/gcsx/client_readers/multi_range_reader_test.go b/internal/gcsx/client_readers/multi_range_reader_test.go index 7c89ded6c2..d22dabfede 100644 --- a/internal/gcsx/client_readers/multi_range_reader_test.go +++ b/internal/gcsx/client_readers/multi_range_reader_test.go @@ -21,13 +21,13 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" testUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -67,7 +67,7 @@ func (t *multiRangeReaderTest) SetupTest() { Size: 17, Generation: 1234, } - t.multiRangeReader = NewMultiRangeReader(t.object, common.NewNoopMetrics(), nil) + t.multiRangeReader = NewMultiRangeReader(t.object, metrics.NewNoopMetrics(), nil) } func (t *multiRangeReaderTest) TearDownTest() { diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index 89550dbb52..cf8ac5ab14 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -22,11 +22,11 @@ import ( "math" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" ) const ( @@ -62,10 +62,10 @@ type RangeReader struct { readType string readConfig *cfg.ReadConfig - metricHandle common.MetricHandle + metricHandle metrics.MetricHandle } -func NewRangeReader(object *gcs.MinObject, bucket gcs.Bucket, readConfig *cfg.ReadConfig, metricHandle common.MetricHandle) *RangeReader { +func NewRangeReader(object *gcs.MinObject, bucket gcs.Bucket, readConfig *cfg.ReadConfig, metricHandle metrics.MetricHandle) *RangeReader { return &RangeReader{ object: object, bucket: bucket, @@ -187,7 +187,7 @@ func (rr *RangeReader) readFromRangeReader(ctx context.Context, p []byte, offset } requestedDataSize := end - offset - common.CaptureGCSReadMetrics(ctx, rr.metricHandle, readType, requestedDataSize) + metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, readType, requestedDataSize) return n, err } @@ -279,7 +279,7 @@ func (rr *RangeReader) startRead(start int64, end int64) error { rr.limit = end requestedDataSize := end - start - common.CaptureGCSReadMetrics(ctx, rr.metricHandle, common.ReadTypeSequential, requestedDataSize) + metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeSequential, requestedDataSize) return nil } diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go index 46b61f7521..fac3675b10 100644 --- a/internal/gcsx/client_readers/range_reader_test.go +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -24,13 +24,13 @@ import ( "testing/iotest" "time" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" testUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" @@ -64,7 +64,7 @@ func (t *rangeReaderTest) SetupTest() { Generation: 1234, } t.mockBucket = new(storage.TestifyMockBucket) - t.rangeReader = NewRangeReader(t.object, t.mockBucket, nil, common.NewNoopMetrics()) + t.rangeReader = NewRangeReader(t.object, t.mockBucket, nil, metrics.NewNoopMetrics()) t.ctx = context.Background() } @@ -146,11 +146,11 @@ func (t *rangeReaderTest) Test_NewRangeReader() { Generation: 4321, } - reader := NewRangeReader(object, t.mockBucket, nil, common.NewNoopMetrics()) + reader := NewRangeReader(object, t.mockBucket, nil, metrics.NewNoopMetrics()) assert.Equal(t.T(), object, reader.object) assert.Equal(t.T(), t.mockBucket, reader.bucket) - assert.Equal(t.T(), common.NewNoopMetrics(), reader.metricHandle) + assert.Equal(t.T(), metrics.NewNoopMetrics(), reader.metricHandle) assert.Equal(t.T(), int64(-1), reader.start) assert.Equal(t.T(), int64(-1), reader.limit) } diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index 9c372044b7..da1276f7ba 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -22,12 +22,12 @@ import ( "time" "github.com/google/uuid" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" cacheUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/fuse/fuseops" ) @@ -54,10 +54,10 @@ type FileCacheReader struct { // using fileCacheHandler for the given object and bucket. fileCacheHandle *file.CacheHandle - metricHandle common.MetricHandle + metricHandle metrics.MetricHandle } -func NewFileCacheReader(o *gcs.MinObject, bucket gcs.Bucket, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle) *FileCacheReader { +func NewFileCacheReader(o *gcs.MinObject, bucket gcs.Bucket, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle metrics.MetricHandle) *FileCacheReader { return &FileCacheReader{ object: o, bucket: bucket, @@ -117,9 +117,9 @@ func (fc *FileCacheReader) tryReadingFromFileCache(ctx context.Context, p []byte logger.Tracef("%.13v -> %s", requestID, requestOutput) - readType := common.ReadTypeRandom + readType := metrics.ReadTypeRandom if isSequential { - readType = common.ReadTypeSequential + readType = metrics.ReadTypeSequential } captureFileCacheMetrics(ctx, fc.metricHandle, readType, bytesRead, cacheHit, executionTime) }() @@ -207,8 +207,8 @@ func (fc *FileCacheReader) ReadAt(ctx context.Context, p []byte, offset int64) ( return readerResponse, err } -func captureFileCacheMetrics(ctx context.Context, metricHandle common.MetricHandle, readType string, readDataSize int, cacheHit bool, readLatency time.Duration) { - metricHandle.FileCacheReadCount(ctx, 1, common.CacheHitReadType{ +func captureFileCacheMetrics(ctx context.Context, metricHandle metrics.MetricHandle, readType string, readDataSize int, cacheHit bool, readLatency time.Duration) { + metricHandle.FileCacheReadCount(ctx, 1, metrics.CacheHitReadType{ ReadType: readType, CacheHit: cacheHit, }) diff --git a/internal/gcsx/file_cache_reader_test.go b/internal/gcsx/file_cache_reader_test.go index f3941e76be..dfa468adc1 100644 --- a/internal/gcsx/file_cache_reader_test.go +++ b/internal/gcsx/file_cache_reader_test.go @@ -26,7 +26,6 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" @@ -35,6 +34,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -90,10 +90,10 @@ func (t *fileCacheReaderTest) SetupTest() { t.mockBucket = new(storage.TestifyMockBucket) t.cacheDir = path.Join(os.Getenv("HOME"), "test_cache_dir") lruCache := lru.NewCache(cacheMaxSize) - t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{EnableCrc: false}, common.NewNoopMetrics()) + t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{EnableCrc: false}, metrics.NewNoopMetrics()) t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm, "") - t.reader = NewFileCacheReader(t.object, t.mockBucket, t.cacheHandler, true, common.NewNoopMetrics()) - t.reader_unfinalized_object = NewFileCacheReader(t.unfinalized_object, t.mockBucket, t.cacheHandler, true, common.NewNoopMetrics()) + t.reader = NewFileCacheReader(t.object, t.mockBucket, t.cacheHandler, true, metrics.NewNoopMetrics()) + t.reader_unfinalized_object = NewFileCacheReader(t.unfinalized_object, t.mockBucket, t.cacheHandler, true, metrics.NewNoopMetrics()) t.ctx = context.Background() } diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go index 278f384677..14726d8325 100644 --- a/internal/gcsx/multi_range_downloader_wrapper.go +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -22,11 +22,11 @@ import ( "time" "github.com/google/uuid" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/monitor" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/net/context" ) @@ -186,7 +186,7 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) ensureMultiRangeDownloader() (err // Reads the data using MultiRangeDownloader. func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []byte, - startOffset int64, endOffset int64, timeout time.Duration, metricHandle common.MetricHandle) (bytesRead int, err error) { + startOffset int64, endOffset int64, timeout time.Duration, metricHandle metrics.MetricHandle) (bytesRead int, err error) { // Bidi Api with 0 as read_limit means no limit whereas we do not want to read anything with empty buffer. // Hence, handling it separately. if len(buf) == 0 { diff --git a/internal/gcsx/multi_range_downloader_wrapper_test.go b/internal/gcsx/multi_range_downloader_wrapper_test.go index a81ef5c9b1..01e2924f17 100644 --- a/internal/gcsx/multi_range_downloader_wrapper_test.go +++ b/internal/gcsx/multi_range_downloader_wrapper_test.go @@ -22,12 +22,12 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" @@ -157,7 +157,7 @@ func (t *mrdWrapperTest) Test_Read() { t.mrdWrapper.Wrapped = nil t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond)) - bytesRead, err := t.mrdWrapper.Read(context.Background(), buf, int64(tc.start), int64(tc.end), 10*time.Millisecond, common.NewNoopMetrics()) + bytesRead, err := t.mrdWrapper.Read(context.Background(), buf, int64(tc.start), int64(tc.end), 10*time.Millisecond, metrics.NewNoopMetrics()) assert.NoError(t.T(), err) assert.Equal(t.T(), tc.end-tc.start, bytesRead) @@ -170,7 +170,7 @@ func (t *mrdWrapperTest) Test_Read_ErrorInCreatingMRD() { t.mrdWrapper.Wrapped = nil t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("Error in creating MRD")).Once() - bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, common.NewNoopMetrics()) + bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) assert.ErrorContains(t.T(), err, "MultiRangeDownloaderWrapper::Read: Error in creating MultiRangeDownloader") assert.Equal(t.T(), 0, bytesRead) @@ -180,7 +180,7 @@ func (t *mrdWrapperTest) Test_Read_Timeout() { t.mrdWrapper.Wrapped = nil t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, t.mrdTimeout+2*time.Millisecond), nil).Once() - bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, common.NewNoopMetrics()) + bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) assert.ErrorContains(t.T(), err, "Timeout") assert.Equal(t.T(), 0, bytesRead) @@ -192,7 +192,7 @@ func (t *mrdWrapperTest) Test_Read_ContextCancelled() { ctx, cancel := context.WithCancel(context.Background()) cancel() - bytesRead, err := t.mrdWrapper.Read(ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, common.NewNoopMetrics()) + bytesRead, err := t.mrdWrapper.Read(ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) assert.ErrorContains(t.T(), err, "Context Cancelled") assert.Equal(t.T(), 0, bytesRead) @@ -202,7 +202,7 @@ func (t *mrdWrapperTest) Test_Read_EOF() { t.mrdWrapper.Wrapped = nil t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleepAndDefaultError(t.object, t.objectData, time.Microsecond, io.EOF), nil).Once() - _, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, common.NewNoopMetrics()) + _, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) assert.ErrorIs(t.T(), err, io.EOF) } @@ -211,7 +211,7 @@ func (t *mrdWrapperTest) Test_Read_Error() { t.mrdWrapper.Wrapped = nil t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleepAndDefaultError(t.object, t.objectData, time.Microsecond, fmt.Errorf("Error")), nil).Once() - bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, common.NewNoopMetrics()) + bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) assert.ErrorContains(t.T(), err, "Error in Add Call") assert.Equal(t.T(), 0, bytesRead) diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 835b43c519..5bc8c6f28f 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -23,13 +23,13 @@ import ( "github.com/google/uuid" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" cacheutil "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/fuse/fuseops" "golang.org/x/net/context" ) @@ -103,7 +103,7 @@ const ( // NewRandomReader create a random reader for the supplied object record that // reads using the given bucket. -func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb int32, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle common.MetricHandle, mrdWrapper *MultiRangeDownloaderWrapper, config *cfg.ReadConfig) RandomReader { +func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb int32, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle metrics.MetricHandle, mrdWrapper *MultiRangeDownloaderWrapper, config *cfg.ReadConfig) RandomReader { return &randomReader{ object: o, bucket: bucket, @@ -111,7 +111,7 @@ func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb i limit: -1, seeks: 0, totalReadBytes: 0, - readType: common.ReadTypeSequential, + readType: metrics.ReadTypeSequential, sequentialReadSizeMb: sequentialReadSizeMb, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, @@ -172,7 +172,7 @@ type randomReader struct { // boolean variable to determine if MRD is being used or not. isMRDInUse bool - metricHandle common.MetricHandle + metricHandle metrics.MetricHandle config *cfg.ReadConfig @@ -247,9 +247,9 @@ func (rr *randomReader) tryReadingFromFileCache(ctx context.Context, // Here rr.fileCacheHandle will not be nil since we return from the above in those cases. logger.Tracef("%.13v -> %s", requestId, requestOutput) - readType := common.ReadTypeRandom + readType := metrics.ReadTypeRandom if isSeq { - readType = common.ReadTypeSequential + readType = metrics.ReadTypeSequential } captureFileCacheMetrics(ctx, rr.metricHandle, readType, n, cacheHit, executionTime) }() @@ -511,7 +511,7 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { rr.limit = end requestedDataSize := end - start - common.CaptureGCSReadMetrics(ctx, rr.metricHandle, common.ReadTypeSequential, requestedDataSize) + metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeSequential, requestedDataSize) return } @@ -549,7 +549,7 @@ func (rr *randomReader) getReadInfo( // (average read size in bytes rounded up to the next MiB). end = int64(rr.object.Size) if rr.seeks >= minSeeksForRandom { - rr.readType = common.ReadTypeRandom + rr.readType = metrics.ReadTypeRandom averageReadBytes := rr.totalReadBytes / rr.seeks if averageReadBytes < maxReadSize { randomReadSize := int64(((averageReadBytes / MiB) + 1) * MiB) @@ -579,7 +579,7 @@ func (rr *randomReader) getReadInfo( // readerType specifies the go-sdk interface to use for reads. func readerType(readType string, start int64, end int64, bucketType gcs.BucketType) ReaderType { bytesToBeRead := end - start - if readType == common.ReadTypeRandom && bytesToBeRead < maxReadSize && bucketType.Zonal { + if readType == metrics.ReadTypeRandom && bytesToBeRead < maxReadSize && bucketType.Zonal { return MultiRangeReader } return RangeReader @@ -644,7 +644,7 @@ func (rr *randomReader) readFromRangeReader(ctx context.Context, p []byte, offse } requestedDataSize := end - offset - common.CaptureGCSReadMetrics(ctx, rr.metricHandle, readType, requestedDataSize) + metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, readType, requestedDataSize) rr.updateExpectedOffset(offset + int64(n)) return diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 945937f5fc..3692e75544 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -26,7 +26,6 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" @@ -36,6 +35,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -79,7 +79,7 @@ func (t *RandomReaderStretchrTest) SetupTest() { t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm, "") // Set up the reader. - rr := NewRandomReader(t.object, t.mockBucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics(), nil, nil) + rr := NewRandomReader(t.object, t.mockBucket, sequentialReadSizeInMb, nil, false, metrics.NewNoopMetrics(), nil, nil) t.rr.wrapped = rr.(*randomReader) } @@ -141,7 +141,7 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo_Sequential() { end, err := t.rr.wrapped.getReadInfo(tc.start, 10) assert.NoError(t.T(), err) - assert.Equal(t.T(), common.ReadTypeSequential, t.rr.wrapped.readType) + assert.Equal(t.T(), metrics.ReadTypeSequential, t.rr.wrapped.readType) assert.Equal(t.T(), tc.expectedEnd, end) }) } @@ -174,7 +174,7 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo_Random() { end, err := t.rr.wrapped.getReadInfo(tc.start, 10) assert.NoError(t.T(), err) - assert.Equal(t.T(), common.ReadTypeRandom, t.rr.wrapped.readType) + assert.Equal(t.T(), metrics.ReadTypeRandom, t.rr.wrapped.readType) assert.Equal(t.T(), tc.expectedEnd, end) }) } @@ -191,7 +191,7 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { }{ { name: "ZonalBucketRandomRead", - readType: common.ReadTypeRandom, + readType: metrics.ReadTypeRandom, start: 50, end: 68, bucketType: gcs.BucketType{Zonal: true}, @@ -199,7 +199,7 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { }, { name: "ZonalBucketRandomReadLargerThan8MB", - readType: common.ReadTypeRandom, + readType: metrics.ReadTypeRandom, start: 0, end: 9 * MiB, bucketType: gcs.BucketType{Zonal: true}, @@ -207,7 +207,7 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { }, { name: "ZonalBucketSequentialRead", - readType: common.ReadTypeSequential, + readType: metrics.ReadTypeSequential, start: 50, end: 68, bucketType: gcs.BucketType{Zonal: true}, @@ -215,7 +215,7 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { }, { name: "RegularBucketRandomRead", - readType: common.ReadTypeRandom, + readType: metrics.ReadTypeRandom, start: 50, end: 68, bucketType: gcs.BucketType{Zonal: false}, @@ -223,7 +223,7 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { }, { name: "RegularBucketSequentialRead", - readType: common.ReadTypeSequential, + readType: metrics.ReadTypeSequential, start: 50, end: 68, bucketType: gcs.BucketType{Zonal: false}, @@ -641,7 +641,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential}, + expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -649,7 +649,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeSequential}, + expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -657,7 +657,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeRandom, common.ReadTypeRandom, common.ReadTypeRandom}, + expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, { @@ -665,7 +665,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{common.ReadTypeSequential, common.ReadTypeSequential, common.ReadTypeRandom, common.ReadTypeRandom, common.ReadTypeRandom}, + expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, } @@ -676,7 +676,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { t.rr.wrapped.reader = nil t.rr.wrapped.isMRDInUse = false t.rr.wrapped.seeks = 0 - t.rr.wrapped.readType = common.ReadTypeSequential + t.rr.wrapped.readType = metrics.ReadTypeSequential t.rr.wrapped.expectedOffset = 0 t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) @@ -706,7 +706,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateZonalRandomReads() { t.rr.wrapped.reader = nil t.rr.wrapped.isMRDInUse = false t.rr.wrapped.seeks = 0 - t.rr.wrapped.readType = common.ReadTypeSequential + t.rr.wrapped.readType = metrics.ReadTypeSequential t.rr.wrapped.expectedOffset = 0 t.rr.wrapped.totalReadBytes = 0 t.object.Size = 20 * MiB @@ -737,7 +737,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateZonalRandomReads() { _, err := t.rr.wrapped.ReadAt(t.rr.ctx, buf, int64(readRange[0])) assert.NoError(t.T(), err) - assert.Equal(t.T(), common.ReadTypeRandom, t.rr.wrapped.readType) + assert.Equal(t.T(), metrics.ReadTypeRandom, t.rr.wrapped.readType) assert.Equal(t.T(), int64(readRange[1]), t.rr.wrapped.expectedOffset) assert.Equal(t.T(), uint64(seeks), t.rr.wrapped.seeks) } diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index a92094d176..9ab04e6371 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -27,7 +27,6 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" @@ -37,6 +36,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/fuse/fuseops" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/oglemock" @@ -181,11 +181,11 @@ func (t *RandomReaderTest) SetUp(ti *TestInfo) { lruCache := lru.NewCache(cacheMaxSize) t.jobManager = downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, t.cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{ EnableCrc: false, - }, common.NewNoopMetrics()) + }, metrics.NewNoopMetrics()) t.cacheHandler = file.NewCacheHandler(lruCache, t.jobManager, t.cacheDir, util.DefaultFilePerm, util.DefaultDirPerm, "") // Set up the reader. - rr := NewRandomReader(t.object, t.bucket, sequentialReadSizeInMb, nil, false, common.NewNoopMetrics(), nil, nil) + rr := NewRandomReader(t.object, t.bucket, sequentialReadSizeInMb, nil, false, metrics.NewNoopMetrics(), nil, nil) t.rr.wrapped = rr.(*randomReader) } @@ -536,7 +536,7 @@ func (t *RandomReaderTest) UpgradesSequentialReads_NoExistingReader() { t.object.Size = 1 << 40 const readSize = 1 * MiB // Set up the custom randomReader. - rr := NewRandomReader(t.object, t.bucket, readSize/MiB, nil, false, common.NewNoopMetrics(), nil, nil) + rr := NewRandomReader(t.object, t.bucket, readSize/MiB, nil, false, metrics.NewNoopMetrics(), nil, nil) t.rr.wrapped = rr.(*randomReader) // Simulate a previous exhausted reader that ended at the offset from which diff --git a/internal/gcsx/read_manager/read_manager.go b/internal/gcsx/read_manager/read_manager.go index 26f1fd7ee7..fb6de3c675 100644 --- a/internal/gcsx/read_manager/read_manager.go +++ b/internal/gcsx/read_manager/read_manager.go @@ -20,11 +20,11 @@ import ( "io" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" clientReaders "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx/client_readers" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" ) type ReadManager struct { @@ -41,7 +41,7 @@ type ReadManagerConfig struct { SequentialReadSizeMB int32 FileCacheHandler *file.CacheHandler CacheFileForRangeRead bool - MetricHandle common.MetricHandle + MetricHandle metrics.MetricHandle MrdWrapper *gcsx.MultiRangeDownloaderWrapper ReadConfig *cfg.ReadConfig } diff --git a/internal/gcsx/read_manager/read_manager_test.go b/internal/gcsx/read_manager/read_manager_test.go index af12d2166b..389c3aa14a 100644 --- a/internal/gcsx/read_manager/read_manager_test.go +++ b/internal/gcsx/read_manager/read_manager_test.go @@ -26,7 +26,6 @@ import ( "testing/iotest" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" @@ -38,6 +37,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" testUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" @@ -57,13 +57,13 @@ func (t *readManagerTest) readManagerConfig(fileCacheEnable bool) *ReadManagerCo config := &ReadManagerConfig{ SequentialReadSizeMB: sequentialReadSizeInMb, CacheFileForRangeRead: false, - MetricHandle: common.NewNoopMetrics(), + MetricHandle: metrics.NewNoopMetrics(), MrdWrapper: nil, } if fileCacheEnable { cacheDir := path.Join(os.Getenv("HOME"), "test_cache_dir") lruCache := lru.NewCache(cacheMaxSize) - jobManager := downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{EnableCrc: false}, common.NewNoopMetrics()) + jobManager := downloader.NewJobManager(lruCache, util.DefaultFilePerm, util.DefaultDirPerm, cacheDir, sequentialReadSizeInMb, &cfg.FileCacheConfig{EnableCrc: false}, metrics.NewNoopMetrics()) config.FileCacheHandler = file.NewCacheHandler(lruCache, jobManager, cacheDir, util.DefaultFilePerm, util.DefaultDirPerm, "") } else { config.FileCacheHandler = nil diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index 638dc87ee6..1e1193a440 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -20,23 +20,23 @@ import ( "time" storagev2 "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" ) // recordRequest records a request and its latency. -func recordRequest(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time) { +func recordRequest(ctx context.Context, metricHandle metrics.MetricHandle, method string, start time.Time) { metricHandle.GCSRequestCount(ctx, 1, method) metricHandle.GCSRequestLatency(ctx, time.Since(start), method) } -func CaptureMultiRangeDownloaderMetrics(ctx context.Context, metricHandle common.MetricHandle, method string, start time.Time) { +func CaptureMultiRangeDownloaderMetrics(ctx context.Context, metricHandle metrics.MetricHandle, method string, start time.Time) { recordRequest(ctx, metricHandle, method, start) } // NewMonitoringBucket returns a gcs.Bucket that exports metrics for monitoring -func NewMonitoringBucket(b gcs.Bucket, m common.MetricHandle) gcs.Bucket { +func NewMonitoringBucket(b gcs.Bucket, m metrics.MetricHandle) gcs.Bucket { return &monitoringBucket{ wrapped: b, metricHandle: m, @@ -45,7 +45,7 @@ func NewMonitoringBucket(b gcs.Bucket, m common.MetricHandle) gcs.Bucket { type monitoringBucket struct { wrapped gcs.Bucket - metricHandle common.MetricHandle + metricHandle metrics.MetricHandle } func (mb *monitoringBucket) Name() string { @@ -216,12 +216,12 @@ func (mb *monitoringBucket) GCSName(obj *gcs.MinObject) string { } // recordReader increments the reader count when it's opened or closed. -func recordReader(ctx context.Context, metricHandle common.MetricHandle, ioMethod string) { +func recordReader(ctx context.Context, metricHandle metrics.MetricHandle, ioMethod string) { metricHandle.GCSReaderCount(ctx, 1, ioMethod) } // Monitoring on the object reader -func newMonitoringReadCloser(ctx context.Context, object string, rc gcs.StorageReader, metricHandle common.MetricHandle) gcs.StorageReader { +func newMonitoringReadCloser(ctx context.Context, object string, rc gcs.StorageReader, metricHandle metrics.MetricHandle) gcs.StorageReader { recordReader(ctx, metricHandle, "opened") return &monitoringReadCloser{ ctx: ctx, @@ -235,7 +235,7 @@ type monitoringReadCloser struct { ctx context.Context object string wrapped gcs.StorageReader - metricHandle common.MetricHandle + metricHandle metrics.MetricHandle } func (mrc *monitoringReadCloser) Read(p []byte) (n int, err error) { diff --git a/internal/storage/storageutil/client.go b/internal/storage/storageutil/client.go index a6ed6d6eb0..313b282d32 100644 --- a/internal/storage/storageutil/client.go +++ b/internal/storage/storageutil/client.go @@ -22,8 +22,8 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/auth" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/net/context" "golang.org/x/oauth2" ) @@ -59,7 +59,7 @@ type StorageClientConfig struct { ReadStallRetryConfig cfg.ReadStallGcsRetriesConfig - MetricHandle common.MetricHandle + MetricHandle metrics.MetricHandle } func CreateHttpClient(storageClientConfig *StorageClientConfig) (httpClient *http.Client, err error) { diff --git a/internal/storage/storageutil/custom_retry.go b/internal/storage/storageutil/custom_retry.go index 0c3d83d115..7fd6f91dc1 100644 --- a/internal/storage/storageutil/custom_retry.go +++ b/internal/storage/storageutil/custom_retry.go @@ -19,8 +19,8 @@ import ( "errors" "cloud.google.com/go/storage" - "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "google.golang.org/api/googleapi" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -61,7 +61,7 @@ func ShouldRetry(err error) (b bool) { return } -func ShouldRetryWithMonitoring(ctx context.Context, err error, metricHandle common.MetricHandle) bool { +func ShouldRetryWithMonitoring(ctx context.Context, err error, metricHandle metrics.MetricHandle) bool { if err == nil { return false } diff --git a/internal/storage/storageutil/custom_retry_test.go b/internal/storage/storageutil/custom_retry_test.go index cc5b617f2a..9e12d35654 100644 --- a/internal/storage/storageutil/custom_retry_test.go +++ b/internal/storage/storageutil/custom_retry_test.go @@ -22,7 +22,7 @@ import ( "net/url" "testing" - "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "google.golang.org/api/googleapi" "google.golang.org/grpc/codes" @@ -150,7 +150,7 @@ func TestShouldRetryReturnsTrueForUnauthenticatedGrpcErrors(t *testing.T) { } type fakeMetricHandle struct { - common.MetricHandle + metrics.MetricHandle gcsRetryCountCalled bool gcsRetryCountInc int64 @@ -181,7 +181,7 @@ func TestShouldRetryWithMonitoringForNonRetryableErrors(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { fakeMetrics := &fakeMetricHandle{ - MetricHandle: common.NewNoopMetrics(), + MetricHandle: metrics.NewNoopMetrics(), } shouldRetry := ShouldRetryWithMonitoring(context.Background(), tc.err, fakeMetrics) @@ -215,7 +215,7 @@ func TestShouldRetryWithMonitoringForRetryableErrors(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { fakeMetrics := &fakeMetricHandle{ - MetricHandle: common.NewNoopMetrics(), + MetricHandle: metrics.NewNoopMetrics(), } shouldRetry := ShouldRetryWithMonitoring(context.Background(), tc.err, fakeMetrics) diff --git a/metrics/constants.go b/metrics/constants.go new file mode 100644 index 0000000000..6009ba2f3c --- /dev/null +++ b/metrics/constants.go @@ -0,0 +1,21 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +const ( + ReadTypeSequential = "Sequential" + ReadTypeRandom = "Random" + ReadTypeParallel = "Parallel" +) diff --git a/common/noop_metrics.go b/metrics/noop_metrics.go similarity index 99% rename from common/noop_metrics.go rename to metrics/noop_metrics.go index ac69062697..000b1b3064 100644 --- a/common/noop_metrics.go +++ b/metrics/noop_metrics.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package common +package metrics import ( "context" diff --git a/common/otel_metrics.go b/metrics/otel_metrics.go similarity index 99% rename from common/otel_metrics.go rename to metrics/otel_metrics.go index 1b2bd0afd8..319bd1eea1 100644 --- a/common/otel_metrics.go +++ b/metrics/otel_metrics.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package common +package metrics import ( "context" diff --git a/common/otel_metrics_test.go b/metrics/otel_metrics_test.go similarity index 99% rename from common/otel_metrics_test.go rename to metrics/otel_metrics_test.go index 585713abf1..670bdc7f63 100644 --- a/common/otel_metrics_test.go +++ b/metrics/otel_metrics_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package common +package metrics import ( "context" diff --git a/common/telemetry.go b/metrics/telemetry.go similarity index 83% rename from common/telemetry.go rename to metrics/telemetry.go index f956b4fb22..b1ffed5950 100644 --- a/common/telemetry.go +++ b/metrics/telemetry.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,29 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -package common +package metrics import ( "context" - "errors" "fmt" "time" "go.opentelemetry.io/otel/metric" ) -type ShutdownFn func(ctx context.Context) error - // The default time buckets for latency metrics. // The unit can however change for different units i.e. for one metric the unit could be microseconds and for another it could be milliseconds. var defaultLatencyDistribution = metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000) -const ( - ReadTypeSequential = "Sequential" - ReadTypeRandom = "Random" - ReadTypeParallel = "Parallel" -) - // Pair of CacheHit and ReadType attributes type CacheHitReadType struct { CacheHit bool @@ -47,20 +38,6 @@ type FSOpsErrorCategory struct { ErrorCategory string } -// JoinShutdownFunc combines the provided shutdown functions into a single function. -func JoinShutdownFunc(shutdownFns ...ShutdownFn) ShutdownFn { - return func(ctx context.Context) error { - var err error - for _, fn := range shutdownFns { - if fn == nil { - continue - } - err = errors.Join(err, fn(ctx)) - } - return err - } -} - // MetricAttr represents the attributes associated with a metric. type MetricAttr struct { Key, Value string diff --git a/common/telemetry_test.go b/metrics/telemetry_test.go similarity index 56% rename from common/telemetry_test.go rename to metrics/telemetry_test.go index 980b7ba36e..3765ff57fd 100644 --- a/common/telemetry_test.go +++ b/metrics/telemetry_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -package common +package metrics import ( "context" - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -26,70 +25,6 @@ import ( "go.opentelemetry.io/otel/metric" ) -func TestJoinShutdownFunc(t *testing.T) { - t.Parallel() - tests := []struct { - name string - fns []ShutdownFn - expectedErrs []string - }{ - { - name: "normal", - fns: []ShutdownFn{func(_ context.Context) error { return nil }}, - expectedErrs: nil, - }, - { - name: "one_err", - fns: []ShutdownFn{func(_ context.Context) error { return fmt.Errorf("err") }}, - expectedErrs: []string{"err"}, - }, - { - name: "two_err", - fns: []ShutdownFn{ - func(_ context.Context) error { return fmt.Errorf("err1") }, - func(_ context.Context) error { return fmt.Errorf("err2") }, - }, - expectedErrs: []string{"err1", "err2"}, - }, - { - name: "two_err_one_normal", - fns: []ShutdownFn{ - func(_ context.Context) error { return fmt.Errorf("err1") }, - func(_ context.Context) error { return nil }, - func(_ context.Context) error { return fmt.Errorf("err2") }, - }, - expectedErrs: []string{"err1", "err2"}, - }, - { - name: "nil", - fns: []ShutdownFn{ - func(_ context.Context) error { return fmt.Errorf("err1") }, - nil, - func(_ context.Context) error { return fmt.Errorf("err2") }, - }, - expectedErrs: []string{"err1", "err2"}, - }, - } - - for _, tc := range tests { - tc := tc - t.Run(tc.name, func(t *testing.T) { - t.Parallel() - - err := JoinShutdownFunc(tc.fns...)(context.Background()) - - if len(tc.expectedErrs) == 0 { - assert.NoError(t, err) - } else { - require.Error(t, err) - for _, e := range tc.expectedErrs { - assert.ErrorContains(t, err, e) - } - } - }) - } -} - type int64DataPoint struct { v int64 attr metric.MeasurementOption From 70b9fd7dcc5be450823916e1406ccb94ba6597a2 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 21 Jul 2025 15:04:45 +0530 Subject: [PATCH 0576/1298] refactor(block-pool): making the block-pool implementation generic (#3503) --- internal/block/block.go | 122 +------- internal/block/block_pool.go | 57 +++- internal/block/block_pool_test.go | 38 +-- internal/block/block_test.go | 239 -------------- internal/block/prefetch_block.go | 182 +++++++++++ internal/block/prefetch_block_test.go | 292 ++++++++++++++++++ internal/bufferedread/download_task.go | 4 +- internal/bufferedread/download_task_test.go | 4 +- .../bufferedwrites/buffered_write_handler.go | 2 +- internal/bufferedwrites/upload_handler.go | 4 +- .../bufferedwrites/upload_handler_test.go | 2 +- 11 files changed, 545 insertions(+), 401 deletions(-) create mode 100644 internal/block/prefetch_block.go create mode 100644 internal/block/prefetch_block_test.go diff --git a/internal/block/block.go b/internal/block/block.go index 44dd5a4bcf..e11116d404 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -16,34 +16,15 @@ package block import ( "bytes" - "context" "fmt" "io" "syscall" ) -// BlockStatus represents the status of a block. -// It contains the state of the block and an error -// that may have occurred during the block's operation. -type BlockStatus struct { - State BlockState - Err error -} - -// BlockState represents the state of the block. -type BlockState int - -const ( - BlockStateInProgress BlockState = iota // Download of this block is in progress - BlockStateDownloaded // Download of this block is complete - BlockStateDownloadFailed // Download of this block has failed - BlockStateDownloadCancelled // Download of this block has been cancelled -) - // Block represents the buffer which holds the data. type Block interface { - // Reuse resets the blocks for reuse. - Reuse() + // GenBlock defines reuse and deallocation of the block. + GenBlock // Size provides the current data size of the block. The capacity of the block // can be >= data_size. @@ -58,33 +39,6 @@ type Block interface { // Reader interface helps in copying the data directly to storage.writer // while uploading to GCS. Reader() io.Reader - - Deallocate() error - - // Follows io.ReaderAt interface. - // Here, off is relative to the start of the block. - ReadAt(p []byte, off int64) (n int, err error) - - // AbsStartOff returns the absolute start offset of the block. - // Panics if the absolute start offset is not set. - AbsStartOff() int64 - - // SetAbsStartOff sets the absolute start offset of the block. - // This should be called only once just after getting the block from the pool. - // It returns an error if the startOff is negative or if it is already set. - // TODO(princer): check if a way to set it as part of constructor. - SetAbsStartOff(startOff int64) error - - // AwaitReady waits for the block to be ready to consume. - // It returns the status of the block and an error if any. - AwaitReady(ctx context.Context) (BlockStatus, error) - - // NotifyReady is used by producer to mark the block as ready to consume. - // The value indicates the status of the block: - // - BlockStatusDownloaded: Download of this block is complete. - // - BlockStatusDownloadFailed: Download of this block has failed. - // - BlockStatusDownloadCancelled: Download of this block has been cancelled. - NotifyReady(val BlockStatus) } // TODO: check if we need offset or just storing end is sufficient. We might need @@ -97,15 +51,6 @@ type memoryBlock struct { Block buffer []byte offset offset - - // Indicates if block is in progress, downloaded, download failed or download cancelled. - status BlockStatus - - // notification is a channel that notifies when the block is ready to consume. - notification chan BlockStatus - - // Stores the absolute start offset of the block-segment in the file. - absStartOff int64 } func (m *memoryBlock) Reuse() { @@ -113,9 +58,6 @@ func (m *memoryBlock) Reuse() { m.offset.end = 0 m.offset.start = 0 - m.notification = make(chan BlockStatus, 1) - m.status = BlockStatus{State: BlockStateInProgress} - m.absStartOff = -1 } func (m *memoryBlock) Size() int64 { @@ -168,11 +110,8 @@ func createBlock(blockSize int64) (Block, error) { } mb := memoryBlock{ - buffer: addr, - offset: offset{0, 0}, - notification: make(chan BlockStatus, 1), - status: BlockStatus{State: BlockStateInProgress}, - absStartOff: -1, + buffer: addr, + offset: offset{0, 0}, } return &mb, nil } @@ -192,56 +131,3 @@ func (m *memoryBlock) ReadAt(p []byte, off int64) (n int, err error) { } return n, nil } - -func (m *memoryBlock) AbsStartOff() int64 { - if m.absStartOff < 0 { - panic("AbsStartOff is not set, it should be set before calling this method.") - } - return m.absStartOff -} - -func (m *memoryBlock) SetAbsStartOff(startOff int64) error { - if startOff < 0 { - return fmt.Errorf("startOff cannot be negative, got %d", startOff) - } - - // If absStartOff is already set, then return an error. - if m.absStartOff >= 0 { - return fmt.Errorf("AbsStartOff is already set, it should be set only once.") - } - - m.absStartOff = startOff - return nil -} - -// AwaitReady waits for the block to be ready to consume. -// It returns the status of the block and an error if any. -func (m *memoryBlock) AwaitReady(ctx context.Context) (BlockStatus, error) { - select { - case val, ok := <-m.notification: - if !ok { - return m.status, nil - } - - // Close the notification channel to prevent further notifications. - close(m.notification) - // Save the last status for subsequent AwaitReady calls. - m.status = val - - return m.status, nil - case <-ctx.Done(): - return BlockStatus{State: BlockStateInProgress}, ctx.Err() - } -} - -// NotifyReady is used by the producer to mark the block as ready to consume. -// This should be called only once to notify the consumer. -// If called multiple times, it will panic - either because of writing to the -// closed channel or blocking due to writing over full notification channel. -func (m *memoryBlock) NotifyReady(val BlockStatus) { - select { - case m.notification <- val: - default: - panic("Expected to notify only once, but got multiple notifications.") - } -} diff --git a/internal/block/block_pool.go b/internal/block/block_pool.go index 5998cc0e44..a6bef63304 100644 --- a/internal/block/block_pool.go +++ b/internal/block/block_pool.go @@ -23,10 +23,18 @@ import ( var CantAllocateAnyBlockError error = errors.New("cant allocate any streaming write block as global max blocks limit is reached") -// BlockPool handles the creation of blocks as per the user configuration. -type BlockPool struct { +type GenBlock interface { + // Reuse resets the block for reuse. + Reuse() + + // Deallocate releases the resources held by the block. + Deallocate() error +} + +// GenBlockPool handles the creation of blocks as per the user configuration. +type GenBlockPool[T GenBlock] struct { // Channel holding free blocks. - freeBlocksCh chan Block + freeBlocksCh chan T // Size of each block this pool holds. blockSize int64 @@ -40,21 +48,25 @@ type BlockPool struct { // Semaphore used to limit the total number of blocks created across // different files. globalMaxBlocksSem *semaphore.Weighted + + // createBlockFunc is a function that creates a new block of type T + createBlockFunc func(blockSize int64) (T, error) } -// NewBlockPool creates the blockPool based on the user configuration. -func NewBlockPool(blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphore.Weighted) (bp *BlockPool, err error) { +// NewGenBlockPool creates the blockPool based on the user configuration. +func NewGenBlockPool[T GenBlock](blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphore.Weighted, createBlockFunc func(blockSize int64) (T, error)) (bp *GenBlockPool[T], err error) { if blockSize <= 0 || maxBlocks <= 0 { err = fmt.Errorf("invalid configuration provided for blockPool, blocksize: %d, maxBlocks: %d", blockSize, maxBlocks) return } - bp = &BlockPool{ - freeBlocksCh: make(chan Block, maxBlocks), + bp = &GenBlockPool[T]{ + freeBlocksCh: make(chan T, maxBlocks), blockSize: blockSize, maxBlocks: maxBlocks, totalBlocks: 0, globalMaxBlocksSem: globalMaxBlocksSem, + createBlockFunc: createBlockFunc, } semAcquired := bp.globalMaxBlocksSem.TryAcquire(1) if !semAcquired { @@ -66,7 +78,9 @@ func NewBlockPool(blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphor // Get returns a block. It returns an existing block if it's ready for reuse or // creates a new one if required. -func (bp *BlockPool) Get() (Block, error) { +// Not thread-safe, calling from multiple goroutines may lead memory leaks because +// of race conditions. +func (bp *GenBlockPool[T]) Get() (T, error) { for { select { case b := <-bp.freeBlocksCh: @@ -75,12 +89,11 @@ func (bp *BlockPool) Get() (Block, error) { return b, nil default: - // No lock is required here since blockPool is per file and all write - // calls to a single file are serialized because of inode.lock(). if bp.canAllocateBlock() { - b, err := createBlock(bp.blockSize) + b, err := bp.createBlockFunc(bp.blockSize) if err != nil { - return nil, err + var zero T + return zero, err } bp.totalBlocks++ @@ -91,7 +104,7 @@ func (bp *BlockPool) Get() (Block, error) { } // canAllocateBlock checks if a new block can be allocated. -func (bp *BlockPool) canAllocateBlock() bool { +func (bp *GenBlockPool[T]) canAllocateBlock() bool { // If max blocks limit is reached, then no more blocks can be allocated. if bp.totalBlocks >= bp.maxBlocks { return false @@ -109,7 +122,7 @@ func (bp *BlockPool) canAllocateBlock() bool { } // Release puts the block back into the free blocks channel for reuse. -func (bp *BlockPool) Release(b Block) { +func (bp *GenBlockPool[T]) Release(b T) { select { case bp.freeBlocksCh <- b: default: @@ -118,11 +131,11 @@ func (bp *BlockPool) Release(b Block) { } // BlockSize returns the block size used by the blockPool. -func (bp *BlockPool) BlockSize() int64 { +func (bp *GenBlockPool[T]) BlockSize() int64 { return bp.blockSize } -func (bp *BlockPool) ClearFreeBlockChannel(releaseLastBlock bool) error { +func (bp *GenBlockPool[T]) ClearFreeBlockChannel(releaseLastBlock bool) error { for { select { case b := <-bp.freeBlocksCh: @@ -145,6 +158,16 @@ func (bp *BlockPool) ClearFreeBlockChannel(releaseLastBlock bool) error { // TotalFreeBlocks returns the total number of free blocks available in the pool. // This is useful for testing and debugging purposes. -func (bp *BlockPool) TotalFreeBlocks() int { +func (bp *GenBlockPool[T]) TotalFreeBlocks() int { return len(bp.freeBlocksCh) } + +// NewBlockPool creates GenBlockPool for block.Block interface. +func NewBlockPool(blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphore.Weighted) (bp *GenBlockPool[Block], err error) { + return NewGenBlockPool(blockSize, maxBlocks, globalMaxBlocksSem, createBlock) +} + +// NewPrefetchBlockPool creates GenBlockPool for block.PrefetchBlock interface. +func NewPrefetchBlockPool(blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphore.Weighted) (bp *GenBlockPool[PrefetchBlock], err error) { + return NewGenBlockPool(blockSize, maxBlocks, globalMaxBlocksSem, createPrefetchBlock) +} diff --git a/internal/block/block_pool_test.go b/internal/block/block_pool_test.go index b17c3eaa70..72fd6274ed 100644 --- a/internal/block/block_pool_test.go +++ b/internal/block/block_pool_test.go @@ -37,7 +37,7 @@ func TestBlockPoolTestSuite(t *testing.T) { } func (t *BlockPoolTest) TestInitBlockPool() { - bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(10)) + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) require.NotNil(t.T(), bp) @@ -47,28 +47,28 @@ func (t *BlockPoolTest) TestInitBlockPool() { } func (t *BlockPoolTest) TestInitBlockPoolForZeroBlockSize() { - _, err := NewBlockPool(0, 10, semaphore.NewWeighted(10)) + _, err := NewGenBlockPool(0, 10, semaphore.NewWeighted(10), createBlock) require.NotNil(t.T(), err) assert.Equal(t.T(), fmt.Errorf(invalidConfigError, 0, 10), err) } func (t *BlockPoolTest) TestInitBlockPoolForNegativeBlockSize() { - _, err := NewBlockPool(-1, 10, semaphore.NewWeighted(10)) + _, err := NewGenBlockPool(-1, 10, semaphore.NewWeighted(10), createBlock) require.NotNil(t.T(), err) assert.Equal(t.T(), fmt.Errorf(invalidConfigError, -1, 10), err) } func (t *BlockPoolTest) TestInitBlockPoolForZeroMaxBlocks() { - _, err := NewBlockPool(10, 0, semaphore.NewWeighted(10)) + _, err := NewGenBlockPool(10, 0, semaphore.NewWeighted(10), createBlock) require.NotNil(t.T(), err) assert.Equal(t.T(), fmt.Errorf(invalidConfigError, 10, 0), err) } func (t *BlockPoolTest) TestInitBlockPoolForNegativeMaxBlocks() { - _, err := NewBlockPool(10, -1, semaphore.NewWeighted(10)) + _, err := NewGenBlockPool(10, -1, semaphore.NewWeighted(10), createBlock) require.NotNil(t.T(), err) assert.Equal(t.T(), fmt.Errorf(invalidConfigError, 10, -1), err) @@ -76,7 +76,7 @@ func (t *BlockPoolTest) TestInitBlockPoolForNegativeMaxBlocks() { // Represents when block is available on the freeBlocksCh. func (t *BlockPoolTest) TestGetWhenBlockIsAvailableForReuse() { - bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(10)) + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) // Creating a block with some data and send it to blockCh. b, err := createBlock(2) @@ -102,7 +102,7 @@ func (t *BlockPoolTest) TestGetWhenBlockIsAvailableForReuse() { } func (t *BlockPoolTest) TestGetWhenTotalBlocksIsLessThanThanMaxBlocks() { - bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(10)) + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) block, err := bp.Get() @@ -114,7 +114,7 @@ func (t *BlockPoolTest) TestGetWhenTotalBlocksIsLessThanThanMaxBlocks() { func (t *BlockPoolTest) TestCreateBlockWithLargeSize() { // Creating block of size 1TB - bp, err := NewBlockPool(1024*1024*1024*1024, 10, semaphore.NewWeighted(10)) + bp, err := NewGenBlockPool(1024*1024*1024*1024, 10, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) _, err = bp.Get() @@ -124,7 +124,7 @@ func (t *BlockPoolTest) TestCreateBlockWithLargeSize() { } func (t *BlockPoolTest) TestBlockSize() { - bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(10)) + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) require.Equal(t.T(), int64(1024), bp.BlockSize()) @@ -150,7 +150,7 @@ func (t *BlockPoolTest) TestClearFreeBlockChannel() { for _, tt := range tests { t.Run(tt.name, func() { - bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(4)) + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(4), createBlock) require.Nil(t.T(), err) blocks := make([]Block, 4) for i := 0; i < 4; i++ { @@ -179,7 +179,7 @@ func (t *BlockPoolTest) TestClearFreeBlockChannel() { func (t *BlockPoolTest) TestBlockPoolCreationAcquiresGlobalSem() { globalBlocksSem := semaphore.NewWeighted(1) - bp, err := NewBlockPool(1024, 3, globalBlocksSem) + bp, err := NewGenBlockPool(1024, 3, globalBlocksSem, createBlock) require.Nil(t.T(), err) // Validate that semaphore got acquired. @@ -203,9 +203,9 @@ func (t *BlockPoolTest) TestBlockPoolCreationAcquiresGlobalSem() { func (t *BlockPoolTest) TestClearFreeBlockChannelWithMultipleBlockPools() { globalMaxBlocksSem := semaphore.NewWeighted(3) - bp1, err := NewBlockPool(1024, 3, globalMaxBlocksSem) + bp1, err := NewGenBlockPool(1024, 3, globalMaxBlocksSem, createBlock) require.Nil(t.T(), err) - bp2, err := NewBlockPool(1024, 3, globalMaxBlocksSem) + bp2, err := NewGenBlockPool(1024, 3, globalMaxBlocksSem, createBlock) require.Nil(t.T(), err) // Create 2 blocks in bp1. b1 := t.validateGetBlockIsNotBlocked(bp1) @@ -236,7 +236,7 @@ func (t *BlockPoolTest) TestClearFreeBlockChannelWithMultipleBlockPools() { } func (t *BlockPoolTest) TestBlockPoolCreationFailsWhenGlobalMaxBlocksIsZero() { - bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(0)) + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(0), createBlock) require.Error(t.T(), err) assert.Nil(t.T(), bp) @@ -244,7 +244,7 @@ func (t *BlockPoolTest) TestBlockPoolCreationFailsWhenGlobalMaxBlocksIsZero() { } func (t *BlockPoolTest) TestGetWhenLimitedByGlobalBlocks() { - bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(2)) + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(2), createBlock) require.Nil(t.T(), err) // 2 blocks can be created. @@ -257,14 +257,14 @@ func (t *BlockPoolTest) TestGetWhenLimitedByGlobalBlocks() { } func (t *BlockPoolTest) TestGetWhenTotalBlocksEqualToMaxBlocks() { - bp, err := NewBlockPool(1024, 10, semaphore.NewWeighted(2)) + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(2), createBlock) require.Nil(t.T(), err) bp.totalBlocks = 10 t.validateGetBlockIsBlocked(bp) } -func (t *BlockPoolTest) validateGetBlockIsBlocked(bp *BlockPool) { +func (t *BlockPoolTest) validateGetBlockIsBlocked(bp *GenBlockPool[Block]) { t.T().Helper() done := make(chan bool, 1) go func() { @@ -281,7 +281,7 @@ func (t *BlockPoolTest) validateGetBlockIsBlocked(bp *BlockPool) { } } -func (t *BlockPoolTest) validateGetBlockIsNotBlocked(bp *BlockPool) Block { +func (t *BlockPoolTest) validateGetBlockIsNotBlocked(bp *GenBlockPool[Block]) Block { t.T().Helper() done := make(chan Block, 1) go func() { @@ -354,7 +354,7 @@ func (t *BlockPoolTest) TestCanAllocateBlock() { for _, tt := range tests { t.Run(tt.name, func() { - bp := &BlockPool{ + bp := &GenBlockPool[Block]{ maxBlocks: tt.maxBlocks, totalBlocks: tt.totalBlocks, globalMaxBlocksSem: tt.globalSem, diff --git a/internal/block/block_test.go b/internal/block/block_test.go index e7577afe54..7807fcb5b7 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -15,12 +15,8 @@ package block import ( - "context" - "fmt" "io" - "sync" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -103,8 +99,6 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockReuse() { require.Nil(testSuite.T(), err) require.Equal(testSuite.T(), content, output) require.Equal(testSuite.T(), int64(2), mb.Size()) - err = mb.SetAbsStartOff(23) - require.Nil(testSuite.T(), err) mb.Reuse() @@ -112,9 +106,6 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockReuse() { assert.Nil(testSuite.T(), err) assert.Empty(testSuite.T(), output) assert.Equal(testSuite.T(), int64(0), mb.Size()) - assert.Panics(testSuite.T(), func() { - _ = mb.AbsStartOff() - }) } // Other cases for Size are covered as part of write tests. @@ -154,117 +145,6 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockDeAllocate() { assert.Nil(testSuite.T(), mb.(*memoryBlock).buffer) } -func (testSuite *MemoryBlockTest) TestMemoryBlockReadAtSuccess() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - content := []byte("hello world") - _, err = mb.Write(content) - require.Nil(testSuite.T(), err) - readBuffer := make([]byte, 5) - - n, err := mb.ReadAt(readBuffer, 6) // Read "world" - - assert.Nil(testSuite.T(), err) - assert.Equal(testSuite.T(), 5, n) - assert.Equal(testSuite.T(), []byte("world"), readBuffer) -} - -func (testSuite *MemoryBlockTest) TestMemoryBlockReadAtBeyondBlockSize() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - content := []byte("hello world") - _, err = mb.Write(content) - require.Nil(testSuite.T(), err) - readBuffer := make([]byte, 5) - - n, err := mb.ReadAt(readBuffer, 15) // Read beyond the block size - - assert.NotNil(testSuite.T(), err) - assert.NotErrorIs(testSuite.T(), err, io.EOF) - assert.Equal(testSuite.T(), 0, n) -} - -func (testSuite *MemoryBlockTest) TestMemoryBlockReadAtWithNegativeOffset() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - content := []byte("hello world") - _, err = mb.Write(content) - require.Nil(testSuite.T(), err) - readBuffer := make([]byte, 5) - - n, err := mb.ReadAt(readBuffer, -1) // Negative offset - - assert.NotNil(testSuite.T(), err) - assert.NotErrorIs(testSuite.T(), err, io.EOF) - assert.Equal(testSuite.T(), 0, n) -} - -func (testSuite *MemoryBlockTest) TestMemoryBlockReadAtEOF() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - content := []byte("hello world") - _, err = mb.Write(content) - require.Nil(testSuite.T(), err) - readBuffer := make([]byte, 15) - - n, err := mb.ReadAt(readBuffer, 6) // Read "world" - - assert.Equal(testSuite.T(), io.EOF, err) - assert.Equal(testSuite.T(), 5, n) - assert.Equal(testSuite.T(), []byte("world"), readBuffer[0:n]) -} - -func (testSuite *MemoryBlockTest) TestMemoryBlockAbsStartOffsetPanicsOnEmptyBlock() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - - // The absolute start offset should be -1 initially. - assert.Panics(testSuite.T(), func() { - _ = mb.AbsStartOff() - }) -} - -func (testSuite *MemoryBlockTest) TestMemoryBlockAbsStartOffsetValid() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - - // Set the absolute start offset to a valid value. - mb.(*memoryBlock).absStartOff = 100 - - // The absolute start offset should return the set value. - assert.Equal(testSuite.T(), int64(100), mb.AbsStartOff()) -} - -func (testSuite *MemoryBlockTest) TestMemoryBlockSetAbsStartOffsetInvalid() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - - err = mb.SetAbsStartOff(-23) - - assert.Error(testSuite.T(), err) -} - -func (testSuite *MemoryBlockTest) TestMemoryBlockSetAbsStartOffsetValid() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - - err = mb.SetAbsStartOff(23) - - assert.NoError(testSuite.T(), err) - assert.Equal(testSuite.T(), int64(23), mb.AbsStartOff()) -} - -func (testSuite *MemoryBlockTest) TestMemoryBlockSetAbsStartOffsetTwiceInvalid() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - err = mb.SetAbsStartOff(23) - require.Nil(testSuite.T(), err) - - err = mb.SetAbsStartOff(42) - - assert.Error(testSuite.T(), err) -} - func (testSuite *MemoryBlockTest) TestMemoryBlockCap() { mb, err := createBlock(12) require.Nil(testSuite.T(), err) @@ -282,122 +162,3 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockCapAfterWrite() { assert.Equal(testSuite.T(), int64(12), mb.Cap()) } - -func (testSuite *MemoryBlockTest) TestAwaitReadyWaitIfNotNotify() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - ctx, cancel := context.WithTimeout(testSuite.T().Context(), 100*time.Millisecond) - defer cancel() - - _, err = mb.AwaitReady(ctx) - - assert.NotNil(testSuite.T(), err) - assert.EqualError(testSuite.T(), context.DeadlineExceeded, err.Error()) -} - -func (testSuite *MemoryBlockTest) TestAwaitReadyReturnsErrorOnContextCancellation() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - ctx, cancel := context.WithCancel(testSuite.T().Context()) - cancel() // Cancel the context immediately - - _, err = mb.AwaitReady(ctx) - - require.NotNil(testSuite.T(), err) - assert.EqualError(testSuite.T(), context.Canceled, err.Error()) -} - -func (testSuite *MemoryBlockTest) TestAwaitReadyNotifyVariants() { - tests := []struct { - name string - notifyStatus BlockStatus - wantStatus BlockStatus - }{ - { - name: "AfterNotifySuccess", - notifyStatus: BlockStatus{State: BlockStateDownloaded}, - wantStatus: BlockStatus{State: BlockStateDownloaded}, - }, - { - name: "AfterNotifyError", - notifyStatus: BlockStatus{State: BlockStateDownloadFailed, Err: fmt.Errorf("download failed")}, - wantStatus: BlockStatus{State: BlockStateDownloadFailed, Err: fmt.Errorf("download failed")}, - }, - { - name: "AfterNotifyCancelled", - notifyStatus: BlockStatus{State: BlockStateDownloadCancelled}, - wantStatus: BlockStatus{State: BlockStateDownloadCancelled}, - }, - } - - for _, tt := range tests { - testSuite.T().Run(tt.name, func(t *testing.T) { - mb, err := createBlock(12) - require.Nil(t, err) - go func() { - time.Sleep(time.Millisecond) - mb.NotifyReady(tt.notifyStatus) - }() - - status, err := mb.AwaitReady(context.Background()) - - require.Nil(t, err) - assert.Equal(t, tt.wantStatus, status) - }) - } -} - -func (testSuite *MemoryBlockTest) TestTwoNotifyReadyWithoutAwaitReady() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - - mb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) - // 2nd notify will lead to panic since it is not allowed to notify a block more than once. - assert.Panics(testSuite.T(), func() { - mb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) - }) -} - -func (testSuite *MemoryBlockTest) TestNotifyReadyAfterAwaitReady() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - ctx, cancel := context.WithTimeout(testSuite.T().Context(), 100*time.Millisecond) - defer cancel() - go func() { - mb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) - }() - status, err := mb.AwaitReady(ctx) - require.Nil(testSuite.T(), err) - assert.Equal(testSuite.T(), BlockStatus{State: BlockStateDownloaded}, status) - - // 2nd notify will lead to panic since channel is closed after first await ready. - assert.Panics(testSuite.T(), func() { - mb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) - }) -} - -func (testSuite *MemoryBlockTest) TestSingleNotifyAndMultipleAwaitReady() { - mb, err := createBlock(12) - require.Nil(testSuite.T(), err) - go func() { - mb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) - }() - ctx, cancel := context.WithTimeout(testSuite.T().Context(), 5*time.Millisecond) - defer cancel() - var wg sync.WaitGroup - wg.Add(5) - - // Multiple goroutines waiting for the same block to be ready. - // They should all receive the same status once the block is notified. - for i := 0; i < 5; i++ { - go func() { - defer wg.Done() - - status, err := mb.AwaitReady(ctx) - - require.Nil(testSuite.T(), err) - assert.Equal(testSuite.T(), BlockStatus{State: BlockStateDownloaded}, status) - }() - } - wg.Wait() -} diff --git a/internal/block/prefetch_block.go b/internal/block/prefetch_block.go new file mode 100644 index 0000000000..ebc1a2dff9 --- /dev/null +++ b/internal/block/prefetch_block.go @@ -0,0 +1,182 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package block + +import ( + "context" + "fmt" + "io" + "syscall" +) + +// BlockStatus represents the status of a block. +// It contains the state of the block and an error +// that may have occurred during the block's operation. +type BlockStatus struct { + State BlockState + Err error +} + +// BlockState represents the state of the block. +type BlockState int + +const ( + BlockStateInProgress BlockState = iota // Download of this block is in progress + BlockStateDownloaded // Download of this block is complete + BlockStateDownloadFailed // Download of this block has failed + BlockStateDownloadCancelled // Download of this block has been cancelled +) + +type PrefetchBlock interface { + Block + + // Follows io.ReaderAt interface. + // Here, off is relative to the start of the block. + ReadAt(p []byte, off int64) (n int, err error) + + // AbsStartOff returns the absolute start offset of the block. + // Panics if the absolute start offset is not set. + AbsStartOff() int64 + + // SetAbsStartOff sets the absolute start offset of the block. + // This should be called only once just after getting the block from the pool. + // It returns an error if the startOff is negative or if it is already set. + // TODO(princer): check if a way to set it as part of constructor. + SetAbsStartOff(startOff int64) error + + // AwaitReady waits for the block to be ready to consume. + // It returns the status of the block and an error if any. + AwaitReady(ctx context.Context) (BlockStatus, error) + + // NotifyReady is used by producer to mark the block as ready to consume. + // The value indicates the status of the block: + // - BlockStatusDownloaded: Download of this block is complete. + // - BlockStatusDownloadFailed: Download of this block has failed. + // - BlockStatusDownloadCancelled: Download of this block has been cancelled. + NotifyReady(val BlockStatus) +} + +type prefetchMemoryBlock struct { + memoryBlock + + // Indicates if block is in progress, downloaded, download failed or download cancelled. + status BlockStatus + + // notification is a channel that notifies when the block is ready to consume. + notification chan BlockStatus + + // Stores the absolute start offset of the block-segment in the file. + absStartOff int64 +} + +func (pmb *prefetchMemoryBlock) Reuse() { + pmb.memoryBlock.Reuse() + + pmb.notification = make(chan BlockStatus, 1) + pmb.status = BlockStatus{State: BlockStateInProgress} + pmb.absStartOff = -1 +} + +// createPrefetchBlock creates a new PrefetchBlock. +func createPrefetchBlock(blockSize int64) (PrefetchBlock, error) { + prot, flags := syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANON|syscall.MAP_PRIVATE + addr, err := syscall.Mmap(-1, 0, int(blockSize), prot, flags) + if err != nil { + return nil, fmt.Errorf("mmap error: %v", err) + } + + mb := memoryBlock{ + buffer: addr, + offset: offset{0, 0}, + } + + pmb := prefetchMemoryBlock{ + memoryBlock: mb, + status: BlockStatus{State: BlockStateInProgress}, + notification: make(chan BlockStatus, 1), + absStartOff: -1, + } + + return &pmb, nil +} + +// ReadAt reads data from the block at the specified offset. +// The offset is relative to the start of the block. +// It returns the number of bytes read and an error if any. +func (pmb *prefetchMemoryBlock) ReadAt(p []byte, off int64) (n int, err error) { + if off < 0 || off >= pmb.Size() { + return 0, fmt.Errorf("offset %d is out of bounds for block size %d", off, pmb.Size()) + } + + n = copy(p, pmb.buffer[pmb.offset.start+off:pmb.offset.end]) + + if n < len(p) { + return n, io.EOF + } + return n, nil +} + +func (pmb *prefetchMemoryBlock) AbsStartOff() int64 { + if pmb.absStartOff < 0 { + panic("AbsStartOff is not set, it should be set before calling this method.") + } + return pmb.absStartOff +} + +func (pmb *prefetchMemoryBlock) SetAbsStartOff(startOff int64) error { + if startOff < 0 { + return fmt.Errorf("startOff cannot be negative, got %d", startOff) + } + + // If absStartOff is already set, then return an error. + if pmb.absStartOff >= 0 { + return fmt.Errorf("AbsStartOff is already set, it should be set only once.") + } + + pmb.absStartOff = startOff + return nil +} + +// AwaitReady waits for the block to be ready to consume. +// It returns the status of the block and an error if any. +func (pmb *prefetchMemoryBlock) AwaitReady(ctx context.Context) (BlockStatus, error) { + select { + case val, ok := <-pmb.notification: + if !ok { + return pmb.status, nil + } + + // Close the notification channel to prevent further notifications. + close(pmb.notification) + // Save the last status for subsequent AwaitReady calls. + pmb.status = val + + return pmb.status, nil + case <-ctx.Done(): + return BlockStatus{State: BlockStateInProgress}, ctx.Err() + } +} + +// NotifyReady is used by the producer to mark the block as ready to consume. +// This should be called only once to notify the consumer. +// If called multiple times, it will panic - either because of writing to the +// closed channel or blocking due to writing over full notification channel. +func (pmb *prefetchMemoryBlock) NotifyReady(val BlockStatus) { + select { + case pmb.notification <- val: + default: + panic("Expected to notify only once, but got multiple notifications.") + } +} diff --git a/internal/block/prefetch_block_test.go b/internal/block/prefetch_block_test.go new file mode 100644 index 0000000000..0cd13d4f41 --- /dev/null +++ b/internal/block/prefetch_block_test.go @@ -0,0 +1,292 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package block + +import ( + "context" + "io" + "sync" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +// const outOfCapacityError string = "received data more than capacity of the block" + +type PrefetchMemoryBlockTest struct { + MemoryBlockTest +} + +func TestPrefetchMemoryBlockTestSuite(t *testing.T) { + suite.Run(t, new(PrefetchMemoryBlockTest)) +} + +func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockReuse() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hi") + n, err := pmb.Write(content) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), 2, n) + output, err := io.ReadAll(pmb.Reader()) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), content, output) + require.Equal(testSuite.T(), int64(2), pmb.Size()) + err = pmb.SetAbsStartOff(23) + require.Nil(testSuite.T(), err) + + pmb.Reuse() + + output, err = io.ReadAll(pmb.Reader()) + assert.Nil(testSuite.T(), err) + assert.Empty(testSuite.T(), output) + assert.Equal(testSuite.T(), int64(0), pmb.Size()) + assert.Panics(testSuite.T(), func() { + _ = pmb.AbsStartOff() + }) +} + +func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockReadAtSuccess() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + _, err = pmb.Write(content) + require.Nil(testSuite.T(), err) + readBuffer := make([]byte, 5) + + n, err := pmb.ReadAt(readBuffer, 6) // Read "world" + + assert.Nil(testSuite.T(), err) + assert.Equal(testSuite.T(), 5, n) + assert.Equal(testSuite.T(), []byte("world"), readBuffer) +} + +func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockReadAtBeyondBlockSize() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + _, err = pmb.Write(content) + require.Nil(testSuite.T(), err) + readBuffer := make([]byte, 5) + + n, err := pmb.ReadAt(readBuffer, 15) // Read beyond the block size + + assert.NotNil(testSuite.T(), err) + assert.NotErrorIs(testSuite.T(), err, io.EOF) + assert.Equal(testSuite.T(), 0, n) +} + +func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockReadAtWithNegativeOffset() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + _, err = pmb.Write(content) + require.Nil(testSuite.T(), err) + readBuffer := make([]byte, 5) + + n, err := pmb.ReadAt(readBuffer, -1) // Negative offset + + assert.NotNil(testSuite.T(), err) + assert.NotErrorIs(testSuite.T(), err, io.EOF) + assert.Equal(testSuite.T(), 0, n) +} + +func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockReadAtEOF() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + _, err = pmb.Write(content) + require.Nil(testSuite.T(), err) + readBuffer := make([]byte, 15) + + n, err := pmb.ReadAt(readBuffer, 6) // Read "world" + + assert.Equal(testSuite.T(), io.EOF, err) + assert.Equal(testSuite.T(), 5, n) + assert.Equal(testSuite.T(), []byte("world"), readBuffer[0:n]) +} + +func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockAbsStartOffsetPanicsOnEmptyBlock() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + + // The absolute start offset should be -1 initially. + assert.Panics(testSuite.T(), func() { + _ = pmb.AbsStartOff() + }) +} + +func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockAbsStartOffsetValid() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + + // Set the absolute start offset to a valid value. + pmb.(*prefetchMemoryBlock).absStartOff = 100 + + // The absolute start offset should return the set value. + assert.Equal(testSuite.T(), int64(100), pmb.AbsStartOff()) +} + +func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockSetAbsStartOffsetInvalid() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + + err = pmb.SetAbsStartOff(-23) + + assert.Error(testSuite.T(), err) +} + +func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockSetAbsStartOffsetValid() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + + err = pmb.SetAbsStartOff(23) + + assert.NoError(testSuite.T(), err) + assert.Equal(testSuite.T(), int64(23), pmb.AbsStartOff()) +} + +func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockSetAbsStartOffsetTwiceInvalid() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + err = pmb.SetAbsStartOff(23) + require.Nil(testSuite.T(), err) + + err = pmb.SetAbsStartOff(42) + + assert.Error(testSuite.T(), err) +} + +func (testSuite *PrefetchMemoryBlockTest) TestAwaitReadyWaitIfNotNotify() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + ctx, cancel := context.WithTimeout(testSuite.T().Context(), 100*time.Millisecond) + defer cancel() + + _, err = pmb.AwaitReady(ctx) + + assert.NotNil(testSuite.T(), err) + assert.EqualError(testSuite.T(), context.DeadlineExceeded, err.Error()) +} + +func (testSuite *PrefetchMemoryBlockTest) TestAwaitReadyReturnsErrorOnContextCancellation() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + ctx, cancel := context.WithCancel(testSuite.T().Context()) + cancel() // Cancel the context immediately + + _, err = pmb.AwaitReady(ctx) + + require.NotNil(testSuite.T(), err) + assert.EqualError(testSuite.T(), context.Canceled, err.Error()) +} + +func (testSuite *PrefetchMemoryBlockTest) TestAwaitReadyNotifyVariants() { + tests := []struct { + name string + notifyStatus BlockStatus + wantStatus BlockStatus + }{ + { + name: "AfterNotifySuccess", + notifyStatus: BlockStatus{State: BlockStateDownloaded}, + wantStatus: BlockStatus{State: BlockStateDownloaded}, + }, + { + name: "AfterNotifyError", + notifyStatus: BlockStatus{State: BlockStateDownloadFailed}, + wantStatus: BlockStatus{State: BlockStateDownloadFailed}, + }, + { + name: "AfterNotifyCancelled", + notifyStatus: BlockStatus{State: BlockStateDownloadCancelled}, + wantStatus: BlockStatus{State: BlockStateDownloadCancelled}, + }, + } + + for _, tt := range tests { + testSuite.T().Run(tt.name, func(t *testing.T) { + pmb, err := createPrefetchBlock(12) + require.Nil(t, err) + go func() { + time.Sleep(time.Millisecond) + pmb.NotifyReady(tt.notifyStatus) + }() + + status, err := pmb.AwaitReady(context.Background()) + + require.Nil(t, err) + assert.Equal(t, tt.wantStatus, status) + }) + } +} + +func (testSuite *PrefetchMemoryBlockTest) TestTwoNotifyReadyWithoutAwaitReady() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + + pmb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) + // 2nd notify will lead to panic since it is not allowed to notify a block more than once. + assert.Panics(testSuite.T(), func() { + pmb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) + }) +} + +func (testSuite *PrefetchMemoryBlockTest) TestNotifyReadyAfterAwaitReady() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + ctx, cancel := context.WithTimeout(testSuite.T().Context(), 100*time.Millisecond) + defer cancel() + go func() { + pmb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) + }() + status, err := pmb.AwaitReady(ctx) + require.Nil(testSuite.T(), err) + assert.Equal(testSuite.T(), BlockStatus{State: BlockStateDownloaded}, status) + + // 2nd notify will lead to panic since channel is closed after first await ready. + assert.Panics(testSuite.T(), func() { + pmb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) + }) +} + +func (testSuite *PrefetchMemoryBlockTest) TestSingleNotifyAndMultipleAwaitReady() { + pmb, err := createPrefetchBlock(12) + require.Nil(testSuite.T(), err) + go func() { + pmb.NotifyReady(BlockStatus{State: BlockStateDownloaded}) + }() + ctx, cancel := context.WithTimeout(testSuite.T().Context(), 5*time.Millisecond) + defer cancel() + var wg sync.WaitGroup + wg.Add(5) + + // Multiple goroutines waiting for the same block to be ready. + // They should all receive the same status once the block is notified. + for i := 0; i < 5; i++ { + go func() { + defer wg.Done() + + status, err := pmb.AwaitReady(ctx) + + require.Nil(testSuite.T(), err) + assert.Equal(testSuite.T(), BlockStatus{State: BlockStateDownloaded}, status) + }() + } + wg.Wait() +} diff --git a/internal/bufferedread/download_task.go b/internal/bufferedread/download_task.go index 223a378def..afeea72876 100644 --- a/internal/bufferedread/download_task.go +++ b/internal/bufferedread/download_task.go @@ -33,7 +33,7 @@ type DownloadTask struct { bucket gcs.Bucket // block is the block to which the data will be downloaded. - block block.Block + block block.PrefetchBlock // ctx is the context for the download task. It is used to cancel the download. ctx context.Context @@ -42,7 +42,7 @@ type DownloadTask struct { readHandle []byte } -func NewDownloadTask(ctx context.Context, object *gcs.MinObject, bucket gcs.Bucket, block block.Block, readHandle []byte) *DownloadTask { +func NewDownloadTask(ctx context.Context, object *gcs.MinObject, bucket gcs.Bucket, block block.PrefetchBlock, readHandle []byte) *DownloadTask { return &DownloadTask{ ctx: ctx, object: object, diff --git a/internal/bufferedread/download_task_test.go b/internal/bufferedread/download_task_test.go index 6485522c32..3b4660924f 100644 --- a/internal/bufferedread/download_task_test.go +++ b/internal/bufferedread/download_task_test.go @@ -44,7 +44,7 @@ type DownloadTaskTestSuite struct { suite.Suite object *gcs.MinObject mockBucket *storage.TestifyMockBucket - blockPool *block.BlockPool + blockPool *block.GenBlockPool[block.PrefetchBlock] } func TestDownloadTaskTestSuite(t *testing.T) { @@ -59,7 +59,7 @@ func (dts *DownloadTaskTestSuite) SetupTest() { } dts.mockBucket = new(storage.TestifyMockBucket) var err error - dts.blockPool, err = block.NewBlockPool(testBlockSize, 10, semaphore.NewWeighted(100)) + dts.blockPool, err = block.NewPrefetchBlockPool(testBlockSize, 10, semaphore.NewWeighted(100)) require.NoError(dts.T(), err, "Failed to create block pool") } diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 8b24e32a3d..9c82ad3462 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -64,7 +64,7 @@ type BufferedWriteHandler interface { // as it receives and handing over to uploadHandler which uploads to GCS. type bufferedWriteHandlerImpl struct { current block.Block - blockPool *block.BlockPool + blockPool *block.GenBlockPool[block.Block] uploadHandler *UploadHandler // Total size of data buffered so far. Some part of buffered data might have // been uploaded to GCS as well. Depending on the state we are in, it might or diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 2df49bc9c3..10814a56e6 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -40,7 +40,7 @@ type UploadHandler struct { wg sync.WaitGroup // Used to release the free (uploaded) block back to the pool. - blockPool *block.BlockPool + blockPool *block.GenBlockPool[block.Block] // writer to resumable upload the blocks to GCS. writer gcs.Writer @@ -63,7 +63,7 @@ type CreateUploadHandlerRequest struct { Object *gcs.Object ObjectName string Bucket gcs.Bucket - BlockPool *block.BlockPool + BlockPool *block.GenBlockPool[block.Block] MaxBlocksPerFile int64 BlockSize int64 ChunkTransferTimeoutSecs int64 diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index c2b74effe1..33b2bd4881 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -42,7 +42,7 @@ var finalized = time.Date(2025, time.June, 18, 23, 30, 0, 0, time.UTC) type UploadHandlerTest struct { uh *UploadHandler - blockPool *block.BlockPool + blockPool *block.GenBlockPool[block.Block] mockBucket *storagemock.TestifyMockBucket suite.Suite } From f2fc763d6c7fff5b2c141149ab3ed467accb8b50 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 21 Jul 2025 16:41:52 +0530 Subject: [PATCH 0577/1298] fix: remove extra go routine in read flow when interrupts are disabled (#3448) * remove extra go routine in read flow * empty commit * change in read manager too * change in UT * rebase changes --- internal/fs/fs.go | 4 +- internal/fs/handle/file.go | 12 ++--- internal/fs/handle/file_test.go | 16 +++---- internal/gcsx/client_readers/gcs_reader.go | 4 +- .../gcsx/client_readers/gcs_reader_test.go | 18 +++++--- internal/gcsx/client_readers/range_reader.go | 46 ++++++++++--------- .../gcsx/client_readers/range_reader_test.go | 2 + internal/gcsx/random_reader.go | 44 +++++++++--------- internal/gcsx/random_reader_stretchr_test.go | 6 +-- internal/gcsx/random_reader_test.go | 1 + internal/gcsx/read_manager/read_manager.go | 4 +- 11 files changed, 85 insertions(+), 72 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 8b3508b3e1..b0109c1371 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -1951,7 +1951,7 @@ func (fs *fileSystem) CreateFile( // CreateFile() invoked to create new files, can be safely considered as filehandle // opened in append mode. - fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, util.Append, &fs.newConfig.Read) + fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, util.Append, fs.newConfig) op.Handle = handleID fs.mu.Unlock() @@ -2714,7 +2714,7 @@ func (fs *fileSystem) OpenFile( // Figure out the mode in which the file is being opened. openMode := util.FileOpenMode(op) - fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, openMode, &fs.newConfig.Read) + fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, openMode, fs.newConfig) op.Handle = handleID // When we observe object generations that we didn't create, we assign them diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 4e5d35663c..a5929624e5 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -63,19 +63,19 @@ type FileHandle struct { // openMode is used to store the mode in which the file is opened. openMode util.OpenMode - // Read related mounting configuration. - readConfig *cfg.ReadConfig + // Mount configuration. + config *cfg.Config } // LOCKS_REQUIRED(fh.inode.mu) -func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle metrics.MetricHandle, openMode util.OpenMode, rc *cfg.ReadConfig) (fh *FileHandle) { +func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle metrics.MetricHandle, openMode util.OpenMode, c *cfg.Config) (fh *FileHandle) { fh = &FileHandle{ inode: inode, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, metricHandle: metricHandle, openMode: openMode, - readConfig: rc, + config: c, } fh.inode.RegisterFileHandle(fh.openMode == util.Read) @@ -270,7 +270,7 @@ func (fh *FileHandle) tryEnsureReader(ctx context.Context, sequentialReadSizeMb } // Attempt to create an appropriate reader. - rr := gcsx.NewRandomReader(fh.inode.Source(), fh.inode.Bucket(), sequentialReadSizeMb, fh.fileCacheHandler, fh.cacheFileForRangeRead, fh.metricHandle, &fh.inode.MRDWrapper, fh.readConfig) + rr := gcsx.NewRandomReader(fh.inode.Source(), fh.inode.Bucket(), sequentialReadSizeMb, fh.fileCacheHandler, fh.cacheFileForRangeRead, fh.metricHandle, &fh.inode.MRDWrapper, fh.config) fh.reader = rr return @@ -314,7 +314,7 @@ func (fh *FileHandle) tryEnsureReadManager(ctx context.Context, sequentialReadSi CacheFileForRangeRead: fh.cacheFileForRangeRead, MetricHandle: fh.metricHandle, MrdWrapper: &fh.inode.MRDWrapper, - ReadConfig: fh.readConfig, + Config: fh.config, }) return nil diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 8ad4855607..e08e7dc5b0 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -144,7 +144,7 @@ func (t *fileTest) TestFileHandleWrite() { parent := createDirInode(&t.bucket, &t.clock, "parentRoot") config := &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: false}} in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj", nil, false) - fh := NewFileHandle(in, nil, false, nil, util.Write, &cfg.ReadConfig{}) + fh := NewFileHandle(in, nil, false, nil, util.Write, &cfg.Config{}) data := []byte("hello") _, err := fh.Write(t.ctx, data, 0) @@ -167,7 +167,7 @@ func (t *fileTest) Test_Read_Success() { expectedData := []byte("hello from reader") parent := createDirInode(&t.bucket, &t.clock, "parentRoot") in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, "test_obj_reader", expectedData, false) - fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) buf := make([]byte, len(expectedData)) fh.inode.Lock() @@ -183,7 +183,7 @@ func (t *fileTest) Test_ReadWithReadManager_Success() { expectedData := []byte("hello from readManager") parent := createDirInode(&t.bucket, &t.clock, "parentRoot") in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, "test_obj_readManager", expectedData, false) - fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) buf := make([]byte, len(expectedData)) fh.inode.Lock() @@ -215,7 +215,7 @@ func (t *fileTest) Test_ReadWithReadManager_ErrorScenarios() { t.SetupTest() parent := createDirInode(&t.bucket, &t.clock, "parentRoot") testInode := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, []byte("data"), false) - fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) fh.inode.Lock() mockRM := new(read_manager.MockReadManager) mockRM.On("ReadAt", t.ctx, dst, int64(0)).Return(gcsx.ReaderResponse{}, tc.returnErr) @@ -253,7 +253,7 @@ func (t *fileTest) Test_Read_ErrorScenarios() { t.SetupTest() parent := createDirInode(&t.bucket, &t.clock, "parentRoot") testInode := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, []byte("data"), false) - fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) fh.inode.Lock() mockReader := new(gcsx.MockRandomReader) mockReader.On("ReadAt", t.ctx, dst, int64(0)).Return(gcsx.ObjectData{}, tc.returnErr) @@ -278,7 +278,7 @@ func (t *fileTest) Test_ReadWithReadManager_FallbackToInode() { object := gcs.MinObject{Name: "test_obj", Generation: 0} parent := createDirInode(&t.bucket, &t.clock, "parentRoot") in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, objectData, true) - fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) fh.inode.Lock() mockRM := new(read_manager.MockReadManager) mockRM.On("Destroy").Return() @@ -300,7 +300,7 @@ func (t *fileTest) Test_Read_FallbackToInode() { object := gcs.MinObject{Name: "test_obj", Generation: 0} parent := createDirInode(&t.bucket, &t.clock, "parentRoot") in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, objectData, true) - fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.ReadConfig{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) fh.inode.Lock() mockR := new(gcsx.MockRandomReader) mockR.On("Destroy").Return() @@ -336,7 +336,7 @@ func (t *fileTest) TestOpenMode() { parent := createDirInode(&t.bucket, &t.clock, "parentRoot") config := &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: false}} in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj", nil, false) - fh := NewFileHandle(in, nil, false, nil, tc.openMode, &cfg.ReadConfig{}) + fh := NewFileHandle(in, nil, false, nil, tc.openMode, &cfg.Config{}) openMode := fh.OpenMode() diff --git a/internal/gcsx/client_readers/gcs_reader.go b/internal/gcsx/client_readers/gcs_reader.go index 596b817c97..d80714200b 100644 --- a/internal/gcsx/client_readers/gcs_reader.go +++ b/internal/gcsx/client_readers/gcs_reader.go @@ -79,7 +79,7 @@ type GCSReaderConfig struct { MetricHandle metrics.MetricHandle MrdWrapper *gcsx.MultiRangeDownloaderWrapper SequentialReadSizeMb int32 - ReadConfig *cfg.ReadConfig + Config *cfg.Config } func NewGCSReader(obj *gcs.MinObject, bucket gcs.Bucket, config *GCSReaderConfig) *GCSReader { @@ -87,7 +87,7 @@ func NewGCSReader(obj *gcs.MinObject, bucket gcs.Bucket, config *GCSReaderConfig object: obj, bucket: bucket, sequentialReadSizeMb: config.SequentialReadSizeMb, - rangeReader: NewRangeReader(obj, bucket, config.ReadConfig, config.MetricHandle), + rangeReader: NewRangeReader(obj, bucket, config.Config, config.MetricHandle), mrr: NewMultiRangeReader(obj, config.MetricHandle, config.MrdWrapper), readType: metrics.ReadTypeSequential, } diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go index 04428db87a..d8c6430ab2 100644 --- a/internal/gcsx/client_readers/gcs_reader_test.go +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -78,7 +78,7 @@ func (t *gcsReaderTest) SetupTest() { MetricHandle: metrics.NewNoopMetrics(), MrdWrapper: nil, SequentialReadSizeMb: sequentialReadSizeInMb, - ReadConfig: nil, + Config: nil, }) t.ctx = context.Background() } @@ -102,7 +102,7 @@ func (t *gcsReaderTest) Test_NewGCSReader() { MetricHandle: metrics.NewNoopMetrics(), MrdWrapper: nil, SequentialReadSizeMb: 200, - ReadConfig: nil, + Config: nil, }) assert.Equal(t.T(), object, gcsReader.object) @@ -350,6 +350,12 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { } func (t *gcsReaderTest) Test_ReadAt_PropagatesCancellation() { + t.gcsReader = NewGCSReader(t.object, t.mockBucket, &GCSReaderConfig{ + MetricHandle: metrics.NewNoopMetrics(), + MrdWrapper: nil, + SequentialReadSizeMb: sequentialReadSizeInMb, + Config: &cfg.Config{FileSystem: cfg.FileSystemConfig{IgnoreInterrupts: false}}, + }) // Set up a blocking reader finishRead := make(chan struct{}) blocking := &blockingReader{c: finishRead} @@ -536,7 +542,7 @@ func (t *gcsReaderTest) Test_ReadInfo_Random() { func (t *gcsReaderTest) Test_ReadAt_WithAndWithoutReadConfig() { testCases := []struct { name string - config *cfg.ReadConfig + config *cfg.Config expectInactiveTimeoutReader bool }{ { @@ -546,12 +552,12 @@ func (t *gcsReaderTest) Test_ReadAt_WithAndWithoutReadConfig() { }, { name: "WithReadConfigAndZeroTimeout", - config: &cfg.ReadConfig{InactiveStreamTimeout: 0}, + config: &cfg.Config{Read: cfg.ReadConfig{InactiveStreamTimeout: 0}}, expectInactiveTimeoutReader: false, }, { name: "WithReadConfigAndPositiveTimeout", - config: &cfg.ReadConfig{InactiveStreamTimeout: 10 * time.Millisecond}, + config: &cfg.Config{Read: cfg.ReadConfig{InactiveStreamTimeout: 10 * time.Millisecond}}, expectInactiveTimeoutReader: true, }, } @@ -565,7 +571,7 @@ func (t *gcsReaderTest) Test_ReadAt_WithAndWithoutReadConfig() { t.SetupTest() // Resets mockBucket, rr, etc. for each sub-test defer t.TearDownTest() - t.gcsReader.rangeReader.readConfig = tc.config + t.gcsReader.rangeReader.config = tc.config t.gcsReader.rangeReader.reader = nil // Ensure startRead path is taken in ReadAt t.object.Size = objectSize // Prepare fake content for the GCS object. diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index cf8ac5ab14..aab0128261 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -61,16 +61,16 @@ type RangeReader struct { cancel func() readType string - readConfig *cfg.ReadConfig + config *cfg.Config metricHandle metrics.MetricHandle } -func NewRangeReader(object *gcs.MinObject, bucket gcs.Bucket, readConfig *cfg.ReadConfig, metricHandle metrics.MetricHandle) *RangeReader { +func NewRangeReader(object *gcs.MinObject, bucket gcs.Bucket, config *cfg.Config, metricHandle metrics.MetricHandle) *RangeReader { return &RangeReader{ object: object, bucket: bucket, metricHandle: metricHandle, - readConfig: readConfig, + config: config, start: -1, limit: -1, } @@ -196,28 +196,30 @@ func (rr *RangeReader) readFromRangeReader(ctx context.Context, p []byte, offset // // REQUIRES: rr.reader != nil func (rr *RangeReader) readFull(ctx context.Context, p []byte) (int, error) { - // Start a goroutine that will cancel the read operation we block on below if - // the calling context is cancelled, but only if this method has not already - // returned (to avoid souring the reader for the next read if this one is - // successful, since the calling context will eventually be cancelled). - readDone := make(chan struct{}) - defer close(readDone) - - go func() { - select { - case <-readDone: - return - - case <-ctx.Done(): + if rr.config != nil && !rr.config.FileSystem.IgnoreInterrupts { + // Start a goroutine that will cancel the read operation we block on below if + // the calling context is cancelled, but only if this method has not already + // returned (to avoid souring the reader for the next read if this one is + // successful, since the calling context will eventually be cancelled). + readDone := make(chan struct{}) + defer close(readDone) + + go func() { select { case <-readDone: return - default: - rr.cancel() + case <-ctx.Done(): + select { + case <-readDone: + return + + default: + rr.cancel() + } } - } - }() + }() + } return io.ReadFull(rr.reader, p) } @@ -229,7 +231,7 @@ func (rr *RangeReader) startRead(start int64, end int64) error { ctx, cancel := context.WithCancel(context.Background()) var err error - if rr.readConfig != nil && rr.readConfig.InactiveStreamTimeout > 0 { + if rr.config != nil && rr.config.Read.InactiveStreamTimeout > 0 { rr.reader, err = gcsx.NewInactiveTimeoutReader( ctx, rr.bucket, @@ -239,7 +241,7 @@ func (rr *RangeReader) startRead(start int64, end int64) error { Start: uint64(start), Limit: uint64(end), }, - rr.readConfig.InactiveStreamTimeout) + rr.config.Read.InactiveStreamTimeout) } else { rr.reader, err = rr.bucket.NewReaderWithReadHandle( ctx, diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go index fac3675b10..fd1955da25 100644 --- a/internal/gcsx/client_readers/range_reader_test.go +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -24,6 +24,7 @@ import ( "testing/iotest" "time" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" @@ -418,6 +419,7 @@ func (t *rangeReaderTest) Test_ReadFromRangeReader_WhenReaderReturnedMoreData() } func (t *rangeReaderTest) Test_ReadAt_PropagatesCancellation() { + t.rangeReader = NewRangeReader(t.object, t.mockBucket, &cfg.Config{FileSystem: cfg.FileSystemConfig{IgnoreInterrupts: false}}, metrics.NewNoopMetrics()) // Set up a blocking reader finishRead := make(chan struct{}) blocking := &blockingReader{c: finishRead} diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 5bc8c6f28f..1a823f9acd 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -103,7 +103,7 @@ const ( // NewRandomReader create a random reader for the supplied object record that // reads using the given bucket. -func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb int32, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle metrics.MetricHandle, mrdWrapper *MultiRangeDownloaderWrapper, config *cfg.ReadConfig) RandomReader { +func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb int32, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle metrics.MetricHandle, mrdWrapper *MultiRangeDownloaderWrapper, config *cfg.Config) RandomReader { return &randomReader{ object: o, bucket: bucket, @@ -174,7 +174,7 @@ type randomReader struct { metricHandle metrics.MetricHandle - config *cfg.ReadConfig + config *cfg.Config // Specifies the next expected offset for the reads. Used to distinguish between // sequential and random reads. @@ -427,28 +427,30 @@ func (rr *randomReader) Destroy() { func (rr *randomReader) readFull( ctx context.Context, p []byte) (n int, err error) { - // Start a goroutine that will cancel the read operation we block on below if - // the calling context is cancelled, but only if this method has not already - // returned (to avoid souring the reader for the next read if this one is - // successful, since the calling context will eventually be cancelled). - readDone := make(chan struct{}) - defer close(readDone) - - go func() { - select { - case <-readDone: - return - - case <-ctx.Done(): + if rr.config != nil && !rr.config.FileSystem.IgnoreInterrupts { + // Start a goroutine that will cancel the read operation we block on below if + // the calling context is cancelled, but only if this method has not already + // returned (to avoid souring the reader for the next read if this one is + // successful, since the calling context will eventually be cancelled). + readDone := make(chan struct{}) + defer close(readDone) + + go func() { select { case <-readDone: return - default: - rr.cancel() + case <-ctx.Done(): + select { + case <-readDone: + return + + default: + rr.cancel() + } } - } - }() + }() + } // Call through. n, err = io.ReadFull(rr.reader, p) @@ -463,7 +465,7 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { // Begin the read. ctx, cancel := context.WithCancel(context.Background()) - if rr.config != nil && rr.config.InactiveStreamTimeout > 0 { + if rr.config != nil && rr.config.Read.InactiveStreamTimeout > 0 { rr.reader, err = NewInactiveTimeoutReader( ctx, rr.bucket, @@ -473,7 +475,7 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { Start: uint64(start), Limit: uint64(end), }, - rr.config.InactiveStreamTimeout) + rr.config.Read.InactiveStreamTimeout) } else { rr.reader, err = rr.bucket.NewReaderWithReadHandle( ctx, diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 3692e75544..86f0d5de4f 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -934,7 +934,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ValidateTimeout func (t *RandomReaderStretchrTest) Test_ReadAt_WithAndWithoutReadConfig() { testCases := []struct { name string - config *cfg.ReadConfig + config *cfg.Config expectInactiveTimeoutReader bool }{ { @@ -944,12 +944,12 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_WithAndWithoutReadConfig() { }, { name: "WithReadConfigAndZeroTimeout", - config: &cfg.ReadConfig{InactiveStreamTimeout: 0}, + config: &cfg.Config{Read: cfg.ReadConfig{InactiveStreamTimeout: 0}}, expectInactiveTimeoutReader: false, }, { name: "WithReadConfigAndPositiveTimeout", - config: &cfg.ReadConfig{InactiveStreamTimeout: 10 * time.Millisecond}, + config: &cfg.Config{Read: cfg.ReadConfig{InactiveStreamTimeout: 10 * time.Millisecond}}, expectInactiveTimeoutReader: true, }, } diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index 9ab04e6371..1dc34131db 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -357,6 +357,7 @@ func (t *RandomReaderTest) PropagatesCancellation() { t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: rc} t.rr.wrapped.start = 1 t.rr.wrapped.limit = 4 + t.rr.wrapped.config = &cfg.Config{FileSystem: cfg.FileSystemConfig{IgnoreInterrupts: false}} // Snoop on when cancel is called. cancelCalled := make(chan struct{}) diff --git a/internal/gcsx/read_manager/read_manager.go b/internal/gcsx/read_manager/read_manager.go index fb6de3c675..69c03e2b00 100644 --- a/internal/gcsx/read_manager/read_manager.go +++ b/internal/gcsx/read_manager/read_manager.go @@ -43,7 +43,7 @@ type ReadManagerConfig struct { CacheFileForRangeRead bool MetricHandle metrics.MetricHandle MrdWrapper *gcsx.MultiRangeDownloaderWrapper - ReadConfig *cfg.ReadConfig + Config *cfg.Config } // NewReadManager creates a new ReadManager for the given GCS object, @@ -73,7 +73,7 @@ func NewReadManager(object *gcs.MinObject, bucket gcs.Bucket, config *ReadManage MetricHandle: config.MetricHandle, MrdWrapper: config.MrdWrapper, SequentialReadSizeMb: config.SequentialReadSizeMB, - ReadConfig: config.ReadConfig, + Config: config.Config, }, ) // Add the GCS reader as a fallback. From e35337e8f037be0f5fe2209a65738d759048e53d Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:15:17 +0000 Subject: [PATCH 0578/1298] fix(caching): Invalidate fast stat cache on NotFoundError (#3551) * invalidate upon not found error --- internal/storage/caching/fast_stat_bucket.go | 6 +++ .../storage/caching/fast_stat_bucket_test.go | 46 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/internal/storage/caching/fast_stat_bucket.go b/internal/storage/caching/fast_stat_bucket.go index adde06d5ae..0e3e8913a8 100644 --- a/internal/storage/caching/fast_stat_bucket.go +++ b/internal/storage/caching/fast_stat_bucket.go @@ -15,6 +15,7 @@ package caching import ( + "errors" "fmt" "strings" "sync" @@ -214,6 +215,11 @@ func (b *fastStatBucket) NewReaderWithReadHandle( ctx context.Context, req *gcs.ReadObjectRequest) (rd gcs.StorageReader, err error) { rd, err = b.wrapped.NewReaderWithReadHandle(ctx, req) + + var notFoundError *gcs.NotFoundError + if errors.As(err, ¬FoundError) { + b.invalidate(req.Name) + } return } diff --git a/internal/storage/caching/fast_stat_bucket_test.go b/internal/storage/caching/fast_stat_bucket_test.go index 514e09eccd..d459c83730 100644 --- a/internal/storage/caching/fast_stat_bucket_test.go +++ b/internal/storage/caching/fast_stat_bucket_test.go @@ -17,6 +17,8 @@ package caching_test import ( "errors" "fmt" + "io" + "strings" "testing" "time" @@ -24,6 +26,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/caching" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/caching/mock_gcscaching" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/oglemock" @@ -1230,3 +1233,46 @@ func (t *MoveObjectTest) MoveObjectSucceeds() { AssertEq(nil, err) ExpectEq(obj, o) } + +//////////////////////////////////////////////////////////////////////// +// NewReaderWithReadHandleTest +//////////////////////////////////////////////////////////////////////// + +type NewReaderWithReadHandleTest struct { + fastStatBucketTest +} + +func init() { RegisterTestSuite(&NewReaderWithReadHandleTest{}) } + +func (t *NewReaderWithReadHandleTest) CallsWrappedAndInvalidatesOnNotFound() { + const name = "some-name" + // Expect: wrapped bucket returns NotFoundError + var wrappedReq *gcs.ReadObjectRequest + ExpectCall(t.wrapped, "NewReaderWithReadHandle")(Any(), Any()). + WillOnce(DoAll(SaveArg(1, &wrappedReq), Return(nil, &gcs.NotFoundError{Err: errors.New("not found")}))) + // Expect: cache invalidate is called + ExpectCall(t.cache, "Erase")(name) + + // Call + req := &gcs.ReadObjectRequest{Name: name} + rd, err := t.bucket.NewReaderWithReadHandle(context.TODO(), req) + + AssertEq(nil, rd) + ExpectThat(err, Error(HasSubstr("not found"))) + AssertEq(name, wrappedReq.Name) +} + +func (t *NewReaderWithReadHandleTest) CallsWrappedAndDoesNotInvalidateOnSuccess() { + const name = "some-name" + expectedReader := &fake.FakeReader{ReadCloser: io.NopCloser(strings.NewReader("abc")), Handle: []byte("fake")} + // Expect: wrapped returns reader, no error + ExpectCall(t.wrapped, "NewReaderWithReadHandle")(Any(), Any()). + WillOnce(Return(expectedReader, nil)) + + // Call + req := &gcs.ReadObjectRequest{Name: name} + rd, err := t.bucket.NewReaderWithReadHandle(context.TODO(), req) + + AssertEq(nil, err) + ExpectEq(expectedReader, rd) +} From d79e0af2268f3f8cb90a8abe7b4583c4b7461ead Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 21 Jul 2025 19:33:10 +0530 Subject: [PATCH 0579/1298] chore(ci): Don't run go specific test if PR doesn't contain any related file (#3534) * chores(ci): run go test for changes in go specific files * Skip tools and perfmetrics directories in CI workflow * not bypassing lint and go formatting test --- .github/workflows/ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11d197be24..838061977f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,8 +7,27 @@ on: pull_request: branches: - '*' +permissions: + contents: read jobs: + filter: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + run_tests: ${{ steps.filter.outputs.run_tests }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3.0.2 + id: filter + with: + predicate-quantifier: 'every' + filters: | + run_tests: + - '!**/*.md' + - '!tools/**' + - '!perfmetrics/**' + - '!samples/**' format-test: runs-on: ubuntu-latest timeout-minutes: 5 @@ -24,6 +43,8 @@ jobs: run: go fmt ./... && go mod tidy && git diff --exit-code --name-only linux-tests: + needs: filter + if: ${{ needs.filter.outputs.run_tests == 'true' }} strategy: matrix: go: [ 1.24.x ] From af302260f582d10f2f6e13965acd6a07b947e572 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 22 Jul 2025 07:41:34 +0530 Subject: [PATCH 0580/1298] ci: moving path filters check at job.steps level (#3558) --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 838061977f..13668de1ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,6 @@ jobs: linux-tests: needs: filter - if: ${{ needs.filter.outputs.run_tests == 'true' }} strategy: matrix: go: [ 1.24.x ] @@ -60,13 +59,16 @@ jobs: - name: Install fuse run: sudo apt-get update && sudo apt-get install -y fuse3 libfuse-dev - name: Build + if: ${{ needs.filter.outputs.run_tests == 'true' }} run: | CGO_ENABLED=0 go build ./... go install ./tools/build_gcsfuse build_gcsfuse . /tmp ${GITHUB_SHA} - name: Test all + if: ${{ needs.filter.outputs.run_tests == 'true' }} run: CGO_ENABLED=0 go test -p 1 -count 1 -covermode=atomic -coverprofile=coverage.out -coverpkg=./... -v -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` `go list ./...` - name: RaceDetector Test + if: ${{ needs.filter.outputs.run_tests == 'true' }} run: go test -p 1 -count 1 -v -race -skip `cat flaky_tests.lst | go run tools/scripts/skip_tests/main.go` ./internal/cache/... ./internal/gcsx/... - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4.3.1 From f3ed14d2d39c78b95e0485953a35e9cc4cd57ac6 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 22 Jul 2025 09:38:59 +0530 Subject: [PATCH 0581/1298] Update ci.yml (#3554) ### Description Currently, we have a host of lint issues on master which we don't plan to fix anytime soon. Until then, the post-submit ci run always fails due to lint issues. Let's stop the linter run in postsubmits until all the lint issues are fixed. ### Link to the issue in case of a bug fix. b/433235596 ### Testing details 1. Manual - NA 2. Unit tests - NA 3. Integration tests - NA ### Any backward incompatible change? If so, please explain. No --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13668de1ea..fe498f3449 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,6 +80,7 @@ jobs: lint: name: Lint + if: github.ref != 'refs/heads/master' runs-on: ubuntu-latest steps: - name: Setup Go From 43355521f389c2f2c2dd624d79679ac5043955f2 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 22 Jul 2025 12:50:21 +0530 Subject: [PATCH 0582/1298] Increase flake-detector timeout (#3559) Increase flake-detector timeout since the tests have recently started taking more time. --- .github/workflows/flake-detector.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flake-detector.yml b/.github/workflows/flake-detector.yml index 165b9aff65..695710254b 100644 --- a/.github/workflows/flake-detector.yml +++ b/.github/workflows/flake-detector.yml @@ -8,7 +8,7 @@ on: jobs: flake-detector: runs-on: ubuntu-latest - timeout-minutes: 75 + timeout-minutes: 150 steps: - name: Checkout uses: actions/checkout@v4 From 28b71229936fa18560f57a2c1fa90d395507a5d2 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 23 Jul 2025 08:24:45 +0530 Subject: [PATCH 0583/1298] chore(PR Template): Update pull_request_template.md (#3549) Update pull_request_template.md to show the available PR types. --- .github/pull_request_template.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6107ec0ebf..abd95e8498 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,13 +3,25 @@ type(scope): subject ``` Example: -feat(api): add user login endpoint +`feat(api): add user login endpoint` + +Available types: +- `feat`: A new feature +- `fix`: A bug fix +- `docs`: Documentation only changes +- `style`: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) +- `refactor`: A code change that neither fixes a bug nor adds a feature +- `perf`: A code change that improves performance +- `test`: Adding missing tests or correcting existing tests +- `build`: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm) +- `ci`: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs) +- `chore`: Other changes that don't modify src or test files +- `revert`: Reverts a previous commit ### Description ### Link to the issue in case of a bug fix. - ### Testing details 1. Manual - NA 2. Unit tests - NA From 8e9c4a756c86acbe0bbe1e349bf79ccd5fc21ed1 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 23 Jul 2025 08:30:27 +0530 Subject: [PATCH 0584/1298] fix: retry log from info to warning (#3564) --- internal/storage/storageutil/custom_retry.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/storage/storageutil/custom_retry.go b/internal/storage/storageutil/custom_retry.go index 7fd6f91dc1..99c6698e5c 100644 --- a/internal/storage/storageutil/custom_retry.go +++ b/internal/storage/storageutil/custom_retry.go @@ -29,7 +29,7 @@ import ( func ShouldRetry(err error) (b bool) { b = storage.ShouldRetry(err) if b { - logger.Infof("Retrying for the error: %v", err) + logger.Warnf("Retrying for the error: %v", err) return } @@ -43,7 +43,7 @@ func ShouldRetry(err error) (b bool) { if typed, ok := err.(*googleapi.Error); ok { if typed.Code == 401 { b = true - logger.Infof("Retrying for error-code 401: %v", err) + logger.Warnf("Retrying for error-code 401: %v", err) return } } @@ -54,7 +54,7 @@ func ShouldRetry(err error) (b bool) { if status, ok := status.FromError(err); ok { if status.Code() == codes.Unauthenticated { b = true - logger.Infof("Retrying for UNAUTHENTICATED error: %v", err) + logger.Warnf("Retrying for UNAUTHENTICATED error: %v", err) return } } From 61ed3302f64030930a9c830b436af7a185ae40ab Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Wed, 23 Jul 2025 04:20:27 +0000 Subject: [PATCH 0585/1298] feat(Notifier): Use notifier to invalidate stale dentry cache on clobber (#3555) * Notifier integration --- cmd/mount.go | 3 + common/util.go | 42 ++++++++++ common/util_test.go | 46 +++++++++++ internal/fs/fs.go | 88 ++++++++++++++++++++- internal/fs/inode/name.go | 24 ++++++ internal/fs/inode/name_test.go | 44 +++++++++++ internal/fs/notifier_test.go | 136 +++++++++++++++++++++++++++++++++ internal/fs/server.go | 3 + 8 files changed, 385 insertions(+), 1 deletion(-) create mode 100644 internal/fs/notifier_test.go diff --git a/cmd/mount.go b/cmd/mount.go index f06b1b3164..9d734bea28 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -122,6 +122,9 @@ be interacting with the file system.`) NewConfig: newConfig, MetricHandle: metricHandle, } + if serverCfg.NewConfig.FileSystem.ExperimentalEnableDentryCache { + serverCfg.Notifier = fuse.NewNotifier() + } logger.Infof("Creating a new server...\n") server, err := fs.NewServer(ctx, serverCfg) diff --git a/common/util.go b/common/util.go index e17b0d440f..4468251f73 100644 --- a/common/util.go +++ b/common/util.go @@ -17,6 +17,9 @@ package common import ( "context" "errors" + "fmt" + "log" + "os" "os/exec" "regexp" "strings" @@ -78,3 +81,42 @@ func IsKLCacheEvictionUnSupported() (bool, error) { return false, nil } + +func CloseFile(file *os.File) { + if err := file.Close(); err != nil { + log.Fatalf("error in closing: %v", err) + } +} + +func WriteFile(fileName string, content string) (err error) { + f, err := os.OpenFile(fileName, os.O_RDWR, 0600) + if err != nil { + err = fmt.Errorf("open file for write at start: %v", err) + return + } + + // Closing file at the end. + defer CloseFile(f) + + _, err = f.WriteAt([]byte(content), 0) + + return +} + +func ReadFile(filePath string) (content []byte, err error) { + f, err := os.OpenFile(filePath, os.O_RDONLY, 0600) + if err != nil { + err = fmt.Errorf("error in the opening the file %v", err) + return + } + + // Closing file at the end. + defer CloseFile(f) + + content, err = os.ReadFile(f.Name()) + if err != nil { + err = fmt.Errorf("ReadAll: %v", err) + return + } + return +} diff --git a/common/util_test.go b/common/util_test.go index 95bd0a037e..b150f06d5c 100644 --- a/common/util_test.go +++ b/common/util_test.go @@ -16,6 +16,7 @@ package common import ( "context" "fmt" + "os" "testing" "github.com/stretchr/testify/assert" @@ -146,3 +147,48 @@ func TestJoinShutdownFunc(t *testing.T) { }) } } + +func TestCloseFile(t *testing.T) { + // Setup + f, err := os.CreateTemp("", "testFile-*") + require.NoError(t, err) + + // Close file and assert + assert.NotPanics(t, func() { CloseFile(f) }) +} + +func TestWriteFile(t *testing.T) { + // Setup + tmpFile, err := os.CreateTemp("", "testFile-*") + require.NoError(t, err) + filePath := tmpFile.Name() + defer os.Remove(filePath) + require.NoError(t, tmpFile.Close()) + + // Call WriteFile + err = WriteFile(filePath, "content") + + // Assertions + assert.NoError(t, err) + data, err := ReadFile(filePath) + require.NoError(t, err) + assert.Equal(t, "content", string(data)) +} + +func TestReadFile(t *testing.T) { + // Setup + file, err := os.CreateTemp("", "testFile-*") + require.NoError(t, err) + fileName := file.Name() + defer os.Remove(fileName) + _, err = file.WriteString("content") + require.NoError(t, err) + require.NoError(t, file.Close()) + + // Call ReadFile + content, err := ReadFile(fileName) + + // Assertions + assert.NoError(t, err) + assert.Equal(t, "content", string(content)) +} diff --git a/internal/fs/fs.go b/internal/fs/fs.go index b0109c1371..0466973c18 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -29,6 +29,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/sync/semaphore" @@ -131,6 +132,12 @@ type ServerConfig struct { NewConfig *cfg.Config MetricHandle metrics.MetricHandle + + // Notifier allows the file system to send invalidation messages to the FUSE + // kernel module. This enables proactive cache invalidation (e.g., for dentries) + // when underlying content changes, improving consistency while still leveraging + // kernel caching. + Notifier *fuse.Notifier } // Create a fuse file system server according to the supplied configuration. @@ -199,6 +206,9 @@ func NewFileSystem(ctx context.Context, serverCfg *ServerConfig) (fuseutil.FileS enableAtomicRenameObject: serverCfg.NewConfig.EnableAtomicRenameObject, globalMaxWriteBlocksSem: semaphore.NewWeighted(serverCfg.NewConfig.Write.GlobalMaxBlocks), } + if serverCfg.Notifier != nil { + fs.notifier = serverCfg.Notifier + } // Set up root bucket var root inode.DirInode @@ -494,6 +504,11 @@ type fileSystem struct { // Limits the max number of blocks that can be created across file system when // streaming writes are enabled. globalMaxWriteBlocksSem *semaphore.Weighted + + // notifier allows sending invalidation messages to the FUSE kernel module. + // It is used to invalidate the kernel's dentry cache, + // providing feedback to the kernel about dynamic content changes. + notifier *fuse.Notifier } //////////////////////////////////////////////////////////////////////// @@ -1554,6 +1569,53 @@ func (fs *fileSystem) lookupAndFetchAttributesForLocalFileEntriesPlus(parentName return } +// invalidateCachedEntry sends a notification to the kernel to invalidate a stale +// directory entry, ensuring consistency when file content changes dynamically. +// It identifies the parent of the given child inode and sends a notification +// to the kernel to remove the entry corresponding to the child's +// name within that parent directory. +// +// LOCKS_EXCLUDED(fs.mu) +func (fs *fileSystem) invalidateCachedEntry(childID fuseops.InodeID) error { + fs.mu.Lock() + childInode, ok := fs.inodes[childID] + fs.mu.Unlock() + if !ok { + return fmt.Errorf("invalidateCachedEntry: inode with ID %d not found", childID) + } + + childName := childInode.Name() + parentPath := path.Dir(childName.LocalName()) + // If the parent path resolves to the current directory ".", it means the parent + // is the root of the file system. + if parentPath == "." { + return fs.notifier.InvalidateEntry(fuseops.RootInodeID, path.Base(childInode.Name().LocalName())) + } + + parentName, err := childName.ParentName() + if err != nil { + return fmt.Errorf("invalidateCachedEntry: cannot find Parent name: %w", err) + } + childBase := path.Base(childName.LocalName()) + + fs.mu.Lock() + defer fs.mu.Unlock() + + var parentInodeID fuseops.InodeID + // Check in all maps: implicit dirs → folders → generation-backed + if parentInode, ok := fs.implicitDirInodes[parentName]; ok { + parentInodeID = parentInode.ID() + } else if parentInode, ok := fs.folderInodes[parentName]; ok { + parentInodeID = parentInode.ID() + } else if parentInode, ok := fs.generationBackedInodes[parentName]; ok { + parentInodeID = parentInode.ID() + } else { + return fmt.Errorf("invalidateCachedEntry: failed to invalidate the entry, parent inode not found for child ID %d (parent: %s)", childID, parentName.String()) + } + + return fs.notifier.InvalidateEntry(parentInodeID, childBase) +} + //////////////////////////////////////////////////////////////////////// // fuse.FileSystem methods //////////////////////////////////////////////////////////////////////// @@ -2769,6 +2831,18 @@ func (fs *fileSystem) ReadFile( op.Dst, op.BytesRead, err = fh.Read(ctx, op.Dst, op.Offset, fs.sequentialReadSizeMb) } + // A FileClobberedError indicates the underlying GCS object has changed, + // making the kernel's dentry for this file stale. We use the notifier to + // invalidate this entry, providing feedback to the kernel about the dynamic + // content change and ensuring subsequent lookups fetch the correct metadata. + if fs.newConfig.FileSystem.ExperimentalEnableDentryCache { + var clobberedErr *gcsfuse_errors.FileClobberedError + if err != nil && errors.As(err, &clobberedErr) { + if invalidateErr := fs.invalidateCachedEntry(op.Inode); invalidateErr != nil { + err = fmt.Errorf("%w; additionally failed to invalidate entry: %w", err, invalidateErr) + } + } + } // As required by fuse, we don't treat EOF as an error. if err == io.EOF { err = nil @@ -2815,7 +2889,19 @@ func (fs *fileSystem) WriteFile( in.Lock() defer in.Unlock() if err = fs.initBufferedWriteHandlerAndSyncFileIfEligible(ctx, in, fh.OpenMode()); err != nil { - return + // A FileClobberedError on write indicates the file was modified in GCS, + // making the kernel's dentry stale. By invalidating the cache + // entry, we ensure the filesystem corrects the inconsistency caused by this + // dynamic content change. + if fs.newConfig.FileSystem.ExperimentalEnableDentryCache { + var clobberedErr *gcsfuse_errors.FileClobberedError + if errors.As(err, &clobberedErr) { + if invalidateErr := fs.invalidateCachedEntry(op.Inode); invalidateErr != nil { + err = fmt.Errorf("%w; additionally failed to invalidate entry: %w", err, invalidateErr) + } + } + } + return err } if fs.newConfig.Write.ExperimentalEnableRapidAppends { // Serve the request via the file handle. diff --git a/internal/fs/inode/name.go b/internal/fs/inode/name.go index 289e2a4356..2949e023dd 100644 --- a/internal/fs/inode/name.go +++ b/internal/fs/inode/name.go @@ -15,6 +15,7 @@ package inode import ( + "errors" "fmt" "strings" ) @@ -123,3 +124,26 @@ func (name Name) IsDirectChildOf(parent Name) bool { cleanDiff := strings.TrimSuffix(diff, "/") return !strings.Contains(cleanDiff, "/") } + +// ParentName returns the Name of the parent directory of the current Name. +func (name Name) ParentName() (Name, error) { + if name.IsBucketRoot() { + return Name{}, errors.New("root has no parent") + } + + objectName := strings.TrimSuffix(name.objectName, "/") // normalize for dir or file + lastSlash := strings.LastIndex(objectName, "/") + if lastSlash == -1 { + // Direct child of bucket root + return Name{ + bucketName: name.bucketName, + objectName: "", + }, nil + } + + parentObjectName := objectName[:lastSlash+1] // include trailing slash for dir + return Name{ + bucketName: name.bucketName, + objectName: parentObjectName, + }, nil +} diff --git a/internal/fs/inode/name_test.go b/internal/fs/inode/name_test.go index 7dbd92f935..28c3eb6532 100644 --- a/internal/fs/inode/name_test.go +++ b/internal/fs/inode/name_test.go @@ -18,6 +18,7 @@ import ( "testing" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" + . "github.com/jacobsa/oglematchers" . "github.com/jacobsa/ogletest" ) @@ -106,3 +107,46 @@ func TestNameAsMapKey(t *testing.T) { _, ok := count[bar] ExpectFalse(ok) } + +func TestParentName(t *testing.T) { + for _, bucketName := range []string{"", "bucketx"} { + // Setup + root := inode.NewRootName(bucketName) // "" + foo := inode.NewDirName(root, "foo") // "foo/" + bar := inode.NewDirName(foo, "bar") // "foo/bar/" + baz := inode.NewFileName(root, "baz") // "baz" + qux := inode.NewFileName(bar, "qux") // "foo/bar/qux" + // Test cases + testCases := []struct { + name inode.Name + expectedParentName inode.Name + }{ + {name: foo, expectedParentName: root}, + {name: bar, expectedParentName: foo}, + {name: baz, expectedParentName: root}, + {name: qux, expectedParentName: bar}, + } + + for _, tc := range testCases { + parent, err := tc.name.ParentName() + + ExpectEq(nil, err) + ExpectEq(tc.expectedParentName.GcsObjectName(), parent.GcsObjectName()) + ExpectEq(tc.expectedParentName.LocalName(), parent.LocalName()) + } + } +} + +func TestParentNameReturnsErrorOnBucketRoot(t *testing.T) { + for _, bucketName := range []string{"", "bucketx"} { + // Setup + root := inode.NewRootName(bucketName) // "" + + // Call ParentName on bucket root + _, err := root.ParentName() + + // Expect an error + ExpectNe(nil, err) + ExpectThat(err, Error(HasSubstr("root has no parent"))) + } +} diff --git a/internal/fs/notifier_test.go b/internal/fs/notifier_test.go new file mode 100644 index 0000000000..4e64758e73 --- /dev/null +++ b/internal/fs/notifier_test.go @@ -0,0 +1,136 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// A collection of tests to check notifier is invalidating the entry. +package fs_test + +import ( + "os" + "path" + "syscall" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "github.com/jacobsa/fuse" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type NotifierTest struct { + suite.Suite + fsTest +} + +func TestNotifierTestSuite(t *testing.T) { + suite.Run(t, new(NotifierTest)) +} + +func (t *NotifierTest) SetupSuite() { + t.serverCfg.ImplicitDirectories = true + t.serverCfg.InodeAttributeCacheTTL = 1000 * time.Second + t.serverCfg.Notifier = fuse.NewNotifier() + t.serverCfg.NewConfig = &cfg.Config{ + FileSystem: cfg.FileSystemConfig{ + ExperimentalEnableDentryCache: true, + PreconditionErrors: true, + }, + Write: cfg.WriteConfig{ + EnableStreamingWrites: true, + }, + } + t.fsTest.SetUpTestSuite() +} + +func (t *NotifierTest) TearDownTest() { + t.fsTest.TearDown() +} + +func (t *NotifierTest) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} + +func (t *NotifierTest) TestWriteFileWithRootDirParent() { + filePath := path.Join(mntDir, fileName) + // Create a file in GCS. + _, err := storageutil.CreateObject(ctx, bucket, fileName, []byte("initial content")) + require.NoError(t.T(), err) + // Stat file to cache its entry. + _, err = os.Stat(filePath) + require.NoError(t.T(), err) + // Clobber the file in GCS. This changes the object's generation, making + // our file handle stale. + _, err = storageutil.CreateObject(ctx, bucket, fileName, []byte("modified")) + require.NoError(t.T(), err) + + // Attempt to write. + err = common.WriteFile(filePath, "new data") + + // Should return stale file handle error. + require.Error(t.T(), err) + assert.Regexp(t.T(), syscall.ESTALE.Error(), err.Error()) + // Attempt to write again, the entry has now been invalidated. + err = common.WriteFile(filePath, "new data") + assert.NoError(t.T(), err) +} + +func (t *NotifierTest) TestWriteFileWithNonRootDirParent() { + filePath := path.Join(mntDir, "dir/foo") + // Create a file in GCS. + _, err := storageutil.CreateObject(ctx, bucket, "dir/foo", []byte("initial content")) + require.NoError(t.T(), err) + // Stat file to cache its entry. + _, err = os.Stat(filePath) + require.NoError(t.T(), err) + // Clobber the file in GCS. This changes the object's generation, making + // our file handle stale. + _, err = storageutil.CreateObject(ctx, bucket, "dir/foo", []byte("modified")) + require.NoError(t.T(), err) + + // Attempt to write. + err = common.WriteFile(filePath, "new data") + + // Should return stale file handle error. + require.Error(t.T(), err) + assert.Regexp(t.T(), syscall.ESTALE.Error(), err.Error()) + // Attempt to write again, the entry has now been invalidated. + err = common.WriteFile(filePath, "new data") + assert.NoError(t.T(), err) +} + +func (t *NotifierTest) TestReadFileDoNotFailPersistently() { + filePath := path.Join(mntDir, fileName) + // Create a file in GCS. + _, err := storageutil.CreateObject(ctx, bucket, fileName, []byte("initial content")) + require.NoError(t.T(), err) + // Stat file to cache its entry. + _, err = os.Stat(filePath) + require.NoError(t.T(), err) + // Clobber the file in GCS. This changes the object's generation, making + // our file handle stale. + _, err = storageutil.CreateObject(ctx, bucket, fileName, []byte("modified")) + require.NoError(t.T(), err) + + // Attempt to read file. + _, err = common.ReadFile(filePath) + + // Should return error. + assert.NotNil(t.T(), err) + // Attempt to read again, the entry has now been invalidated. + _, err = common.ReadFile(filePath) + assert.NoError(t.T(), err) +} diff --git a/internal/fs/server.go b/internal/fs/server.go index df8d41c694..a38fd32e3f 100644 --- a/internal/fs/server.go +++ b/internal/fs/server.go @@ -36,5 +36,8 @@ func NewServer(ctx context.Context, cfg *ServerConfig) (fuse.Server, error) { fs = wrappers.WithTracing(fs) } fs = wrappers.WithMonitoring(fs, cfg.MetricHandle) + if cfg.Notifier != nil { + return fuse.NewServerWithNotifier(cfg.Notifier, fuseutil.NewFileSystemServer(fs)), nil + } return fuseutil.NewFileSystemServer(fs), nil } From 377052af79bec844703213abda77666ac9eba7bb Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 23 Jul 2025 09:54:39 +0530 Subject: [PATCH 0586/1298] docs(semantics): minor doc change to test ci changes (#3553) * doc(semantics): minor doc change to test ci changes * dummy commit --- docs/semantics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/semantics.md b/docs/semantics.md index 752e1f185b..e44c33f21c 100644 --- a/docs/semantics.md +++ b/docs/semantics.md @@ -342,7 +342,7 @@ Cloud Storage Fuse also offers seamless support for buckets with hierarchical na HNS-enabled buckets offer several advantages over standard buckets when used with cloud storage fuse: -- HNS-enabled buckets eliminates the need for --implicit-dirs flag. HNS buckets inherently understand directories, so gcsfuse does not need to simulate directories using placeholder objects ( 0-byte objects ending with '/' ). Users will see consistent directory listings with or without the flag. +- HNS-enabled buckets eliminate the need for --implicit-dirs flag. HNS buckets inherently understand directories, so gcsfuse does not need to simulate directories using placeholder objects ( 0-byte objects ending with '/' ). Users will see consistent directory listings with or without the flag. - In HNS buckets, renaming a folder and its child folders is an atomic operation, meaning all associated resources—including objects and managed folders—are renamed in a single step. This ensures data consistency and significantly improves operation performance. - HNS buckets treat folders as first-class entities, closely aligning with traditional file system semantics. Commands like mkdir now directly create folder resources within the bucket, unlike with traditional buckets where directories were simulated using prefixes and 0-byte objects. - List object calls ([BucketHandle.Objects](https://cloud.google.com/storage/docs/json_api/v1/objects/list)), are replaced with [get folder](https://cloud.google.com/storage/docs/json_api/v1/folders/getfoldermetadata) calls, resulting in quicker response times and fewer overall list calls for every lookup operation. From 8b4e5ee56bdf8040975f61abc5a1d1a928b89508 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 23 Jul 2025 10:24:24 +0530 Subject: [PATCH 0587/1298] feat(bufferedread): Add initial structs for buffered reader (#3552) * Add Buffered Read Config and Buffered Reader * Add Destroy and CheckInvariant methods * rebase * formatting * Add unit tests * Updated MaxPrefetchBlockCnt --- internal/bufferedread/buffered_reader.go | 138 ++++++++++++++ internal/bufferedread/buffered_reader_test.go | 168 ++++++++++++++++++ 2 files changed, 306 insertions(+) create mode 100644 internal/bufferedread/buffered_reader.go create mode 100644 internal/bufferedread/buffered_reader_test.go diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go new file mode 100644 index 0000000000..9a02b7bc37 --- /dev/null +++ b/internal/bufferedread/buffered_reader.go @@ -0,0 +1,138 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bufferedread + +import ( + "fmt" + + "context" + + "github.com/googlecloudplatform/gcsfuse/v3/common" + "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" + "golang.org/x/sync/semaphore" +) + +type BufferedReadConfig struct { + MaxPrefetchBlockCnt int64 // Maximum number of blocks that can be prefetched + PrefetchBlockSizeBytes int64 // Size of each block to be prefetched. + InitialPrefetchBlockCnt int64 // Number of blocks to prefetch initially. + PrefetchMultiplier int64 // Multiplier for number of blocks to prefetch. + RandomReadsThreshold int64 // Number of random reads after which the reader falls back to GCS reader. +} + +// blockQueueEntry holds a data block with a function +// to cancel its in-flight download. +type blockQueueEntry struct { + block block.PrefetchBlock + cancel context.CancelFunc +} + +type BufferedReader struct { + gcsx.Reader + object *gcs.MinObject + bucket gcs.Bucket + config *BufferedReadConfig + + // nextBlockIndexToPrefetch is the index of the next block to be + // prefetched. + nextBlockIndexToPrefetch int64 + + // randomSeekCount is the number of random seeks performed. This is used to + // detect if the read pattern is random and fall back to a simpler GCS reader. + randomSeekCount int64 + + // numPrefetchBlocks is the number of blocks to prefetch in the next + // prefetching operation. + numPrefetchBlocks int64 + + blockQueue common.Queue[*blockQueueEntry] + + // TODO: Add readHandle for zonal bucket optimization. + + blockPool *block.GenBlockPool[block.PrefetchBlock] + workerPool workerpool.WorkerPool + metricHandle metrics.MetricHandle + + ctx context.Context + cancelFunc context.CancelFunc +} + +// NewBufferedReader returns a new bufferedReader instance. +func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *BufferedReadConfig, globalMaxBlocksSem *semaphore.Weighted, workerPool workerpool.WorkerPool, metricHandle metrics.MetricHandle) (*BufferedReader, error) { + blockpool, err := block.NewPrefetchBlockPool(config.PrefetchBlockSizeBytes, config.MaxPrefetchBlockCnt, globalMaxBlocksSem) + if err != nil { + return nil, fmt.Errorf("failed to create worker pool: %w", err) + } + + reader := &BufferedReader{ + object: object, + bucket: bucket, + config: config, + nextBlockIndexToPrefetch: 0, + randomSeekCount: 0, + numPrefetchBlocks: config.InitialPrefetchBlockCnt, + blockQueue: common.NewLinkedListQueue[*blockQueueEntry](), + blockPool: blockpool, + workerPool: workerPool, + metricHandle: metricHandle, + } + + reader.ctx, reader.cancelFunc = context.WithCancel(context.Background()) + return reader, nil +} + +func (p *BufferedReader) Destroy() { + if p.cancelFunc != nil { + p.cancelFunc() + p.cancelFunc = nil + } + + for !p.blockQueue.IsEmpty() { + bqe := p.blockQueue.Pop() + bqe.cancel() + + // We expect a context.Canceled error here, but we wait to ensure the + // block's worker goroutine has finished before releasing the block. + if _, err := bqe.block.AwaitReady(p.ctx); err != nil && err != context.Canceled { + logger.Warnf("bufferedread: error waiting for block on destroy: %v", err) + } + p.blockPool.Release(bqe.block) + } + + err := p.blockPool.ClearFreeBlockChannel(true) + if err != nil { + logger.Warnf("bufferedread: error clearing free block channel: %v", err) + } + p.blockPool = nil +} + +// CheckInvariants checks for internal consistency of the reader. +func (p *BufferedReader) CheckInvariants() { + + // The number of items in the blockQueue should not exceed the configured limit. + if int64(p.blockQueue.Len()) > p.config.MaxPrefetchBlockCnt { + panic(fmt.Sprintf("BufferedReader: blockQueue length %d exceeds limit %d", p.blockQueue.Len(), p.config.MaxPrefetchBlockCnt)) + } + + // The random seek count should never exceed the configured threshold. + if p.randomSeekCount > p.config.RandomReadsThreshold { + panic(fmt.Sprintf("BufferedReader: randomSeekCount %d exceeds threshold %d", p.randomSeekCount, p.config.RandomReadsThreshold)) + } +} diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go new file mode 100644 index 0000000000..41b99a67c3 --- /dev/null +++ b/internal/bufferedread/buffered_reader_test.go @@ -0,0 +1,168 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); + +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bufferedread + +import ( + "context" + "errors" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + "golang.org/x/sync/semaphore" +) + +const ( + testMaxPrefetchBlockCnt int64 = 10 + testGlobalMaxBlocks int64 = 20 + testPrefetchBlockSizeBytes int64 = 4096 + testInitialPrefetchBlockCnt int64 = 2 + testPrefetchMultiplier int64 = 2 + testRandomReadsThreshold int64 = 3 +) + +type BufferedReaderTest struct { + suite.Suite + ctx context.Context + object *gcs.MinObject + bucket *storage.TestifyMockBucket + globalMaxBlocksSem *semaphore.Weighted + config *BufferedReadConfig + workerPool workerpool.WorkerPool + metricHandle metrics.MetricHandle +} + +func TestBufferedReaderTestSuite(t *testing.T) { + suite.Run(t, new(BufferedReaderTest)) +} + +func (t *BufferedReaderTest) SetupTest() { + t.object = &gcs.MinObject{ + Name: "test_object", + Size: 1024, + Generation: 1234567890, + } + t.bucket = new(storage.TestifyMockBucket) + t.globalMaxBlocksSem = semaphore.NewWeighted(testGlobalMaxBlocks) + t.config = &BufferedReadConfig{ + MaxPrefetchBlockCnt: testMaxPrefetchBlockCnt, + PrefetchBlockSizeBytes: testPrefetchBlockSizeBytes, + InitialPrefetchBlockCnt: testInitialPrefetchBlockCnt, + PrefetchMultiplier: testPrefetchMultiplier, + RandomReadsThreshold: testRandomReadsThreshold, + } + var err error + t.workerPool, err = workerpool.NewStaticWorkerPool(2, 5) + require.NoError(t.T(), err, "Failed to create worker pool") + t.workerPool.Start() + t.metricHandle = metrics.NewNoopMetrics() + t.ctx = context.Background() +} + +func (t *BufferedReaderTest) TearDownTest() { + t.workerPool.Stop() +} + +func (t *BufferedReaderTest) TestNewBufferedReader() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err, "NewBufferedReader should not return error") + + assert.Equal(t.T(), t.object, reader.object, "object should match") + assert.Equal(t.T(), t.bucket, reader.bucket, "bucket should match") + assert.Equal(t.T(), t.config, reader.config, "config should match") + assert.Equal(t.T(), int64(0), reader.nextBlockIndexToPrefetch, "nextBlockIndexToPrefetch should be 0") + assert.Equal(t.T(), int64(0), reader.randomSeekCount, "randomSeekCount should be 0") + assert.Equal(t.T(), testInitialPrefetchBlockCnt, reader.numPrefetchBlocks, "numPrefetchBlocks should match") + assert.NotNil(t.T(), reader.blockQueue, "blockQueue should not be nil") + assert.NotNil(t.T(), reader.blockPool, "blockPool should have been created") + assert.Equal(t.T(), t.workerPool, reader.workerPool) + assert.Equal(t.T(), t.metricHandle, reader.metricHandle) + assert.NotNil(t.T(), reader.ctx) + assert.NotNil(t.T(), reader.cancelFunc) +} + +func (t *BufferedReaderTest) TestDestroySuccess() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err, "NewBufferedReader should not return error") + b, err := reader.blockPool.Get() + require.NoError(t.T(), err, "Failed to get block from pool") + reader.blockQueue.Push(&blockQueueEntry{ + block: b, + cancel: func() {}, + }) + + reader.Destroy() + + assert.Nil(t.T(), reader.cancelFunc) + assert.True(t.T(), reader.blockQueue.IsEmpty()) + assert.Nil(t.T(), reader.blockPool) +} + +func (t *BufferedReaderTest) TestDestroyAwaitReadyError() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err, "NewBufferedReader should not return error") + b, err := reader.blockPool.Get() + require.NoError(t.T(), err, "Failed to get block from pool") + reader.blockQueue.Push(&blockQueueEntry{ + block: b, + cancel: func() {}, + }) + + b.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadCancelled, Err: errors.New("test error")}) + reader.Destroy() + + assert.Nil(t.T(), reader.cancelFunc) + assert.True(t.T(), reader.blockQueue.IsEmpty(), "blockQueue should be empty after Destroy") + assert.Nil(t.T(), reader.blockPool) +} + +func (t *BufferedReaderTest) TestCheckInvariantsBlockQueueExceedsLimit() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err, "NewBufferedReader should not return error") + b, err := reader.blockPool.Get() + require.NoError(t.T(), err, "Failed to get block from pool") + + for range int(t.config.MaxPrefetchBlockCnt + 1) { + reader.blockQueue.Push(&blockQueueEntry{ + block: b, + cancel: func() {}, + }) + } + + assert.Panics(t.T(), func() { reader.CheckInvariants() }) +} + +func (t *BufferedReaderTest) TestCheckInvariantsRandomSeekCountExceedsThreshold() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err, "NewBufferedReader should not return error") + + reader.randomSeekCount = t.config.RandomReadsThreshold + 1 + + assert.Panics(t.T(), func() { reader.CheckInvariants() }) +} + +func (t *BufferedReaderTest) TestCheckInvariantsNoPanic() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err, "NewBufferedReader should not return error") + + assert.NotPanics(t.T(), func() { reader.CheckInvariants() }) +} From 6a995a4157b75e35a9553136794c23900553cd1f Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 23 Jul 2025 12:13:28 +0530 Subject: [PATCH 0588/1298] docs(semantics): Supporting symlinks to external paths (#3569) * Add a comment about supporting symlinks that point to files external to the mount-point. --- docs/semantics.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/semantics.md b/docs/semantics.md index e44c33f21c..8b61ccfe74 100644 --- a/docs/semantics.md +++ b/docs/semantics.md @@ -437,7 +437,13 @@ ___ # Symlink inodes -Cloud Storage FUSE represents symlinks with empty Cloud Storage objects that contain the custom metadata key ```gcsfuse_symlink_target```, with the value giving the target of a symlink. In other respects they work like a file inode, including receiving the same permissions. +Cloud Storage FUSE represents symlinks with empty Cloud Storage objects that contain the custom metadata key ```gcsfuse_symlink_target```, with the value giving the target of a symlink. In other respects they work like a file inode, including receiving the same permissions. + + +**Note** + +While GCSFuse supports symlinks that point to paths external to the mount point, it should be avoided as it could lead to broken links and security issues. + ___ From 6e272e1d5ade66ef9c92af82f7d49ef3ccb813a6 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:19:52 +0530 Subject: [PATCH 0589/1298] feat(Migrate Auth TPC): Function for creating credentials/tokenSource/UniverseDomain and add option for client (#3544) * adding token source function and unit tests * review comment from gemini * adding functions and unit tests * rebase * fmt lint * review comments * adding one more test * reformat the function * removing unnecessary fucntions --- go.mod | 2 +- .../storage/storageutil/auth_client_option.go | 56 +++++++++++++ .../storageutil/auth_client_option_test.go | 83 +++++++++++++++++++ .../storage/storageutil/testdata/key.json | 13 +++ 4 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 internal/storage/storageutil/auth_client_option.go create mode 100644 internal/storage/storageutil/auth_client_option_test.go create mode 100644 internal/storage/storageutil/testdata/key.json diff --git a/go.mod b/go.mod index 16ae999665..9df201c532 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.24.0 require ( cloud.google.com/go/auth v0.16.1 + cloud.google.com/go/auth/oauth2adapt v0.2.8 cloud.google.com/go/compute/metadata v0.7.0 cloud.google.com/go/iam v1.5.2 cloud.google.com/go/profiler v0.4.2 @@ -56,7 +57,6 @@ require ( require ( cel.dev/expr v0.24.0 // indirect cloud.google.com/go v0.121.2 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/longrunning v0.6.7 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect cloud.google.com/go/pubsub v1.49.0 // indirect diff --git a/internal/storage/storageutil/auth_client_option.go b/internal/storage/storageutil/auth_client_option.go new file mode 100644 index 0000000000..38cbbc32fd --- /dev/null +++ b/internal/storage/storageutil/auth_client_option.go @@ -0,0 +1,56 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package storageutil + +import ( + "context" + "fmt" + + "cloud.google.com/go/auth/oauth2adapt" + auth2 "github.com/googlecloudplatform/gcsfuse/v3/internal/auth" + "golang.org/x/oauth2" + "google.golang.org/api/option" +) + +// GetClientAuthOptionsAndToken returns client options and a token source using either a token URL or fallback to key file/ADC. +func GetClientAuthOptionsAndToken(ctx context.Context, config *StorageClientConfig) ([]option.ClientOption, oauth2.TokenSource, error) { + // If Token URL is provided, attempt to fetch token source directly. + if config.TokenUrl != "" { + tokenSrc, err := auth2.NewTokenSourceFromURL(ctx, config.TokenUrl, config.ReuseTokenFromUrl) + if err != nil { + return nil, nil, fmt.Errorf("while fetching token source: %w", err) + } + + clientOpts := []option.ClientOption{option.WithTokenSource(tokenSrc)} + return clientOpts, tokenSrc, nil + } + + // Fallback: Use key file credentials. + cred, err := auth2.GetCredentials(config.KeyFile) + if err != nil { + return nil, nil, fmt.Errorf("while fetching credentials: %w", err) + } + + tokenSrc := oauth2adapt.TokenSourceFromTokenProvider(cred.TokenProvider) + + domain, err := cred.UniverseDomain(ctx) + if err != nil { + return nil, nil, fmt.Errorf("failed to get UniverseDomain: %w", err) + } + + clientOpts := []option.ClientOption{option.WithUniverseDomain(domain), option.WithAuthCredentials(cred)} + + return clientOpts, tokenSrc, nil +} diff --git a/internal/storage/storageutil/auth_client_option_test.go b/internal/storage/storageutil/auth_client_option_test.go new file mode 100644 index 0000000000..e28ff4b822 --- /dev/null +++ b/internal/storage/storageutil/auth_client_option_test.go @@ -0,0 +1,83 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package storageutil + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "google.golang.org/api/option" +) + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func Test_GetClientAuthOptionsAndToken_TokenUrlPreferredSuccess(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + fmt.Fprintln(w, `{"access_token":"dummy-token","token_type":"Bearer"}`) + })) + defer server.Close() + config := &StorageClientConfig{ + TokenUrl: server.URL, + ReuseTokenFromUrl: false, + KeyFile: "testdata/key.json", + } + + clientOpts, tokenSrc, err := GetClientAuthOptionsAndToken(context.TODO(), config) + + assert.NoError(t, err) + assert.NotNil(t, tokenSrc) + assert.Len(t, clientOpts, 1) // Only tokenSource option attached +} + +func Test_GetClientAuthOptionsAndToken_TokenUrlPreferredError(t *testing.T) { + config := &StorageClientConfig{TokenUrl: ":"} + + clientOpts, tokenSrc, err := GetClientAuthOptionsAndToken(context.TODO(), config) + + assert.Error(t, err) + assert.Nil(t, tokenSrc) + assert.Empty(t, clientOpts) +} + +func Test_GetClientAuthOptionsAndToken_FallbackToKeyFileSuccess(t *testing.T) { + config := &StorageClientConfig{ + TokenUrl: "", // triggers fallback + KeyFile: "testdata/key.json", + } + + clientOpts, tokenSrc, err := GetClientAuthOptionsAndToken(context.TODO(), config) + + assert.NoError(t, err) + assert.NotNil(t, tokenSrc) + assert.Len(t, clientOpts, 2) // UniverseDomain + AuthCredentials +} + +func Test_GetClientAuthOptionsAndToken_FallbackToKeyFileError(t *testing.T) { + config := &StorageClientConfig{TokenUrl: "", KeyFile: "fake-key"} + var clientOpts []option.ClientOption + + clientOpts, tokenSrc, err := GetClientAuthOptionsAndToken(context.TODO(), config) + + assert.Error(t, err) + assert.Nil(t, tokenSrc) + assert.Empty(t, clientOpts) +} diff --git a/internal/storage/storageutil/testdata/key.json b/internal/storage/storageutil/testdata/key.json new file mode 100644 index 0000000000..59d8020acb --- /dev/null +++ b/internal/storage/storageutil/testdata/key.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "project_id", + "private_key_id": "private_key_id", + "private_key": "private_key", + "client_email": "client_email", + "client_id": "client_id", + "auth_uri": "auth_uri", + "token_uri": "token_uri", + "auth_provider_x509_cert_url": "auth_provider_x509_cert_url", + "client_x509_cert_url": "client_x509_cert_url", + "universe_domain": "googleapis.com" +} From 2636fa09d1c90daffe211ccdb361333c87f23b06 Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Wed, 23 Jul 2025 07:11:36 +0000 Subject: [PATCH 0590/1298] refactor(parallel random reads): Changes to start using atomic variables for shared state in read path (#3565) Changes to migrate shares state variables in GCS read path to atomic variables, laying the the groundwork for future performance optimizations by enabling the removal of locks for random reads for ZB with same file handle. --- internal/cache/file/downloader/job.go | 2 +- .../file/downloader/parallel_downloads_job.go | 2 +- internal/gcsx/client_readers/gcs_reader.go | 26 ++--- .../gcsx/client_readers/gcs_reader_test.go | 58 +++++----- internal/gcsx/client_readers/range_reader.go | 8 +- .../gcsx/client_readers/range_reader_test.go | 6 +- internal/gcsx/file_cache_reader.go | 2 +- internal/gcsx/random_reader.go | 44 ++++---- internal/gcsx/random_reader_stretchr_test.go | 106 +++++++++--------- internal/gcsx/random_reader_test.go | 6 +- metrics/constants.go | 14 ++- 11 files changed, 139 insertions(+), 135 deletions(-) diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index dc7cec7914..16e19e17da 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -326,7 +326,7 @@ func (job *Job) downloadObjectToFile(cacheFile *os.File) (err error) { if newReader != nil { readHandle = newReader.ReadHandle() } - metrics.CaptureGCSReadMetrics(job.cancelCtx, job.metricsHandle, metrics.ReadTypeSequential, newReaderLimit-start) + metrics.CaptureGCSReadMetrics(job.cancelCtx, job.metricsHandle, metrics.ReadTypeNames[metrics.ReadTypeSequential], newReaderLimit-start) } maxRead := min(ReadChunkSize, newReaderLimit-start) diff --git a/internal/cache/file/downloader/parallel_downloads_job.go b/internal/cache/file/downloader/parallel_downloads_job.go index 6f03b60095..676720a2a8 100644 --- a/internal/cache/file/downloader/parallel_downloads_job.go +++ b/internal/cache/file/downloader/parallel_downloads_job.go @@ -60,7 +60,7 @@ func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, e } }() - metrics.CaptureGCSReadMetrics(ctx, job.metricsHandle, metrics.ReadTypeParallel, end-start) + metrics.CaptureGCSReadMetrics(ctx, job.metricsHandle, metrics.ReadTypeNames[metrics.ReadTypeParallel], end-start) // Use standard copy function if O_DIRECT is disabled and memory aligned // buffer otherwise. diff --git a/internal/gcsx/client_readers/gcs_reader.go b/internal/gcsx/client_readers/gcs_reader.go index d80714200b..7ad75388c8 100644 --- a/internal/gcsx/client_readers/gcs_reader.go +++ b/internal/gcsx/client_readers/gcs_reader.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "io" + "sync/atomic" "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" @@ -60,19 +61,19 @@ type GCSReader struct { mrr *MultiRangeReader // ReadType of the reader. Will be sequential by default. - readType string + readType atomic.Int64 sequentialReadSizeMb int32 // Specifies the next expected offset for the reads. Used to distinguish between // sequential and random reads. - expectedOffset int64 + expectedOffset atomic.Int64 // seeks represents the number of random reads performed by the reader. - seeks uint64 + seeks atomic.Uint64 // totalReadBytes is the total number of bytes read by the reader. - totalReadBytes uint64 + totalReadBytes atomic.Uint64 } type GCSReaderConfig struct { @@ -89,7 +90,6 @@ func NewGCSReader(obj *gcs.MinObject, bucket gcs.Bucket, config *GCSReaderConfig sequentialReadSizeMb: config.SequentialReadSizeMb, rangeReader: NewRangeReader(obj, bucket, config.Config, config.MetricHandle), mrr: NewMultiRangeReader(obj, config.MetricHandle, config.MrdWrapper), - readType: metrics.ReadTypeSequential, } } @@ -109,7 +109,7 @@ func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.R } defer func() { gr.updateExpectedOffset(offset + int64(readerResponse.Size)) - gr.totalReadBytes += uint64(readerResponse.Size) + gr.totalReadBytes.Add(uint64(readerResponse.Size)) }() var err error @@ -123,8 +123,8 @@ func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.R // If the data can't be served from the existing reader, then we need to update the seeks. // If current offset is not same as expected offset, it's a random read. - if gr.expectedOffset != 0 && gr.expectedOffset != offset { - gr.seeks++ + if expectedOffset := gr.expectedOffset.Load(); expectedOffset != 0 && expectedOffset != offset { + gr.seeks.Add(1) } // If we don't have a reader, determine whether to read from RangeReader or MultiRangeReader. @@ -149,7 +149,7 @@ func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.R // readerType specifies the go-sdk interface to use for reads. func (gr *GCSReader) readerType(start int64, end int64, bucketType gcs.BucketType) ReaderType { bytesToBeRead := end - start - if gr.readType == metrics.ReadTypeRandom && bytesToBeRead < maxReadSize && bucketType.Zonal { + if gr.readType.Load() == metrics.ReadTypeRandom && bytesToBeRead < maxReadSize && bucketType.Zonal { return MultiRangeReaderType } return RangeReaderType @@ -176,9 +176,9 @@ func (gr *GCSReader) getReadInfo(start int64, size int64) (int64, error) { // determineEnd calculates the end position for a read operation based on the current read pattern. func (gr *GCSReader) determineEnd(start int64) int64 { end := int64(gr.object.Size) - if gr.seeks >= minSeeksForRandom { - gr.readType = metrics.ReadTypeRandom - averageReadBytes := gr.totalReadBytes / gr.seeks + if seeks := gr.seeks.Load(); seeks >= minSeeksForRandom { + gr.readType.Store(metrics.ReadTypeRandom) + averageReadBytes := gr.totalReadBytes.Load() / seeks if averageReadBytes < maxReadSize { randomReadSize := int64(((averageReadBytes / MB) + 1) * MB) if randomReadSize < minReadSize { @@ -206,7 +206,7 @@ func (gr *GCSReader) limitEnd(start, currentEnd int64) int64 { } func (gr *GCSReader) updateExpectedOffset(offset int64) { - gr.expectedOffset = offset + gr.expectedOffset.Store(offset) } func (gr *GCSReader) Destroy() { diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go index d8c6430ab2..5124946c4e 100644 --- a/internal/gcsx/client_readers/gcs_reader_test.go +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -36,8 +36,6 @@ import ( ) const ( - sequential = "Sequential" - random = "Random" sequentialReadSizeInMb = 22 ) @@ -107,7 +105,7 @@ func (t *gcsReaderTest) Test_NewGCSReader() { assert.Equal(t.T(), object, gcsReader.object) assert.Equal(t.T(), t.mockBucket, gcsReader.bucket) - assert.Equal(t.T(), metrics.ReadTypeSequential, gcsReader.readType) + assert.Equal(t.T(), metrics.ReadTypeSequential, gcsReader.readType.Load()) } func (t *gcsReaderTest) Test_ReadAt_InvalidOffset() { @@ -171,8 +169,8 @@ func (t *gcsReaderTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedDataSi assert.Equal(t.T(), rc, t.gcsReader.rangeReader.reader) assert.Equal(t.T(), requestSize, readerResponse.Size) assert.Equal(t.T(), content, string(readerResponse.DataBuf[:readerResponse.Size])) - assert.Equal(t.T(), uint64(requestSize), t.gcsReader.totalReadBytes) - assert.Equal(t.T(), int64(2+requestSize), t.gcsReader.expectedOffset) + assert.Equal(t.T(), uint64(requestSize), t.gcsReader.totalReadBytes.Load()) + assert.Equal(t.T(), int64(2+requestSize), t.gcsReader.expectedOffset.Load()) assert.Equal(t.T(), expectedHandleInRequest, t.gcsReader.rangeReader.readHandle) } @@ -206,7 +204,7 @@ func (t *gcsReaderTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedObject assert.Nil(t.T(), t.gcsReader.rangeReader.reader) assert.Equal(t.T(), int(t.object.Size), readerResponse.Size) assert.Equal(t.T(), content, string(readerResponse.DataBuf[:readerResponse.Size])) - assert.Equal(t.T(), int64(t.object.Size), t.gcsReader.expectedOffset) + assert.Equal(t.T(), int64(t.object.Size), t.gcsReader.expectedOffset.Load()) assert.Equal(t.T(), []byte(nil), t.gcsReader.rangeReader.readHandle) } @@ -217,7 +215,7 @@ func (t *gcsReaderTest) Test_ReadAt_ExistingReaderIsFine() { t.gcsReader.rangeReader.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte(content)), Handle: []byte("fake")} t.gcsReader.rangeReader.cancel = func() {} t.gcsReader.rangeReader.start = 2 - t.gcsReader.totalReadBytes = 2 + t.gcsReader.totalReadBytes.Store(2) t.gcsReader.rangeReader.limit = 5 requestSize := 3 @@ -226,8 +224,8 @@ func (t *gcsReaderTest) Test_ReadAt_ExistingReaderIsFine() { assert.NoError(t.T(), err) assert.Equal(t.T(), 3, readerResponse.Size) assert.Equal(t.T(), content, string(readerResponse.DataBuf[:readerResponse.Size])) - assert.Equal(t.T(), uint64(5), t.gcsReader.totalReadBytes) - assert.Equal(t.T(), int64(5), t.gcsReader.expectedOffset) + assert.Equal(t.T(), uint64(5), t.gcsReader.totalReadBytes.Load()) + assert.Equal(t.T(), int64(5), t.gcsReader.expectedOffset.Load()) assert.Equal(t.T(), []byte("fake"), t.gcsReader.rangeReader.readHandle) } @@ -282,7 +280,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize int bucketType gcs.BucketType readRanges [][]int - expectedReadTypes []string + expectedReadTypes []int64 expectedSeeks []int }{ { @@ -290,7 +288,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, + expectedReadTypes: []int64{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -298,7 +296,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, + expectedReadTypes: []int64{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -306,7 +304,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, + expectedReadTypes: []int64{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, { @@ -314,7 +312,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, + expectedReadTypes: []int64{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, } @@ -324,9 +322,9 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { t.SetupTest() require.Equal(t.T(), len(tc.readRanges), len(tc.expectedReadTypes), "Test Parameter Error: readRanges and expectedReadTypes should have same length") t.gcsReader.mrr.isMRDInUse = false - t.gcsReader.seeks = 0 + t.gcsReader.seeks.Store(0) t.gcsReader.rangeReader.readType = metrics.ReadTypeSequential - t.gcsReader.expectedOffset = 0 + t.gcsReader.expectedOffset.Store(0) t.object.Size = uint64(tc.dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) @@ -341,9 +339,9 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { _, err = t.readAt(int64(readRange[0]), int64(readRange[1]-readRange[0])) assert.NoError(t.T(), err) - assert.Equal(t.T(), tc.expectedReadTypes[i], t.gcsReader.readType) - assert.Equal(t.T(), int64(readRange[1]), t.gcsReader.expectedOffset) - assert.Equal(t.T(), uint64(tc.expectedSeeks[i]), t.gcsReader.seeks) + assert.Equal(t.T(), tc.expectedReadTypes[i], t.gcsReader.readType.Load()) + assert.Equal(t.T(), int64(readRange[1]), t.gcsReader.expectedOffset.Load()) + assert.Equal(t.T(), uint64(tc.expectedSeeks[i]), t.gcsReader.seeks.Load()) } }) } @@ -477,14 +475,14 @@ func (t *gcsReaderTest) Test_ReadInfo_Sequential() { end, err := t.gcsReader.getReadInfo(tc.start, int64(tc.objectSize)) assert.NoError(t.T(), err) - assert.Equal(t.T(), sequential, t.gcsReader.readType) + assert.Equal(t.T(), metrics.ReadTypeSequential, t.gcsReader.readType.Load()) assert.Equal(t.T(), tc.expectedEnd, end) }) } } func (t *gcsReaderTest) Test_ReadInfo_Random() { - t.gcsReader.seeks = 2 + t.gcsReader.seeks.Store(2) testCases := []struct { name string start int64 @@ -525,12 +523,12 @@ func (t *gcsReaderTest) Test_ReadInfo_Random() { for _, tc := range testCases { t.Run(tc.name, func() { t.object.Size = tc.objectSize - t.gcsReader.totalReadBytes = tc.totalReadBytes + t.gcsReader.totalReadBytes.Store(tc.totalReadBytes) end, err := t.gcsReader.getReadInfo(tc.start, int64(tc.objectSize)) assert.NoError(t.T(), err) - assert.Equal(t.T(), random, t.gcsReader.readType) + assert.Equal(t.T(), metrics.ReadTypeRandom, t.gcsReader.readType.Load()) assert.Equal(t.T(), tc.expectedEnd, end) }) } @@ -613,10 +611,10 @@ func (t *gcsReaderTest) Test_ReadAt_WithAndWithoutReadConfig() { func (t *gcsReaderTest) Test_ReadAt_ValidateZonalRandomReads() { t.gcsReader.rangeReader.reader = nil t.gcsReader.mrr.isMRDInUse = false - t.gcsReader.seeks = 0 + t.gcsReader.seeks.Store(0) t.gcsReader.rangeReader.readType = metrics.ReadTypeSequential - t.gcsReader.expectedOffset = 0 - t.gcsReader.totalReadBytes = 0 + t.gcsReader.expectedOffset.Store(0) + t.gcsReader.totalReadBytes.Store(0) t.object.Size = 20 * MiB t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) @@ -633,7 +631,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateZonalRandomReads() { seeks := 1 _, err = t.gcsReader.ReadAt(t.ctx, buf, 12*MiB) assert.NoError(t.T(), err) - assert.Equal(t.T(), uint64(seeks), t.gcsReader.seeks) + assert.Equal(t.T(), uint64(seeks), t.gcsReader.seeks.Load()) readRanges := [][]int{{11 * MiB, 15 * MiB}, {12 * MiB, 14 * MiB}, {10 * MiB, 12 * MiB}, {9 * MiB, 11 * MiB}, {8 * MiB, 10 * MiB}} // Series of random reads to check if seeks are updated correctly and MRD is invoked always @@ -645,8 +643,8 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateZonalRandomReads() { _, err := t.gcsReader.ReadAt(t.ctx, buf, int64(readRange[0])) assert.NoError(t.T(), err) - assert.Equal(t.T(), uint64(seeks), t.gcsReader.seeks) - assert.Equal(t.T(), metrics.ReadTypeRandom, t.gcsReader.readType) - assert.Equal(t.T(), int64(readRange[1]), t.gcsReader.expectedOffset) + assert.Equal(t.T(), uint64(seeks), t.gcsReader.seeks.Load()) + assert.Equal(t.T(), metrics.ReadTypeRandom, t.gcsReader.readType.Load()) + assert.Equal(t.T(), int64(readRange[1]), t.gcsReader.expectedOffset.Load()) } } diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index aab0128261..57662b8ded 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -60,7 +60,7 @@ type RangeReader struct { readHandle []byte cancel func() - readType string + readType int64 config *cfg.Config metricHandle metrics.MetricHandle } @@ -131,7 +131,7 @@ func (rr *RangeReader) ReadAt(ctx context.Context, req *gcsx.GCSReaderRequest) ( // readFromRangeReader reads using the NewReader interface of go-sdk. It uses // the existing reader if available, otherwise makes a call to GCS. // Before calling this method we have to use invalidateReaderIfMisalignedOrTooSmall to get the reader start at the correct position. -func (rr *RangeReader) readFromRangeReader(ctx context.Context, p []byte, offset int64, end int64, readType string) (int, error) { +func (rr *RangeReader) readFromRangeReader(ctx context.Context, p []byte, offset int64, end int64, readType int64) (int, error) { var err error // If we don't have a reader, start a read operation. if rr.reader == nil { @@ -187,7 +187,7 @@ func (rr *RangeReader) readFromRangeReader(ctx context.Context, p []byte, offset } requestedDataSize := end - offset - metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, readType, requestedDataSize) + metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeNames[readType], requestedDataSize) return n, err } @@ -281,7 +281,7 @@ func (rr *RangeReader) startRead(start int64, end int64) error { rr.limit = end requestedDataSize := end - start - metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeSequential, requestedDataSize) + metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeNames[metrics.ReadTypeSequential], requestedDataSize) return nil } diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go index fd1955da25..c23927dc82 100644 --- a/internal/gcsx/client_readers/range_reader_test.go +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -406,7 +406,7 @@ func (t *rangeReaderTest) Test_ReadFromRangeReader_WhenReaderReturnedMoreData() } t.rangeReader.cancel = func() {} - n, err := t.rangeReader.readFromRangeReader(t.ctx, make([]byte, 10), 0, 10, "unhandled") + n, err := t.rangeReader.readFromRangeReader(t.ctx, make([]byte, 10), 0, 10, metrics.ReadTypeUnknown) assert.Error(t.T(), err) assert.Zero(t.T(), n) @@ -535,7 +535,7 @@ func (t *rangeReaderTest) Test_ReadFromRangeReader_WhenAllDataFromReaderIsRead() t.rangeReader.cancel = func() {} buf := make([]byte, dataSize) - n, err := t.rangeReader.readFromRangeReader(t.ctx, buf, 4, 10, "unhandled") + n, err := t.rangeReader.readFromRangeReader(t.ctx, buf, 4, 10, metrics.ReadTypeUnknown) assert.NoError(t.T(), err) assert.Equal(t.T(), dataSize, n) @@ -578,7 +578,7 @@ func (t *rangeReaderTest) Test_ReadFromRangeReader_WhenReaderHasLessDataThanRequ t.rangeReader.cancel = func() {} buf := make([]byte, 10) - n, err := t.rangeReader.readFromRangeReader(t.ctx, buf, 0, 10, "unhandled") + n, err := t.rangeReader.readFromRangeReader(t.ctx, buf, 0, 10, metrics.ReadTypeUnknown) assert.NoError(t.T(), err) assert.Equal(t.T(), dataSize, n) diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index da1276f7ba..03ba9c7d49 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -121,7 +121,7 @@ func (fc *FileCacheReader) tryReadingFromFileCache(ctx context.Context, p []byte if isSequential { readType = metrics.ReadTypeSequential } - captureFileCacheMetrics(ctx, fc.metricHandle, readType, bytesRead, cacheHit, executionTime) + captureFileCacheMetrics(ctx, fc.metricHandle, metrics.ReadTypeNames[readType], bytesRead, cacheHit, executionTime) }() // Create fileCacheHandle if not already. diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 1a823f9acd..d1548d4947 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -19,6 +19,7 @@ import ( "fmt" "io" "math" + "sync/atomic" "time" "github.com/google/uuid" @@ -109,9 +110,6 @@ func NewRandomReader(o *gcs.MinObject, bucket gcs.Bucket, sequentialReadSizeMb i bucket: bucket, start: -1, limit: -1, - seeks: 0, - totalReadBytes: 0, - readType: metrics.ReadTypeSequential, sequentialReadSizeMb: sequentialReadSizeMb, fileCacheHandler: fileCacheHandler, cacheFileForRangeRead: cacheFileForRangeRead, @@ -141,11 +139,11 @@ type randomReader struct { // reads from cache. start int64 limit int64 - seeks uint64 - totalReadBytes uint64 + seeks atomic.Uint64 + totalReadBytes atomic.Uint64 // ReadType of the reader. Will be sequential by default. - readType string + readType atomic.Int64 sequentialReadSizeMb int32 @@ -178,7 +176,7 @@ type randomReader struct { // Specifies the next expected offset for the reads. Used to distinguish between // sequential and random reads. - expectedOffset int64 + expectedOffset atomic.Int64 } func (rr *randomReader) CheckInvariants() { @@ -251,7 +249,7 @@ func (rr *randomReader) tryReadingFromFileCache(ctx context.Context, if isSeq { readType = metrics.ReadTypeSequential } - captureFileCacheMetrics(ctx, rr.metricHandle, readType, n, cacheHit, executionTime) + captureFileCacheMetrics(ctx, rr.metricHandle, metrics.ReadTypeNames[readType], n, cacheHit, executionTime) }() // Create fileCacheHandle if not already. @@ -361,14 +359,14 @@ func (rr *randomReader) ReadAt( } if rr.reader != nil { - objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, -1, rr.readType) + objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, -1, rr.readType.Load()) return } // If the data can't be served from the existing reader, then we need to update the seeks. // If current offset is not same as expected offset, its a random read. - if rr.expectedOffset != 0 && rr.expectedOffset != offset { - rr.seeks++ + if expectedOffset := rr.expectedOffset.Load(); expectedOffset != 0 && expectedOffset != offset { + rr.seeks.Add(1) } // If we don't have a reader, determine whether to read from NewReader or MRR. @@ -378,9 +376,9 @@ func (rr *randomReader) ReadAt( return } - readerType := readerType(rr.readType, offset, end, rr.bucket.BucketType()) + readerType := readerType(rr.readType.Load(), offset, end, rr.bucket.BucketType()) if readerType == RangeReader { - objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, end, rr.readType) + objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, end, rr.readType.Load()) return } @@ -513,7 +511,7 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { rr.limit = end requestedDataSize := end - start - metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeSequential, requestedDataSize) + metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeNames[metrics.ReadTypeSequential], requestedDataSize) return } @@ -550,9 +548,9 @@ func (rr *randomReader) getReadInfo( // optimise for random reads. Random reads will read data in chunks of // (average read size in bytes rounded up to the next MiB). end = int64(rr.object.Size) - if rr.seeks >= minSeeksForRandom { - rr.readType = metrics.ReadTypeRandom - averageReadBytes := rr.totalReadBytes / rr.seeks + if seeks := rr.seeks.Load(); seeks >= minSeeksForRandom { + rr.readType.Store(metrics.ReadTypeRandom) + averageReadBytes := rr.totalReadBytes.Load() / seeks if averageReadBytes < maxReadSize { randomReadSize := int64(((averageReadBytes / MiB) + 1) * MiB) if randomReadSize < minReadSize { @@ -579,7 +577,7 @@ func (rr *randomReader) getReadInfo( } // readerType specifies the go-sdk interface to use for reads. -func readerType(readType string, start int64, end int64, bucketType gcs.BucketType) ReaderType { +func readerType(readType int64, start int64, end int64, bucketType gcs.BucketType) ReaderType { bytesToBeRead := end - start if readType == metrics.ReadTypeRandom && bytesToBeRead < maxReadSize && bucketType.Zonal { return MultiRangeReader @@ -589,7 +587,7 @@ func readerType(readType string, start int64, end int64, bucketType gcs.BucketTy // readFromRangeReader reads using the NewReader interface of go-sdk. Its uses // the existing reader if available, otherwise makes a call to GCS. -func (rr *randomReader) readFromRangeReader(ctx context.Context, p []byte, offset int64, end int64, readType string) (n int, err error) { +func (rr *randomReader) readFromRangeReader(ctx context.Context, p []byte, offset int64, end int64, readType int64) (n int, err error) { // If we don't have a reader, start a read operation. if rr.reader == nil { err = rr.startRead(offset, end) @@ -603,7 +601,7 @@ func (rr *randomReader) readFromRangeReader(ctx context.Context, p []byte, offse // it as possible. n, err = rr.readFull(ctx, p) rr.start += int64(n) - rr.totalReadBytes += uint64(n) + rr.totalReadBytes.Add(uint64(n)) // Sanity check. if rr.start > rr.limit { @@ -646,7 +644,7 @@ func (rr *randomReader) readFromRangeReader(ctx context.Context, p []byte, offse } requestedDataSize := end - offset - metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, readType, requestedDataSize) + metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeNames[readType], requestedDataSize) rr.updateExpectedOffset(offset + int64(n)) return @@ -663,7 +661,7 @@ func (rr *randomReader) readFromMultiRangeReader(ctx context.Context, p []byte, } bytesRead, err = rr.mrdWrapper.Read(ctx, p, offset, end, timeout, rr.metricHandle) - rr.totalReadBytes += uint64(bytesRead) + rr.totalReadBytes.Add(uint64(bytesRead)) rr.updateExpectedOffset(offset + int64(bytesRead)) return } @@ -678,5 +676,5 @@ func (rr *randomReader) closeReader() { } func (rr *randomReader) updateExpectedOffset(offset int64) { - rr.expectedOffset = offset + rr.expectedOffset.Store(offset) } diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 86f0d5de4f..3fbe885e07 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -141,14 +141,14 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo_Sequential() { end, err := t.rr.wrapped.getReadInfo(tc.start, 10) assert.NoError(t.T(), err) - assert.Equal(t.T(), metrics.ReadTypeSequential, t.rr.wrapped.readType) + assert.Equal(t.T(), metrics.ReadTypeSequential, t.rr.wrapped.readType.Load()) assert.Equal(t.T(), tc.expectedEnd, end) }) } } func (t *RandomReaderStretchrTest) Test_ReadInfo_Random() { - t.rr.wrapped.seeks = 2 + t.rr.wrapped.seeks.Store(2) var testCases = []struct { testName string expectedEnd int64 @@ -170,11 +170,11 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo_Random() { for _, tc := range testCases { t.Run(tc.testName, func() { t.object.Size = tc.objectSize - t.rr.wrapped.totalReadBytes = tc.totalReadBytes + t.rr.wrapped.totalReadBytes.Store(tc.totalReadBytes) end, err := t.rr.wrapped.getReadInfo(tc.start, 10) assert.NoError(t.T(), err) - assert.Equal(t.T(), metrics.ReadTypeRandom, t.rr.wrapped.readType) + assert.Equal(t.T(), metrics.ReadTypeRandom, t.rr.wrapped.readType.Load()) assert.Equal(t.T(), tc.expectedEnd, end) }) } @@ -183,7 +183,7 @@ func (t *RandomReaderStretchrTest) Test_ReadInfo_Random() { func (t *RandomReaderStretchrTest) Test_ReaderType() { testCases := []struct { name string - readType string + readType int64 start int64 end int64 bucketType gcs.BucketType @@ -277,7 +277,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenExistingReaderIs t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil).Times(1) buf := make([]byte, dataSize) - n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, int64(t.object.Size), "unhandled") + n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, int64(t.object.Size), metrics.ReadTypeUnknown) t.mockBucket.AssertExpectations(t.T()) assert.NoError(t.T(), err) @@ -288,7 +288,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenExistingReaderIs assert.Nil(t.T(), t.rr.wrapped.cancel) assert.Equal(t.T(), int64(5), t.rr.wrapped.start) assert.Equal(t.T(), int64(5), t.rr.wrapped.limit) - assert.Equal(t.T(), int64(t.object.Size), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), int64(t.object.Size), t.rr.wrapped.expectedOffset.Load()) }) } } @@ -296,7 +296,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenExistingReaderIs func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenExistingReaderIsNotNil() { t.rr.wrapped.start = 4 t.rr.wrapped.limit = 10 - t.rr.wrapped.totalReadBytes = 4 + t.rr.wrapped.totalReadBytes.Store(4) t.object.Size = 10 dataSize := 4 testContent := testutil.GenerateRandomBytes(int(t.object.Size)) @@ -305,7 +305,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenExistingReaderIs t.rr.wrapped.cancel = func() {} buf := make([]byte, dataSize) - n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 4, 8, "unhandled") + n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 4, 8, metrics.ReadTypeUnknown) assert.NoError(t.T(), err) assert.Equal(t.T(), dataSize, n) @@ -314,8 +314,8 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenExistingReaderIs assert.NotNil(t.T(), t.rr.wrapped.cancel) assert.Equal(t.T(), int64(8), t.rr.wrapped.start) assert.Equal(t.T(), int64(10), t.rr.wrapped.limit) - assert.Equal(t.T(), uint64(8), t.rr.wrapped.totalReadBytes) - assert.Equal(t.T(), int64(8), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), uint64(8), t.rr.wrapped.totalReadBytes.Load()) + assert.Equal(t.T(), int64(8), t.rr.wrapped.expectedOffset.Load()) } func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenAllDataFromReaderIsRead() { @@ -337,7 +337,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenAllDataFromReade t.Run(tc.name, func() { t.rr.wrapped.start = 4 t.rr.wrapped.limit = 10 - t.rr.wrapped.totalReadBytes = 4 + t.rr.wrapped.totalReadBytes.Store(4) t.object.Size = 10 dataSize := 6 testContent := testutil.GenerateRandomBytes(int(t.object.Size)) @@ -349,7 +349,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenAllDataFromReade t.rr.wrapped.cancel = func() {} buf := make([]byte, dataSize) - n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 4, 10, "unhandled") + n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 4, 10, metrics.ReadTypeUnknown) assert.NoError(t.T(), err) assert.Equal(t.T(), dataSize, n) @@ -358,8 +358,8 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenAllDataFromReade assert.Nil(t.T(), t.rr.wrapped.cancel) assert.Equal(t.T(), int64(10), t.rr.wrapped.start) assert.Equal(t.T(), int64(10), t.rr.wrapped.limit) - assert.Equal(t.T(), uint64(10), t.rr.wrapped.totalReadBytes) - assert.Equal(t.T(), int64(10), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), uint64(10), t.rr.wrapped.totalReadBytes.Load()) + assert.Equal(t.T(), int64(10), t.rr.wrapped.expectedOffset.Load()) expectedReadHandle := tc.readHandle assert.Equal(t.T(), expectedReadHandle, t.rr.wrapped.readHandle) }) @@ -385,7 +385,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderHasLessDat t.Run(tc.name, func() { t.rr.wrapped.start = 0 t.rr.wrapped.limit = 6 - t.rr.wrapped.totalReadBytes = 0 + t.rr.wrapped.totalReadBytes.Store(0) dataSize := 6 testContent := testutil.GenerateRandomBytes(dataSize) rc := &fake.FakeReader{ @@ -396,7 +396,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderHasLessDat t.rr.wrapped.cancel = func() {} buf := make([]byte, 10) - n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, 10, "unhandled") + n, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, 10, metrics.ReadTypeUnknown) assert.NoError(t.T(), err) assert.Equal(t.T(), dataSize, n) @@ -405,8 +405,8 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderHasLessDat assert.Nil(t.T(), t.rr.wrapped.cancel) assert.Equal(t.T(), int64(dataSize), t.rr.wrapped.start) assert.Equal(t.T(), int64(dataSize), t.rr.wrapped.limit) - assert.Equal(t.T(), uint64(dataSize), t.rr.wrapped.totalReadBytes) - assert.Equal(t.T(), int64(dataSize), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), uint64(dataSize), t.rr.wrapped.totalReadBytes.Load()) + assert.Equal(t.T(), int64(dataSize), t.rr.wrapped.expectedOffset.Load()) expectedReadHandle := tc.readHandle assert.Equal(t.T(), expectedReadHandle, t.rr.wrapped.readHandle) }) @@ -432,7 +432,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderReturnedMo t.Run(tc.name, func() { t.rr.wrapped.start = 0 t.rr.wrapped.limit = 6 - t.rr.wrapped.totalReadBytes = 0 + t.rr.wrapped.totalReadBytes.Store(0) dataSize := 8 testContent := testutil.GenerateRandomBytes(dataSize) rc := &fake.FakeReader{ @@ -443,15 +443,15 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderReturnedMo t.rr.wrapped.cancel = func() {} buf := make([]byte, 10) - _, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, 10, "unhandled") + _, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, 10, metrics.ReadTypeUnknown) assert.True(t.T(), strings.Contains(err.Error(), "extra bytes: 2")) assert.Nil(t.T(), t.rr.wrapped.reader) assert.Nil(t.T(), t.rr.wrapped.cancel) assert.Equal(t.T(), int64(-1), t.rr.wrapped.start) assert.Equal(t.T(), int64(-1), t.rr.wrapped.limit) - assert.Equal(t.T(), uint64(8), t.rr.wrapped.totalReadBytes) - assert.Equal(t.T(), int64(0), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), uint64(8), t.rr.wrapped.totalReadBytes.Load()) + assert.Equal(t.T(), int64(0), t.rr.wrapped.expectedOffset.Load()) expectedReadHandle := tc.readHandle assert.Equal(t.T(), expectedReadHandle, t.rr.wrapped.readHandle) }) @@ -468,10 +468,10 @@ func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenReaderReturnedEO t.rr.wrapped.cancel = func() {} buf := make([]byte, 10) - _, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, 10, "unhandled") + _, err := t.rr.wrapped.readFromRangeReader(t.rr.ctx, buf, 0, 10, metrics.ReadTypeUnknown) assert.True(t.T(), strings.Contains(err.Error(), "skipping 4 bytes")) - assert.Equal(t.T(), int64(0), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), int64(0), t.rr.wrapped.expectedOffset.Load()) } func (t *RandomReaderStretchrTest) Test_ExistingReader_WrongOffset() { @@ -556,8 +556,8 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequ require.Equal(t.T(), rc, t.rr.wrapped.reader) require.Equal(t.T(), requestSize, data.Size) require.Equal(t.T(), "abcdef", string(buf[:data.Size])) - assert.Equal(t.T(), uint64(requestSize), t.rr.wrapped.totalReadBytes) - assert.Equal(t.T(), int64(2+requestSize), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), uint64(requestSize), t.rr.wrapped.totalReadBytes.Load()) + assert.Equal(t.T(), int64(2+requestSize), t.rr.wrapped.expectedOffset.Load()) assert.Equal(t.T(), expectedHandleInRequest, t.rr.wrapped.readHandle) } @@ -591,8 +591,8 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequ require.Nil(t.T(), t.rr.wrapped.reader) require.Equal(t.T(), int(t.object.Size), data.Size) require.Equal(t.T(), "abcde", string(buf[:data.Size])) - assert.Equal(t.T(), t.object.Size, t.rr.wrapped.totalReadBytes) - assert.Equal(t.T(), int64(t.object.Size), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), t.object.Size, t.rr.wrapped.totalReadBytes.Load()) + assert.Equal(t.T(), int64(t.object.Size), t.rr.wrapped.expectedOffset.Load()) assert.Equal(t.T(), []byte(nil), t.rr.wrapped.readHandle) } @@ -633,7 +633,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize int bucketType gcs.BucketType readRanges [][]int - expectedReadTypes []string + expectedReadTypes []int64 expectedSeeks []int }{ { @@ -641,7 +641,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, + expectedReadTypes: []int64{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -649,7 +649,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 10}, {10, 20}, {20, 35}, {35, 50}}, - expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, + expectedReadTypes: []int64{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeSequential}, expectedSeeks: []int{0, 0, 0, 0, 0}, }, { @@ -657,7 +657,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: false}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, + expectedReadTypes: []int64{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, { @@ -665,7 +665,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { dataSize: 100, bucketType: gcs.BucketType{Zonal: true}, readRanges: [][]int{{0, 50}, {30, 40}, {10, 20}, {20, 30}, {30, 40}}, - expectedReadTypes: []string{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, + expectedReadTypes: []int64{metrics.ReadTypeSequential, metrics.ReadTypeSequential, metrics.ReadTypeRandom, metrics.ReadTypeRandom, metrics.ReadTypeRandom}, expectedSeeks: []int{0, 1, 2, 2, 2}, }, } @@ -675,9 +675,9 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { assert.Equal(t.T(), len(tc.readRanges), len(tc.expectedReadTypes), "Test Parameter Error: readRanges and expectedReadTypes should have same length") t.rr.wrapped.reader = nil t.rr.wrapped.isMRDInUse = false - t.rr.wrapped.seeks = 0 - t.rr.wrapped.readType = metrics.ReadTypeSequential - t.rr.wrapped.expectedOffset = 0 + t.rr.wrapped.seeks.Store(0) + t.rr.wrapped.readType.Store(metrics.ReadTypeSequential) + t.rr.wrapped.expectedOffset.Store(0) t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) @@ -693,9 +693,9 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { _, err := t.rr.wrapped.ReadAt(t.rr.ctx, buf, int64(readRange[0])) assert.NoError(t.T(), err) - assert.Equal(t.T(), tc.expectedReadTypes[i], t.rr.wrapped.readType) - assert.Equal(t.T(), int64(readRange[1]), t.rr.wrapped.expectedOffset) - assert.Equal(t.T(), uint64(tc.expectedSeeks[i]), t.rr.wrapped.seeks) + assert.Equal(t.T(), tc.expectedReadTypes[i], t.rr.wrapped.readType.Load()) + assert.Equal(t.T(), int64(readRange[1]), t.rr.wrapped.expectedOffset.Load()) + assert.Equal(t.T(), uint64(tc.expectedSeeks[i]), t.rr.wrapped.seeks.Load()) } }) } @@ -705,10 +705,10 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateZonalRandomReads() { t.rr.wrapped.reader = nil t.rr.wrapped.isMRDInUse = false - t.rr.wrapped.seeks = 0 - t.rr.wrapped.readType = metrics.ReadTypeSequential - t.rr.wrapped.expectedOffset = 0 - t.rr.wrapped.totalReadBytes = 0 + t.rr.wrapped.seeks.Store(0) + t.rr.wrapped.readType.Store(metrics.ReadTypeSequential) + t.rr.wrapped.expectedOffset.Store(0) + t.rr.wrapped.totalReadBytes.Store(0) t.object.Size = 20 * MiB t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) @@ -725,7 +725,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateZonalRandomReads() { seeks := 1 _, err = t.rr.wrapped.ReadAt(t.rr.ctx, buf, 12*MiB) assert.NoError(t.T(), err) - assert.Equal(t.T(), uint64(seeks), t.rr.wrapped.seeks) + assert.Equal(t.T(), uint64(seeks), t.rr.wrapped.seeks.Load()) readRanges := [][]int{{11 * MiB, 15 * MiB}, {12 * MiB, 14 * MiB}, {10 * MiB, 12 * MiB}, {9 * MiB, 11 * MiB}, {8 * MiB, 10 * MiB}} // Series of random reads to check if seeks are updated correctly and MRD is invoked always @@ -737,9 +737,9 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateZonalRandomReads() { _, err := t.rr.wrapped.ReadAt(t.rr.ctx, buf, int64(readRange[0])) assert.NoError(t.T(), err) - assert.Equal(t.T(), metrics.ReadTypeRandom, t.rr.wrapped.readType) - assert.Equal(t.T(), int64(readRange[1]), t.rr.wrapped.expectedOffset) - assert.Equal(t.T(), uint64(seeks), t.rr.wrapped.seeks) + assert.Equal(t.T(), metrics.ReadTypeRandom, t.rr.wrapped.readType.Load()) + assert.Equal(t.T(), int64(readRange[1]), t.rr.wrapped.expectedOffset.Load()) + assert.Equal(t.T(), uint64(seeks), t.rr.wrapped.seeks.Load()) } } @@ -768,8 +768,8 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_MRDRead() { t.Run(tc.name, func() { t.rr.wrapped.reader = nil t.rr.wrapped.isMRDInUse = false - t.rr.wrapped.expectedOffset = 10 - t.rr.wrapped.seeks = minSeeksForRandom + 1 + t.rr.wrapped.expectedOffset.Store(10) + t.rr.wrapped.seeks.Store(minSeeksForRandom + 1) t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) @@ -787,7 +787,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_MRDRead() { assert.Equal(t.T(), tc.bytesToRead, objData.Size) assert.Equal(t.T(), testContent[tc.offset:tc.offset+tc.bytesToRead], objData.DataBuf[:objData.Size]) if tc.bytesToRead != 0 { - assert.Equal(t.T(), int64(tc.offset+tc.bytesToRead), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), int64(tc.offset+tc.bytesToRead), t.rr.wrapped.expectedOffset.Load()) } }) } @@ -829,7 +829,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadFull() { assert.NoError(t.T(), err) assert.Equal(t.T(), tc.dataSize, bytesRead) assert.Equal(t.T(), testContent[:tc.dataSize], buf[:bytesRead]) - assert.Equal(t.T(), int64(t.object.Size), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), int64(t.object.Size), t.rr.wrapped.expectedOffset.Load()) }) } } @@ -865,7 +865,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadChunk() { assert.NoError(t.T(), err) assert.Equal(t.T(), tc.end-tc.start, bytesRead) assert.Equal(t.T(), testContent[tc.start:tc.end], buf[:bytesRead]) - assert.Equal(t.T(), int64(tc.end), t.rr.wrapped.expectedOffset) + assert.Equal(t.T(), int64(tc.end), t.rr.wrapped.expectedOffset.Load()) } } diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index 1dc34131db..f6b7fdd937 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -467,13 +467,13 @@ func (t *RandomReaderTest) UpgradeReadsToAverageSize() { const readSize = 2 * minReadSize // Simulate an existing reader at a mismatched offset. - t.rr.wrapped.seeks = numReads - t.rr.wrapped.totalReadBytes = totalReadBytes + t.rr.wrapped.seeks.Store(numReads) + t.rr.wrapped.totalReadBytes.Store(totalReadBytes) t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte("xxx"))} t.rr.wrapped.cancel = func() {} t.rr.wrapped.start = 2 t.rr.wrapped.limit = 5 - t.rr.wrapped.expectedOffset = 2 + t.rr.wrapped.expectedOffset.Store(2) // The bucket should be asked to read expectedBytesToRead bytes. r := strings.NewReader(strings.Repeat("x", expectedBytesToRead)) diff --git a/metrics/constants.go b/metrics/constants.go index 6009ba2f3c..e0b533856b 100644 --- a/metrics/constants.go +++ b/metrics/constants.go @@ -15,7 +15,15 @@ package metrics const ( - ReadTypeSequential = "Sequential" - ReadTypeRandom = "Random" - ReadTypeParallel = "Parallel" + ReadTypeUnknown int64 = -1 + ReadTypeSequential int64 = 0 + ReadTypeRandom int64 = 1 + ReadTypeParallel int64 = 2 ) + +var ReadTypeNames = map[int64]string{ + ReadTypeUnknown: "Unhandled", + ReadTypeSequential: "Sequential", + ReadTypeRandom: "Random", + ReadTypeParallel: "Parallel", +} From d4bc340d07df62123910b0fc2fb4f584d5b980a0 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 23 Jul 2025 14:24:35 +0530 Subject: [PATCH 0591/1298] chore: disable gemini assisted code review on draft PRs (#3566) * chore: disable gemini code review be default This is primarily targeted at disabling gemini code assist review of draft github PRs by default. After this change, enable gemini code by explicitly commenting '/gemini review' on a PR. * disable gemini code summary also by default * Request gemini review for PRs ready for review --- .gemini/config.yml | 20 ++++++++++ ...st-gemini-review-on-pr-read-for-review.yml | 39 +++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 .gemini/config.yml create mode 100644 .github/workflows/request-gemini-review-on-pr-read-for-review.yml diff --git a/.gemini/config.yml b/.gemini/config.yml new file mode 100644 index 0000000000..d6c5e91027 --- /dev/null +++ b/.gemini/config.yml @@ -0,0 +1,20 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Custom configuration guide: https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github?content_ref=the+gemini+folder+hosts+any+gemini+code+assist+related+configuration+files+like+config+yaml+and+styleguide+md#custom-configuration + +code_review: + pull_request_opened: + code_review: false + summary: false diff --git a/.github/workflows/request-gemini-review-on-pr-read-for-review.yml b/.github/workflows/request-gemini-review-on-pr-read-for-review.yml new file mode 100644 index 0000000000..08700bcc1d --- /dev/null +++ b/.github/workflows/request-gemini-review-on-pr-read-for-review.yml @@ -0,0 +1,39 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Presubmit test that ensures that source files contain valid license headers +# https://github.com/googleapis/repo-automation-bots/tree/main/packages/header-checker-lint +# Install: https://github.com/apps/license-header-lint-gcf + +name: 'Request Gemini Review on Ready for Review' + +on: + pull_request: + types: + - ready_for_review + +jobs: + add-gemini-comment: + if: github.base_ref == 'master' + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Add Gemini review and summary comment + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + /gemini summary + /gemini review From 982b5005774da8f8830b0f76071e4506ae91f2ab Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 23 Jul 2025 15:07:11 +0530 Subject: [PATCH 0592/1298] .gemini/config.yml -> .gemini/config.yaml (#3575) This is fix in the name of the gemini config file as per the documentation at https://developers.google.com/gemini-code-assist/docs/customize-gemini-behavior-github?content_ref=the+gemini+folder+hosts+any+gemini+code+assist+related+configuration+files+like+config+yaml+and+styleguide+md#add_configuration_files Without this fix, gemini seems have ignored this file generating its code review on draft PR https://github.com/GoogleCloudPlatform/gcsfuse/pull/3574. Bug: b/433642695 --- .gemini/{config.yml => config.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .gemini/{config.yml => config.yaml} (100%) diff --git a/.gemini/config.yml b/.gemini/config.yaml similarity index 100% rename from .gemini/config.yml rename to .gemini/config.yaml From 6ada4898b823bae6c0c7d751931820d3ba755801 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 23 Jul 2025 23:34:21 +0530 Subject: [PATCH 0593/1298] feat(bufferedread): Add ScheduleNextBlock to Buffered Reader (#3571) * Add ScheduleNextBlock * Resolved comments * Update internal/bufferedread/buffered_reader.go Co-authored-by: Prince Kumar * Update internal/bufferedread/buffered_reader.go Co-authored-by: Prince Kumar * Update internal/bufferedread/buffered_reader.go Co-authored-by: Prince Kumar * Update internal/bufferedread/buffered_reader_test.go Co-authored-by: Prince Kumar * Update internal/bufferedread/buffered_reader_test.go Co-authored-by: Prince Kumar * Refactore Tests * Update internal/bufferedread/buffered_reader.go Co-authored-by: Prince Kumar * nit fix * nit fix --------- Co-authored-by: Prince Kumar --- internal/bufferedread/buffered_reader.go | 51 +++++- internal/bufferedread/buffered_reader_test.go | 152 +++++++++++++++++- 2 files changed, 196 insertions(+), 7 deletions(-) diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index 9a02b7bc37..f8dcc8fd37 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -15,9 +15,9 @@ package bufferedread import ( - "fmt" - "context" + "errors" + "fmt" "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/block" @@ -29,8 +29,13 @@ import ( "golang.org/x/sync/semaphore" ) +// ErrPrefetchBlockNotAvailable is returned when a block cannot be +// acquired from the pool for prefetching. This can be used by callers to +// implement a fallback mechanism, e.g. falling back to a direct GCS read. +var ErrPrefetchBlockNotAvailable = errors.New("block for prefetching not available") + type BufferedReadConfig struct { - MaxPrefetchBlockCnt int64 // Maximum number of blocks that can be prefetched + MaxPrefetchBlockCnt int64 // Maximum number of blocks that can be prefetched. PrefetchBlockSizeBytes int64 // Size of each block to be prefetched. InitialPrefetchBlockCnt int64 // Number of blocks to prefetch initially. PrefetchMultiplier int64 // Multiplier for number of blocks to prefetch. @@ -64,7 +69,7 @@ type BufferedReader struct { blockQueue common.Queue[*blockQueueEntry] - // TODO: Add readHandle for zonal bucket optimization. + readHandle []byte // For zonal bucket. blockPool *block.GenBlockPool[block.PrefetchBlock] workerPool workerpool.WorkerPool @@ -98,6 +103,44 @@ func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *Buffere return reader, nil } +// scheduleNextBlock schedules the next block for prefetch. +func (p *BufferedReader) scheduleNextBlock(urgent bool) error { + // TODO(b/426060431): Replace Get() with TryGet(). Assuming, the current blockPool.Get() gets blocked if block is not available. + b, err := p.blockPool.Get() + if err != nil || b == nil { + if err != nil { + logger.Warnf("failed to get block from pool: %v", err) + } + return ErrPrefetchBlockNotAvailable + } + + if err := p.scheduleBlockWithIndex(b, p.nextBlockIndexToPrefetch, urgent); err != nil { + p.blockPool.Release(b) + return err + } + p.nextBlockIndexToPrefetch++ + return nil +} + +// scheduleBlockWithIndex schedules a block with a specific index. +func (p *BufferedReader) scheduleBlockWithIndex(b block.PrefetchBlock, blockIndex int64, urgent bool) error { + startOffset := blockIndex * p.config.PrefetchBlockSizeBytes + if err := b.SetAbsStartOff(startOffset); err != nil { + return fmt.Errorf("failed to set start offset on block: %w", err) + } + + ctx, cancel := context.WithCancel(p.ctx) + task := NewDownloadTask(ctx, p.object, p.bucket, b, p.readHandle) + + logger.Tracef("Scheduling block (%s, offset %d).", p.object.Name, startOffset) + p.blockQueue.Push(&blockQueueEntry{ + block: b, + cancel: cancel, + }) + p.workerPool.Schedule(urgent, task) + return nil +} + func (p *BufferedReader) Destroy() { if p.cancelFunc != nil { p.cancelFunc() diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 41b99a67c3..94ed979dbe 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -16,15 +16,20 @@ package bufferedread import ( + "bytes" "context" "errors" + "io" "testing" "github.com/googlecloudplatform/gcsfuse/v3/internal/block" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" "github.com/googlecloudplatform/gcsfuse/v3/metrics" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" @@ -34,7 +39,7 @@ import ( const ( testMaxPrefetchBlockCnt int64 = 10 testGlobalMaxBlocks int64 = 20 - testPrefetchBlockSizeBytes int64 = 4096 + testPrefetchBlockSizeBytes int64 = 1024 testInitialPrefetchBlockCnt int64 = 2 testPrefetchMultiplier int64 = 2 testRandomReadsThreshold int64 = 3 @@ -51,6 +56,39 @@ type BufferedReaderTest struct { metricHandle metrics.MetricHandle } +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +// createFakeReader returns a FakeReader with deterministic, non-zero content. +func createFakeReader(t *testing.T, size int) *fake.FakeReader { + t.Helper() + content := make([]byte, size) + for i := range content { + content[i] = byte('A' + (i % 26)) // A-Z repeating pattern + } + return &fake.FakeReader{ + ReadCloser: io.NopCloser(bytes.NewReader(content)), + } +} + +// assertBlockContent validates that block data matches expected pattern (A-Z loop). +func assertBlockContent(t *testing.T, blk block.PrefetchBlock, expectedOffset int64, length int) { + t.Helper() + buf := make([]byte, length) + n, err := blk.ReadAt(buf, 0) + require.NoError(t, err) + require.Equal(t, length, n) + for i := 0; i < n; i++ { + expected := byte('A' + (i % 26)) + assert.Equalf(t, expected, buf[i], "Mismatch at offset %d", expectedOffset+int64(i)) + } +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + func TestBufferedReaderTestSuite(t *testing.T) { suite.Run(t, new(BufferedReaderTest)) } @@ -58,7 +96,7 @@ func TestBufferedReaderTestSuite(t *testing.T) { func (t *BufferedReaderTest) SetupTest() { t.object = &gcs.MinObject{ Name: "test_object", - Size: 1024, + Size: 8192, Generation: 1234567890, } t.bucket = new(storage.TestifyMockBucket) @@ -71,7 +109,7 @@ func (t *BufferedReaderTest) SetupTest() { RandomReadsThreshold: testRandomReadsThreshold, } var err error - t.workerPool, err = workerpool.NewStaticWorkerPool(2, 5) + t.workerPool, err = workerpool.NewStaticWorkerPool(5, 10) require.NoError(t.T(), err, "Failed to create worker pool") t.workerPool.Start() t.metricHandle = metrics.NewNoopMetrics() @@ -166,3 +204,111 @@ func (t *BufferedReaderTest) TestCheckInvariantsNoPanic() { assert.NotPanics(t.T(), func() { reader.CheckInvariants() }) } + +func (t *BufferedReaderTest) TestScheduleNextBlock() { + testCases := []struct { + name string + urgent bool + }{ + {name: "non-urgent", urgent: false}, + {name: "urgent", urgent: true}, + } + for _, tc := range testCases { + t.Run(tc.name, func() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + initialBlockCount := reader.blockQueue.Len() + t.bucket.On("NewReaderWithReadHandle", + mock.Anything, + mock.AnythingOfType("*gcs.ReadObjectRequest"), + ).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + + err = reader.scheduleNextBlock(tc.urgent) + + require.NoError(t.T(), err) + bqe := reader.blockQueue.Peek() + assert.Equal(t.T(), int64(1), reader.nextBlockIndexToPrefetch) + status, err := bqe.block.AwaitReady(t.ctx) + require.NoError(t.T(), err) + assert.Equal(t.T(), block.BlockStateDownloaded, status.State) + assert.Equal(t.T(), initialBlockCount+1, reader.blockQueue.Len()) + assert.Equal(t.T(), int64(0), bqe.block.AbsStartOff()) + assertBlockContent(t.T(), bqe.block, bqe.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) + t.bucket.AssertExpectations(t.T()) + }) + } +} + +func (t *BufferedReaderTest) TestScheduleNextBlockSuccessive() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + initialBlockCount := reader.blockQueue.Len() + t.bucket.On("NewReaderWithReadHandle", + mock.Anything, + mock.AnythingOfType("*gcs.ReadObjectRequest"), + ).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + err = reader.scheduleNextBlock(false) + require.NoError(t.T(), err) + bqe1 := reader.blockQueue.Pop() + assert.Equal(t.T(), int64(1), reader.nextBlockIndexToPrefetch) + status1, err := bqe1.block.AwaitReady(t.ctx) + require.NoError(t.T(), err) + assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + assert.Equal(t.T(), int64(0), bqe1.block.AbsStartOff()) + assertBlockContent(t.T(), bqe1.block, bqe1.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) + t.bucket.On("NewReaderWithReadHandle", + mock.Anything, + mock.AnythingOfType("*gcs.ReadObjectRequest"), + ).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + + err = reader.scheduleNextBlock(false) + + require.NoError(t.T(), err) + bqe2 := reader.blockQueue.Pop() + assert.Equal(t.T(), int64(2), reader.nextBlockIndexToPrefetch) + status2, err := bqe2.block.AwaitReady(t.ctx) + require.NoError(t.T(), err) + assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + assert.Equal(t.T(), int64(testPrefetchBlockSizeBytes), bqe2.block.AbsStartOff()) + assert.Equal(t.T(), int64(2), reader.nextBlockIndexToPrefetch) + assert.Equal(t.T(), initialBlockCount, reader.blockQueue.Len()) + assertBlockContent(t.T(), bqe2.block, bqe2.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestScheduleBlockWithIndex() { + testCases := []struct { + name string + urgent bool + blockIndex int64 + }{ + {name: "non-urgent", urgent: false, blockIndex: 5}, + {name: "urgent", urgent: true, blockIndex: 3}, + } + for _, tc := range testCases { + t.Run(tc.name, func() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + initialBlockCount := reader.blockQueue.Len() + startOffset := tc.blockIndex * reader.config.PrefetchBlockSizeBytes + t.bucket.On("NewReaderWithReadHandle", + mock.Anything, + mock.AnythingOfType("*gcs.ReadObjectRequest"), + ).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + b, err := reader.blockPool.Get() + require.NoError(t.T(), err) + + err = reader.scheduleBlockWithIndex(b, tc.blockIndex, tc.urgent) + + require.NoError(t.T(), err) + bqe := reader.blockQueue.Peek() + status, err := bqe.block.AwaitReady(t.ctx) + require.NoError(t.T(), err) + assert.Equal(t.T(), block.BlockStateDownloaded, status.State) + assert.Equal(t.T(), initialBlockCount+1, reader.blockQueue.Len()) + assert.Equal(t.T(), startOffset, bqe.block.AbsStartOff()) + assertBlockContent(t.T(), bqe.block, bqe.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) + t.bucket.AssertExpectations(t.T()) + }) + } +} From 97ece1cf00c59a8f9b93015d01722351095f4bb2 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:52:25 +0530 Subject: [PATCH 0594/1298] test(rapid appends): appends from a handle after takeover from another client must fail (#3522) * Add initial test for unfinalized appends * Add initial two append test * fix test for mountedDir * test unfinalized_appends package * test mount failure * test changes * stop kokoro machine from getting killed on completion/failure * fix test package * fix tests * revert temporary changes * ft: append after takeover by another client must result in precond error * updated with latest change * other validations * update setuptest --------- Co-authored-by: Mohit Yadav Co-authored-by: Nitin Garg --- ...{rapid_appends_test.go => appends_test.go} | 50 +++++++++++++++++-- .../rapid_appends/setup_test.go | 2 + 2 files changed, 47 insertions(+), 5 deletions(-) rename tools/integration_tests/rapid_appends/{rapid_appends_test.go => appends_test.go} (69%) diff --git a/tools/integration_tests/rapid_appends/rapid_appends_test.go b/tools/integration_tests/rapid_appends/appends_test.go similarity index 69% rename from tools/integration_tests/rapid_appends/rapid_appends_test.go rename to tools/integration_tests/rapid_appends/appends_test.go index b950220b8d..7738b88a8c 100644 --- a/tools/integration_tests/rapid_appends/rapid_appends_test.go +++ b/tools/integration_tests/rapid_appends/appends_test.go @@ -65,17 +65,20 @@ func (t *RapidAppendsSuite) TearDownSuite() { } func (t *RapidAppendsSuite) SetupSubTest() { + t.createUnfinalizedObject() +} + +func (t *RapidAppendsSuite) SetupTest() { + t.createUnfinalizedObject() +} + +func (t *RapidAppendsSuite) createUnfinalizedObject() { t.fileName = fileNamePrefix + setup.GenerateRandomString(5) // Create unfinalized object. t.fileContent = setup.GenerateRandomString(unfinalizedObjectSize) client.CreateUnfinalizedObject(ctx, t.T(), storageClient, path.Join(testDirName, t.fileName), t.fileContent) } -func (t *RapidAppendsSuite) TearDownSubTest() { - err := os.Remove(path.Join(primaryMntTestDirPath, t.fileName)) - require.NoError(t.T(), err) -} - // appendToFile appends "appendContent" to the given file. func (t *RapidAppendsSuite) appendToFile(file *os.File, appendContent string) { t.T().Helper() @@ -129,6 +132,43 @@ func (t *RapidAppendsSuite) TestAppendsAndRead() { } } +func (t *RapidAppendsSuite) TestAppendSessionInvalidatedByAnotherClientUponTakeover() { + // Initiate an append session using the primary file handle opened in append mode. + appendFileHandle := operations.OpenFileInMode(t.T(), path.Join(primaryMntTestDirPath, t.fileName), os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + _, err := appendFileHandle.WriteString(initialContent) + require.NoError(t.T(), err) + + // Open a new file handle from the secondary mount to the same file. + newAppendFileHandle := operations.OpenFileInMode(t.T(), path.Join(secondaryMntTestDirPath, t.fileName), os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + defer operations.CloseFileShouldNotThrowError(t.T(), newAppendFileHandle) + + // Attempt to append using the newly opened file handle. + // This append should succeed, confirming the takeover. + _, err = newAppendFileHandle.WriteString(appendContent) + assert.NoError(t.T(), err) + + // Attempt to append using the original file handle. + // This should now fail, as its append session has been invalidated by the takeover. + _, _ = appendFileHandle.WriteString(appendContent) + err = appendFileHandle.Sync() + operations.ValidateESTALEError(t.T(), err) + + // Syncing from the newly created file handle must succeed since it holds the active + // append session. + err = newAppendFileHandle.Sync() + assert.NoError(t.T(), err) + + // Read from primary mount to validate the contents which has persisted in GCS after + // takeover from the secondary mount. + // Close the open append handle before issuing read on the file as Sync() triggered on + // ReadFile() due to BWH still being initialized, is expected to error out with stale NFS file handle. + operations.CloseFileShouldThrowError(t.T(), appendFileHandle) + expectedContent := t.fileContent + appendContent + content, err := operations.ReadFile(path.Join(primaryMntTestDirPath, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), expectedContent, string(content)) +} + //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/rapid_appends/setup_test.go b/tools/integration_tests/rapid_appends/setup_test.go index 322f7cf768..d3bb6ae573 100644 --- a/tools/integration_tests/rapid_appends/setup_test.go +++ b/tools/integration_tests/rapid_appends/setup_test.go @@ -30,6 +30,8 @@ import ( const ( testDirName = "RapidAppendsTest" fileNamePrefix = "rapid-append-file-" + initialContent = "dummy content" + appendContent = "appended content" ) var ( From 954ac3fbf88c7b20090e72da0dc608eab2dceff2 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Thu, 24 Jul 2025 12:35:13 +0530 Subject: [PATCH 0595/1298] test(rapid appends): validate content appended in non-append mode available on GCS only after close() (#3530) * Add initial test for unfinalized appends * Add initial two append test * fix test for mountedDir * test mount failure * test changes * stop kokoro machine from getting killed on completion/failure * fix test package * fix tests * ft: append after takeover by another client must result in precond error * updated with latest change * merge updates * read directly from GCS * update the data written * comment update --------- Co-authored-by: Mohit Yadav Co-authored-by: Nitin Garg --- .../rapid_appends/appends_test.go | 32 +++++++++++++++++++ .../rapid_appends/setup_test.go | 9 ++++-- 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/rapid_appends/appends_test.go b/tools/integration_tests/rapid_appends/appends_test.go index 7738b88a8c..0fcdbc74d7 100644 --- a/tools/integration_tests/rapid_appends/appends_test.go +++ b/tools/integration_tests/rapid_appends/appends_test.go @@ -169,6 +169,38 @@ func (t *RapidAppendsSuite) TestAppendSessionInvalidatedByAnotherClientUponTakeo assert.Equal(t.T(), expectedContent, string(content)) } +func (t *RapidAppendsSuite) TestContentAppendedInNonAppendModeNotVisibleTillClose() { + // Skipping test for now until CreateObject() is supported for unfinalized objects. + // Ref: b/424253611 + t.T().Skip() + + initialContent := t.fileContent + // Append to the file from the primary mount in non-append mode + wh, err := os.OpenFile(path.Join(primaryMntTestDirPath, t.fileName), os.O_WRONLY|syscall.O_DIRECT, operations.FilePermission_0600) + require.NoError(t.T(), err) + + // Write sufficient data to the end of file. + data := setup.GenerateRandomString(contentSizeForBW * operations.OneMiB) + n, err := wh.WriteAt([]byte(data), int64(len(initialContent))) + require.NoError(t.T(), err) + require.Equal(t.T(), len(data), n) + + // Read from back-door to validate that appended content is yet not visible on GCS. + contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), initialContent, contentBeforeClose) + + // Close() from primary mount to ensure data persists in GCS. + err = wh.Close() + require.NoError(t.T(), err) + + // Validate that appended content is visible in GCS. + expectedContent := initialContent + data + contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(path.Join(testDirName, t.fileName))) + require.NoError(t.T(), err) + assert.Equal(t.T(), expectedContent, contentAfterClose) +} + //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/rapid_appends/setup_test.go b/tools/integration_tests/rapid_appends/setup_test.go index d3bb6ae573..683338df5a 100644 --- a/tools/integration_tests/rapid_appends/setup_test.go +++ b/tools/integration_tests/rapid_appends/setup_test.go @@ -32,6 +32,9 @@ const ( fileNamePrefix = "rapid-append-file-" initialContent = "dummy content" appendContent = "appended content" + // Minimum content size to write in order to trigger block upload while writing ; calculated as (2*blocksize+1) mb. + // Block size for buffered writes is set to 1MiB. + contentSizeForBW = 3 ) var ( @@ -91,7 +94,7 @@ func TestMain(m *testing.M) { primaryMntLogFilePath = setup.LogFile() // TODO(b/432179045): `--write-global-max-blocks=-1` is needed right now because of a bug in global semaphore release. // Remove this flag once bug is fixed. - primaryMountFlags := []string{"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0", "--write-global-max-blocks=-1"} + primaryMountFlags := []string{"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0", "--write-global-max-blocks=-1", "--write-block-size-mb=1"} err := static_mounting.MountGcsfuseWithStaticMounting(primaryMountFlags) if err != nil { log.Fatalf("Unable to mount primary mount: %v", err) @@ -116,8 +119,8 @@ func TestMain(m *testing.M) { }() // Define flag set for secondary mount to run the tests. flagsSet := [][]string{ - {"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0"}, - {"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0", "--file-cache-max-size-mb=-1", "--cache-dir=" + rapidAppendsCacheDir}, + {"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0", "--write-block-size-mb=1"}, + {"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0", "--write-block-size-mb=1", "--file-cache-max-size-mb=-1", "--cache-dir=" + rapidAppendsCacheDir}, } log.Println("Running static mounting tests...") From 3f3ba40355b3b5bce2825d8267af00b2e847c10b Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Thu, 24 Jul 2025 14:28:16 +0530 Subject: [PATCH 0596/1298] style: removing unused code part -1 (#3576) * removing unused code * removing unused code * removing unused code --- cmd/legacy_main.go | 4 +- internal/fs/foreign_modifications_test.go | 2 +- internal/fs/fs.go | 10 ++-- internal/fs/fs_test.go | 2 +- internal/fs/handle/dir_handle_test.go | 24 -------- internal/fs/local_modifications_test.go | 1 + internal/fs/read_cache_test.go | 6 +- internal/storage/bucket_handle.go | 13 ----- internal/storage/bucket_handle_test.go | 56 +++++++++---------- internal/storage/fake/testing/bucket_tests.go | 4 +- internal/storage/storageutil/client_test.go | 16 ------ internal/util/sizeof.go | 15 +---- tools/integration_tests/gzip/gzip_test.go | 8 +-- .../integration_tests/gzip/read_gzip_test.go | 6 +- .../disabled_kernel_list_cache_test.go | 2 +- .../finite_kernel_list_cache_test.go | 2 +- ...inite_kernel_list_cache_delete_dir_test.go | 2 +- .../infinite_kernel_list_cache_test.go | 2 +- .../kernel_list_cache/setup_test.go | 2 +- .../read_cache/helpers_test.go | 2 +- .../read_cache/remount_test.go | 4 +- .../readdirplus_with_dentry_cache_test.go | 2 +- .../readdirplus_without_dentry_cache_test.go | 2 +- .../readdirplus/setup_test.go | 2 +- .../perisistent_mounting.go | 7 +-- 25 files changed, 64 insertions(+), 132 deletions(-) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 80beb682d7..6c83f1953e 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -58,7 +58,7 @@ const ( // Helpers //////////////////////////////////////////////////////////////////////// -func registerTerminatingSignalHandler(mountPoint string, c *cfg.Config) { +func registerTerminatingSignalHandler(mountPoint string) { // Register for SIGINT. signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, os.Interrupt, unix.SIGTERM) @@ -461,7 +461,7 @@ func Mount(newConfig *cfg.Config, bucketName, mountPoint string) (err error) { } // Let the user unmount with Ctrl-C (SIGINT). - registerTerminatingSignalHandler(mfs.Dir(), newConfig) + registerTerminatingSignalHandler(mfs.Dir()) // Wait for the file system to be unmounted. if err = mfs.Join(ctx); err != nil { diff --git a/internal/fs/foreign_modifications_test.go b/internal/fs/foreign_modifications_test.go index 748e922699..30d89a04bc 100644 --- a/internal/fs/foreign_modifications_test.go +++ b/internal/fs/foreign_modifications_test.go @@ -601,7 +601,7 @@ func (t *ForeignModsTest) ReadBeyondEndOfFile() { AssertEq(contents[contentLen-1], buf[0]) if err == nil { - n, err = f.Read(buf) + n, _ = f.Read(buf) AssertEq(0, n) } } diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 0466973c18..f6f75d376f 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -221,7 +221,7 @@ func NewFileSystem(ctx context.Context, serverCfg *ServerConfig) (fuseutil.FileS if err != nil { return nil, fmt.Errorf("SetUpBucket: %w", err) } - root = makeRootForBucket(ctx, fs, syncerBucket) + root = makeRootForBucket(fs, syncerBucket) } root.Lock() root.IncrementLookupCount() @@ -266,7 +266,6 @@ func createFileCacheHandler(serverCfg *ServerConfig) (fileCacheHandler *file.Cac } func makeRootForBucket( - ctx context.Context, fs *fileSystem, syncerBucket gcsx.SyncerBucket) inode.DirInode { return inode.NewDirInode( @@ -1857,7 +1856,7 @@ func (fs *fileSystem) MkNode( } // Create the child. - child, err := fs.createFile(ctx, op.Parent, op.Name, op.Mode) + child, err := fs.createFile(ctx, op.Parent, op.Name) if err != nil { return err } @@ -1885,8 +1884,7 @@ func (fs *fileSystem) MkNode( func (fs *fileSystem) createFile( ctx context.Context, parentID fuseops.InodeID, - name string, - mode os.FileMode) (child inode.Inode, err error) { + name string) (child inode.Inode, err error) { // Find the parent. fs.mu.Lock() parent := fs.dirInodeOrDie(parentID) @@ -1994,7 +1992,7 @@ func (fs *fileSystem) CreateFile( // Create the child. var child inode.Inode if fs.newConfig.Write.CreateEmptyFile { - child, err = fs.createFile(ctx, op.Parent, op.Name, op.Mode) + child, err = fs.createFile(ctx, op.Parent, op.Name) } else { child, err = fs.createLocalFile(ctx, op.Parent, op.Name) } diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index a6fc062282..f6a7c6ae74 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -230,7 +230,7 @@ func (t *fsTest) TearDownTestSuite() { // Unlink the mount point. if err = os.Remove(mntDir); err != nil { - err = fmt.Errorf("Unlinking mount point: %w", err) + logger.Errorf("Unlinking mount point: %v", err) return } diff --git a/internal/fs/handle/dir_handle_test.go b/internal/fs/handle/dir_handle_test.go index a96580c79b..68b7dc09bc 100644 --- a/internal/fs/handle/dir_handle_test.go +++ b/internal/fs/handle/dir_handle_test.go @@ -16,15 +16,12 @@ package handle import ( "context" - "math" "path" "testing" "time" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" - "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/internal/contentcache" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" @@ -34,7 +31,6 @@ import ( "github.com/jacobsa/fuse/fuseutil" . "github.com/jacobsa/ogletest" "github.com/jacobsa/timeutil" - "golang.org/x/sync/semaphore" ) func TestDirHandle(t *testing.T) { RunTests(t) } @@ -94,26 +90,6 @@ func (t *DirHandleTest) resetDirHandle() { ) } -func (t *DirHandleTest) createLocalFileInode(name string, id fuseops.InodeID) (in inode.Inode) { - in = inode.NewFileInode( - id, - inode.NewFileName(t.dh.in.Name(), name), - nil, - fuseops.InodeAttributes{ - Uid: 123, - Gid: 456, - Mode: 0712, - }, - &t.bucket, - false, // localFileCache - contentcache.New("", &t.clock), - &t.clock, - true, // localFile - &cfg.Config{}, - semaphore.NewWeighted(math.MaxInt64)) - return -} - func (t *DirHandleTest) validateEntry(entry fuseutil.Dirent, name string, filetype fuseutil.DirentType) { AssertEq(name, entry.Name) AssertEq(filetype, entry.Type) diff --git a/internal/fs/local_modifications_test.go b/internal/fs/local_modifications_test.go index c4e44dea3e..b2f8159ef4 100644 --- a/internal/fs/local_modifications_test.go +++ b/internal/fs/local_modifications_test.go @@ -1244,6 +1244,7 @@ func (t *DirectoryTest) Rmdir_OpenedForReading() { // We should still be able to stat the open file handle. fi, err := t.f1.Stat() + AssertEq(nil, err) ExpectEq("dir", fi.Name()) // Attempt to read from the directory. Unfortunately we can't implement the diff --git a/internal/fs/read_cache_test.go b/internal/fs/read_cache_test.go index b445412fc3..b3e0b69216 100644 --- a/internal/fs/read_cache_test.go +++ b/internal/fs/read_cache_test.go @@ -142,7 +142,7 @@ func cacheFilePermissionTest(t *fsTest, fileMode os.FileMode) { AssertEq(fileMode, stat.Mode()) } -func writeShouldNotPopulateCache(t *fsTest) { +func writeShouldNotPopulateCache() { objectContent := generateRandomString(DefaultObjectSizeInMb * util.MiB) filePath := path.Join(mntDir, DefaultObjectName) file, err := os.OpenFile(filePath, os.O_RDWR|syscall.O_DIRECT|os.O_CREATE, util.DefaultFilePerm) @@ -290,7 +290,7 @@ func (t *FileCacheTest) CacheFilePermission() { } func (t *FileCacheTest) WriteShouldNotPopulateCache() { - writeShouldNotPopulateCache(&t.fsTest) + writeShouldNotPopulateCache() } func (t *FileCacheTest) FileSizeGreaterThanCacheSize() { @@ -689,7 +689,7 @@ func (t *FileCacheWithCacheForRangeRead) CacheFilePermission() { } func (t *FileCacheWithCacheForRangeRead) WriteShouldNotPopulateCache() { - writeShouldNotPopulateCache(&t.fsTest) + writeShouldNotPopulateCache() } func (t *FileCacheWithCacheForRangeRead) SequentialToRandomReadShouldPopulateCache() { diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 4eaf2542c5..f17dca6c19 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -647,19 +647,6 @@ func (bh *bucketHandle) RenameFolder(ctx context.Context, folderName string, des return } -// TODO: Consider adding this method to the bucket interface if additional -// layout options are needed in the future. -func (bh *bucketHandle) getStorageLayout() (*controlpb.StorageLayout, error) { - var callOptions []gax.CallOption - stoargeLayout, err := bh.controlClient.GetStorageLayout(context.Background(), &controlpb.GetStorageLayoutRequest{ - Name: fmt.Sprintf("projects/_/buckets/%s/storageLayout", bh.bucketName), - Prefix: "", - RequestId: "", - }, callOptions...) - - return stoargeLayout, err -} - func (bh *bucketHandle) GetFolder(ctx context.Context, folderName string) (folder *gcs.Folder, err error) { defer func() { err = gcs.GetGCSError(err) diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 802219a5ef..0feb5bef9b 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -62,10 +62,10 @@ func minObjectsToMinObjectNames(minObjects []*gcs.MinObject) (objectNames []stri return } -func createBucketHandle(testSuite *BucketHandleTest, resp *controlpb.StorageLayout, err1 error) { +func createBucketHandle(testSuite *BucketHandleTest, resp *controlpb.StorageLayout) { var err error testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). - Return(resp, err1) + Return(resp, nil) testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "", false) testSuite.bucketHandle.controlClient = testSuite.mockClient @@ -284,7 +284,7 @@ func (testSuite *BucketHandleTest) TestNewReaderWithReadHandleMethodWithReadHand } func (testSuite *BucketHandleTest) TestDeleteObjectMethodWithValidObject() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) err := testSuite.bucketHandle.DeleteObject(context.Background(), &gcs.DeleteObjectRequest{ Name: TestObjectName, @@ -317,7 +317,7 @@ func (testSuite *BucketHandleTest) TestDeleteObjectMethodWithMissingGeneration() } func (testSuite *BucketHandleTest) TestDeleteObjectMethodWithZeroGeneration() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) // Note: fake-gcs-server doesn't respect Generation or other conditions in // delete operations. This unit test will be helpful when fake-gcs-server // start respecting these conditions, or we move to other testing framework. @@ -416,7 +416,7 @@ func (testSuite *BucketHandleTest) TestCopyObjectMethodWithInvalidGeneration() { } func (testSuite *BucketHandleTest) TestCreateObjectMethodWithValidObject() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) content := "Creating a new object" obj, err := testSuite.bucketHandle.CreateObject(context.Background(), @@ -431,7 +431,7 @@ func (testSuite *BucketHandleTest) TestCreateObjectMethodWithValidObject() { } func (testSuite *BucketHandleTest) TestCreateObjectMethodWithGenerationAsZero() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) content := "Creating a new object" var generation int64 = 0 @@ -448,7 +448,7 @@ func (testSuite *BucketHandleTest) TestCreateObjectMethodWithGenerationAsZero() } func (testSuite *BucketHandleTest) TestCreateObjectMethodWithGenerationAsZeroWhenObjectAlreadyExists() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) content := "Creating a new object" var generation int64 = 0 @@ -476,7 +476,7 @@ func (testSuite *BucketHandleTest) TestCreateObjectMethodWithGenerationAsZeroWhe } func (testSuite *BucketHandleTest) TestCreateObjectMethodWhenGivenGenerationObjectNotExist() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) var precondition *gcs.PreconditionError content := "Creating a new object" @@ -496,7 +496,7 @@ func (testSuite *BucketHandleTest) TestCreateObjectMethodWhenGivenGenerationObje } func (testSuite *BucketHandleTest) TestBucketHandle_CreateObjectChunkWriter() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) var generation0 int64 = 0 var generationNon0 int64 = 786 @@ -595,7 +595,7 @@ func (testSuite *BucketHandleTest) TestBucketHandle_CreateObjectChunkWriterWithN } func (testSuite *BucketHandleTest) TestBucketHandle_FinalizeUploadSuccess() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) var generation0 int64 = 0 @@ -632,7 +632,7 @@ func (testSuite *BucketHandleTest) TestBucketHandle_FinalizeUploadSuccess() { } func (testSuite *BucketHandleTest) TestFinalizeUploadWithGenerationAsZeroWhenObjectAlreadyExists() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) // Pre-create the object (creating writer and finalizing upload). objName := "pre_created_test_object" @@ -692,7 +692,7 @@ func (testSuite *BucketHandleTest) TestFlushPendingWritesFails() { testSuite.T().Run(tt.bucketType, func(t *testing.T) { createBucketHandle(testSuite, &controlpb.StorageLayout{ LocationType: tt.bucketType, - }, nil) + }) wr := testSuite.createObjectChunkWriter(t, TestObjectName, &generation0, 100) _, err := testSuite.bucketHandle.FlushPendingWrites(context.Background(), wr) @@ -1028,7 +1028,7 @@ func (testSuite *BucketHandleTest) TestComposeObjectMethodWithDstObjectExist() { } func (testSuite *BucketHandleTest) TestComposeObjectMethodWithOneSrcObject() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) var notfound *gcs.NotFoundError // Checking that dstObject does not exist _, _, err := testSuite.bucketHandle.StatObject(context.Background(), @@ -1092,7 +1092,7 @@ func (testSuite *BucketHandleTest) TestComposeObjectMethodWithOneSrcObject() { } func (testSuite *BucketHandleTest) TestComposeObjectMethodWithTwoSrcObjects() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) var notfound *gcs.NotFoundError _, _, err := testSuite.bucketHandle.StatObject(context.Background(), &gcs.StatObjectRequest{ @@ -1354,7 +1354,7 @@ func (testSuite *BucketHandleTest) TestComposeObjectMethodWhenDstObjectDoesNotEx func (testSuite *BucketHandleTest) TestComposeObjectMethodWithOneSrcObjectIsDstObject() { // Checking source object 1 exists. This will also be the destination object. - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) srcMinObj1, _, err := testSuite.bucketHandle.StatObject(context.Background(), &gcs.StatObjectRequest{ Name: TestObjectName, @@ -1453,7 +1453,7 @@ func (testSuite *BucketHandleTest) TestComposeObjectMethodWithOneSrcObjectIsDstO func (testSuite *BucketHandleTest) TestBucketTypeForHierarchicalNameSpaceTrue() { createBucketHandle(testSuite, &controlpb.StorageLayout{ HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, - }, nil) + }) testSuite.bucketHandle.BucketType() @@ -1463,7 +1463,7 @@ func (testSuite *BucketHandleTest) TestBucketTypeForHierarchicalNameSpaceTrue() func (testSuite *BucketHandleTest) TestBucketTypeForZonalLocationType() { createBucketHandle(testSuite, &controlpb.StorageLayout{ LocationType: "zone", - }, nil) + }) testSuite.bucketHandle.BucketType() @@ -1474,7 +1474,7 @@ func (testSuite *BucketHandleTest) TestBucketTypeForZonalLocationTypeAndHierarch createBucketHandle(testSuite, &controlpb.StorageLayout{ HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, LocationType: "zone", - }, nil) + }) testSuite.bucketHandle.BucketType() @@ -1484,7 +1484,7 @@ func (testSuite *BucketHandleTest) TestBucketTypeForZonalLocationTypeAndHierarch func (testSuite *BucketHandleTest) TestBucketTypeForHierarchicalNameSpaceFalse() { createBucketHandle(testSuite, &controlpb.StorageLayout{ HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: false}, - }, nil) + }) testSuite.bucketHandle.BucketType() @@ -1515,7 +1515,7 @@ func (testSuite *BucketHandleTest) TestBucketHandleWithRapidAppendsEnabled() { } func (testSuite *BucketHandleTest) TestBucketTypeWithHierarchicalNamespaceIsNil() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) testSuite.bucketHandle.BucketType() @@ -1523,7 +1523,7 @@ func (testSuite *BucketHandleTest) TestBucketTypeWithHierarchicalNamespaceIsNil( } func (testSuite *BucketHandleTest) TestDefaultBucketTypeWithControlClientNil() { - createBucketHandle(testSuite, &controlpb.StorageLayout{}, nil) + createBucketHandle(testSuite, &controlpb.StorageLayout{}) var nilControlClient *control.StorageControlClient = nil testSuite.bucketHandle.controlClient = nilControlClient @@ -1535,7 +1535,7 @@ func (testSuite *BucketHandleTest) TestDefaultBucketTypeWithControlClientNil() { func (testSuite *BucketHandleTest) TestDeleteFolderWhenFolderExitForHierarchicalBucket() { createBucketHandle(testSuite, &controlpb.StorageLayout{ HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, - }, nil) + }) ctx := context.Background() deleteFolderReq := controlpb.DeleteFolderRequest{Name: fmt.Sprintf(FullFolderPathHNS, TestBucketName, TestFolderName)} testSuite.mockClient.On("DeleteFolder", ctx, &deleteFolderReq, mock.Anything).Return(nil) @@ -1551,7 +1551,7 @@ func (testSuite *BucketHandleTest) TestDeleteFolderWhenFolderNotExistForHierarch ctx := context.Background() createBucketHandle(testSuite, &controlpb.StorageLayout{ HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, - }, nil) + }) deleteFolderReq := controlpb.DeleteFolderRequest{Name: fmt.Sprintf(FullFolderPathHNS, TestBucketName, missingFolderName)} testSuite.mockClient.On("DeleteFolder", mock.Anything, &deleteFolderReq, mock.Anything).Return(errors.New("mock error")) testSuite.bucketHandle.bucketType = &gcs.BucketType{Hierarchical: true} @@ -1566,7 +1566,7 @@ func (testSuite *BucketHandleTest) TestGetFolderWhenFolderExistsForHierarchicalB ctx := context.Background() createBucketHandle(testSuite, &controlpb.StorageLayout{ HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, - }, nil) + }) folderPath := fmt.Sprintf(FullFolderPathHNS, TestBucketName, TestFolderName) getFolderReq := controlpb.GetFolderRequest{Name: folderPath} mockFolder := controlpb.Folder{ @@ -1586,7 +1586,7 @@ func (testSuite *BucketHandleTest) TestGetFolderWhenFolderDoesNotExistsForHierar ctx := context.Background() createBucketHandle(testSuite, &controlpb.StorageLayout{ HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, - }, nil) + }) folderPath := fmt.Sprintf(FullFolderPathHNS, TestBucketName, missingFolderName) getFolderReq := controlpb.GetFolderRequest{Name: folderPath} testSuite.mockClient.On("GetFolder", ctx, &getFolderReq, mock.Anything).Return(nil, status.Error(codes.NotFound, "folder not found")) @@ -1603,7 +1603,7 @@ func (testSuite *BucketHandleTest) TestRenameFolderWithError() { ctx := context.Background() createBucketHandle(testSuite, &controlpb.StorageLayout{ HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, - }, nil) + }) renameFolderReq := controlpb.RenameFolderRequest{Name: fmt.Sprintf(FullFolderPathHNS, TestBucketName, TestFolderName), DestinationFolderId: TestRenameFolder} testSuite.mockClient.On("RenameFolder", mock.Anything, &renameFolderReq, mock.Anything).Return(nil, errors.New("mock error")) testSuite.bucketHandle.bucketType = &gcs.BucketType{Hierarchical: true} @@ -1617,7 +1617,7 @@ func (testSuite *BucketHandleTest) TestRenameFolderWithError() { func (testSuite *BucketHandleTest) TestCreateFolderWithError() { createBucketHandle(testSuite, &controlpb.StorageLayout{ HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, - }, nil) + }) createFolderReq := controlpb.CreateFolderRequest{Parent: fmt.Sprintf(FullBucketPathHNS, TestBucketName), FolderId: TestFolderName, Recursive: true} testSuite.mockClient.On("CreateFolder", context.Background(), &createFolderReq, mock.Anything).Return(nil, errors.New("mock error")) testSuite.bucketHandle.bucketType = &gcs.BucketType{Hierarchical: true} @@ -1632,7 +1632,7 @@ func (testSuite *BucketHandleTest) TestCreateFolderWithError() { func (testSuite *BucketHandleTest) TestCreateFolderWithGivenName() { createBucketHandle(testSuite, &controlpb.StorageLayout{ HierarchicalNamespace: &controlpb.StorageLayout_HierarchicalNamespace{Enabled: true}, - }, nil) + }) mockFolder := controlpb.Folder{ Name: fmt.Sprintf(FullFolderPathHNS, TestBucketName, TestFolderName), } diff --git a/internal/storage/fake/testing/bucket_tests.go b/internal/storage/fake/testing/bucket_tests.go index 2fa1f7280e..9a83f4d835 100644 --- a/internal/storage/fake/testing/bucket_tests.go +++ b/internal/storage/fake/testing/bucket_tests.go @@ -342,7 +342,7 @@ func readMultipleUsingMultiRangeDownloader( contents = make([]*bytes.Buffer, len(ranges)) errs = make([]error, len(ranges)) - handleRequest := func(ctx context.Context, i int) { + handleRequest := func(i int) { if ranges[i].Limit > ranges[i].Start { size := ranges[i].Limit - ranges[i].Start contents[i] = bytes.NewBuffer(make([]byte, 0, size)) @@ -367,7 +367,7 @@ func readMultipleUsingMultiRangeDownloader( for i := 0; i < parallelism; i++ { group.Go(func() (err error) { for i := range indices { - handleRequest(ctx, i) + handleRequest(i) } return diff --git a/internal/storage/storageutil/client_test.go b/internal/storage/storageutil/client_test.go index 30795b4c9f..667e39fdea 100644 --- a/internal/storage/storageutil/client_test.go +++ b/internal/storage/storageutil/client_test.go @@ -15,12 +15,10 @@ package storageutil import ( - "net/http" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" - "golang.org/x/oauth2" ) func TestClient(t *testing.T) { @@ -31,20 +29,6 @@ type clientTest struct { suite.Suite } -// Helpers - -func (t *clientTest) validateProxyInTransport(httpClient *http.Client) { - userAgentRT, ok := httpClient.Transport.(*userAgentRoundTripper) - assert.True(t.T(), ok) - oauthTransport, ok := userAgentRT.wrapped.(*oauth2.Transport) - assert.True(t.T(), ok) - transport, ok := oauthTransport.Base.(*http.Transport) - assert.True(t.T(), ok) - if ok { - assert.Equal(t.T(), http.ProxyFromEnvironment, transport.Proxy) - } -} - // Tests func (t *clientTest) TestCreateHttpClientWithHttp1() { diff --git a/internal/util/sizeof.go b/internal/util/sizeof.go index f4a5a74d45..d13fe95bed 100644 --- a/internal/util/sizeof.go +++ b/internal/util/sizeof.go @@ -19,29 +19,18 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "google.golang.org/api/googleapi" - storagev1 "google.golang.org/api/storage/v1" ) var ( - // pointerSize represents the size of the pointer of any type. - pointerSize int - emptyStringSize int - emptyStringArraySize int - emptyObjectAccessControlSize int - emptyObjectAccessControlProjectTeamSize int + emptyStringSize int + emptyStringArraySize int ) func init() { - var i int - pointerSize = int(reflect.TypeOf(&i).Size()) var s string emptyStringSize = int(reflect.TypeOf(s).Size()) var sArray []string emptyStringArraySize = int(reflect.TypeOf(sArray).Size()) - var emptyObjectAccessControl storagev1.ObjectAccessControl - emptyObjectAccessControlSize = int(reflect.TypeOf(emptyObjectAccessControl).Size()) - var emptyObjectAccessControlProjectTeam storagev1.ObjectAccessControlProjectTeam - emptyObjectAccessControlProjectTeamSize = int(reflect.TypeOf(emptyObjectAccessControlProjectTeam).Size()) } // Definitions/conventions (not based on a standard, but just made up for convenience). diff --git a/tools/integration_tests/gzip/gzip_test.go b/tools/integration_tests/gzip/gzip_test.go index 389fe8b99e..0fa96f766e 100644 --- a/tools/integration_tests/gzip/gzip_test.go +++ b/tools/integration_tests/gzip/gzip_test.go @@ -60,7 +60,7 @@ var ( ctx context.Context ) -func setup_testdata(m *testing.M) error { +func setup_testdata() error { fmds := []struct { filename string filesize int @@ -168,7 +168,7 @@ func setup_testdata(m *testing.M) error { return nil } -func destroy_testdata(m *testing.M, storageClient *storage.Client) error { +func destroy_testdata(storageClient *storage.Client) error { for _, gcsObjectPath := range gcsObjectsToBeDeletedEventually { err := client.DeleteObjectOnGCS(ctx, storageClient, gcsObjectPath) if err != nil { @@ -218,13 +218,13 @@ func TestMain(m *testing.M) { log.Fatal("Please pass the name of bucket mounted at mountedDirectory to --testBucket flag.") } - err = setup_testdata(m) + err = setup_testdata() if err != nil { log.Fatalf("Failed to setup test data: %v", err) } defer func() { - err := destroy_testdata(m, storageClient) + err := destroy_testdata(storageClient) if err != nil { log.Printf("Failed to destoy gzip test data: %v", err) } diff --git a/tools/integration_tests/gzip/read_gzip_test.go b/tools/integration_tests/gzip/read_gzip_test.go index f20389085a..a43bfdeab8 100644 --- a/tools/integration_tests/gzip/read_gzip_test.go +++ b/tools/integration_tests/gzip/read_gzip_test.go @@ -52,7 +52,7 @@ func verifyFileSizeAndFullFileRead(t *testing.T, filename string) { mountedFilePath, (*fi).Size(), gcsObjectPath, gcsObjectSize) } - localCopy, err := downloadGzipGcsObjectAsCompressed(t, setup.TestBucket(), path.Join(TestBucketPrefixPath, filename)) + localCopy, err := downloadGzipGcsObjectAsCompressed(t, path.Join(TestBucketPrefixPath, filename)) if err != nil { t.Fatalf("failed to download gcs object (gs:/%s) to local-disk: %v", gcsObjectPath, err) } @@ -83,7 +83,7 @@ func verifyRangedRead(t *testing.T, filename string) { t.Fatalf("Failed to open local mounted file %s: %v", mountedFilePath, err) } - localCopy, err := downloadGzipGcsObjectAsCompressed(t, setup.TestBucket(), path.Join(TestBucketPrefixPath, filename)) + localCopy, err := downloadGzipGcsObjectAsCompressed(t, path.Join(TestBucketPrefixPath, filename)) if err != nil { t.Fatalf("failed to download gcs object (gs:/%s) to local-disk: %v", gcsObjectPath, err) } @@ -117,7 +117,7 @@ func verifyRangedRead(t *testing.T, filename string) { // Uses go storage client library to download object. Use of gsutil/gcloud is not // possible as they both always read back objects with content-encoding: gzip as // uncompressed/decompressed irrespective of any argument passed. -func downloadGzipGcsObjectAsCompressed(t *testing.T, bucketName, objPathInBucket string) (tempfile string, err error) { +func downloadGzipGcsObjectAsCompressed(t *testing.T, objPathInBucket string) (tempfile string, err error) { gcsObjectSize, err := client.GetGcsObjectSize(ctx, storageClient, objPathInBucket) if err != nil { diff --git a/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go index e2be0264d0..0daff66e4a 100644 --- a/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go @@ -37,7 +37,7 @@ type disabledKernelListCacheTest struct { } func (s *disabledKernelListCacheTest) Setup(t *testing.T) { - mountGCSFuseAndSetupTestDir(s.flags, ctx, storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, testDirName) } func (s *disabledKernelListCacheTest) Teardown(t *testing.T) { diff --git a/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go index 384785484f..ab17247c4a 100644 --- a/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go @@ -38,7 +38,7 @@ type finiteKernelListCacheTest struct { } func (s *finiteKernelListCacheTest) Setup(t *testing.T) { - mountGCSFuseAndSetupTestDir(s.flags, ctx, storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, testDirName) } func (s *finiteKernelListCacheTest) Teardown(t *testing.T) { diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go index fcbbb63791..679b283488 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go @@ -37,7 +37,7 @@ type infiniteKernelListCacheDeleteDirTest struct { } func (s *infiniteKernelListCacheDeleteDirTest) Setup(t *testing.T) { - mountGCSFuseAndSetupTestDir(s.flags, ctx, storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, testDirName) } func (s *infiniteKernelListCacheDeleteDirTest) Teardown(t *testing.T) { diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go index ef1e3aee08..015ef2029f 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go @@ -38,7 +38,7 @@ type infiniteKernelListCacheTest struct { } func (s *infiniteKernelListCacheTest) Setup(t *testing.T) { - mountGCSFuseAndSetupTestDir(s.flags, ctx, storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, testDirName) } func (s *infiniteKernelListCacheTest) Teardown(t *testing.T) { diff --git a/tools/integration_tests/kernel_list_cache/setup_test.go b/tools/integration_tests/kernel_list_cache/setup_test.go index f82c0c64a4..b5be7635a9 100644 --- a/tools/integration_tests/kernel_list_cache/setup_test.go +++ b/tools/integration_tests/kernel_list_cache/setup_test.go @@ -49,7 +49,7 @@ var ( // Helpers //////////////////////////////////////////////////////////////////////// -func mountGCSFuseAndSetupTestDir(flags []string, ctx context.Context, storageClient *storage.Client, testDirName string) { +func mountGCSFuseAndSetupTestDir(flags []string, testDirName string) { // When tests are running in GKE environment, use the mounted directory provided as test flag. if setup.MountedDirectory() != "" { mountDir = setup.MountedDirectory() diff --git a/tools/integration_tests/read_cache/helpers_test.go b/tools/integration_tests/read_cache/helpers_test.go index 3e9fcdbe6b..79db5b3dc8 100644 --- a/tools/integration_tests/read_cache/helpers_test.go +++ b/tools/integration_tests/read_cache/helpers_test.go @@ -166,7 +166,7 @@ func validateFileIsNotCached(fileName string, t *testing.T) { } } -func remountGCSFuse(flags []string, t *testing.T) { +func remountGCSFuse(flags []string) { setup.SetMntDir(rootDir) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) diff --git a/tools/integration_tests/read_cache/remount_test.go b/tools/integration_tests/read_cache/remount_test.go index d1848f6676..86ce287e48 100644 --- a/tools/integration_tests/read_cache/remount_test.go +++ b/tools/integration_tests/read_cache/remount_test.go @@ -76,7 +76,7 @@ func (s *remountTest) TestCacheIsNotReusedOnRemount(t *testing.T) { expectedOutcome2 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) structuredReadLogsMount1 := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) // Re-mount GCSFuse. - remountGCSFuse(s.flags, t) + remountGCSFuse(s.flags) // Run read operations again on GCSFuse mount. expectedOutcome3 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, false, t) expectedOutcome4 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, false, t) @@ -112,7 +112,7 @@ func (s *remountTest) TestCacheIsNotReusedOnDynamicRemount(t *testing.T) { expectedOutcome1 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket1, s.ctx, s.storageClient, testFileName1, true, t) expectedOutcome2 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket2, s.ctx, s.storageClient, testFileName2, true, t) structuredReadLogs1 := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - remountGCSFuse(s.flags, t) + remountGCSFuse(s.flags) // Reading files in different buckets again. expectedOutcome3 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket1, s.ctx, s.storageClient, testFileName1, false, t) expectedOutcome4 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket2, s.ctx, s.storageClient, testFileName2, false, t) diff --git a/tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go b/tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go index 0a71bf4adc..194a578668 100644 --- a/tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go +++ b/tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go @@ -34,7 +34,7 @@ type readdirplusWithDentryCacheTest struct { } func (s *readdirplusWithDentryCacheTest) Setup(t *testing.T) { - mountGCSFuseAndSetupTestDir(t, s.flags, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, testDirName) } func (s *readdirplusWithDentryCacheTest) Teardown(t *testing.T) { diff --git a/tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go b/tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go index ea44509fe6..b206e86e83 100644 --- a/tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go +++ b/tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go @@ -34,7 +34,7 @@ type readdirplusWithoutDentryCacheTest struct { } func (s *readdirplusWithoutDentryCacheTest) Setup(t *testing.T) { - mountGCSFuseAndSetupTestDir(t, s.flags, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, testDirName) } func (s *readdirplusWithoutDentryCacheTest) Teardown(t *testing.T) { diff --git a/tools/integration_tests/readdirplus/setup_test.go b/tools/integration_tests/readdirplus/setup_test.go index c5783eeedc..65171288bd 100644 --- a/tools/integration_tests/readdirplus/setup_test.go +++ b/tools/integration_tests/readdirplus/setup_test.go @@ -108,7 +108,7 @@ func validateLogsForReaddirplus(t *testing.T, logFile string, dentryCacheEnabled } } -func mountGCSFuseAndSetupTestDir(t *testing.T, flags []string, testDirName string) { +func mountGCSFuseAndSetupTestDir(flags []string, testDirName string) { setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) setup.SetMntDir(mountDir) testDirPath = setup.SetupTestDirectory(testDirName) diff --git a/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go b/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go index b36689ebdb..a606d6e2fe 100644 --- a/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go +++ b/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go @@ -25,7 +25,7 @@ import ( ) // Change e.g --log_severity=trace to log_severity=trace -func makePersistentMountingArgs(flags []string) (args []string, err error) { +func makePersistentMountingArgs(flags []string) (args []string) { var s string for i := range flags { // We are already passing flags with -o flag. @@ -48,10 +48,7 @@ func mountGcsfuseWithPersistentMounting(flags []string) (err error) { "log_file=" + setup.LogFile(), } - persistentMountingArgs, err := makePersistentMountingArgs(flags) - if err != nil { - setup.LogAndExit("Error in converting flags for persistent mounting.") - } + persistentMountingArgs := makePersistentMountingArgs(flags) for i := 0; i < len(persistentMountingArgs); i++ { // e.g. -o flag1, -o flag2, ... From 984f8815435fe299b4ac76d479e3a5a14b3bf9cc Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Thu, 24 Jul 2025 14:40:25 +0530 Subject: [PATCH 0597/1298] Block: Making block interface a reader (#3476) * Making block interface a reader * review comments * fixing lint --- internal/block/block.go | 83 +++++++++++----- internal/block/block_pool_test.go | 3 +- internal/block/block_test.go | 114 ++++++++++++++++++++-- internal/block/prefetch_block_test.go | 5 +- internal/bufferedwrites/upload_handler.go | 59 +++++++---- 5 files changed, 212 insertions(+), 52 deletions(-) diff --git a/internal/block/block.go b/internal/block/block.go index e11116d404..ed99b4d6a7 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -15,7 +15,6 @@ package block import ( - "bytes" "fmt" "io" "syscall" @@ -23,6 +22,13 @@ import ( // Block represents the buffer which holds the data. type Block interface { + // ReadSeeker is the interface that groups the basic Read and Seek methods. + // It is used to read data from the block. + io.ReadSeeker + + // Writer provides a way to write data to the block. + io.Writer + // GenBlock defines reuse and deallocation of the block. GenBlock @@ -35,10 +41,6 @@ type Block interface { // Write writes the given data to block. Write(bytes []byte) (n int, err error) - - // Reader interface helps in copying the data directly to storage.writer - // while uploading to GCS. - Reader() io.Reader } // TODO: check if we need offset or just storing end is sufficient. We might need @@ -51,6 +53,9 @@ type memoryBlock struct { Block buffer []byte offset offset + + // readSeek is used to track the position for reading data. + readSeek int64 } func (m *memoryBlock) Reuse() { @@ -58,6 +63,7 @@ func (m *memoryBlock) Reuse() { m.offset.end = 0 m.offset.start = 0 + m.readSeek = 0 } func (m *memoryBlock) Size() int64 { @@ -68,6 +74,53 @@ func (m *memoryBlock) Cap() int64 { return int64(cap(m.buffer)) } +// Read reads data from the block into the provided byte slice. +// Please make sure to call Seek before calling Read if you want to read from a specific position. +func (m *memoryBlock) Read(bytes []byte) (int, error) { + if m.readSeek < m.offset.start { + return 0, fmt.Errorf("readSeek %d is less than start offset %d", m.readSeek, m.offset.start) + } + + if m.readSeek >= m.offset.end { + return 0, io.EOF + } + + n := copy(bytes, m.buffer[m.readSeek:m.offset.end]) + m.readSeek += int64(n) + + return n, nil +} + +// Seek sets the readSeek position in the block. +// It returns the new readSeek position and an error if any. +// The whence argument specifies how the offset should be interpreted: +// - io.SeekStart: offset is relative to the start of the block. +// - io.SeekCurrent: offset is relative to the current readSeek position. +// - io.SeekEnd: offset is relative to the end of the block. +// +// It returns an error if the whence value is invalid or if the new +// readSeek position is out of bounds. +func (m *memoryBlock) Seek(offset int64, whence int) (int64, error) { + newReadSeek := m.readSeek + switch whence { + case io.SeekStart: + m.readSeek = m.offset.start + offset + case io.SeekCurrent: + newReadSeek += offset + case io.SeekEnd: + newReadSeek = m.offset.end + offset + default: + return 0, fmt.Errorf("invalid whence value: %d", whence) + } + + if newReadSeek < m.offset.start || newReadSeek > m.offset.end { + return 0, fmt.Errorf("new readSeek position %d is out of bounds", newReadSeek) + } + + m.readSeek = newReadSeek + return m.readSeek, nil +} + func (m *memoryBlock) Write(bytes []byte) (int, error) { if m.Size()+int64(len(bytes)) > int64(cap(m.buffer)) { return 0, fmt.Errorf("received data more than capacity of the block") @@ -82,10 +135,6 @@ func (m *memoryBlock) Write(bytes []byte) (int, error) { return n, nil } -func (m *memoryBlock) Reader() io.Reader { - return bytes.NewReader(m.buffer[0:m.offset.end]) -} - func (m *memoryBlock) Deallocate() error { if m.buffer == nil { return fmt.Errorf("invalid buffer") @@ -115,19 +164,3 @@ func createBlock(blockSize int64) (Block, error) { } return &mb, nil } - -// ReadAt reads data from the block at the specified offset. -// The offset is relative to the start of the block. -// It returns the number of bytes read and an error if any. -func (m *memoryBlock) ReadAt(p []byte, off int64) (n int, err error) { - if off < 0 || off >= m.Size() { - return 0, fmt.Errorf("offset %d is out of bounds for block size %d", off, m.Size()) - } - - n = copy(p, m.buffer[m.offset.start+off:m.offset.end]) - - if n < len(p) { - return n, io.EOF - } - return n, nil -} diff --git a/internal/block/block_pool_test.go b/internal/block/block_pool_test.go index 72fd6274ed..9fd1df39a2 100644 --- a/internal/block/block_pool_test.go +++ b/internal/block/block_pool_test.go @@ -86,7 +86,8 @@ func (t *BlockPoolTest) TestGetWhenBlockIsAvailableForReuse() { require.Equal(t.T(), 2, n) require.Nil(t.T(), err) // Validating the content of the block - output, err := io.ReadAll(b.Reader()) + require.Equal(t.T(), int64(0), b.(*memoryBlock).readSeek) + output, err := io.ReadAll(b) require.Nil(t.T(), err) require.Equal(t.T(), content, output) bp.freeBlocksCh <- b diff --git a/internal/block/block_test.go b/internal/block/block_test.go index 7807fcb5b7..c977eb4188 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -15,6 +15,8 @@ package block import ( + "errors" + "fmt" "io" "testing" @@ -41,7 +43,8 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockWrite() { assert.Nil(testSuite.T(), err) assert.Equal(testSuite.T(), len(content), n) - output, err := io.ReadAll(mb.Reader()) + assert.Equal(testSuite.T(), int64(0), mb.(*memoryBlock).readSeek) + output, err := io.ReadAll(mb) assert.Nil(testSuite.T(), err) assert.Equal(testSuite.T(), content, output) assert.Equal(testSuite.T(), int64(2), mb.Size()) @@ -68,7 +71,8 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockWriteWithMultipleWrites() { require.Nil(testSuite.T(), err) require.Equal(testSuite.T(), 5, n) - output, err := io.ReadAll(mb.Reader()) + assert.Equal(testSuite.T(), int64(0), mb.(*memoryBlock).readSeek) + output, err := io.ReadAll(mb) assert.Nil(testSuite.T(), err) assert.Equal(testSuite.T(), []byte("hihello"), output) assert.Equal(testSuite.T(), int64(7), mb.Size()) @@ -95,14 +99,16 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockReuse() { n, err := mb.Write(content) require.Nil(testSuite.T(), err) require.Equal(testSuite.T(), 2, n) - output, err := io.ReadAll(mb.Reader()) + require.Equal(testSuite.T(), int64(0), mb.(*memoryBlock).readSeek) + output, err := io.ReadAll(mb) require.Nil(testSuite.T(), err) require.Equal(testSuite.T(), content, output) require.Equal(testSuite.T(), int64(2), mb.Size()) mb.Reuse() - output, err = io.ReadAll(mb.Reader()) + assert.Equal(testSuite.T(), int64(0), mb.(*memoryBlock).readSeek) + output, err = io.ReadAll(mb) assert.Nil(testSuite.T(), err) assert.Empty(testSuite.T(), output) assert.Equal(testSuite.T(), int64(0), mb.Size()) @@ -121,7 +127,8 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockReaderForEmptyBlock() { mb, err := createBlock(12) require.Nil(testSuite.T(), err) - output, err := io.ReadAll(mb.Reader()) + assert.Equal(testSuite.T(), int64(0), mb.(*memoryBlock).readSeek) + output, err := io.ReadAll(mb) assert.Nil(testSuite.T(), err) assert.Empty(testSuite.T(), output) assert.Equal(testSuite.T(), int64(0), mb.Size()) @@ -134,7 +141,8 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockDeAllocate() { n, err := mb.Write(content) require.Nil(testSuite.T(), err) require.Equal(testSuite.T(), 2, n) - output, err := io.ReadAll(mb.Reader()) + require.Equal(testSuite.T(), int64(0), mb.(*memoryBlock).readSeek) + output, err := io.ReadAll(mb) require.Nil(testSuite.T(), err) require.Equal(testSuite.T(), content, output) require.Equal(testSuite.T(), int64(2), mb.Size()) @@ -162,3 +170,97 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockCapAfterWrite() { assert.Equal(testSuite.T(), int64(12), mb.Cap()) } + +func (testSuite *MemoryBlockTest) TestMemoryBlockReadSuccess() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + n, err := mb.Write(content) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), len(content), n) + readBuffer := make([]byte, 5) + + n, err = mb.Read(readBuffer) + + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), 5, n) + assert.Equal(testSuite.T(), "hello", string(readBuffer)) +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockReadWithReadBufferMoreThanBlockSize() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + n, err := mb.Write(content) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), len(content), n) + readBuffer := make([]byte, 20) + + n, err = mb.Read(readBuffer) + + require.NoError(testSuite.T(), err) + require.Equal(testSuite.T(), 11, n) // Read should return all bytes written. +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockReadSeekBeyondEnd() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + n, err := mb.Write(content) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), len(content), n) + readBuffer := make([]byte, 12) + mb.(*memoryBlock).readSeek = 13 // Set readSeek to a position beyond the end of the block. + + n, err = mb.Read(readBuffer) + + require.Equal(testSuite.T(), io.EOF, err) + require.Equal(testSuite.T(), 0, n) +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockReadFailure() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + readBuffer := make([]byte, 5) + mb.(*memoryBlock).readSeek = -1 // Simulate an invalid readSeek position. + require.Nil(testSuite.T(), err) + + n, err := mb.Read(readBuffer) + + require.Equal(testSuite.T(), errors.New("readSeek -1 is less than start offset 0"), err) + require.Equal(testSuite.T(), 0, n) // Read should return 0 bytes read. +} + +func (testSuite *MemoryBlockTest) TestMemoryBlockSeek() { + mb, err := createBlock(12) + require.Nil(testSuite.T(), err) + content := []byte("hello world") + n, err := mb.Write(content) + require.Nil(testSuite.T(), err) + require.Equal(testSuite.T(), len(content), n) + + tests := []struct { + whence int + offset int64 + expectedOutput string + expectedOffset int64 + }{ + {io.SeekStart, 0, "hello", 0}, // After this, readSeek = 5 + {io.SeekCurrent, 1, "world", 6}, // After this readSeek = 11 + {io.SeekEnd, -6, " worl", 5}, + } + + for _, tt := range tests { + testSuite.T().Run(fmt.Sprintf("whence=%d, offset=%d, expectedOutput:%s", tt.whence, tt.offset, tt.expectedOutput), func(t *testing.T) { + offset, err := mb.Seek(tt.offset, tt.whence) + + require.Nil(t, err) + require.Equal(t, tt.expectedOffset, offset) + readBuffer := make([]byte, 5) + n, err = mb.Read(readBuffer) + require.Nil(t, err) + require.Equal(t, 5, n) + assert.Equal(t, tt.expectedOutput, string(readBuffer)) + }) + } +} diff --git a/internal/block/prefetch_block_test.go b/internal/block/prefetch_block_test.go index 0cd13d4f41..9f61458517 100644 --- a/internal/block/prefetch_block_test.go +++ b/internal/block/prefetch_block_test.go @@ -43,7 +43,7 @@ func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockReuse() { n, err := pmb.Write(content) require.Nil(testSuite.T(), err) require.Equal(testSuite.T(), 2, n) - output, err := io.ReadAll(pmb.Reader()) + output, err := io.ReadAll(pmb) require.Nil(testSuite.T(), err) require.Equal(testSuite.T(), content, output) require.Equal(testSuite.T(), int64(2), pmb.Size()) @@ -52,7 +52,8 @@ func (testSuite *PrefetchMemoryBlockTest) TestPrefetchMemoryBlockReuse() { pmb.Reuse() - output, err = io.ReadAll(pmb.Reader()) + assert.Equal(testSuite.T(), int64(0), pmb.(*prefetchMemoryBlock).readSeek) + output, err = io.ReadAll(pmb) assert.Nil(testSuite.T(), err) assert.Empty(testSuite.T(), output) assert.Equal(testSuite.T(), int64(0), pmb.Size()) diff --git a/internal/bufferedwrites/upload_handler.go b/internal/bufferedwrites/upload_handler.go index 10814a56e6..98a729c1ec 100644 --- a/internal/bufferedwrites/upload_handler.go +++ b/internal/bufferedwrites/upload_handler.go @@ -130,29 +130,52 @@ func (uh *UploadHandler) UploadError() (err error) { // uploader is the single-threaded goroutine that uploads blocks. func (uh *UploadHandler) uploader() { for currBlock := range uh.uploadCh { - if uh.UploadError() != nil { - uh.blockPool.Release(currBlock) - uh.wg.Done() - continue - } - _, err := io.Copy(uh.writer, currBlock.Reader()) - if errors.Is(err, context.Canceled) { - // Context canceled error indicates that the file was deleted from the - // same mount. In this case, we suppress the error to match local - // filesystem behavior. - err = nil - } - if err != nil { - logger.Errorf("buffered write upload failed for object %s: error in io.Copy: %v", uh.objectName, err) - err = gcs.GetGCSError(err) - uh.uploadError.Store(&err) - } - // Put back the uploaded block on the block pool for re-use. + uh.uploadBlock(currBlock) + + // Put back the uploaded block to the pool for re-use, + // irrespective of whether the upload was successful or not. uh.blockPool.Release(currBlock) uh.wg.Done() } } +// uploadBlock uploads the block content to GCS writer. +// It is called by the uploader goroutine. +// If the block is nil, it logs a warning and returns. +// If there is already an error in uploadError, it returns without doing anything. +// If there is an error during upload, it returns after storing the error in uploadError. +func (uh *UploadHandler) uploadBlock(b block.Block) { + if b == nil { + logger.Warnf("uploadBlock: received nil block for object %s", uh.objectName) + return + } + + if uh.UploadError() != nil { + return + } + + // Reset the readSeek to 0 before uploading. + if off, err := b.Seek(0, io.SeekStart); err != nil || off != 0 { + err := fmt.Errorf("buffered write upload failed for object %s: error in block.Seek: %v with offset: %d", uh.objectName, err, off) + uh.uploadError.Store(&err) + logger.Errorf("uploadBlock: %v", err) + return + } + + _, err := io.Copy(uh.writer, b) + if errors.Is(err, context.Canceled) { + // Context canceled error indicates that the file was deleted from the + // same mount. In this case, we suppress the error to match local + // filesystem behavior. + err = nil + } + if err != nil { + err = gcs.GetGCSError(err) + uh.uploadError.Store(&err) + logger.Errorf("uploadBlock: failed for object %s: error in io.Copy: %v", uh.objectName, err) + } +} + // Finalize finalizes the upload. func (uh *UploadHandler) Finalize() (*gcs.MinObject, error) { uh.wg.Wait() From 7c8d31e2e216b0aa5d05df25aa2d297acde33208 Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Thu, 24 Jul 2025 11:23:42 +0000 Subject: [PATCH 0598/1298] feat(dentry cache): Add integration tests for dentry cache (#3562) * Add e2e tests for dentry cache --- internal/fs/wrappers/error_mapping.go | 18 +-- tools/cd_scripts/e2e_test.sh | 2 + .../dentry_cache/notifier_test.go | 111 ++++++++++++++++++ .../dentry_cache/setup_test.go | 89 ++++++++++++++ .../dentry_cache/stat_test.go | 88 ++++++++++++++ .../improved_run_e2e_tests.sh | 1 + tools/integration_tests/run_e2e_tests.sh | 3 + .../run_tests_mounted_directory.sh | 18 +++ 8 files changed, 321 insertions(+), 9 deletions(-) create mode 100644 tools/integration_tests/dentry_cache/notifier_test.go create mode 100644 tools/integration_tests/dentry_cache/setup_test.go create mode 100644 tools/integration_tests/dentry_cache/stat_test.go diff --git a/internal/fs/wrappers/error_mapping.go b/internal/fs/wrappers/error_mapping.go index 16eaf6ca64..fb077574bd 100644 --- a/internal/fs/wrappers/error_mapping.go +++ b/internal/fs/wrappers/error_mapping.go @@ -40,6 +40,15 @@ func errno(err error, preconditionErrCfg bool) error { return nil } + // The object is modified or deleted by a concurrent process. + var clobberedErr *gcsfuse_errors.FileClobberedError + if errors.As(err, &clobberedErr) { + if preconditionErrCfg { + return syscall.ESTALE + } + return nil + } + // Use existing em errno var errno syscall.Errno if errors.As(err, &errno) { @@ -51,15 +60,6 @@ func errno(err error, preconditionErrCfg bool) error { return syscall.EINTR } - // The object is modified or deleted by a concurrent process. - var clobberedErr *gcsfuse_errors.FileClobberedError - if errors.As(err, &clobberedErr) { - if preconditionErrCfg { - return syscall.ESTALE - } - return nil - } - if errors.Is(err, storage.ErrObjectNotExist) { return syscall.ENOENT } diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 59d9efe15c..a1e4c45561 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -206,6 +206,8 @@ TEST_DIR_PARALLEL=( "negative_stat_cache" "streaming_writes" "release_version" + "readdirplus" + "dentry_cache" ) # These tests never become parallel as they are changing bucket permissions. diff --git a/tools/integration_tests/dentry_cache/notifier_test.go b/tools/integration_tests/dentry_cache/notifier_test.go new file mode 100644 index 0000000000..f4bd5f6771 --- /dev/null +++ b/tools/integration_tests/dentry_cache/notifier_test.go @@ -0,0 +1,111 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dentry_cache + +import ( + "log" + "os" + "path" + "testing" + + "cloud.google.com/go/storage" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type notifierTest struct { + flags []string +} + +func (s *notifierTest) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(s.flags, testDirName) +} + +func (s *notifierTest) Teardown(t *testing.T) { + if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory + setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) + setup.UnmountGCSFuseAndDeleteLogFile(rootDir) + } +} + +func (s *notifierTest) TestWriteFileWithDentryCacheEnabled(t *testing.T) { + // Create a file with initial content directly in GCS. + filePath := path.Join(setup.MntDir(), testDirName, testFileName) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + // Stat file to cache the entry + _, err := os.Stat(filePath) + require.Nil(t, err) + // Modify the object on GCS. + objectName := path.Join(testDirName, testFileName) + smallContent, err := operations.GenerateRandomData(updatedContentSize) + require.Nil(t, err) + require.Nil(t, client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) + + // First Write File attempt. + err = operations.WriteFile(filePath, "ShouldNotWrite") + + // First Write File attempt should fail because file has been clobbered. + operations.ValidateESTALEError(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. + // Therefore, the second write succeeds even before the metadata cache TTL expires. + assert.Nil(t, err) +} + +func (s *notifierTest) TestReadFileWithDentryCacheEnabled(t *testing.T) { + // Create a file with initial content directly in GCS. + filePath := path.Join(setup.MntDir(), testDirName, testFileName) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + // Stat file to cache the entry + _, err := os.Stat(filePath) + require.Nil(t, err) + // Modify the object on GCS. + objectName := path.Join(testDirName, testFileName) + smallContent, err := operations.GenerateRandomData(updatedContentSize) + require.Nil(t, err) + require.Nil(t, client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) + + // First Read File attempt. + _, err = operations.ReadFile(filePath) + + // First Read File attempt should fail because file has been clobbered. + operations.ValidateESTALEError(t, err) + // Second Read File attempt. + _, err = operations.ReadFile(filePath) + // The notifier is triggered after the first read failure, invalidating the kernel cache entry. + // Therefore, the second read succeeds even before the metadata cache TTL expires. + assert.Nil(t, err) +} + +func TestNotifierTest(t *testing.T) { + ts := ¬ifierTest{} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + // Setup flags and run tests. + ts.flags = []string{"--implicit-dirs", "--experimental-enable-dentry-cache", "--metadata-cache-ttl-secs=1000"} + log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) +} diff --git a/tools/integration_tests/dentry_cache/setup_test.go b/tools/integration_tests/dentry_cache/setup_test.go new file mode 100644 index 0000000000..bd3bcd9b02 --- /dev/null +++ b/tools/integration_tests/dentry_cache/setup_test.go @@ -0,0 +1,89 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Provides integration tests for enabling dentry cache. +package dentry_cache + +import ( + "context" + "log" + "os" + "testing" + + "cloud.google.com/go/storage" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" +) + +const ( + testDirName = "testDirForDentryCache" + testFileName = "testFile" + initialContentSize = 5 + updatedContentSize = 10 +) + +var ( + storageClient *storage.Client + ctx context.Context + testDirPath string + mountFunc func([]string) error + // mount directory is where our tests run. + mountDir string + // root directory is the directory to be unmounted. + rootDir string +) + +func mountGCSFuseAndSetupTestDir(flags []string, testDirName string) { + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) + setup.SetMntDir(mountDir) + testDirPath = client.SetupTestDirectory(ctx, storageClient, testDirName) +} + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + if setup.TestBucket() == "" { + log.Print("--testbucket must be specified") + os.Exit(1) + } + + // Create common storage client to be used in test. + ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() + + if setup.MountedDirectory() != "" { + mountDir = setup.MountedDirectory() + // Run tests for mounted directory if the flag is set. + os.Exit(m.Run()) + } + // Else run tests for testBucket. + // Set up test directory. + setup.SetUpTestDirForTestBucketFlag() + + // Save mount and root directory variables. + mountDir, rootDir = setup.MntDir(), setup.MntDir() + + log.Println("Running static mounting tests...") + mountFunc = static_mounting.MountGcsfuseWithStaticMounting + successCode := m.Run() + + os.Exit(successCode) +} diff --git a/tools/integration_tests/dentry_cache/stat_test.go b/tools/integration_tests/dentry_cache/stat_test.go new file mode 100644 index 0000000000..db02e6ff24 --- /dev/null +++ b/tools/integration_tests/dentry_cache/stat_test.go @@ -0,0 +1,88 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dentry_cache + +import ( + "log" + "os" + "path" + "testing" + "time" + + "cloud.google.com/go/storage" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type statWithDentryCacheEnabledTest struct { + flags []string +} + +func (s *statWithDentryCacheEnabledTest) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(s.flags, testDirName) +} + +func (s *statWithDentryCacheEnabledTest) Teardown(t *testing.T) { + if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory + setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) + setup.UnmountGCSFuseAndDeleteLogFile(rootDir) + } +} + +func (s *statWithDentryCacheEnabledTest) TestStatWithDentryCacheEnabled(t *testing.T) { + // Create a file with initial content directly in GCS. + filePath := path.Join(setup.MntDir(), testDirName, testFileName) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + // Stat file to cache the entry + _, err := os.Stat(filePath) + require.Nil(t, err) + // Modify the object on GCS. + objectName := path.Join(testDirName, testFileName) + smallContent, err := operations.GenerateRandomData(updatedContentSize) + require.Nil(t, err) + require.Nil(t, client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) + + // Stat again, it should give old cached attributes. + fileInfo, err := os.Stat(filePath) + + assert.Nil(t, err) + assert.Equal(t, int64(initialContentSize), fileInfo.Size()) + // Wait until entry expires in cache. + time.Sleep(1100 * time.Millisecond) + // Stat again, it should give updated attributes. + fileInfo, err = os.Stat(filePath) + assert.Nil(t, err) + assert.Equal(t, int64(updatedContentSize), fileInfo.Size()) +} + +func TestStatWithDentryCacheEnabledTest(t *testing.T) { + ts := &statWithDentryCacheEnabledTest{} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + // Setup flags and run tests. + ts.flags = []string{"--implicit-dirs", "--experimental-enable-dentry-cache", "--metadata-cache-ttl-secs=1"} + log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) +} diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh index 4410bb7fbf..03e3328488 100755 --- a/tools/integration_tests/improved_run_e2e_tests.sh +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -217,6 +217,7 @@ TEST_PACKAGES_COMMON=( "stale_handle" "release_version" "readdirplus" + "dentry_cache" ) # Test packages for regional buckets. diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 4fe3a6e16a..585e0149c2 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -120,6 +120,7 @@ TEST_DIR_PARALLEL=( "cloud_profiler" "release_version" "readdirplus" + "dentry_cache" ) # These tests never become parallel as it is changing bucket permissions. @@ -154,6 +155,8 @@ TEST_DIR_PARALLEL_FOR_ZB=( "write_large_files" "unfinalized_object" "release_version" + "readdirplus" + "dentry_cache" ) # Subset of TEST_DIR_NON_PARALLEL, diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index 4127724ee3..870e9ecb20 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -679,3 +679,21 @@ gcsfuse --implicit-dirs --experimental-enable-readdirplus --log-file $log_file - GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/readdirplus/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run $test_case sudo umount "$MOUNT_DIR" rm -rf $log_dir + +# Test package: dentry_cache +# Run stat with dentry cache enabled +test_case="TestStatWithDentryCacheEnabledTest/TestStatWithDentryCacheEnabled" +gcsfuse --implicit-dirs --experimental-enable-dentry-cache --metadata-cache-ttl-secs=1 "$TEST_BUCKET_NAME" "$MOUNT_DIR" +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/dentry_cache/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run $test_case +sudo umount "$MOUNT_DIR" + +# Run notifier tests +test_cases=( + "TestNotifierTest/TestReadFileWithDentryCacheEnabled" + "TestNotifierTest/TestWriteFileWithDentryCacheEnabled" +) +for test_case in "${test_cases[@]}"; do + gcsfuse --implicit-dirs --experimental-enable-dentry-cache --metadata-cache-ttl-secs=1000 "$TEST_BUCKET_NAME" "$MOUNT_DIR" + GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/dentry_cache/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run $test_case + sudo umount "$MOUNT_DIR" +done From 57f702be08c297b63cc269f3dd45986c5bbc7daf Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Thu, 24 Jul 2025 17:37:35 +0530 Subject: [PATCH 0599/1298] test(rapid appends): Appends to finalized object is not visible on GCS till Close() (#3538) * test changes * read directly from GCS * read data 2bs+1 --- .../rapid_appends/appends_test.go | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tools/integration_tests/rapid_appends/appends_test.go b/tools/integration_tests/rapid_appends/appends_test.go index 0fcdbc74d7..01e3a3e42f 100644 --- a/tools/integration_tests/rapid_appends/appends_test.go +++ b/tools/integration_tests/rapid_appends/appends_test.go @@ -201,6 +201,35 @@ func (t *RapidAppendsSuite) TestContentAppendedInNonAppendModeNotVisibleTillClos assert.Equal(t.T(), expectedContent, contentAfterClose) } +func (t *RapidAppendsSuite) TestAppendsToFinalizedObjectNotVisibleUntilClose() { + t.fileName = fileNamePrefix + setup.GenerateRandomString(5) + // Create Finalized Object in the GCS bucket. + client.CreateObjectInGCSTestDir( + ctx, storageClient, testDirName, t.fileName, initialContent, t.T()) + + // Append to the finalized object from the primary mount. + data := setup.GenerateRandomString(contentSizeForBW * operations.OneMiB) + filePath := path.Join(primaryMntTestDirPath, t.fileName) + fh, err := os.OpenFile(filePath, os.O_APPEND|os.O_RDWR|syscall.O_DIRECT, operations.FilePermission_0600) + require.NoError(t.T(), err) + _, err = fh.Write([]byte(data)) + require.NoError(t.T(), err) + + // Read from back-door to validate that appended content is yet not visible on GCS. + contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), initialContent, string(contentBeforeClose)) + + // Close the file handle used for appending. + require.NoError(t.T(), fh.Close()) + + // Read from back-door to validate that appended content is now visible on GCS. + expectedContent := initialContent + data + contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), expectedContent, string(contentAfterClose)) +} + //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// From 01c762c9d2d0e124a042aeaa97d61243275aa4f3 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 25 Jul 2025 10:32:18 +0530 Subject: [PATCH 0600/1298] ci: Correction from Mbps to MB/s (#3568) * update write threshold * Correcting Mbps to MB/s * revert threshold changes --- perfmetrics/scripts/micro_benchmarks/helper.py | 10 +++++----- .../scripts/micro_benchmarks/read_single_thread.py | 2 +- .../scripts/micro_benchmarks/write_single_thread.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/perfmetrics/scripts/micro_benchmarks/helper.py b/perfmetrics/scripts/micro_benchmarks/helper.py index 2158d08569..7b582bee72 100644 --- a/perfmetrics/scripts/micro_benchmarks/helper.py +++ b/perfmetrics/scripts/micro_benchmarks/helper.py @@ -134,7 +134,7 @@ def get_last_n_days_bandwidth_entries( days: int = 3 ) -> list[float]: """ - Fetches bandwidth measurements (in Mbps) for a given workload type + Fetches bandwidth measurements (in MB/s) for a given workload type from the last 'n' days of records in the specified BigQuery table. Args: @@ -144,7 +144,7 @@ def get_last_n_days_bandwidth_entries( days (int): Number of past days to look back from the current time. Returns: - list[float]: A list of bandwidth values (in Mbps). Returns an empty list if no data is found or an error occurs. + list[float]: A list of bandwidth values (in MB/s). Returns an empty list if no data is found or an error occurs. """ full_table_name = f"`{table_ref.project}.{table_ref.dataset_id}.{table_ref.table_id}`" time_ago = datetime.now() - timedelta(days=days) @@ -177,7 +177,7 @@ def check_and_alert_bandwidth(bandwidth_threshold_mbps: float, workload_type: st the defined threshold, the function prints a warning and exits with status code 1. Args: - bandwidth_threshold_mbps (float): Minimum acceptable bandwidth (in Mbps). + bandwidth_threshold_mbps (float): Minimum acceptable bandwidth (in MB/s). workload_type (str): The type of workload being evaluated (e.g., "read" or "write"). """ client = bigquery.Client(project=PROJECT_ID) @@ -191,8 +191,8 @@ def check_and_alert_bandwidth(bandwidth_threshold_mbps: float, workload_type: st if last_three_days_bandwidths: avg_past_bandwidth = sum(last_three_days_bandwidths) / len(last_three_days_bandwidths) print(f"Workload Type : {workload_type}") - print(f"3-Day Average : {avg_past_bandwidth:.2f} Mbps") - print(f"Configured Threshold: {bandwidth_threshold_mbps:.2f} Mbps") + print(f"3-Day Average : {avg_past_bandwidth:.2f} MB/s") + print(f"Configured Threshold: {bandwidth_threshold_mbps:.2f} MB/s") if avg_past_bandwidth < bandwidth_threshold_mbps: print("FAILURE: 3-day average bandwidth is below the threshold.") diff --git a/perfmetrics/scripts/micro_benchmarks/read_single_thread.py b/perfmetrics/scripts/micro_benchmarks/read_single_thread.py index cdb2246771..d12c8f1855 100644 --- a/perfmetrics/scripts/micro_benchmarks/read_single_thread.py +++ b/perfmetrics/scripts/micro_benchmarks/read_single_thread.py @@ -147,7 +147,7 @@ def main(): ) # TODO: Remove this once alerts are configured. - # 160 Mbps is the minimum threshold based on the 3-runs average bandwidth + # 160 MB/s is the minimum threshold based on the 3-runs average bandwidth helper.check_and_alert_bandwidth(160, workflow_type) if __name__ == "__main__": diff --git a/perfmetrics/scripts/micro_benchmarks/write_single_thread.py b/perfmetrics/scripts/micro_benchmarks/write_single_thread.py index 100aafc146..c67b7c9b0a 100644 --- a/perfmetrics/scripts/micro_benchmarks/write_single_thread.py +++ b/perfmetrics/scripts/micro_benchmarks/write_single_thread.py @@ -133,7 +133,7 @@ def main(): ) # TODO: Remove this once alerts are configured. - # 80 Mbps is the minimum threshold based on the 3-runs average bandwidth + # 80 MB/s is the minimum threshold based on the 3-runs average bandwidth helper.check_and_alert_bandwidth(80, workflow_type) if __name__ == "__main__": From c6c50f8aabbbcd2a2d518092d16d96cfc8a5ee60 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Fri, 25 Jul 2025 10:56:39 +0530 Subject: [PATCH 0601/1298] test(rapid appends): appends with concurrent file handles must be real time (#3545) * test for concurrent file handle * triggering CI --- .../rapid_appends/appends_test.go | 33 +++++++++++++++++++ .../rapid_appends/setup_test.go | 2 ++ 2 files changed, 35 insertions(+) diff --git a/tools/integration_tests/rapid_appends/appends_test.go b/tools/integration_tests/rapid_appends/appends_test.go index 01e3a3e42f..edc8403c09 100644 --- a/tools/integration_tests/rapid_appends/appends_test.go +++ b/tools/integration_tests/rapid_appends/appends_test.go @@ -230,6 +230,39 @@ func (t *RapidAppendsSuite) TestAppendsToFinalizedObjectNotVisibleUntilClose() { assert.Equal(t.T(), expectedContent, string(contentAfterClose)) } +func (t *RapidAppendsSuite) TestAppendsVisibleInRealTimeWithConcurrentRPlusHandle() { + primaryPath := path.Join(primaryMntTestDirPath, t.fileName) + // Open first handle in append mode. + appendFileHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + defer appendFileHandle.Close() + + // Open second handle in "r+" mode. + readHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_RDWR|syscall.O_DIRECT) + defer readHandle.Close() + + // Write initial content using append handle to trigger BW workflow. + n, err := appendFileHandle.Write([]byte(initialContent)) + require.NoError(t.T(), err) + require.NotZero(t.T(), n) + + // Append additional content using "r+" handle. + data := setup.GenerateRandomString(contentSizeForBW * blockSize) + appendOffset := int64(unfinalizedObjectSize + len(initialContent)) + _, err = readHandle.WriteAt([]byte(data), appendOffset) + require.NoError(t.T(), err) + + // Read from back-door to validate visibility on GCS. + // The first 1MiB block is guaranteed to be flushed due to implicit behavior. + // That block includes both the initial content (written via "a" file handle ) + // and some part of data written by the "r+" file handle. + dataInBlockOffset := blockSize - len(initialContent) + expectedContent := t.fileContent + initialContent + data[0:dataInBlockOffset] + contentRead, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + // Assert only on the data which is guranteed to have been uploaded to GCS. + assert.Equal(t.T(), expectedContent, string(contentRead[0:len(expectedContent)])) +} + //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/rapid_appends/setup_test.go b/tools/integration_tests/rapid_appends/setup_test.go index 683338df5a..dfc203ee7b 100644 --- a/tools/integration_tests/rapid_appends/setup_test.go +++ b/tools/integration_tests/rapid_appends/setup_test.go @@ -24,6 +24,7 @@ import ( "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) @@ -35,6 +36,7 @@ const ( // Minimum content size to write in order to trigger block upload while writing ; calculated as (2*blocksize+1) mb. // Block size for buffered writes is set to 1MiB. contentSizeForBW = 3 + blockSize = operations.OneMiB ) var ( From 326b6e38ab545e002c81acd39c31f57ed781c69a Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Fri, 25 Jul 2025 10:57:27 +0530 Subject: [PATCH 0602/1298] style: removing unused code - part2 (#3579) * removing unused code * removing unused code * fixing the test --- internal/fs/handle/dir_handle_test.go | 36 ++++++++++--------- internal/fs/handle/file_test.go | 23 ++++++------ internal/fs/inode/dir_test.go | 33 ++++++++--------- .../fs/inode/file_streaming_writes_test.go | 16 ++++----- internal/fs/stale_file_handle_common_test.go | 6 ++-- ...ile_handle_streaming_writes_common_test.go | 2 +- ...handle_streaming_writes_local_file_test.go | 2 +- ...andle_streaming_writes_synced_file_test.go | 4 +-- .../fs/stale_file_handle_synced_file_test.go | 6 ++-- internal/gcsx/file_cache_reader_test.go | 36 +++++++++---------- internal/gcsx/inactive_timeout_reader_test.go | 28 +++++++-------- internal/storage/caching/integration_test.go | 2 +- .../cache_file_for_exclude_regex_test.go | 4 +-- .../cache_file_for_range_read_false_test.go | 8 ++--- .../cache_file_for_range_read_true_test.go | 4 +-- .../read_cache/disabled_cache_ttl_test.go | 4 +-- .../read_cache/helpers_test.go | 12 +++---- .../read_cache/job_chunk_test.go | 28 +++++++-------- .../read_cache/local_modification_test.go | 2 +- .../read_cache/range_read_test.go | 6 ++-- .../read_cache/read_only_test.go | 22 ++++++------ .../read_cache/remount_test.go | 8 ++--- .../read_cache/setup_test.go | 2 +- .../read_cache/small_cache_ttl_test.go | 6 ++-- tools/package_gcsfuse/build.go | 3 +- tools/package_gcsfuse/main.go | 2 +- tools/package_gcsfuse/package.go | 6 ++-- 27 files changed, 156 insertions(+), 155 deletions(-) diff --git a/internal/fs/handle/dir_handle_test.go b/internal/fs/handle/dir_handle_test.go index 68b7dc09bc..0c4a3463a2 100644 --- a/internal/fs/handle/dir_handle_test.go +++ b/internal/fs/handle/dir_handle_test.go @@ -33,6 +33,8 @@ import ( "github.com/jacobsa/timeutil" ) +const testDirentName = "sameName" + func TestDirHandle(t *testing.T) { RunTests(t) } //////////////////////////////////////////////////////////////////////// @@ -95,7 +97,7 @@ func (t *DirHandleTest) validateEntry(entry fuseutil.Dirent, name string, filety AssertEq(filetype, entry.Type) } -func (t *DirHandleTest) createTestDirentPlus(name string, dtype fuseutil.DirentType, childInodeID fuseops.InodeID, size uint64) fuseutil.DirentPlus { +func (t *DirHandleTest) createTestDirentPlus(dtype fuseutil.DirentType, childInodeID fuseops.InodeID, size uint64) fuseutil.DirentPlus { attrs := fuseops.InodeAttributes{ Size: size, Mode: 0777, @@ -109,7 +111,7 @@ func (t *DirHandleTest) createTestDirentPlus(name string, dtype fuseutil.DirentT return fuseutil.DirentPlus{ Dirent: fuseutil.Dirent{ - Name: name, + Name: testDirentName, Type: dtype, }, Entry: fuseops.ChildInodeEntry{ @@ -125,12 +127,12 @@ func (t *DirHandleTest) validateEntryPlus(entry fuseutil.DirentPlus, expectedNam AssertEq(expectedChildInodeID, entry.Entry.Child) } -func (t *DirHandleTest) validateCore(core *inode.Core, expectedName string, expectedType metadata.Type, expectedMinObjectName string) { +func (t *DirHandleTest) validateFileType(core *inode.Core, expectedName string, expectedMinObjectName string) { AssertNe(nil, core) AssertNe(nil, core.MinObject) AssertEq(expectedName, path.Base(core.FullName.LocalName())) AssertEq(expectedMinObjectName, core.MinObject.Name) - AssertEq(expectedType, core.Type()) + AssertEq(metadata.RegularFileType, core.Type()) } //////////////////////////////////////////////////////////////////////// @@ -318,10 +320,10 @@ func (t *DirHandleTest) ReadAllEntryCoresReturnsAllEntryCores() { AssertEq(2, len(cores)) entry1, ok := cores[inode.NewFileName(t.dh.in.Name(), "gcsObject1")] AssertTrue(ok, "Core for gcsObject1 not found") - t.validateCore(entry1, "gcsObject1", metadata.RegularFileType, "testDir/gcsObject1") + t.validateFileType(entry1, "gcsObject1", "testDir/gcsObject1") entry2, ok := cores[inode.NewFileName(t.dh.in.Name(), "gcsObject2")] AssertTrue(ok, "Core for gcsObject2 not found") - t.validateCore(entry2, "gcsObject2", metadata.RegularFileType, "testDir/gcsObject2") + t.validateFileType(entry2, "gcsObject2", "testDir/gcsObject2") } func (t *DirHandleTest) FetchEntryCoresFetchesCores() { @@ -338,7 +340,7 @@ func (t *DirHandleTest) FetchEntryCoresFetchesCores() { AssertEq(1, len(cores)) entry, ok := cores[inode.NewFileName(t.dh.in.Name(), "testFile")] AssertTrue(ok, "Core for gcsFile1 not found") - t.validateCore(entry, "testFile", metadata.RegularFileType, "testDir/testFile") + t.validateFileType(entry, "testFile", "testDir/testFile") } func (t *DirHandleTest) FetchEntryCoresNonZeroOffsetNoFetchIfCacheValid() { @@ -369,7 +371,7 @@ func (t *DirHandleTest) FetchEntryCoresNonZeroOffsetFetchesIfCacheInvalid() { AssertEq(1, len(cores)) entry, ok := cores[inode.NewFileName(t.dh.in.Name(), "fetchThis")] AssertTrue(ok, "Core for fetchThis not found") - t.validateCore(entry, "fetchThis", metadata.RegularFileType, "testDir/fetchThis") + t.validateFileType(entry, "fetchThis", "testDir/fetchThis") } func (t *DirHandleTest) ReadDirPlusResponseForNoFile() { @@ -391,32 +393,32 @@ func (t *DirHandleTest) ReadDirPlusSameNameLocalAndGCSFile() { op := &fuseops.ReadDirPlusOp{ ReadDirOp: fuseops.ReadDirOp{Dst: make([]byte, 1024)}, } - gcsFile := t.createTestDirentPlus("sameName", fuseutil.DT_File, 1001, 10) - localFile := t.createTestDirentPlus("sameName", fuseutil.DT_File, 1002, 0) + gcsFile := t.createTestDirentPlus(fuseutil.DT_File, 1001, 10) + localFile := t.createTestDirentPlus(fuseutil.DT_File, 1002, 0) gcsEntriesPlus := []fuseutil.DirentPlus{gcsFile} - localFileEntriesPlus := map[string]fuseutil.DirentPlus{"sameName": localFile} + localFileEntriesPlus := map[string]fuseutil.DirentPlus{testDirentName: localFile} err := t.dh.ReadDirPlus(op, gcsEntriesPlus, localFileEntriesPlus) AssertEq(nil, err) AssertEq(1, len(t.dh.entriesPlus)) - t.validateEntryPlus(t.dh.entriesPlus[0], "sameName", fuseutil.DT_File, 1001) + t.validateEntryPlus(t.dh.entriesPlus[0], testDirentName, fuseutil.DT_File, 1001) } func (t *DirHandleTest) ReadDirPlusSameNameLocalFileAndGCSDirectory() { op := &fuseops.ReadDirPlusOp{ ReadDirOp: fuseops.ReadDirOp{Dst: make([]byte, 1024)}, } - gcsDir := t.createTestDirentPlus("sameName", fuseutil.DT_Directory, 1001, 0) + gcsDir := t.createTestDirentPlus(fuseutil.DT_Directory, 1001, 0) gcsEntriesPlus := []fuseutil.DirentPlus{gcsDir} - localFile := t.createTestDirentPlus("sameName", fuseutil.DT_File, 2001, 20) - localFileEntriesPlus := map[string]fuseutil.DirentPlus{"sameName": localFile} + localFile := t.createTestDirentPlus(fuseutil.DT_File, 2001, 20) + localFileEntriesPlus := map[string]fuseutil.DirentPlus{testDirentName: localFile} err := t.dh.ReadDirPlus(op, gcsEntriesPlus, localFileEntriesPlus) AssertEq(nil, err) AssertEq(2, len(t.dh.entriesPlus)) - t.validateEntryPlus(t.dh.entriesPlus[0], "sameName", fuseutil.DT_Directory, 1001) - t.validateEntryPlus(t.dh.entriesPlus[1], "sameName"+inode.ConflictingFileNameSuffix, fuseutil.DT_File, 2001) + t.validateEntryPlus(t.dh.entriesPlus[0], testDirentName, fuseutil.DT_Directory, 1001) + t.validateEntryPlus(t.dh.entriesPlus[1], testDirentName+inode.ConflictingFileNameSuffix, fuseutil.DT_File, 2001) AssertEq(t.dh.entriesPlus[1].Dirent.Offset, t.dh.entriesPlus[0].Dirent.Offset+1) } diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index e08e7dc5b0..9e388e9de7 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -39,6 +39,8 @@ import ( "golang.org/x/sync/semaphore" ) +const testDirName = "parentRoot" + //////////////////////////////////////////////////////////////////////// // Boilerplate //////////////////////////////////////////////////////////////////////// @@ -71,11 +73,10 @@ func (t *fileTest) TearDownTest() { // which will be used for testing methods defined on the fileHandle. func createDirInode( bucket *gcsx.SyncerBucket, - clock *timeutil.SimulatedClock, - dirName string) inode.DirInode { + clock *timeutil.SimulatedClock) inode.DirInode { return inode.NewDirInode( 1, - inode.NewDirName(inode.NewRootName(""), dirName), + inode.NewDirName(inode.NewRootName(""), testDirName), fuseops.InodeAttributes{ Uid: 0, Gid: 0, @@ -141,7 +142,7 @@ func createFileInode( //////////////////////////////////////////////////////////////////////// func (t *fileTest) TestFileHandleWrite() { - parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + parent := createDirInode(&t.bucket, &t.clock) config := &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: false}} in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj", nil, false) fh := NewFileHandle(in, nil, false, nil, util.Write, &cfg.Config{}) @@ -165,7 +166,7 @@ func (t *fileTest) TestFileHandleWrite() { // Test_Read_Success validates successful read behavior using the random reader. func (t *fileTest) Test_Read_Success() { expectedData := []byte("hello from reader") - parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + parent := createDirInode(&t.bucket, &t.clock) in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, "test_obj_reader", expectedData, false) fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) buf := make([]byte, len(expectedData)) @@ -181,7 +182,7 @@ func (t *fileTest) Test_Read_Success() { // Test_ReadWithReadManager_Success validates successful read behavior using the readManager. func (t *fileTest) Test_ReadWithReadManager_Success() { expectedData := []byte("hello from readManager") - parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + parent := createDirInode(&t.bucket, &t.clock) in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, "test_obj_readManager", expectedData, false) fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) buf := make([]byte, len(expectedData)) @@ -213,7 +214,7 @@ func (t *fileTest) Test_ReadWithReadManager_ErrorScenarios() { for _, tc := range testCases { t.Run(tc.name, func() { t.SetupTest() - parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + parent := createDirInode(&t.bucket, &t.clock) testInode := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, []byte("data"), false) fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) fh.inode.Lock() @@ -251,7 +252,7 @@ func (t *fileTest) Test_Read_ErrorScenarios() { for _, tc := range testCases { t.Run(tc.name, func() { t.SetupTest() - parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + parent := createDirInode(&t.bucket, &t.clock) testInode := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, []byte("data"), false) fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) fh.inode.Lock() @@ -276,7 +277,7 @@ func (t *fileTest) Test_ReadWithReadManager_FallbackToInode() { dst := make([]byte, 100) objectData := []byte("fallback data") object := gcs.MinObject{Name: "test_obj", Generation: 0} - parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + parent := createDirInode(&t.bucket, &t.clock) in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, objectData, true) fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) fh.inode.Lock() @@ -298,7 +299,7 @@ func (t *fileTest) Test_Read_FallbackToInode() { dst := make([]byte, 100) objectData := []byte("fallback data") object := gcs.MinObject{Name: "test_obj", Generation: 0} - parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + parent := createDirInode(&t.bucket, &t.clock) in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, objectData, true) fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) fh.inode.Lock() @@ -333,7 +334,7 @@ func (t *fileTest) TestOpenMode() { }, } for _, tc := range testCases { - parent := createDirInode(&t.bucket, &t.clock, "parentRoot") + parent := createDirInode(&t.bucket, &t.clock) config := &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: false}} in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj", nil, false) fh := NewFileHandle(in, nil, false, nil, tc.openMode, &cfg.Config{}) diff --git a/internal/fs/inode/dir_test.go b/internal/fs/inode/dir_test.go index bc84dc234e..bb44793371 100644 --- a/internal/fs/inode/dir_test.go +++ b/internal/fs/inode/dir_test.go @@ -52,6 +52,7 @@ const dirInodeID = 17 const dirInodeName = "foo/bar/" const dirMode os.FileMode = 0712 | os.ModeDir const typeCacheTTL = time.Second +const testSymlinkTarget = "blah" type DirTest struct { ctx context.Context @@ -77,7 +78,7 @@ func (t *DirTest) SetUp(ti *TestInfo) { ".gcsfuse_tmp/", bucket) // Create the inode. No implicit dirs by default. - t.resetInode(false, false, true) + t.resetInode(false, false) } func (t *DirTest) TearDown() { @@ -97,8 +98,8 @@ func (p DirentSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] } // NOTE: A limitation in the fake bucket's API prevents the direct creation of managed folders. // This poses a challenge for writing unit tests for includeFoldersAsPrefixes. -func (t *DirTest) resetInode(implicitDirs, enableNonexistentTypeCache, enableManagedFoldersListing bool) { - t.resetInodeWithTypeCacheConfigs(implicitDirs, enableNonexistentTypeCache, enableManagedFoldersListing, 4, typeCacheTTL) +func (t *DirTest) resetInode(implicitDirs, enableNonexistentTypeCache bool) { + t.resetInodeWithTypeCacheConfigs(implicitDirs, enableNonexistentTypeCache, true, 4, typeCacheTTL) } func (t *DirTest) resetInodeWithTypeCacheConfigs(implicitDirs, enableNonexistentTypeCache, enableManagedFoldersListing bool, typeCacheMaxSizeMB int64, typeCacheTTL time.Duration) { @@ -199,8 +200,8 @@ func (t *DirTest) readAllEntryCores() (cores map[Name]*Core, err error) { } func (t *DirTest) setSymlinkTarget( - objName string, - target string) (err error) { + objName string) (err error) { + target := testSymlinkTarget _, err = t.bucket.UpdateObject( t.ctx, &gcs.UpdateObjectRequest{ @@ -390,7 +391,7 @@ func (t *DirTest) LookUpChild_ImplicitDirOnly_Enabled() { var err error // Enable implicit dirs. - t.resetInode(true, false, true) + t.resetInode(true, false) // Create an object that implicitly defines the directory. otherObjName := path.Join(objName, "asdf") @@ -464,7 +465,7 @@ func (t *DirTest) LookUpChild_SymlinkAndDir() { linkObj, err := storageutil.CreateObject(t.ctx, t.bucket, linkObjName, []byte("taco")) AssertEq(nil, err) - err = t.setSymlinkTarget(linkObjName, "blah") + err = t.setSymlinkTarget(linkObjName) AssertEq(nil, err) dirObj, err := storageutil.CreateObject(t.ctx, t.bucket, dirObjName, []byte("")) @@ -554,7 +555,7 @@ func (t *DirTest) LookUpChild_FileAndDirAndImplicitDir_Enabled() { var err error // Enable implicit dirs. - t.resetInode(true, false, true) + t.resetInode(true, false) // Create backing objects. fileObj, err := storageutil.CreateObject(t.ctx, t.bucket, fileObjName, []byte("taco")) @@ -642,7 +643,7 @@ func (t *DirTest) LookUpChild_TypeCaching() { func (t *DirTest) LookUpChild_NonExistentTypeCache_ImplicitDirsDisabled() { // Enable enableNonexistentTypeCache for type cache - t.resetInode(false, true, true) + t.resetInode(false, true) const name = "qux" objName := path.Join(dirInodeName, name) + "/" @@ -682,7 +683,7 @@ func (t *DirTest) LookUpChild_NonExistentTypeCache_ImplicitDirsDisabled() { func (t *DirTest) LookUpChild_NonExistentTypeCache_ImplicitDirsEnabled() { // Enable implicitDirs and enableNonexistentTypeCache for type cache - t.resetInode(true, true, true) + t.resetInode(true, true) const name = "qux" objName := path.Join(dirInodeName, name) + "/" @@ -854,7 +855,7 @@ func (t *DirTest) ReadEntries_NonEmpty_ImplicitDirsDisabled() { AssertEq(nil, err) // Set up the symlink target. - err = t.setSymlinkTarget(dirInodeName+"symlink", "blah") + err = t.setSymlinkTarget(dirInodeName + "symlink") AssertEq(nil, err) // Nil prevDirListingTimeStamp @@ -897,7 +898,7 @@ func (t *DirTest) ReadEntries_NonEmpty_ImplicitDirsEnabled() { var entry fuseutil.Dirent // Enable implicit dirs. - t.resetInode(true, false, true) + t.resetInode(true, false) // Set up contents. objs := []string{ @@ -913,7 +914,7 @@ func (t *DirTest) ReadEntries_NonEmpty_ImplicitDirsEnabled() { AssertEq(nil, err) // Set up the symlink target. - err = t.setSymlinkTarget(dirInodeName+"symlink", "blah") + err = t.setSymlinkTarget(dirInodeName + "symlink") AssertEq(nil, err) // Nil prevDirListingTimeStamp @@ -1044,7 +1045,7 @@ func (t *DirTest) ReadEntryCores_NonEmpty_ImplicitDirsDisabled() { AssertEq(nil, err) // Set up the symlink target. - err = t.setSymlinkTarget(dirInodeName+"symlink", "blah") + err = t.setSymlinkTarget(dirInodeName + "symlink") AssertEq(nil, err) // Nil prevDirListingTimeStamp @@ -1070,7 +1071,7 @@ func (t *DirTest) ReadEntryCores_NonEmpty_ImplicitDirsEnabled() { var cores map[Name]*Core // Enable implicit dirs. - t.resetInode(true, false, true) + t.resetInode(true, false) // Set up contents. backedDirEmptyName := path.Join(dirInodeName, "backed_dir_empty") + "/" @@ -1093,7 +1094,7 @@ func (t *DirTest) ReadEntryCores_NonEmpty_ImplicitDirsEnabled() { AssertEq(nil, err) // Set up the symlink target. - err = t.setSymlinkTarget(dirInodeName+"symlink", "blah") + err = t.setSymlinkTarget(dirInodeName + "symlink") AssertEq(nil, err) // Nil prevDirListingTimeStamp diff --git a/internal/fs/inode/file_streaming_writes_test.go b/internal/fs/inode/file_streaming_writes_test.go index 734e9a8b8f..dbef59cf50 100644 --- a/internal/fs/inode/file_streaming_writes_test.go +++ b/internal/fs/inode/file_streaming_writes_test.go @@ -81,7 +81,7 @@ func (t *FileStreamingWritesCommon) setupTest() { syncutil.EnableInvariantChecking() t.ctx = context.Background() // Create the inode. - t.createInode(fileName, localFile) + t.createInode(localFile) } func (t *FileStreamingWritesZonalBucketTest) SetupTest() { @@ -104,7 +104,7 @@ func (t *FileStreamingWritesTest) SetupSubTest() { t.SetupTest() } -func (t *FileStreamingWritesCommon) createInode(fileName string, fileType string) { +func (t *FileStreamingWritesCommon) createInode(fileType string) { if fileType != emptyGCSFile && fileType != localFile { t.T().Errorf("fileType should be either local or empty") } @@ -469,7 +469,7 @@ func (t *FileStreamingWritesTest) TestUnlinkLocalFileAfterWrite() { } func (t *FileStreamingWritesTest) TestUnlinkEmptySyncedFile() { - t.createInode(fileName, emptyGCSFile) + t.createInode(emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) t.createBufferedWriteHandler() // Write some content to temp file. @@ -504,7 +504,7 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndFlush() { if tc.isLocal { assert.True(t.T(), t.in.IsLocal()) } else { - t.createInode(fileName, emptyGCSFile) + t.createInode(emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) } t.createBufferedWriteHandler() @@ -564,7 +564,7 @@ func (t *FileStreamingWritesTest) TestFlushEmptyFile() { if tc.isLocal { assert.True(t.T(), t.in.IsLocal()) } else { - t.createInode(fileName, emptyGCSFile) + t.createInode(emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) } t.clock.AdvanceTime(10 * time.Second) @@ -622,7 +622,7 @@ func (t *FileStreamingWritesTest) TestFlushClobberedFile() { if tc.isLocal { assert.True(t.T(), t.in.IsLocal()) } else { - t.createInode(fileName, emptyGCSFile) + t.createInode(emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) } t.createBufferedWriteHandler() @@ -665,7 +665,7 @@ func (t *FileStreamingWritesTest) TestWriteToFileAndSync() { if tc.isLocal { assert.True(t.T(), t.in.IsLocal()) } else { - t.createInode(fileName, emptyGCSFile) + t.createInode(emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) } t.createBufferedWriteHandler() @@ -713,7 +713,7 @@ func (t *FileStreamingWritesTest) TestSourceGenerationSizeForLocalFileIsReflecte } func (t *FileStreamingWritesTest) TestSourceGenerationSizeForSyncedFileIsReflected() { - t.createInode(fileName, emptyGCSFile) + t.createInode(emptyGCSFile) assert.False(t.T(), t.in.IsLocal()) t.createBufferedWriteHandler() gcsSynced, err := t.in.Write(context.Background(), []byte(setup.GenerateRandomString(5)), 0, util.Write) diff --git a/internal/fs/stale_file_handle_common_test.go b/internal/fs/stale_file_handle_common_test.go index 24b47368ee..93952ccaaa 100644 --- a/internal/fs/stale_file_handle_common_test.go +++ b/internal/fs/stale_file_handle_common_test.go @@ -53,7 +53,7 @@ func commonServerConfig() *cfg.Config { } -func clobberFile(t *testing.T, fileName, content string) { +func clobberFile(t *testing.T, content string) { t.Helper() _, err := storageutil.CreateObject( ctx, @@ -63,7 +63,7 @@ func clobberFile(t *testing.T, fileName, content string) { assert.NoError(t, err) } -func createGCSObject(t *testing.T, fileName, content string) *os.File { +func createGCSObject(t *testing.T, content string) *os.File { t.Helper() _, err := storageutil.CreateObject( ctx, @@ -101,7 +101,7 @@ func (t *staleFileHandleCommon) TestClobberedFileSyncAndCloseThrowsStaleFileHand assert.NoError(t.T(), err) assert.Equal(t.T(), 4, n) // Replace the underlying object with a new generation. - clobberFile(t.T(), "foo", "foobar") + clobberFile(t.T(), "foobar") err = t.f1.Sync() diff --git a/internal/fs/stale_file_handle_streaming_writes_common_test.go b/internal/fs/stale_file_handle_streaming_writes_common_test.go index 6cb0b70bb7..fda7adbba3 100644 --- a/internal/fs/stale_file_handle_streaming_writes_common_test.go +++ b/internal/fs/stale_file_handle_streaming_writes_common_test.go @@ -72,7 +72,7 @@ func (t *staleFileHandleStreamingWritesCommon) TestWriteFileSyncFileClobberedFlu err = t.f1.Sync() assert.NoError(t.T(), err) // Replace the underlying object with a new generation. - clobberFile(t.T(), "foo", "foobar") + clobberFile(t.T(), "foobar") err = t.f1.Close() diff --git a/internal/fs/stale_file_handle_streaming_writes_local_file_test.go b/internal/fs/stale_file_handle_streaming_writes_local_file_test.go index c3b82d3104..2e4c578ad2 100644 --- a/internal/fs/stale_file_handle_streaming_writes_local_file_test.go +++ b/internal/fs/stale_file_handle_streaming_writes_local_file_test.go @@ -46,7 +46,7 @@ func (t *staleFileHandleStreamingWritesLocalFile) SetupTest() { func (t *staleFileHandleStreamingWritesLocalFile) TestClobberedWriteFileSyncAndCloseThrowsStaleFileHandleError() { // Replace the underlying object with a new generation. - clobberFile(t.T(), "foo", "foobar") + clobberFile(t.T(), "foobar") // Writing to file will return Stale File Handle Error. data, err := operations.GenerateRandomData(operations.MiB * 4) assert.NoError(t.T(), err) diff --git a/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go b/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go index c8dc9bb662..7e6371c073 100644 --- a/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go +++ b/internal/fs/stale_file_handle_streaming_writes_synced_file_test.go @@ -39,7 +39,7 @@ type staleFileHandleStreamingWritesSyncedFile struct { func (t *staleFileHandleStreamingWritesSyncedFile) SetupTest() { // Create an empty object on bucket. - t.f1 = createGCSObject(t.T(), "foo", "") + t.f1 = createGCSObject(t.T(), "") } // ////////////////////////////////////////////////////////////////////// @@ -48,7 +48,7 @@ func (t *staleFileHandleStreamingWritesSyncedFile) SetupTest() { func (t *staleFileHandleStreamingWritesSyncedFile) TestWriteToClobberedFileThrowsStaleFileHandleError() { // Replace the underlying object with a new generation. - clobberFile(t.T(), "foo", "foobar") + clobberFile(t.T(), "foobar") // Writing to file will return Stale File Handle Error. data, err := operations.GenerateRandomData(operations.MiB * 4) assert.NoError(t.T(), err) diff --git a/internal/fs/stale_file_handle_synced_file_test.go b/internal/fs/stale_file_handle_synced_file_test.go index b37f9cb555..b96f6c7551 100644 --- a/internal/fs/stale_file_handle_synced_file_test.go +++ b/internal/fs/stale_file_handle_synced_file_test.go @@ -39,7 +39,7 @@ type staleFileHandleSyncedFile struct { func (t *staleFileHandleSyncedFile) SetupTest() { // Create an object on bucket. - t.f1 = createGCSObject(t.T(), "foo", "bar") + t.f1 = createGCSObject(t.T(), "bar") } // ////////////////////////////////////////////////////////////////////// @@ -48,7 +48,7 @@ func (t *staleFileHandleSyncedFile) SetupTest() { func (t *staleFileHandleSyncedFile) TestClobberedFileReadThrowsStaleFileHandleError() { // Replace the underlying object with a new generation. - clobberFile(t.T(), "foo", "foobar") + clobberFile(t.T(), "foobar") buffer := make([]byte, 6) _, err := t.f1.Read(buffer) @@ -62,7 +62,7 @@ func (t *staleFileHandleSyncedFile) TestClobberedFileReadThrowsStaleFileHandleEr func (t *staleFileHandleSyncedFile) TestClobberedFileFirstWriteThrowsStaleFileHandleError() { // Replace the underlying object with a new generation. - clobberFile(t.T(), "foo", "foobar") + clobberFile(t.T(), "foobar") _, err := t.f1.Write([]byte("taco")) diff --git a/internal/gcsx/file_cache_reader_test.go b/internal/gcsx/file_cache_reader_test.go index dfa468adc1..ced6fb5f52 100644 --- a/internal/gcsx/file_cache_reader_test.go +++ b/internal/gcsx/file_cache_reader_test.go @@ -105,9 +105,9 @@ func (t *fileCacheReaderTest) TearDownTest() { t.reader.Destroy() } -func (t *fileCacheReaderTest) mockNewReaderWithHandleCallForTestBucket(start uint64, limit uint64, rd gcs.StorageReader) { +func (t *fileCacheReaderTest) mockNewReaderWithHandleCallForTestBucket(limit uint64, rd gcs.StorageReader) { t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(rg *gcs.ReadObjectRequest) bool { - return rg != nil && (*rg.Range).Start == start && (*rg.Range).Limit == limit + return rg != nil && (*rg.Range).Start == 0 && (*rg.Range).Limit == limit })).Return(rd, nil).Once() } @@ -160,7 +160,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_OffsetGreaterThanFileSizeWillReturnEOF func (t *fileCacheReaderTest) Test_tryReadingFromFileCache_CacheHit() { testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) buf := make([]byte, t.object.Size) @@ -183,7 +183,7 @@ func (t *fileCacheReaderTest) Test_tryReadingFromFileCache_SequentialSubsequentR t.object.Size = 20 * util.MiB testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) start1 := 0 @@ -211,7 +211,7 @@ func (t *fileCacheReaderTest) Test_tryReadingFromFileCache_SequentialSubsequentR func (t *fileCacheReaderTest) Test_ReadAt_SequentialRangeRead() { testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) start := 0 @@ -253,7 +253,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_RandomReadNotStartWithZeroOffsetWhenCa end := 10 rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} // Mock for download job's NewReader call - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) buf := make([]byte, end-start) @@ -272,7 +272,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffset testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} // Mock for download job's NewReader call - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) start1 := 0 @@ -303,7 +303,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffset t.object.Size = 20 * util.MiB testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) start1 := 0 @@ -337,7 +337,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_SequentialToRandomSubsequentReadOffset func (t *fileCacheReaderTest) Test_ReadAt_CacheMissDueToInvalidJob() { testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rc1 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rc1) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rc1) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) buf := make([]byte, t.object.Size) @@ -364,7 +364,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInv testContent := testutil.GenerateRandomBytes(int(t.object.Size)) // First successful read with cache rd1 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd1) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd1) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) @@ -384,7 +384,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInv assert.Zero(t.T(), readerResponse.Size) assert.Nil(t.T(), t.reader.fileCacheHandle) rd2 := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd2) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd2) readerResponse, err = t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) @@ -397,7 +397,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInv func (t *fileCacheReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInvalidFileHandleAfterThenCacheHitWithNewFileCacheHandle() { testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) @@ -423,7 +423,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_CachePopulatedAndThenCacheMissDueToInv func (t *fileCacheReaderTest) Test_ReadAt_IfCacheFileGetsDeleted() { testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) @@ -447,7 +447,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_IfCacheFileGetsDeleted() { func (t *fileCacheReaderTest) Test_ReadAt_IfCacheFileGetsDeletedWithCacheHandleOpen() { testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) @@ -485,7 +485,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_FailedJobNextReadCreatesNewJobAndCache job := t.jobManager.GetJob(t.object.Name, t.mockBucket.Name()) assert.True(t.T(), job == nil || job.GetStatus().Name == downloader.Failed) rc := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rc) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rc) // Second ReadAt call: The file cache should be populated as a result of this successful read. readerResponse, err = t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) assert.NoError(t.T(), err) @@ -531,7 +531,7 @@ func (t *fileCacheReaderTest) fullyReadOriginalSizeOfUnfinalizedObject(origObjec t.mockBucket.On("Name").Return("test-bucket") testContent := testutil.GenerateRandomBytes(int(t.unfinalized_object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.unfinalized_object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.unfinalized_object.Size, rd) t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader_unfinalized_object.ReadAt(t.ctx, make([]byte, origObjectSize), 0) assert.NoError(t.T(), err) @@ -653,7 +653,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_FinalizedObjectReadFromOffsetBelowCach t.mockBucket.On("Name").Return("test-bucket") testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd) t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) assert.NoError(t.T(), err) @@ -669,7 +669,7 @@ func (t *fileCacheReaderTest) Test_ReadAt_FinalizedObjectReadFromOffsetBelowCach func (t *fileCacheReaderTest) Test_Destroy_NonNilCacheHandle() { testContent := testutil.GenerateRandomBytes(int(t.object.Size)) rd := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} - t.mockNewReaderWithHandleCallForTestBucket(0, t.object.Size, rd) + t.mockNewReaderWithHandleCallForTestBucket(t.object.Size, rd) t.mockBucket.On("Name").Return("test-bucket") t.mockBucket.On("BucketType").Return(t.bucketType) readerResponse, err := t.reader.ReadAt(t.ctx, make([]byte, t.object.Size), 0) diff --git a/internal/gcsx/inactive_timeout_reader_test.go b/internal/gcsx/inactive_timeout_reader_test.go index a028d10fa4..760dbb5661 100644 --- a/internal/gcsx/inactive_timeout_reader_test.go +++ b/internal/gcsx/inactive_timeout_reader_test.go @@ -77,7 +77,7 @@ func (s *InactiveTimeoutReaderTestSuite) TearDownTest() { // setupReader is a helper within the suite to create the reader under test. // Tests should call this after setting specific suite properties like initialData or timeout. -func (s *InactiveTimeoutReaderTestSuite) setupReader(startOffset int64) { +func (s *InactiveTimeoutReaderTestSuite) setupReader() { s.object.Size = uint64(len(s.initialData)) // Ensure object size matches data readCloser := getReadCloser(s.initialData) s.initialFakeReader = &fake.FakeReader{ReadCloser: readCloser, Handle: s.readHandle} @@ -86,7 +86,7 @@ func (s *InactiveTimeoutReaderTestSuite) setupReader(startOffset int64) { Name: s.object.Name, Generation: s.object.Generation, Range: &gcs.ByteRange{ - Start: uint64(startOffset), + Start: uint64(0), Limit: s.object.Size, }, ReadCompressed: s.object.HasContentEncodingGzip(), @@ -96,7 +96,7 @@ func (s *InactiveTimeoutReaderTestSuite) setupReader(startOffset int64) { var err error // Use NewInactiveTimeoutReader directly as NewStorageReaderWithInactiveTimeout is deprecated. - s.reader, err = NewInactiveTimeoutReaderWithClock(s.ctx, s.mockBucket, s.object, s.readHandle, gcs.ByteRange{Start: uint64(startOffset), Limit: s.object.Size}, s.timeout, s.simulatedClock) + s.reader, err = NewInactiveTimeoutReaderWithClock(s.ctx, s.mockBucket, s.object, s.readHandle, gcs.ByteRange{Start: uint64(0), Limit: s.object.Size}, s.timeout, s.simulatedClock) time.Sleep(5 * time.Millisecond) // Allow time to schedule and create a timer. s.Require().Nil(err) s.Require().NotNil(s.reader) @@ -134,7 +134,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_NewInactiveTimeoutReader_ZeroTimeo func (s *InactiveTimeoutReaderTestSuite) Test_Read_InitialReadNoError() { s.initialData = []byte("hello world") s.timeout = 100 * time.Millisecond - s.setupReader(0) + s.setupReader() buf := make([]byte, 5) n, err := s.reader.Read(buf) @@ -147,7 +147,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_InitialReadNoError() { func (s *InactiveTimeoutReaderTestSuite) Test_NoReadCloserWithinTimeout() { s.initialData = []byte("hello world!") s.timeout = 100 * time.Millisecond - s.setupReader(0) + s.setupReader() buf := make([]byte, 6) n1, err1 := s.reader.Read(buf) s.NoError(err1) @@ -169,7 +169,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_ReadFull_Succeeds() { buf := make([]byte, 16) s.initialData = []byte("hello world!") s.timeout = 100 * time.Millisecond - s.setupReader(0) + s.setupReader() n, err := s.reader.Read(buf) @@ -181,7 +181,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_ReconnectFails() { buf := make([]byte, 5) s.initialData = []byte("reconnect failure") s.timeout = 50 * time.Millisecond - s.setupReader(0) + s.setupReader() n, err := s.reader.Read(buf) s.Require().NoError(err) s.Require().Equal(5, n) @@ -221,7 +221,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_ReconnectFails() { func (s *InactiveTimeoutReaderTestSuite) Test_Read_TimeoutAndSuccessfulReconnect() { s.initialData = []byte("abcdefghijklmnopqrstuvwxyz") s.timeout = 50 * time.Second - s.setupReader(0) + s.setupReader() buf := make([]byte, 10) n, err := s.reader.Read(buf) s.Require().NoError(err) @@ -271,7 +271,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_Read_TimeoutAndSuccessfulReconnect func (s *InactiveTimeoutReaderTestSuite) Test_Close_ExplicitClose() { s.initialData = []byte("close me") s.timeout = 100 * time.Millisecond - s.setupReader(0) + s.setupReader() err := s.reader.Close() @@ -284,7 +284,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_handleTimeout_InactiveClose() { s.initialData = []byte("simple close test") s.timeout = 50 * time.Millisecond s.readHandle = []byte("handle-before-close") - s.setupReader(0) // Sets up s.reader and s.initialFakeReader + s.setupReader() // Sets up s.reader and s.initialFakeReader expectedHandleAfterClose := []byte("handle-after-close") s.initialFakeReader.Handle = expectedHandleAfterClose itr := s.reader.(*InactiveTimeoutReader) @@ -301,7 +301,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_handleTimeout_ActiveBecomeInactive s.initialData = []byte("simple close test") s.timeout = 50 * time.Millisecond s.readHandle = []byte("handle-before-close") - s.setupReader(0) // Sets up s.reader and s.initialFakeReader + s.setupReader() // Sets up s.reader and s.initialFakeReader expectedHandleAfterClose := []byte("handle-after-close") s.initialFakeReader.Handle = expectedHandleAfterClose itr := s.reader.(*InactiveTimeoutReader) @@ -317,7 +317,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_closeGCSReader_NilReader() { s.initialData = []byte("simple close test") s.timeout = 50 * time.Millisecond s.readHandle = []byte("handle-before-close") - s.setupReader(0) // Sets up s.reader and s.initialFakeReader + s.setupReader() // Sets up s.reader and s.initialFakeReader itr := s.reader.(*InactiveTimeoutReader) itr.gcsReader = nil @@ -331,7 +331,7 @@ func (s *InactiveTimeoutReaderTestSuite) Test_closeGCSReader_NonNilReader() { s.initialData = []byte("simple close test") s.timeout = 50 * time.Millisecond s.readHandle = []byte("handle-before-close") - s.setupReader(0) // Sets up s.reader and s.initialFakeReader + s.setupReader() // Sets up s.reader and s.initialFakeReader expectedHandleAfterClose := []byte("handle-after-close") s.initialFakeReader.Handle = expectedHandleAfterClose itr := s.reader.(*InactiveTimeoutReader) @@ -348,7 +348,7 @@ func (s *InactiveTimeoutReaderTestSuite) TestRaceCondition() { s.initialData = []byte(strings.Repeat("abc", 1000)) s.timeout = time.Second s.readHandle = []byte("handle-before-close") - s.setupReader(0) // Sets up s.reader and s.initialFakeReader + s.setupReader() // Sets up s.reader and s.initialFakeReader // Read() go func() { diff --git a/internal/storage/caching/integration_test.go b/internal/storage/caching/integration_test.go index 9a464a7179..b6459cc0b0 100644 --- a/internal/storage/caching/integration_test.go +++ b/internal/storage/caching/integration_test.go @@ -103,7 +103,7 @@ func (t *IntegrationTest) CreateInsertsIntoCache() { } func (t *IntegrationTest) StatInsertsIntoCache() { - const name = "taco" + const name = "foo" var err error // Create an object through the back door. diff --git a/tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go b/tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go index 61ed387e2f..f1057279aa 100644 --- a/tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go @@ -43,7 +43,7 @@ func (s *cacheFileForExcludeRegexTest) Setup(t *testing.T) { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) - mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } func (s *cacheFileForExcludeRegexTest) Teardown(t *testing.T) { @@ -56,7 +56,7 @@ func (s *cacheFileForExcludeRegexTest) Teardown(t *testing.T) { //////////////////////////////////////////////////////////////////////// func (s *cacheFileForExcludeRegexTest) TestReadsForExcludedFile(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSizeForRangeRead, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSizeForRangeRead, t) expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, zeroOffset, t) expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset1000, t) diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go index ee664033ea..dde192a1ac 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go @@ -47,7 +47,7 @@ func (s *cacheFileForRangeReadFalseTest) Setup(t *testing.T) { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) - mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } func (s *cacheFileForRangeReadFalseTest) Teardown(t *testing.T) { @@ -80,7 +80,7 @@ func readFileBetweenOffset(t *testing.T, file *os.File, startOffset, endOffSet i //////////////////////////////////////////////////////////////////////// func (s *cacheFileForRangeReadFalseTest) TestRangeReadsWithCacheMiss(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSizeForRangeRead, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSizeForRangeRead, t) // Do a random read on file and validate from gcs. expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset5000, t) @@ -96,8 +96,8 @@ func (s *cacheFileForRangeReadFalseTest) TestRangeReadsWithCacheMiss(t *testing. func (s *cacheFileForRangeReadFalseTest) TestReadIsTreatedNonSequentialAfterFileIsRemovedFromCache(t *testing.T) { var testFileNames [2]string var expectedOutcome [4]*Expected - testFileNames[0] = setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSizeSameAsCacheCapacity, t) - testFileNames[1] = setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSizeSameAsCacheCapacity, t) + testFileNames[0] = setupFileInTestDir(s.ctx, s.storageClient, fileSizeSameAsCacheCapacity, t) + testFileNames[1] = setupFileInTestDir(s.ctx, s.storageClient, fileSizeSameAsCacheCapacity, t) randomReadChunkCount := fileSizeSameAsCacheCapacity / chunkSizeToRead readTillChunk := randomReadChunkCount / 2 fh1 := operations.OpenFile(path.Join(testDirPath, testFileNames[0]), t) diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go index cff9e4dff0..0e7766f7c3 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go @@ -43,7 +43,7 @@ func (s *cacheFileForRangeReadTrueTest) Setup(t *testing.T) { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) - mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } func (s *cacheFileForRangeReadTrueTest) Teardown(t *testing.T) { @@ -56,7 +56,7 @@ func (s *cacheFileForRangeReadTrueTest) Teardown(t *testing.T) { //////////////////////////////////////////////////////////////////////// func (s *cacheFileForRangeReadTrueTest) TestRangeReadsWithCacheHit(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSizeForRangeRead, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSizeForRangeRead, t) // Do a random read on file and validate from gcs. expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset5000, t) diff --git a/tools/integration_tests/read_cache/disabled_cache_ttl_test.go b/tools/integration_tests/read_cache/disabled_cache_ttl_test.go index 26c9c25f31..88006d79db 100644 --- a/tools/integration_tests/read_cache/disabled_cache_ttl_test.go +++ b/tools/integration_tests/read_cache/disabled_cache_ttl_test.go @@ -42,7 +42,7 @@ func (s *disabledCacheTTLTest) Setup(t *testing.T) { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) - mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } func (s *disabledCacheTTLTest) Teardown(t *testing.T) { @@ -55,7 +55,7 @@ func (s *disabledCacheTTLTest) Teardown(t *testing.T) { //////////////////////////////////////////////////////////////////////// func (s *disabledCacheTTLTest) TestReadAfterObjectUpdateIsCacheMiss(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) // Read file 1st time. expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) diff --git a/tools/integration_tests/read_cache/helpers_test.go b/tools/integration_tests/read_cache/helpers_test.go index 79db5b3dc8..6366d0ac2f 100644 --- a/tools/integration_tests/read_cache/helpers_test.go +++ b/tools/integration_tests/read_cache/helpers_test.go @@ -209,17 +209,17 @@ func readChunkAndValidateObjectContentsFromGCS(ctx context.Context, storageClien } func readFileAndValidateFileIsNotCached(ctx context.Context, storageClient *storage.Client, - filename string, readFullFile bool, offset int64, t *testing.T) (expectedOutcome *Expected) { + readFullFile bool, offset int64, t *testing.T) (expectedOutcome *Expected) { // Read file via gcsfuse mount. - expectedOutcome = readFileAndGetExpectedOutcome(testDirPath, filename, readFullFile, offset, t) + expectedOutcome = readFileAndGetExpectedOutcome(testDirPath, largeFileName, readFullFile, offset, t) // Validate that the file is not cached. - validateFileIsNotCached(filename, t) + validateFileIsNotCached(largeFileName, t) // validate the content read matches the content on GCS. if readFullFile { - client.ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, filename, + client.ValidateObjectContentsFromGCS(ctx, storageClient, testDirName, largeFileName, expectedOutcome.content, t) } else { - client.ValidateObjectChunkFromGCS(ctx, storageClient, testDirName, filename, + client.ValidateObjectChunkFromGCS(ctx, storageClient, testDirName, largeFileName, offset, chunkSizeToRead, expectedOutcome.content, t) } return expectedOutcome @@ -247,7 +247,7 @@ func validateCacheSizeWithinLimit(cacheCapacity int64, t *testing.T) { } } -func setupFileInTestDir(ctx context.Context, storageClient *storage.Client, testDirName string, fileSize int64, t *testing.T) (fileName string) { +func setupFileInTestDir(ctx context.Context, storageClient *storage.Client, fileSize int64, t *testing.T) (fileName string) { testFileName := testFileName + setup.GenerateRandomString(testFileNameSuffixLength) client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, fileSize, t) diff --git a/tools/integration_tests/read_cache/job_chunk_test.go b/tools/integration_tests/read_cache/job_chunk_test.go index d30c99a1d3..f16fb3c738 100644 --- a/tools/integration_tests/read_cache/job_chunk_test.go +++ b/tools/integration_tests/read_cache/job_chunk_test.go @@ -36,6 +36,8 @@ import ( // Boilerplate //////////////////////////////////////////////////////////////////////// +const cacheSizeMB int64 = 48 + type jobChunkTest struct { flags []string storageClient *storage.Client @@ -47,7 +49,7 @@ func (s *jobChunkTest) Setup(t *testing.T) { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) - mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } func (s *jobChunkTest) Teardown(t *testing.T) { @@ -55,13 +57,13 @@ func (s *jobChunkTest) Teardown(t *testing.T) { setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } -func createConfigFileForJobChunkTest(cacheSize int64, cacheFileForRangeRead bool, fileName string, parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB int, clientProtocol string) string { +func createConfigFileForJobChunkTest(cacheFileForRangeRead bool, fileName string, parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB int, clientProtocol string) string { cacheDirPath = path.Join(setup.TestDir(), cacheDirName) // Set up config file for file cache. mountConfig := map[string]interface{}{ "file-cache": map[string]interface{}{ - "max-size-mb": cacheSize, + "max-size-mb": cacheSizeMB, "cache-file-for-range-read": cacheFileForRangeRead, "enable-parallel-downloads": true, "parallel-downloads-per-file": parallelDownloadsPerFile, @@ -84,7 +86,7 @@ func createConfigFileForJobChunkTest(cacheSize int64, cacheFileForRangeRead bool func (s *jobChunkTest) TestJobChunkSizeForSingleFileReads(t *testing.T) { var fileSize int64 = 16 * util.MiB - testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) expectedOutcome := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, false, t) @@ -111,8 +113,8 @@ func (s *jobChunkTest) TestJobChunkSizeForMultipleFileReads(t *testing.T) { var fileSize int64 = 16 * util.MiB var testFileNames [2]string var expectedOutcome [2]*Expected - testFileNames[0] = setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) - testFileNames[1] = setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) + testFileNames[0] = setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) + testFileNames[1] = setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) // Read 2 files in parallel. var wg sync.WaitGroup @@ -178,8 +180,6 @@ func TestJobChunkTest(t *testing.T) { return } - var cacheSizeMB int64 = 48 - // Tests to validate chunk size when read cache parallel downloads are disabled. var chunkSizeForReadCache int64 = 8 ts.flags = []string{"--config-file=" + createConfigFile(&gcsfuseTestFlags{cacheSize: cacheSizeMB, cacheFileForRangeRead: true, fileName: configFileName, enableParallelDownloads: false, enableODirect: false, cacheDirPath: getDefaultCacheDirPathForTests(), clientProtocol: http1ClientProtocol})} @@ -196,7 +196,7 @@ func TestJobChunkTest(t *testing.T) { // Tests to validate chunk size when read cache parallel downloads are enabled // with unlimited max parallel downloads. ts.flags = []string{"--config-file=" + - createConfigFileForJobChunkTest(cacheSizeMB, false, "unlimitedMaxParallelDownloads", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, http1ClientProtocol)} + createConfigFileForJobChunkTest(false, "unlimitedMaxParallelDownloads", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, http1ClientProtocol)} ts.chunkSize = downloadChunkSizeMB * util.MiB log.Printf("Running tests with flags: %s", ts.flags) test_setup.RunTests(t, ts) @@ -204,7 +204,7 @@ func TestJobChunkTest(t *testing.T) { // Tests to validate chunk size when read cache parallel downloads are enabled // with unlimited max parallel downloads with grpc enabled. ts.flags = []string{"--config-file=" + - createConfigFileForJobChunkTest(cacheSizeMB, false, "unlimitedMaxParallelDownloads", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, grpcClientProtocol)} + createConfigFileForJobChunkTest(false, "unlimitedMaxParallelDownloads", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, grpcClientProtocol)} ts.chunkSize = downloadChunkSizeMB * util.MiB log.Printf("Running tests with flags: %s", ts.flags) test_setup.RunTests(t, ts) @@ -215,7 +215,7 @@ func TestJobChunkTest(t *testing.T) { maxParallelDownloads := 9 // maxParallelDownloads > parallelDownloadsPerFile * number of files being accessed concurrently. downloadChunkSizeMB := 4 ts.flags = []string{"--config-file=" + - createConfigFileForJobChunkTest(cacheSizeMB, false, "limitedMaxParallelDownloadsNotEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, http1ClientProtocol)} + createConfigFileForJobChunkTest(false, "limitedMaxParallelDownloadsNotEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, http1ClientProtocol)} ts.chunkSize = int64(downloadChunkSizeMB) * util.MiB log.Printf("Running tests with flags: %s", ts.flags) test_setup.RunTests(t, ts) @@ -223,7 +223,7 @@ func TestJobChunkTest(t *testing.T) { // Tests to validate chunk size when read cache parallel downloads are enabled // with go-routines not limited by max parallel downloads with grpc enabled. ts.flags = []string{"--config-file=" + - createConfigFileForJobChunkTest(cacheSizeMB, false, "limitedMaxParallelDownloadsNotEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, grpcClientProtocol)} + createConfigFileForJobChunkTest(false, "limitedMaxParallelDownloadsNotEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, grpcClientProtocol)} ts.chunkSize = int64(downloadChunkSizeMB) * util.MiB log.Printf("Running tests with flags: %s", ts.flags) test_setup.RunTests(t, ts) @@ -234,7 +234,7 @@ func TestJobChunkTest(t *testing.T) { maxParallelDownloads = 2 downloadChunkSizeMB = 4 ts.flags = []string{"--config-file=" + - createConfigFileForJobChunkTest(cacheSizeMB, false, "limitedMaxParallelDownloadsEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, http1ClientProtocol)} + createConfigFileForJobChunkTest(false, "limitedMaxParallelDownloadsEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, http1ClientProtocol)} ts.chunkSize = int64(downloadChunkSizeMB) * util.MiB log.Printf("Running tests with flags: %s", ts.flags) test_setup.RunTests(t, ts) @@ -242,7 +242,7 @@ func TestJobChunkTest(t *testing.T) { // Tests to validate chunk size when read cache parallel downloads are enabled // with go-routines limited by max parallel downloads with grpc enabled. ts.flags = []string{"--config-file=" + - createConfigFileForJobChunkTest(cacheSizeMB, false, "limitedMaxParallelDownloadsEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, grpcClientProtocol)} + createConfigFileForJobChunkTest(false, "limitedMaxParallelDownloadsEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, grpcClientProtocol)} ts.chunkSize = int64(downloadChunkSizeMB) * util.MiB log.Printf("Running tests with flags: %s", ts.flags) test_setup.RunTests(t, ts) diff --git a/tools/integration_tests/read_cache/local_modification_test.go b/tools/integration_tests/read_cache/local_modification_test.go index 316f39ab70..27f63dc607 100644 --- a/tools/integration_tests/read_cache/local_modification_test.go +++ b/tools/integration_tests/read_cache/local_modification_test.go @@ -41,7 +41,7 @@ func (s *localModificationTest) Setup(t *testing.T) { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) - mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } func (s *localModificationTest) Teardown(t *testing.T) { diff --git a/tools/integration_tests/read_cache/range_read_test.go b/tools/integration_tests/read_cache/range_read_test.go index cd97ae37a1..9e3d751d6e 100644 --- a/tools/integration_tests/read_cache/range_read_test.go +++ b/tools/integration_tests/read_cache/range_read_test.go @@ -43,7 +43,7 @@ func (s *rangeReadTest) Setup(t *testing.T) { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) - mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } func (s *rangeReadTest) Teardown(t *testing.T) { @@ -62,7 +62,7 @@ func (s *rangeReadTest) TestRangeReadsWithinReadChunkSize(t *testing.T) { // we skip this test when parallel downloads are enabled. t.SkipNow() } - testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, largeFileSize, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, largeFileSize, t) expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, zeroOffset, t) expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offsetForRangeReadWithin8MB, t) @@ -73,7 +73,7 @@ func (s *rangeReadTest) TestRangeReadsWithinReadChunkSize(t *testing.T) { } func (s *rangeReadTest) TestRangeReadsBeyondReadChunkSizeWithFileCached(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, largeFileSize, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, largeFileSize, t) expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, zeroOffset, t) validateFileInCacheDirectory(testFileName, largeFileSize, ctx, s.storageClient, t) diff --git a/tools/integration_tests/read_cache/read_only_test.go b/tools/integration_tests/read_cache/read_only_test.go index 219d0ed17e..f1a8d7a09a 100644 --- a/tools/integration_tests/read_cache/read_only_test.go +++ b/tools/integration_tests/read_cache/read_only_test.go @@ -41,7 +41,7 @@ func (s *readOnlyTest) Setup(t *testing.T) { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) - mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } func (s *readOnlyTest) Teardown(t *testing.T) { @@ -53,7 +53,7 @@ func (s *readOnlyTest) Teardown(t *testing.T) { // Helper functions //////////////////////////////////////////////////////////////////////// -func readMultipleFiles(numFiles int, ctx context.Context, storageClient *storage.Client, fileNames []string, fileSize int64, t *testing.T) (expectedOutcome []*Expected) { +func readMultipleFiles(numFiles int, ctx context.Context, storageClient *storage.Client, fileNames []string, t *testing.T) (expectedOutcome []*Expected) { for i := 0; i < numFiles; i++ { expectedOutcome = append(expectedOutcome, readFileAndValidateCacheWithGCS(ctx, storageClient, fileNames[i], fileSize, true, t)) } @@ -73,7 +73,7 @@ func validateCacheOfMultipleObjectsUsingStructuredLogs(startIndex int, numFiles //////////////////////////////////////////////////////////////////////// func (s *readOnlyTest) TestSecondSequentialReadIsCacheHit(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) // Read file 1st time. expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) @@ -92,9 +92,9 @@ func (s *readOnlyTest) TestReadFileSequentiallyLargerThanCacheCapacity(t *testin largeFileName, largeFileSize, t) // Read file 1st time. - expectedOutcome1 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, largeFileName, true, zeroOffset, t) + expectedOutcome1 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, true, zeroOffset, t) // Read file 2nd time. - expectedOutcome2 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, largeFileName, true, zeroOffset, t) + expectedOutcome2 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, true, zeroOffset, t) // Parse the log file and validate cache hit or miss from the structured logs. structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) @@ -108,9 +108,9 @@ func (s *readOnlyTest) TestReadFileRandomlyLargerThanCacheCapacity(t *testing.T) largeFileName, largeFileSize, t) // Do a random read on file. - expectedOutcome1 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, largeFileName, false, randomReadOffset, t) + expectedOutcome1 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, false, randomReadOffset, t) // Read file sequentially again. - expectedOutcome2 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, largeFileName, true, zeroOffset, t) + expectedOutcome2 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, true, zeroOffset, t) // Parse the log file and validate cache hit or miss from the structured logs. structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) @@ -121,8 +121,8 @@ func (s *readOnlyTest) TestReadFileRandomlyLargerThanCacheCapacity(t *testing.T) func (s *readOnlyTest) TestReadMultipleFilesMoreThanCacheLimit(t *testing.T) { fileNames := client.CreateNFilesInDir(s.ctx, s.storageClient, NumberOfFilesMoreThanCacheLimit, testFileName, fileSize, testDirName, t) - expectedOutcome := readMultipleFiles(NumberOfFilesMoreThanCacheLimit, s.ctx, s.storageClient, fileNames, fileSize, t) - expectedOutcome = append(expectedOutcome, readMultipleFiles(NumberOfFilesMoreThanCacheLimit, s.ctx, s.storageClient, fileNames, fileSize, t)...) + expectedOutcome := readMultipleFiles(NumberOfFilesMoreThanCacheLimit, s.ctx, s.storageClient, fileNames, t) + expectedOutcome = append(expectedOutcome, readMultipleFiles(NumberOfFilesMoreThanCacheLimit, s.ctx, s.storageClient, fileNames, t)...) // Parse the log file and validate cache hit or miss from the structured logs. structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) @@ -133,8 +133,8 @@ func (s *readOnlyTest) TestReadMultipleFilesMoreThanCacheLimit(t *testing.T) { func (s *readOnlyTest) TestReadMultipleFilesWithinCacheLimit(t *testing.T) { fileNames := client.CreateNFilesInDir(s.ctx, s.storageClient, NumberOfFilesWithinCacheLimit, testFileName, fileSize, testDirName, t) - expectedOutcome := readMultipleFiles(NumberOfFilesWithinCacheLimit, s.ctx, s.storageClient, fileNames, fileSize, t) - expectedOutcome = append(expectedOutcome, readMultipleFiles(NumberOfFilesWithinCacheLimit, s.ctx, s.storageClient, fileNames, fileSize, t)...) + expectedOutcome := readMultipleFiles(NumberOfFilesWithinCacheLimit, s.ctx, s.storageClient, fileNames, t) + expectedOutcome = append(expectedOutcome, readMultipleFiles(NumberOfFilesWithinCacheLimit, s.ctx, s.storageClient, fileNames, t)...) // Parse the log file and validate cache hit or miss from the structured logs. structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) diff --git a/tools/integration_tests/read_cache/remount_test.go b/tools/integration_tests/read_cache/remount_test.go index 86ce287e48..53040b4cb6 100644 --- a/tools/integration_tests/read_cache/remount_test.go +++ b/tools/integration_tests/read_cache/remount_test.go @@ -43,7 +43,7 @@ type remountTest struct { func (s *remountTest) Setup(t *testing.T) { operations.RemoveDir(cacheDirPath) - mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } func (s *remountTest) Teardown(t *testing.T) { @@ -69,7 +69,7 @@ func readFileAndValidateCacheWithGCSForDynamicMount(bucketName string, ctx conte //////////////////////////////////////////////////////////////////////// func (s *remountTest) TestCacheIsNotReusedOnRemount(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) // Run read operations on GCSFuse mount. expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) @@ -91,7 +91,7 @@ func (s *remountTest) TestCacheIsNotReusedOnRemount(t *testing.T) { func (s *remountTest) TestCacheIsNotReusedOnDynamicRemount(t *testing.T) { runTestsOnlyForDynamicMount(t) testBucket1 := setup.TestBucket() - testFileName1 := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) + testFileName1 := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) testBucket2, err := dynamic_mounting.CreateTestBucketForDynamicMounting(ctx, storageClient) if err != nil { t.Fatalf("Failed to create bucket for dynamic mounting test: %v", err) @@ -106,7 +106,7 @@ func (s *remountTest) TestCacheIsNotReusedOnDynamicRemount(t *testing.T) { // Introducing a sleep of 10 seconds after bucket creation to address propagation delays. time.Sleep(10 * time.Second) client.SetupTestDirectory(s.ctx, s.storageClient, testDirName) - testFileName2 := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) + testFileName2 := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) // Reading files in different buckets. expectedOutcome1 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket1, s.ctx, s.storageClient, testFileName1, true, t) diff --git a/tools/integration_tests/read_cache/setup_test.go b/tools/integration_tests/read_cache/setup_test.go index 40ad9176b2..476a2076d7 100644 --- a/tools/integration_tests/read_cache/setup_test.go +++ b/tools/integration_tests/read_cache/setup_test.go @@ -106,7 +106,7 @@ func setupForMountedDirectoryTests() { } } -func mountGCSFuseAndSetupTestDir(flags []string, ctx context.Context, storageClient *storage.Client, testDirName string) { +func mountGCSFuseAndSetupTestDir(flags []string, ctx context.Context, storageClient *storage.Client) { setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) setup.SetMntDir(mountDir) testDirPath = client.SetupTestDirectory(ctx, storageClient, testDirName) diff --git a/tools/integration_tests/read_cache/small_cache_ttl_test.go b/tools/integration_tests/read_cache/small_cache_ttl_test.go index 928b069d51..57be414eb0 100644 --- a/tools/integration_tests/read_cache/small_cache_ttl_test.go +++ b/tools/integration_tests/read_cache/small_cache_ttl_test.go @@ -44,7 +44,7 @@ func (s *smallCacheTTLTest) Setup(t *testing.T) { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) - mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient, testDirName) + mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } func (s *smallCacheTTLTest) Teardown(t *testing.T) { @@ -57,7 +57,7 @@ func (s *smallCacheTTLTest) Teardown(t *testing.T) { //////////////////////////////////////////////////////////////////////// func (s *smallCacheTTLTest) TestReadAfterUpdateAndCacheExpiryIsCacheMiss(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) // Read file 1st time. expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) @@ -82,7 +82,7 @@ func (s *smallCacheTTLTest) TestReadAfterUpdateAndCacheExpiryIsCacheMiss(t *test } func (s *smallCacheTTLTest) TestReadForLowMetaDataCacheTTLIsCacheHit(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, testDirName, fileSize, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) // Read file 1st time. expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) diff --git a/tools/package_gcsfuse/build.go b/tools/package_gcsfuse/build.go index 6c624df384..7b080ddaca 100644 --- a/tools/package_gcsfuse/build.go +++ b/tools/package_gcsfuse/build.go @@ -28,8 +28,7 @@ import ( // root-relative file system structure we desire. func build( commit string, - version string, - osys string) (dir string, err error) { + version string) (dir string, err error) { log.Printf("Building version %s from %s.", version, commit) // Create a directory to become GOCACHE below. diff --git a/tools/package_gcsfuse/main.go b/tools/package_gcsfuse/main.go index d8c8104161..04d6e282ef 100644 --- a/tools/package_gcsfuse/main.go +++ b/tools/package_gcsfuse/main.go @@ -65,7 +65,7 @@ func run(args []string) (err error) { } // Assemble binaries, mount(8) helper scripts, etc. - buildDir, err := build(commit, version, osys) + buildDir, err := build(commit, version) if err != nil { err = fmt.Errorf("build: %w", err) return diff --git a/tools/package_gcsfuse/package.go b/tools/package_gcsfuse/package.go index 41e5810d9e..79af9119af 100644 --- a/tools/package_gcsfuse/package.go +++ b/tools/package_gcsfuse/package.go @@ -24,8 +24,6 @@ func packageFpm( packageType string, binDir string, version string, - osys string, - arch string, outputDir string) (err error) { // Call fpm. cmd := exec.Command( @@ -63,7 +61,7 @@ func packageDeb( outputDir string) (err error) { log.Println("Building a .deb package.") - err = packageFpm("deb", binDir, version, osys, arch, outputDir) + err = packageFpm("deb", binDir, version, outputDir) return } @@ -77,6 +75,6 @@ func packageRpm( outputDir string) (err error) { log.Println("Building a .rpm package.") - err = packageFpm("rpm", binDir, version, osys, arch, outputDir) + err = packageFpm("rpm", binDir, version, outputDir) return } From 565a7839551e8d6c0d61e575824a78b61d87961e Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 25 Jul 2025 13:48:44 +0530 Subject: [PATCH 0603/1298] feat(bufferedread): Add freshStart and prefetch methods to Buffered Reader (#3580) * add methods * Add freshStart and prefetch methods * add methods * Add freshStart and prefetch methods * Update internal/bufferedread/buffered_reader.go Co-authored-by: Prince Kumar * Update internal/bufferedread/buffered_reader.go Co-authored-by: Prince Kumar * Update internal/bufferedread/buffered_reader.go Co-authored-by: Prince Kumar * Update internal/bufferedread/buffered_reader.go Co-authored-by: Prince Kumar * Update internal/bufferedread/buffered_reader_test.go Co-authored-by: Prince Kumar * Check Download Status for all jobs --------- Co-authored-by: Prince Kumar --- internal/bufferedread/buffered_reader.go | 65 +++- internal/bufferedread/buffered_reader_test.go | 338 +++++++++++++++++- 2 files changed, 394 insertions(+), 9 deletions(-) diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index f8dcc8fd37..2322da55d1 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -103,6 +103,62 @@ func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *Buffere return reader, nil } +// prefetch schedules the next set of blocks for prefetching starting from +// the nextBlockIndexToPrefetch. +func (p *BufferedReader) prefetch() error { + // Do not schedule more than MaxPrefetchBlockCnt. + availableSlots := p.config.MaxPrefetchBlockCnt - int64(p.blockQueue.Len()) + if availableSlots <= 0 { + return nil + } + blockCountToPrefetch := min(p.numPrefetchBlocks, availableSlots) + if blockCountToPrefetch <= 0 { + return nil + } + + logger.Tracef("Prefetching %d blocks", blockCountToPrefetch) + + totalBlockCount := (int64(p.object.Size) + p.config.PrefetchBlockSizeBytes - 1) / p.config.PrefetchBlockSizeBytes + for i := int64(0); i < blockCountToPrefetch; i++ { + if p.nextBlockIndexToPrefetch >= totalBlockCount { + break + } + if err := p.scheduleNextBlock(false); err != nil { + return fmt.Errorf("prefetch: failed to schedule block index %d: %v", p.nextBlockIndexToPrefetch, err) + } + } + + // Set the size for the next multiplicative prefetch. + p.numPrefetchBlocks *= p.config.PrefetchMultiplier + + // Do not prefetch more than MaxPrefetchBlockCnt blocks. + if p.numPrefetchBlocks > p.config.MaxPrefetchBlockCnt { + p.numPrefetchBlocks = p.config.MaxPrefetchBlockCnt + } + return nil +} + +// freshStart resets the prefetching state and schedules the initial set of +// blocks starting from the given offset. +func (p *BufferedReader) freshStart(currentOffset int64) error { + blockIndex := currentOffset / p.config.PrefetchBlockSizeBytes + p.nextBlockIndexToPrefetch = blockIndex + + // Determine the number of blocks for the initial prefetch. + p.numPrefetchBlocks = min(p.config.InitialPrefetchBlockCnt, p.config.MaxPrefetchBlockCnt) + + // Schedule the first block as urgent. + if err := p.scheduleNextBlock(true); err != nil { + return fmt.Errorf("freshStart: initial scheduling failed: %w", err) + } + + // Prefetch the initial blocks. + if err := p.prefetch(); err != nil { + return fmt.Errorf("freshStart: prefetch failed: %w", err) + } + return nil +} + // scheduleNextBlock schedules the next block for prefetch. func (p *BufferedReader) scheduleNextBlock(urgent bool) error { // TODO(b/426060431): Replace Get() with TryGet(). Assuming, the current blockPool.Get() gets blocked if block is not available. @@ -169,12 +225,17 @@ func (p *BufferedReader) Destroy() { // CheckInvariants checks for internal consistency of the reader. func (p *BufferedReader) CheckInvariants() { - // The number of items in the blockQueue should not exceed the configured limit. + // The prefetch block size must be positive. + if p.config.PrefetchBlockSizeBytes <= 0 { + panic(fmt.Sprintf("BufferedReader: PrefetchBlockSizeBytes must be positive, but is %d", p.config.PrefetchBlockSizeBytes)) + } + + // The number of items in the blockQueue should not exceed MaxPrefetchBlockCnt. if int64(p.blockQueue.Len()) > p.config.MaxPrefetchBlockCnt { panic(fmt.Sprintf("BufferedReader: blockQueue length %d exceeds limit %d", p.blockQueue.Len(), p.config.MaxPrefetchBlockCnt)) } - // The random seek count should never exceed the configured threshold. + // The random seek count should never exceed RandomReadsThreshold. if p.randomSeekCount > p.config.RandomReadsThreshold { panic(fmt.Sprintf("BufferedReader: randomSeekCount %d exceeds threshold %d", p.randomSeekCount, p.config.RandomReadsThreshold)) } diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 94ed979dbe..ef7042828e 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -1,7 +1,6 @@ // Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); - // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // @@ -174,17 +173,16 @@ func (t *BufferedReaderTest) TestDestroyAwaitReadyError() { } func (t *BufferedReaderTest) TestCheckInvariantsBlockQueueExceedsLimit() { + t.config.MaxPrefetchBlockCnt = 2 reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err, "NewBufferedReader should not return error") b, err := reader.blockPool.Get() require.NoError(t.T(), err, "Failed to get block from pool") - for range int(t.config.MaxPrefetchBlockCnt + 1) { - reader.blockQueue.Push(&blockQueueEntry{ - block: b, - cancel: func() {}, - }) - } + // Push 3 blocks to exceed the limit of 2. + reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) + reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) + reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) assert.Panics(t.T(), func() { reader.CheckInvariants() }) } @@ -198,6 +196,31 @@ func (t *BufferedReaderTest) TestCheckInvariantsRandomSeekCountExceedsThreshold( assert.Panics(t.T(), func() { reader.CheckInvariants() }) } +func (t *BufferedReaderTest) TestCheckInvariantsPrefetchBlockSizeNotPositive() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err, "NewBufferedReader should not return error") + testCases := []struct { + name string + blockSize int64 + }{ + { + name: "zero block size", + blockSize: 0, + }, + { + name: "negative block size", + blockSize: -1, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func() { + reader.config.PrefetchBlockSizeBytes = tc.blockSize + + assert.Panics(t.T(), func() { reader.CheckInvariants() }, "Should panic for non-positive block size") + }) + } +} + func (t *BufferedReaderTest) TestCheckInvariantsNoPanic() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err, "NewBufferedReader should not return error") @@ -312,3 +335,304 @@ func (t *BufferedReaderTest) TestScheduleBlockWithIndex() { }) } } + +func (t *BufferedReaderTest) TestFreshStart() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + currentOffset := int64(2048) // Start prefetching from offset 2048 (block 2). + // freshStart schedules 1 urgent block and 2 initial prefetch blocks, totaling 3 blocks. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + + err = reader.freshStart(currentOffset) + + require.NoError(t.T(), err) + // nextBlockIndexToPrefetch should be current block index (2) + scheduled blocks (3). + assert.Equal(t.T(), int64(5), reader.nextBlockIndexToPrefetch) + // numPrefetchBlocks for the next prefetch should be initialPrefetchBlockCnt (2) * prefetchMultiplier (2). + assert.Equal(t.T(), int64(4), reader.numPrefetchBlocks) + assert.Equal(t.T(), 3, reader.blockQueue.Len()) + // Pop and verify the downloaded blocks. + bqe1 := reader.blockQueue.Pop() + assert.Equal(t.T(), int64(2048), bqe1.block.AbsStartOff()) + status1, err1 := bqe1.block.AwaitReady(t.ctx) + require.NoError(t.T(), err1) + assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + bqe2 := reader.blockQueue.Pop() + assert.Equal(t.T(), int64(3072), bqe2.block.AbsStartOff()) + status2, err2 := bqe2.block.AwaitReady(t.ctx) + require.NoError(t.T(), err2) + assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + bqe3 := reader.blockQueue.Pop() + assert.Equal(t.T(), int64(4096), bqe3.block.AbsStartOff()) + status3, err3 := bqe3.block.AwaitReady(t.ctx) + require.NoError(t.T(), err3) + assert.Equal(t.T(), block.BlockStateDownloaded, status3.State) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestFreshStartWithNonBlockAlignedOffset() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + currentOffset := int64(2500) // Start prefetching from offset 2500 (inside block 2). + // freshStart should start prefetching from block 2. It schedules 1 urgent block + // and 2 initial prefetch blocks, totaling 3 blocks. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + + err = reader.freshStart(currentOffset) + + require.NoError(t.T(), err) + // nextBlockIndexToPrefetch should be current block index (2) + scheduled blocks (3). + assert.Equal(t.T(), int64(5), reader.nextBlockIndexToPrefetch) + // numPrefetchBlocks for the next prefetch should be initialPrefetchBlockCnt (2) * prefetchMultiplier (2). + assert.Equal(t.T(), int64(4), reader.numPrefetchBlocks) + assert.Equal(t.T(), 3, reader.blockQueue.Len()) + // Pop and verify the downloaded blocks. + bqe1 := reader.blockQueue.Pop() + assert.Equal(t.T(), int64(2048), bqe1.block.AbsStartOff()) + status1, err1 := bqe1.block.AwaitReady(t.ctx) + require.NoError(t.T(), err1) + assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + bqe2 := reader.blockQueue.Pop() + assert.Equal(t.T(), int64(3072), bqe2.block.AbsStartOff()) + status2, err2 := bqe2.block.AwaitReady(t.ctx) + require.NoError(t.T(), err2) + assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + bqe3 := reader.blockQueue.Pop() + assert.Equal(t.T(), int64(4096), bqe3.block.AbsStartOff()) + status3, err3 := bqe3.block.AwaitReady(t.ctx) + require.NoError(t.T(), err3) + assert.Equal(t.T(), block.BlockStateDownloaded, status3.State) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestFreshStartWhenInitialCountGreaterThanMax() { + t.config.MaxPrefetchBlockCnt = 3 + t.config.InitialPrefetchBlockCnt = 4 + t.object.Size = 4096 + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + // freshStart schedules 1 urgent block and 2 prefetch blocks (InitialPrefetchBlockCnt capped by MaxPrefetchBlockCnt). + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), 1024), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), 1024), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), 1024), nil).Once() + + err = reader.freshStart(0) + + require.NoError(t.T(), err) + // nextBlockIndexToPrefetch should be start block index (0) + scheduled blocks (3). + assert.Equal(t.T(), int64(3), reader.nextBlockIndexToPrefetch) + // numPrefetchBlocks for next prefetch should be capped at MaxPrefetchBlockCnt (3). + assert.Equal(t.T(), int64(3), reader.numPrefetchBlocks) + assert.Equal(t.T(), 3, reader.blockQueue.Len()) + // Pop and verify blocks are downloaded. + bqe1 := reader.blockQueue.Pop() + status1, err1 := bqe1.block.AwaitReady(t.ctx) + require.NoError(t.T(), err1) + assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + bqe2 := reader.blockQueue.Pop() + status2, err2 := bqe2.block.AwaitReady(t.ctx) + require.NoError(t.T(), err2) + assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + bqe3 := reader.blockQueue.Pop() + status3, err3 := bqe3.block.AwaitReady(t.ctx) + require.NoError(t.T(), err3) + assert.Equal(t.T(), block.BlockStateDownloaded, status3.State) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestFreshStartStopsAtObjectEnd() { + t.object.Size = 4000 // Object size is 3 blocks + a partial block. + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + currentOffset := int64(2048) // Start from block 2. + // freshStart schedules 1 urgent block (block 2) and 1 prefetch block (block 3). + // Prefetching stops because the object ends after block 3, totaling 2 blocks scheduled. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + + err = reader.freshStart(currentOffset) + + require.NoError(t.T(), err) + // nextBlockIndexToPrefetch should be start block index (2) + scheduled blocks (2). + assert.Equal(t.T(), int64(4), reader.nextBlockIndexToPrefetch) + // numPrefetchBlocks for the next prefetch should be initialPrefetchBlockCnt (2) * prefetchMultiplier (2). + assert.Equal(t.T(), int64(4), reader.numPrefetchBlocks) + assert.Equal(t.T(), 2, reader.blockQueue.Len()) + // Verify block 2. + bqe1 := reader.blockQueue.Pop() + assert.Equal(t.T(), int64(2048), bqe1.block.AbsStartOff()) + status1, err1 := bqe1.block.AwaitReady(t.ctx) + require.NoError(t.T(), err1) + assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + // Verify block 3. + bqe2 := reader.blockQueue.Pop() + assert.Equal(t.T(), int64(3072), bqe2.block.AbsStartOff()) + status2, err2 := bqe2.block.AwaitReady(t.ctx) + require.NoError(t.T(), err2) + assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestPrefetch() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + + err = reader.prefetch() + + require.NoError(t.T(), err) + // nextBlockIndexToPrefetch should be start block index (0) + initialPrefetchBlockCnt (2). + assert.Equal(t.T(), int64(2), reader.nextBlockIndexToPrefetch) + // numPrefetchBlocks for the next prefetch should be initialPrefetchBlockCnt (2) * prefetchMultiplier (2). + assert.Equal(t.T(), int64(4), reader.numPrefetchBlocks) + assert.Equal(t.T(), 2, reader.blockQueue.Len()) + // Wait for all downloads to complete. + bqe1 := reader.blockQueue.Pop() + status1, err1 := bqe1.block.AwaitReady(t.ctx) + require.NoError(t.T(), err1) + assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + bqe2 := reader.blockQueue.Pop() + status2, err2 := bqe2.block.AwaitReady(t.ctx) + require.NoError(t.T(), err2) + assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestPrefetchWithMultiplicativeIncrease() { + t.config.InitialPrefetchBlockCnt = 1 + t.config.PrefetchMultiplier = 2 + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + // First prefetch schedules 1 block. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + err = reader.prefetch() + require.NoError(t.T(), err) + // Wait for the first prefetch to complete and drain the queue. + bqe1 := reader.blockQueue.Pop() + status1, err1 := bqe1.block.AwaitReady(t.ctx) + require.NoError(t.T(), err1) + assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + // Second prefetch should schedule 2 blocks due to multiplicative increase. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + + err = reader.prefetch() + + require.NoError(t.T(), err) + // nextBlockIndexToPrefetch should be blocks from first prefetch (1) + blocks from second prefetch (2). + assert.Equal(t.T(), int64(3), reader.nextBlockIndexToPrefetch) + // numPrefetchBlocks for the next prefetch should be numPrefetchBlocks from previous prefetch (2) * prefetchMultiplier (2). + assert.Equal(t.T(), int64(4), reader.numPrefetchBlocks) + assert.Equal(t.T(), 2, reader.blockQueue.Len()) + // Wait for the second prefetch to complete. + bqe2 := reader.blockQueue.Pop() + status2, err2 := bqe2.block.AwaitReady(t.ctx) + require.NoError(t.T(), err2) + assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + bqe3 := reader.blockQueue.Pop() + status3, err3 := bqe3.block.AwaitReady(t.ctx) + require.NoError(t.T(), err3) + assert.Equal(t.T(), block.BlockStateDownloaded, status3.State) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestPrefetchWhenQueueIsFull() { + t.config.MaxPrefetchBlockCnt = 2 + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + b, err := reader.blockPool.Get() + require.NoError(t.T(), err) + // Fill the block queue to its maximum capacity. + reader.blockQueue.Push(&blockQueueEntry{block: b}) + reader.blockQueue.Push(&blockQueueEntry{block: b}) + + err = reader.prefetch() + + require.NoError(t.T(), err) + // No new blocks should be prefetched, so the index remains 0. + assert.Equal(t.T(), int64(0), reader.nextBlockIndexToPrefetch) + // The queue length should remain at MaxPrefetchBlockCnt. + assert.Equal(t.T(), 2, reader.blockQueue.Len()) + // numPrefetchBlocks should remain at its default/current value (2 in this case, due to InitialPrefetchBlockCnt). + assert.Equal(t.T(), int64(2), reader.numPrefetchBlocks) +} + +func (t *BufferedReaderTest) TestPrefetchWhenQueueIsPartiallyFull() { + t.config.MaxPrefetchBlockCnt = 4 + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + b, err := reader.blockPool.Get() + require.NoError(t.T(), err) + reader.blockQueue.Push(&blockQueueEntry{block: b}) + reader.blockQueue.Push(&blockQueueEntry{block: b}) + + // blockCountToPrefetch = min(numPrefetchBlocks (2), availableSlots (2)) = 2. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + + err = reader.prefetch() + + require.NoError(t.T(), err) + // nextBlockIndexToPrefetch should be the number of scheduled blocks (2). + assert.Equal(t.T(), int64(2), reader.nextBlockIndexToPrefetch) + // blockQueue.Len() should be already in queue (2) + newly scheduled blocks (2). + assert.Equal(t.T(), 4, reader.blockQueue.Len()) + // numPrefetchBlocks for the next prefetch should be previous numPrefetchBlocks (2) * prefetchMultiplier (2). + assert.Equal(t.T(), int64(4), reader.numPrefetchBlocks) + // Wait for the newly scheduled downloads to complete. The old blocks are dummies. + bqe1 := reader.blockQueue.Pop() + reader.blockPool.Release(bqe1.block) + bqe2 := reader.blockQueue.Pop() + reader.blockPool.Release(bqe2.block) + bqe3 := reader.blockQueue.Pop() + status3, err3 := bqe3.block.AwaitReady(t.ctx) + require.NoError(t.T(), err3) + assert.Equal(t.T(), block.BlockStateDownloaded, status3.State) + bqe4 := reader.blockQueue.Pop() + status4, err4 := bqe4.block.AwaitReady(t.ctx) + require.NoError(t.T(), err4) + assert.Equal(t.T(), block.BlockStateDownloaded, status4.State) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestPrefetchLimitedByAvailableSlots() { + t.config.MaxPrefetchBlockCnt = 4 + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + reader.numPrefetchBlocks = 4 + b, err := reader.blockPool.Get() + require.NoError(t.T(), err) + reader.blockQueue.Push(&blockQueueEntry{block: b}) + reader.blockQueue.Push(&blockQueueEntry{block: b}) + reader.blockQueue.Push(&blockQueueEntry{block: b}) + // blockCountToPrefetch = min(numPrefetchBlocks (4), availableSlots (1)) = 1. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + + err = reader.prefetch() + + require.NoError(t.T(), err) + // nextBlockIndexToPrefetch should be the number of scheduled blocks (1). + assert.Equal(t.T(), int64(1), reader.nextBlockIndexToPrefetch) + // blockQueue.Len() should be already in queue (3) + newly scheduled blocks (1). + assert.Equal(t.T(), 4, reader.blockQueue.Len()) + // numPrefetchBlocks for the next prefetch should be current numPrefetchBlocks (4) * prefetchMultiplier (2) = 8, + // but capped at MaxPrefetchBlockCnt (4). + assert.Equal(t.T(), int64(4), reader.numPrefetchBlocks) + // Release dummy blocks and wait for the newly scheduled download to complete. + bqe1 := reader.blockQueue.Pop() + reader.blockPool.Release(bqe1.block) + bqe2 := reader.blockQueue.Pop() + reader.blockPool.Release(bqe2.block) + bqe3 := reader.blockQueue.Pop() + reader.blockPool.Release(bqe3.block) + bqe4 := reader.blockQueue.Pop() + status4, err4 := bqe4.block.AwaitReady(t.ctx) + require.NoError(t.T(), err4) + assert.Equal(t.T(), block.BlockStateDownloaded, status4.State) + t.bucket.AssertExpectations(t.T()) +} From cfcd3c55be82dfa27e808b5ce072167244170685 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Fri, 25 Jul 2025 14:40:30 +0530 Subject: [PATCH 0604/1298] test(rapid appends): random writes with concurrent file handles must not be real time (#3546) * random write with concurrent file handle * triggering CI --- .../rapid_appends/appends_test.go | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tools/integration_tests/rapid_appends/appends_test.go b/tools/integration_tests/rapid_appends/appends_test.go index edc8403c09..71568af676 100644 --- a/tools/integration_tests/rapid_appends/appends_test.go +++ b/tools/integration_tests/rapid_appends/appends_test.go @@ -263,6 +263,44 @@ func (t *RapidAppendsSuite) TestAppendsVisibleInRealTimeWithConcurrentRPlusHandl assert.Equal(t.T(), expectedContent, string(contentRead[0:len(expectedContent)])) } +func (t *RapidAppendsSuite) TestRandomWritesVisibleAfterCloseWithConcurrentRPlusHandle() { + // Skipping test for now until CreateObject() is supported for unfinalized objects. + // Ref: b/424253611 + t.T().Skip() + + primaryPath := path.Join(primaryMntTestDirPath, t.fileName) + // Open first handle in append mode. + appendFileHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + defer appendFileHandle.Close() + + // Open second handle in "r+" mode. + readHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_RDWR|syscall.O_DIRECT) + + // Write initial content using append handle to trigger BW workflow. + n, err := appendFileHandle.Write([]byte(initialContent)) + require.NoError(t.T(), err) + require.NotZero(t.T(), n) + + // Random write additional content using "r+" handle by writing to incorrect offset. + data := setup.GenerateRandomString(contentSizeForBW * blockSize) + _, err = readHandle.WriteAt([]byte(data), int64(len(initialContent))+1) + require.NoError(t.T(), err) + + // Read from back-door to validate that appended content is yet not visible on GCS before close(). + contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), initialContent, string(contentBeforeClose)) + + // Close the file handle. + readHandle.Close() + + // Read from back-door to validate that appended content is now visible on GCS after close(). + expectedContent := t.fileContent + initialContent + "\x00" + data + contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), expectedContent, contentAfterClose) +} + //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// From e2ba0b3d2b839d4f4531411560c9325b0a4572a9 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Fri, 25 Jul 2025 15:10:02 +0530 Subject: [PATCH 0605/1298] test(rapid appends): first write from r+ file handle from amongst concurrent file handles will lead to fallback (#3547) * test concurrent file handles: fallback scenario * triggering CI --- .../rapid_appends/appends_test.go | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tools/integration_tests/rapid_appends/appends_test.go b/tools/integration_tests/rapid_appends/appends_test.go index 71568af676..d29e6da335 100644 --- a/tools/integration_tests/rapid_appends/appends_test.go +++ b/tools/integration_tests/rapid_appends/appends_test.go @@ -301,6 +301,40 @@ func (t *RapidAppendsSuite) TestRandomWritesVisibleAfterCloseWithConcurrentRPlus assert.Equal(t.T(), expectedContent, contentAfterClose) } +func (t *RapidAppendsSuite) TestFallbackHappensWhenNonAppendHandleDoesFirstWrite() { + // Skipping test for now until CreateObject() is supported for unfinalized objects. + // Ref: b/424253611 + t.T().Skip() + + primaryPath := path.Join(primaryMntTestDirPath, t.fileName) + // Open first handle in append mode. + appendFileHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + defer appendFileHandle.Close() + + // Open second handle in "r+" mode. + readHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_RDWR|syscall.O_DIRECT) + + // Append content using "r+" handle. + data := setup.GenerateRandomString(contentSizeForBW * blockSize) + n, err := readHandle.WriteAt([]byte(data), int64(len(t.fileContent))) + require.NoError(t.T(), err) + assert.NotZero(t.T(), n) + + // Read from back-door to validate that appended content is yet not visible on GCS before close(). + contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), t.fileContent, contentBeforeClose) + + // Close the file handle. + readHandle.Close() + + // Read from back-door to validate that appended content is now visible on GCS after close(). + expectedContent := t.fileContent + data + contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), expectedContent, contentAfterClose) +} + //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// From 0360fdcce6860bced62d49551c678da1797305f1 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 25 Jul 2025 22:25:25 +0530 Subject: [PATCH 0606/1298] fix(Random Reader Refactoring): Read Manager Not Destroyed/Checked with File Handle (#3585) * destroy read manager * adding it in checkinvariant * adding unit tetss --- internal/fs/handle/file.go | 8 ++++ internal/fs/handle/file_test.go | 72 +++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index a5929624e5..5a5f3c5749 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -97,6 +97,9 @@ func (fh *FileHandle) Destroy() { if fh.reader != nil { fh.reader.Destroy() } + if fh.readManager != nil { + fh.readManager.Destroy() + } } // Inode returns the inode backing this handle. @@ -232,6 +235,11 @@ func (fh *FileHandle) checkInvariants() { if fh.reader != nil { fh.reader.CheckInvariants() } + + // INVARIANT: If readManager != nil, readManager.CheckInvariants() doesn't panic. + if fh.readManager != nil { + fh.readManager.CheckInvariants() + } } // If possible, ensure that fh.reader is set to an appropriate random reader diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 9e388e9de7..165ea92a5f 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -344,3 +344,75 @@ func (t *fileTest) TestOpenMode() { assert.Equal(t.T(), tc.openMode, openMode) } } + +func (t *fileTest) TestFileHandle_Destroy_WithReaderAndReadManager() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + fileInode := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "destroy_test_obj", nil, false) + // Create mocks + mockReader := new(gcsx.MockRandomReader) + mockReadManager := new(read_manager.MockReadManager) + // Expect Destroy to be called on both + mockReader.On("Destroy").Once() + mockReadManager.On("Destroy").Once() + // Construct file handle with mocks + fh := NewFileHandle(fileInode, nil, false, nil, util.Read, config) + fh.reader = mockReader + fh.readManager = mockReadManager + + fh.Destroy() + + // Assert expectations + mockReader.AssertExpectations(t.T()) + mockReadManager.AssertExpectations(t.T()) +} + +func (t *fileTest) TestFileHandle_Destroy_WithNilReaderAndReadManager() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + fileInode := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "destroy_test_nil_obj", nil, false) + // Construct file handle with nils + fh := NewFileHandle(fileInode, nil, false, nil, util.Read, config) + fh.reader = nil + fh.readManager = nil + + // Should not panic + assert.NotPanics(t.T(), func() { + fh.Destroy() + }) +} + +func (t *fileTest) TestFileHandle_CheckInvariants_WithNonNilReaderAndManager() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + fileInode := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "destroy_test_obj", nil, false) + // Create mocks + mockReader := new(gcsx.MockRandomReader) + mockRM := new(read_manager.MockReadManager) + // Expectations + mockReader.On("CheckInvariants").Once() + mockRM.On("CheckInvariants").Once() + fh := NewFileHandle(fileInode, nil, false, nil, util.Read, config) + fh.reader = mockReader + fh.readManager = mockRM + + assert.NotPanics(t.T(), func() { + fh.checkInvariants() + }) + + mockReader.AssertExpectations(t.T()) + mockRM.AssertExpectations(t.T()) +} + +func (t *fileTest) TestFileHandle_CheckInvariants_WithNilReaderAndManager() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_check_invariants_nil", nil, false) + + fh := NewFileHandle(in, nil, false, nil, util.Read, config) + + // Should not panic even if both are nil + assert.NotPanics(t.T(), func() { + fh.checkInvariants() + }) +} From b850c32e11d28c652555fe2ce5611c326af47595 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Mon, 28 Jul 2025 09:35:04 +0530 Subject: [PATCH 0607/1298] Update jacobsa/fuse (#3587) This will bring in better error reporting in case of mount failure due to lack of write-access to mount-point --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9df201c532..6cdf4b082a 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.2 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec - github.com/jacobsa/fuse v0.0.0-20250717091043-15503eb5696d + github.com/jacobsa/fuse v0.0.0-20250726160139-b8f47b05858b github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 diff --git a/go.sum b/go.sum index 3a011ed2e7..433d6381db 100644 --- a/go.sum +++ b/go.sum @@ -138,8 +138,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= -github.com/jacobsa/fuse v0.0.0-20250717091043-15503eb5696d h1:e1rS3W2isplX97AJ/MsbTJrVohWGzqXx6nvOx3RTikk= -github.com/jacobsa/fuse v0.0.0-20250717091043-15503eb5696d/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= +github.com/jacobsa/fuse v0.0.0-20250726160139-b8f47b05858b h1:Sx1Oj5dTMB43tAPzgwaJ78ODgFddNVN+AoL5onAMV5k= +github.com/jacobsa/fuse v0.0.0-20250726160139-b8f47b05858b/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= From 6c5d1966902149628a004b8bde9fb598fdd467dd Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 28 Jul 2025 12:20:07 +0530 Subject: [PATCH 0608/1298] ci(read unfinalized objects): Extends tests (add md-cache and file-cache configs) and refactors massively (#3539) * Add more configurations in rapid_appends test suite - add test configurations for metadata cache enable/disable - split test suite between single and dual mounts add configurable options in test change to prefix-check for metadata cache enabled test add more test scenarios pure refactor pure refactor pure refactor pure refactor some cosmetic changes based on self-review fix rebase issue restructure primaryMount and secondaryMount remove unwanted code refactor minor cleanup minor cleanup minor fixes address self-review comments Restrict secondary mount to RapidAppendsSuite This restricts primary mount to TestMain and secondary mount to RapidAppendsSuite class. minor code cleanup split RapidAppendsSuite into SingleMount and DualMount cleanup minor refactor refactor code and clean-up Move testdir creation to after mount for primary mount move saving primary mount logfile to TestMain Revert "move saving primary mount logfile to TestMain" This reverts commit de1b76d990a3b2624b82628c5f90e4db69c7bfb9. address a review comment Address a self-review comment hasSecondaryMount -> enableSecondaryMount Address another review comment Split RapidAppendsSuite into single-mount and dual-mount. check read content size before and after metadata cache ttl expiry address some review comments * Refactor rapid_appends tests cleanup further refactor wip stable passing point clear up fmt printf calls minor refactor another minor refactor more refactor --- .../rapid_appends/appends_test.go | 563 +++++++++--------- .../rapid_appends/reads_after_appends_test.go | 121 ++++ .../rapid_appends/setup_test.go | 70 +-- .../rapid_appends/suites_test.go | 206 +++++++ 4 files changed, 606 insertions(+), 354 deletions(-) create mode 100644 tools/integration_tests/rapid_appends/reads_after_appends_test.go create mode 100644 tools/integration_tests/rapid_appends/suites_test.go diff --git a/tools/integration_tests/rapid_appends/appends_test.go b/tools/integration_tests/rapid_appends/appends_test.go index d29e6da335..11154f4bb1 100644 --- a/tools/integration_tests/rapid_appends/appends_test.go +++ b/tools/integration_tests/rapid_appends/appends_test.go @@ -19,327 +19,316 @@ import ( "os" "path" "syscall" - "testing" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" ) -const numAppends = 3 // Number of appends to perform on test file. -const appendSize = 10 // Size in bytes for each append. -const unfinalizedObjectSize = 10 // Size in bytes of initial unfinalized Object. - -// ////////////////////////////////////////////////////////////////////// -// Boilerplate -// ////////////////////////////////////////////////////////////////////// - -// TODO: Split the suite in two suites single mount and multi-mount. -type RapidAppendsSuite struct { - suite.Suite - fileName string - fileContent string -} - -// ////////////////////////////////////////////////////////////////////// -// Helpers -// ////////////////////////////////////////////////////////////////////// - -func (t *RapidAppendsSuite) SetupSuite() { - setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) - secondaryMntTestDirPath = setup.SetupTestDirectory(testDirName) -} - -func (t *RapidAppendsSuite) TearDownSuite() { - setup.UnmountGCSFuse(secondaryMntRootDir) - if t.T().Failed() { - log.Println("Secondary mount log file:") - setup.SaveGCSFuseLogFileInCaseOfFailure(t.T()) - log.Println("Primary mount log file:") - setup.SetLogFile(primaryMntLogFilePath) - setup.SaveGCSFuseLogFileInCaseOfFailure(t.T()) - } -} - -func (t *RapidAppendsSuite) SetupSubTest() { - t.createUnfinalizedObject() -} - -func (t *RapidAppendsSuite) SetupTest() { - t.createUnfinalizedObject() -} - -func (t *RapidAppendsSuite) createUnfinalizedObject() { - t.fileName = fileNamePrefix + setup.GenerateRandomString(5) - // Create unfinalized object. - t.fileContent = setup.GenerateRandomString(unfinalizedObjectSize) - client.CreateUnfinalizedObject(ctx, t.T(), storageClient, path.Join(testDirName, t.fileName), t.fileContent) -} - -// appendToFile appends "appendContent" to the given file. -func (t *RapidAppendsSuite) appendToFile(file *os.File, appendContent string) { - t.T().Helper() - n, err := file.WriteString(appendContent) - assert.NoError(t.T(), err) - assert.Equal(t.T(), len(appendContent), n) - t.fileContent += appendContent -} +const ( + numAppends = 3 // Number of appends to perform on test file. + appendSize = 10 // Size in bytes for each append. + unfinalizedObjectSize = 10 // Size in bytes of initial unfinalized Object. +) //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// -func (t *RapidAppendsSuite) TestAppendsAndRead() { - testCases := []struct { - name string - readMountPath string - syncNeeded bool - }{ - { - name: "reading_seq_from_same_mount", - readMountPath: primaryMntTestDirPath, - syncNeeded: false, // Sync is not required when reading from the same mount. - }, - { - name: "reading_seq_from_different_mount", - readMountPath: secondaryMntTestDirPath, - syncNeeded: true, // Sync is required for writes to be visible on another mount. - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func() { - // Open the file for appending on the primary mount. - appendFileHandle := operations.OpenFileInMode(t.T(), path.Join(primaryMntTestDirPath, t.fileName), os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) - defer operations.CloseFileShouldNotThrowError(t.T(), appendFileHandle) - readPath := path.Join(tc.readMountPath, t.fileName) - for range numAppends { - t.appendToFile(appendFileHandle, setup.GenerateRandomString(appendSize)) - // Sync the file if the test case requires it. - if tc.syncNeeded { - operations.SyncFile(appendFileHandle, t.T()) - } - - gotContent, err := operations.ReadFile(readPath) - - require.NoError(t.T(), err) - assert.Equal(t.T(), t.fileContent, string(gotContent)) - } - }) +func (t *DualMountAppendsSuite) TestAppendSessionInvalidatedByAnotherClientUponTakeover() { + const initialContent = "dummy content" + const appendContent = "appended content" + for _, flags := range [][]string{ + {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + } { + var err error + func() { + t.mountPrimaryMount(flags) + defer t.unmountPrimaryMount() + + log.Printf("Running tests with flags: %v", flags) + + // Initially create an unfinalized object. + t.createUnfinalizedObject() + defer t.deleteUnfinalizedObject() + + // Initiate an append session using the primary file handle opened in append mode. + appendFileHandle := operations.OpenFileInMode(t.T(), path.Join(t.primaryMount.testDirPath, t.fileName), os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + _, err = appendFileHandle.WriteString(initialContent) + require.NoError(t.T(), err) + + // Open a new file handle from the secondary mount to the same file. + newAppendFileHandle := operations.OpenFileInMode(t.T(), path.Join(t.secondaryMount.testDirPath, t.fileName), os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + defer operations.CloseFileShouldNotThrowError(t.T(), newAppendFileHandle) + + // Attempt to append using the newly opened file handle. + // This append should succeed, confirming the takeover. + _, err = newAppendFileHandle.WriteString(appendContent) + assert.NoError(t.T(), err) + + // Attempt to append using the original file handle. + // This should now fail, as its append session has been invalidated by the takeover. + _, _ = appendFileHandle.WriteString(appendContent) + err = appendFileHandle.Sync() + operations.ValidateESTALEError(t.T(), err) + + // Syncing from the newly created file handle must succeed since it holds the active + // append session. + err = newAppendFileHandle.Sync() + assert.NoError(t.T(), err) + + // Read from primary mount to validate the contents which has persisted in GCS after + // takeover from the secondary mount. + // Close the open append handle before issuing read on the file as Sync() triggered on + // ReadFile() due to BWH still being initialized, is expected to error out with stale NFS file handle. + operations.CloseFileShouldThrowError(t.T(), appendFileHandle) + expectedContent := t.fileContent + appendContent + content, err := operations.ReadFile(path.Join(t.primaryMount.testDirPath, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), expectedContent, string(content)) + }() } } -func (t *RapidAppendsSuite) TestAppendSessionInvalidatedByAnotherClientUponTakeover() { - // Initiate an append session using the primary file handle opened in append mode. - appendFileHandle := operations.OpenFileInMode(t.T(), path.Join(primaryMntTestDirPath, t.fileName), os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) - _, err := appendFileHandle.WriteString(initialContent) - require.NoError(t.T(), err) - - // Open a new file handle from the secondary mount to the same file. - newAppendFileHandle := operations.OpenFileInMode(t.T(), path.Join(secondaryMntTestDirPath, t.fileName), os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) - defer operations.CloseFileShouldNotThrowError(t.T(), newAppendFileHandle) - - // Attempt to append using the newly opened file handle. - // This append should succeed, confirming the takeover. - _, err = newAppendFileHandle.WriteString(appendContent) - assert.NoError(t.T(), err) - - // Attempt to append using the original file handle. - // This should now fail, as its append session has been invalidated by the takeover. - _, _ = appendFileHandle.WriteString(appendContent) - err = appendFileHandle.Sync() - operations.ValidateESTALEError(t.T(), err) - - // Syncing from the newly created file handle must succeed since it holds the active - // append session. - err = newAppendFileHandle.Sync() - assert.NoError(t.T(), err) - - // Read from primary mount to validate the contents which has persisted in GCS after - // takeover from the secondary mount. - // Close the open append handle before issuing read on the file as Sync() triggered on - // ReadFile() due to BWH still being initialized, is expected to error out with stale NFS file handle. - operations.CloseFileShouldThrowError(t.T(), appendFileHandle) - expectedContent := t.fileContent + appendContent - content, err := operations.ReadFile(path.Join(primaryMntTestDirPath, t.fileName)) - require.NoError(t.T(), err) - assert.Equal(t.T(), expectedContent, string(content)) -} - -func (t *RapidAppendsSuite) TestContentAppendedInNonAppendModeNotVisibleTillClose() { +func (t *SingleMountAppendsSuite) TestContentAppendedInNonAppendModeNotVisibleTillClose() { // Skipping test for now until CreateObject() is supported for unfinalized objects. // Ref: b/424253611 t.T().Skip() - initialContent := t.fileContent - // Append to the file from the primary mount in non-append mode - wh, err := os.OpenFile(path.Join(primaryMntTestDirPath, t.fileName), os.O_WRONLY|syscall.O_DIRECT, operations.FilePermission_0600) - require.NoError(t.T(), err) - - // Write sufficient data to the end of file. - data := setup.GenerateRandomString(contentSizeForBW * operations.OneMiB) - n, err := wh.WriteAt([]byte(data), int64(len(initialContent))) - require.NoError(t.T(), err) - require.Equal(t.T(), len(data), n) - - // Read from back-door to validate that appended content is yet not visible on GCS. - contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) - require.NoError(t.T(), err) - assert.Equal(t.T(), initialContent, contentBeforeClose) - - // Close() from primary mount to ensure data persists in GCS. - err = wh.Close() - require.NoError(t.T(), err) - - // Validate that appended content is visible in GCS. - expectedContent := initialContent + data - contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(path.Join(testDirName, t.fileName))) - require.NoError(t.T(), err) - assert.Equal(t.T(), expectedContent, contentAfterClose) + for _, flags := range [][]string{ + {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + } { + func() { + t.mountPrimaryMount(flags) + defer t.unmountPrimaryMount() + + log.Printf("Running tests with flags: %v", flags) + + // Initially create an unfinalized object. + t.createUnfinalizedObject() + defer t.deleteUnfinalizedObject() + + initialContent := t.fileContent + // Append to the file from the primary mount in non-append mode + wh, err := os.OpenFile(path.Join(t.primaryMount.testDirPath, t.fileName), os.O_WRONLY|syscall.O_DIRECT, operations.FilePermission_0600) + require.NoError(t.T(), err) + + // Write sufficient data to the end of file. + data := setup.GenerateRandomString(contentSizeForBW * operations.OneMiB) + n, err := wh.WriteAt([]byte(data), int64(len(initialContent))) + require.NoError(t.T(), err) + require.Equal(t.T(), len(data), n) + + // Read from back-door to validate that appended content is yet not visible on GCS. + contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), initialContent, contentBeforeClose) + + // Close() from primary mount to ensure data persists in GCS. + err = wh.Close() + require.NoError(t.T(), err) + + // Validate that appended content is visible in GCS. + expectedContent := initialContent + data + contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), expectedContent, contentAfterClose) + }() + } } -func (t *RapidAppendsSuite) TestAppendsToFinalizedObjectNotVisibleUntilClose() { - t.fileName = fileNamePrefix + setup.GenerateRandomString(5) - // Create Finalized Object in the GCS bucket. - client.CreateObjectInGCSTestDir( - ctx, storageClient, testDirName, t.fileName, initialContent, t.T()) - - // Append to the finalized object from the primary mount. - data := setup.GenerateRandomString(contentSizeForBW * operations.OneMiB) - filePath := path.Join(primaryMntTestDirPath, t.fileName) - fh, err := os.OpenFile(filePath, os.O_APPEND|os.O_RDWR|syscall.O_DIRECT, operations.FilePermission_0600) - require.NoError(t.T(), err) - _, err = fh.Write([]byte(data)) - require.NoError(t.T(), err) - - // Read from back-door to validate that appended content is yet not visible on GCS. - contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) - require.NoError(t.T(), err) - assert.Equal(t.T(), initialContent, string(contentBeforeClose)) - - // Close the file handle used for appending. - require.NoError(t.T(), fh.Close()) - - // Read from back-door to validate that appended content is now visible on GCS. - expectedContent := initialContent + data - contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) - require.NoError(t.T(), err) - assert.Equal(t.T(), expectedContent, string(contentAfterClose)) +func (t *SingleMountAppendsSuite) TestAppendsToFinalizedObjectNotVisibleUntilClose() { + const initialContent = "dummy content" + for _, flags := range [][]string{ + {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + } { + func() { + t.mountPrimaryMount(flags) + defer t.unmountPrimaryMount() + + log.Printf("Running tests with flags: %v", flags) + + t.fileName = fileNamePrefix + setup.GenerateRandomString(5) + // Create Finalized Object in the GCS bucket. + client.CreateObjectInGCSTestDir( + ctx, storageClient, testDirName, t.fileName, initialContent, t.T()) + + // Append to the finalized object from the primary mount. + data := setup.GenerateRandomString(contentSizeForBW * operations.OneMiB) + filePath := path.Join(t.primaryMount.testDirPath, t.fileName) + fh, err := os.OpenFile(filePath, os.O_APPEND|os.O_RDWR|syscall.O_DIRECT, operations.FilePermission_0600) + require.NoError(t.T(), err) + _, err = fh.Write([]byte(data)) + require.NoError(t.T(), err) + + // Read from back-door to validate that appended content is yet not visible on GCS. + contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), initialContent, string(contentBeforeClose)) + + // Close the file handle used for appending. + require.NoError(t.T(), fh.Close()) + + // Read from back-door to validate that appended content is now visible on GCS. + expectedContent := initialContent + data + contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), expectedContent, string(contentAfterClose)) + }() + } } -func (t *RapidAppendsSuite) TestAppendsVisibleInRealTimeWithConcurrentRPlusHandle() { - primaryPath := path.Join(primaryMntTestDirPath, t.fileName) - // Open first handle in append mode. - appendFileHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) - defer appendFileHandle.Close() - - // Open second handle in "r+" mode. - readHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_RDWR|syscall.O_DIRECT) - defer readHandle.Close() - - // Write initial content using append handle to trigger BW workflow. - n, err := appendFileHandle.Write([]byte(initialContent)) - require.NoError(t.T(), err) - require.NotZero(t.T(), n) - - // Append additional content using "r+" handle. - data := setup.GenerateRandomString(contentSizeForBW * blockSize) - appendOffset := int64(unfinalizedObjectSize + len(initialContent)) - _, err = readHandle.WriteAt([]byte(data), appendOffset) - require.NoError(t.T(), err) - - // Read from back-door to validate visibility on GCS. - // The first 1MiB block is guaranteed to be flushed due to implicit behavior. - // That block includes both the initial content (written via "a" file handle ) - // and some part of data written by the "r+" file handle. - dataInBlockOffset := blockSize - len(initialContent) - expectedContent := t.fileContent + initialContent + data[0:dataInBlockOffset] - contentRead, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) - require.NoError(t.T(), err) - // Assert only on the data which is guranteed to have been uploaded to GCS. - assert.Equal(t.T(), expectedContent, string(contentRead[0:len(expectedContent)])) +func (t *SingleMountAppendsSuite) TestAppendsVisibleInRealTimeWithConcurrentRPlusHandle() { + const initialContent = "dummy content" + for _, flags := range [][]string{ + {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + } { + func() { + t.mountPrimaryMount(flags) + defer t.unmountPrimaryMount() + + log.Printf("Running tests with flags: %v", flags) + + // Initially create an unfinalized object. + t.createUnfinalizedObject() + defer t.deleteUnfinalizedObject() + + primaryPath := path.Join(t.primaryMount.testDirPath, t.fileName) + // Open first handle in append mode. + appendFileHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + defer appendFileHandle.Close() + + // Open second handle in "r+" mode. + readHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_RDWR|syscall.O_DIRECT) + defer readHandle.Close() + + // Write initial content using append handle to trigger BW workflow. + n, err := appendFileHandle.Write([]byte(initialContent)) + require.NoError(t.T(), err) + require.NotZero(t.T(), n) + + // Append additional content using "r+" handle. + data := setup.GenerateRandomString(contentSizeForBW * blockSize) + appendOffset := int64(unfinalizedObjectSize + len(initialContent)) + _, err = readHandle.WriteAt([]byte(data), appendOffset) + require.NoError(t.T(), err) + + // Read from back-door to validate visibility on GCS. + // The first 1MiB block is guaranteed to be flushed due to implicit behavior. + // That block includes both the initial content (written via "a" file handle ) + // and some part of data written by the "r+" file handle. + dataInBlockOffset := blockSize - len(initialContent) + expectedContent := t.fileContent + initialContent + data[0:dataInBlockOffset] + contentRead, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + // Assert only on the data which is guranteed to have been uploaded to GCS. + require.GreaterOrEqual(t.T(), len(contentRead), len(expectedContent)) + assert.Equal(t.T(), expectedContent, string(contentRead[0:len(expectedContent)])) + }() + } } -func (t *RapidAppendsSuite) TestRandomWritesVisibleAfterCloseWithConcurrentRPlusHandle() { +func (t *SingleMountAppendsSuite) TestRandomWritesVisibleAfterCloseWithConcurrentRPlusHandle() { // Skipping test for now until CreateObject() is supported for unfinalized objects. // Ref: b/424253611 t.T().Skip() - primaryPath := path.Join(primaryMntTestDirPath, t.fileName) - // Open first handle in append mode. - appendFileHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) - defer appendFileHandle.Close() - - // Open second handle in "r+" mode. - readHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_RDWR|syscall.O_DIRECT) - - // Write initial content using append handle to trigger BW workflow. - n, err := appendFileHandle.Write([]byte(initialContent)) - require.NoError(t.T(), err) - require.NotZero(t.T(), n) - - // Random write additional content using "r+" handle by writing to incorrect offset. - data := setup.GenerateRandomString(contentSizeForBW * blockSize) - _, err = readHandle.WriteAt([]byte(data), int64(len(initialContent))+1) - require.NoError(t.T(), err) - - // Read from back-door to validate that appended content is yet not visible on GCS before close(). - contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) - require.NoError(t.T(), err) - assert.Equal(t.T(), initialContent, string(contentBeforeClose)) - - // Close the file handle. - readHandle.Close() - - // Read from back-door to validate that appended content is now visible on GCS after close(). - expectedContent := t.fileContent + initialContent + "\x00" + data - contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) - require.NoError(t.T(), err) - assert.Equal(t.T(), expectedContent, contentAfterClose) + const initialContent = "dummy content" + for _, flags := range [][]string{ + {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + } { + func() { + t.mountPrimaryMount(flags) + defer t.unmountPrimaryMount() + + log.Printf("Running tests with flags: %v", flags) + + // Initially create an unfinalized object. + t.createUnfinalizedObject() + defer t.deleteUnfinalizedObject() + + primaryPath := path.Join(t.primaryMount.testDirPath, t.fileName) + // Open first handle in append mode. + appendFileHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + defer appendFileHandle.Close() + + // Open second handle in "r+" mode. + readHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_RDWR|syscall.O_DIRECT) + + // Write initial content using append handle to trigger BW workflow. + n, err := appendFileHandle.Write([]byte(initialContent)) + require.NoError(t.T(), err) + require.NotZero(t.T(), n) + + // Random write additional content using "r+" handle by writing to incorrect offset. + data := setup.GenerateRandomString(contentSizeForBW * blockSize) + _, err = readHandle.WriteAt([]byte(data), int64(len(initialContent))+1) + require.NoError(t.T(), err) + + // Read from back-door to validate that appended content is yet not visible on GCS before close(). + contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), initialContent, string(contentBeforeClose)) + + // Close the file handle. + readHandle.Close() + + // Read from back-door to validate that appended content is now visible on GCS after close(). + expectedContent := t.fileContent + initialContent + "\x00" + data + contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), expectedContent, contentAfterClose) + }() + } } -func (t *RapidAppendsSuite) TestFallbackHappensWhenNonAppendHandleDoesFirstWrite() { +func (t *SingleMountAppendsSuite) TestFallbackHappensWhenNonAppendHandleDoesFirstWrite() { // Skipping test for now until CreateObject() is supported for unfinalized objects. // Ref: b/424253611 t.T().Skip() - primaryPath := path.Join(primaryMntTestDirPath, t.fileName) - // Open first handle in append mode. - appendFileHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) - defer appendFileHandle.Close() - - // Open second handle in "r+" mode. - readHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_RDWR|syscall.O_DIRECT) - - // Append content using "r+" handle. - data := setup.GenerateRandomString(contentSizeForBW * blockSize) - n, err := readHandle.WriteAt([]byte(data), int64(len(t.fileContent))) - require.NoError(t.T(), err) - assert.NotZero(t.T(), n) - - // Read from back-door to validate that appended content is yet not visible on GCS before close(). - contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) - require.NoError(t.T(), err) - assert.Equal(t.T(), t.fileContent, contentBeforeClose) - - // Close the file handle. - readHandle.Close() - - // Read from back-door to validate that appended content is now visible on GCS after close(). - expectedContent := t.fileContent + data - contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) - require.NoError(t.T(), err) - assert.Equal(t.T(), expectedContent, contentAfterClose) -} - -//////////////////////////////////////////////////////////////////////// -// Test Function (Runs once before all tests) -//////////////////////////////////////////////////////////////////////// - -func TestRapidAppendsSuite(t *testing.T) { - rapidAppendsSuite := new(RapidAppendsSuite) - suite.Run(t, rapidAppendsSuite) + for _, flags := range [][]string{ + {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + } { + func() { + t.mountPrimaryMount(flags) + defer t.unmountPrimaryMount() + + log.Printf("Running tests with flags: %v", flags) + + // Initially create an unfinalized object. + t.createUnfinalizedObject() + defer t.deleteUnfinalizedObject() + + primaryPath := path.Join(t.primaryMount.testDirPath, t.fileName) + // Open first handle in append mode. + appendFileHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + defer appendFileHandle.Close() + + // Open second handle in "r+" mode. + readHandle := operations.OpenFileInMode(t.T(), primaryPath, os.O_RDWR|syscall.O_DIRECT) + + // Append content using "r+" handle. + data := setup.GenerateRandomString(contentSizeForBW * blockSize) + n, err := readHandle.WriteAt([]byte(data), int64(len(t.fileContent))) + require.NoError(t.T(), err) + assert.NotZero(t.T(), n) + + // Read from back-door to validate that appended content is yet not visible on GCS before close(). + contentBeforeClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), t.fileContent, contentBeforeClose) + + // Close the file handle. + readHandle.Close() + + // Read from back-door to validate that appended content is now visible on GCS after close(). + expectedContent := t.fileContent + data + contentAfterClose, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) + require.NoError(t.T(), err) + assert.Equal(t.T(), expectedContent, contentAfterClose) + }() + } } diff --git a/tools/integration_tests/rapid_appends/reads_after_appends_test.go b/tools/integration_tests/rapid_appends/reads_after_appends_test.go new file mode 100644 index 0000000000..e8b7cfb104 --- /dev/null +++ b/tools/integration_tests/rapid_appends/reads_after_appends_test.go @@ -0,0 +1,121 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rapid_appends + +import ( + "fmt" + "log" + "os" + "path" + "syscall" + "time" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *CommonAppendsSuite) TestAppendsAndReads() { + const metadataCacheTTLSecs = 10 + metadataCacheEnableFlag := fmt.Sprintf("%s%v", "--metadata-cache-ttl-secs=", metadataCacheTTLSecs) + fileCacheDirFlag := func() string { + return "--cache-dir=" + getNewEmptyCacheDir(t.primaryMount.rootDir) + } + + testCases := []struct { + name string + }{ + { + name: "SequentialRead", + }, + } + + for _, scenario := range []struct { + enableMetadataCache bool + enableFileCache bool + flags []string + }{{ + // all cache disabled + enableMetadataCache: false, + enableFileCache: false, + flags: []string{"--write-experimental-enable-rapid-appends=true", "--write-global-max-blocks=-1", "--metadata-cache-ttl-secs=0"}, + }, { + enableMetadataCache: true, + enableFileCache: false, + flags: []string{"--write-experimental-enable-rapid-appends=true", "--write-global-max-blocks=-1", metadataCacheEnableFlag}, + }, { + enableMetadataCache: true, + enableFileCache: true, + flags: []string{"--write-experimental-enable-rapid-appends=true", "--write-global-max-blocks=-1", metadataCacheEnableFlag, "--file-cache-max-size-mb=-1", fileCacheDirFlag()}, + }, { + enableMetadataCache: false, + enableFileCache: true, + flags: []string{"--write-experimental-enable-rapid-appends=true", "--write-global-max-blocks=-1", "--metadata-cache-ttl-secs=0", "--file-cache-max-size-mb=-1", fileCacheDirFlag()}, + }} { + func() { + t.mountPrimaryMount(scenario.flags) + defer t.unmountPrimaryMount() + + log.Printf("Running tests with flags: %v", scenario.flags) + + for _, tc := range testCases { + t.Run(tc.name, func() { + // Initially create an unfinalized object. + t.createUnfinalizedObject() + defer t.deleteUnfinalizedObject() + + // Open this object as a file for appending on the appropriate mount. + appendFileHandle := operations.OpenFileInMode(t.T(), path.Join(t.appendMountPath, t.fileName), os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + defer operations.CloseFileShouldNotThrowError(t.T(), appendFileHandle) + + readPath := path.Join(t.primaryMount.testDirPath, t.fileName) + for i := range numAppends { + sizeBeforeAppend := len(t.fileContent) + t.appendToFile(appendFileHandle, setup.GenerateRandomString(appendSize)) + sizeAfterAppend := len(t.fileContent) + + gotContent, err := operations.ReadFile(readPath) + + require.NoError(t.T(), err) + readContent := string(gotContent) + // If metadata cache is enabled, gcsfuse reads up to the cached file size. + // For same-mount appends/reads, file size is always current. + // The initial read (i=0) bypasses cache, seeing the latest file size. + if !scenario.enableMetadataCache || !t.isSyncNeededAfterAppend || (i == 0) { + assert.Equalf(t.T(), t.fileContent, readContent, "failed to match full content in non-metadata-cache/single-mount after %v appends", i+1) + } else { + // Read only up to the cached file size (before append). + assert.Equalf(t.T(), t.fileContent[:sizeBeforeAppend], readContent, "failed to match partial content in metadata-cache dual-mount after %v appends", i+1) + + // Wait for metadata cache to expire to fetch the latest size for the next read. + time.Sleep(time.Duration(metadataCacheTTLSecs) * time.Second) + gotContent, err = operations.ReadFile(readPath) + + // Expect read up to the latest file size which is the size after the append. + require.NoError(t.T(), err) + readContent = string(gotContent) + assert.Equalf(t.T(), t.fileContent[:sizeAfterAppend], readContent, "failed to match full content in metadata-cache dual-mount after %v appends", i+1) + } + } + }) + } + }() + } +} diff --git a/tools/integration_tests/rapid_appends/setup_test.go b/tools/integration_tests/rapid_appends/setup_test.go index dfc203ee7b..71cc72dc1a 100644 --- a/tools/integration_tests/rapid_appends/setup_test.go +++ b/tools/integration_tests/rapid_appends/setup_test.go @@ -31,36 +31,16 @@ import ( const ( testDirName = "RapidAppendsTest" fileNamePrefix = "rapid-append-file-" - initialContent = "dummy content" - appendContent = "appended content" // Minimum content size to write in order to trigger block upload while writing ; calculated as (2*blocksize+1) mb. - // Block size for buffered writes is set to 1MiB. contentSizeForBW = 3 - blockSize = operations.OneMiB + // Block size for buffered writes is set to 1MiB. + blockSize = operations.OneMiB ) var ( - // Flags for mount options for primaryMntRootDir - flags []string // Mount function to be used for the mounting. mountFunc func([]string) error - // Globals for primary mount which is used to append content to files. - // Other Root directory which is mounted by gcsfuse for multi-mount scenarios. - primaryMntRootDir string - // Stores test directory path in the mounted path for primaryMntRootDir. - primaryMntTestDirPath string - // Stores log file path for the mount primaryMntRootDir. - primaryMntLogFilePath string - - // Globals for secondary mount which is used to verify reads on existing unfinalized objects. - // Root directory which is mounted by gcsfuse. - secondaryMntRootDir string - // Stores test directory path in the mounted path for secondaryMntRootDir. - secondaryMntTestDirPath string - // Stores log file path for the mount secondaryMntRootDir. - secondaryMntLogFilePath string - // Clients to create the object in GCS. storageClient *storage.Client ctx context.Context @@ -90,53 +70,9 @@ func TestMain(m *testing.M) { } }() - // Set up test directory for primary mount. - setup.SetUpTestDirForTestBucketFlag() - primaryMntRootDir = setup.MntDir() - primaryMntLogFilePath = setup.LogFile() - // TODO(b/432179045): `--write-global-max-blocks=-1` is needed right now because of a bug in global semaphore release. - // Remove this flag once bug is fixed. - primaryMountFlags := []string{"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0", "--write-global-max-blocks=-1", "--write-block-size-mb=1"} - err := static_mounting.MountGcsfuseWithStaticMounting(primaryMountFlags) - if err != nil { - log.Fatalf("Unable to mount primary mount: %v", err) - } - // Setup Package Test Directory for primary mount. - primaryMntTestDirPath = setup.SetupTestDirectory(testDirName) - defer setup.UnmountGCSFuse(primaryMntRootDir) - - // Set up test directory for secondary mount. - setup.SetUpTestDirForTestBucketFlag() - secondaryMntRootDir = setup.MntDir() - secondaryMntLogFilePath = setup.LogFile() - rapidAppendsCacheDir, err := os.MkdirTemp("", "rapid_appends_cache_dir_*") - if err != nil { - log.Fatalf("Failed to create cache dir for rapid append tests: %v", err) - } - defer func() { - err := os.RemoveAll(rapidAppendsCacheDir) - if err != nil { - log.Fatalf("Error while cleaning up cache dir %q: %v", rapidAppendsCacheDir, err) - } - }() - // Define flag set for secondary mount to run the tests. - flagsSet := [][]string{ - {"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0", "--write-block-size-mb=1"}, - {"--write-experimental-enable-rapid-appends=true", "--metadata-cache-ttl-secs=0", "--write-block-size-mb=1", "--file-cache-max-size-mb=-1", "--cache-dir=" + rapidAppendsCacheDir}, - } - log.Println("Running static mounting tests...") mountFunc = static_mounting.MountGcsfuseWithStaticMounting - - var successCode int - for i := range flagsSet { - log.Printf("Running tests with flags: %v", flagsSet[i]) - flags = flagsSet[i] - successCode = m.Run() - if successCode != 0 { - break - } - } + successCode := m.Run() setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) os.Exit(successCode) } diff --git a/tools/integration_tests/rapid_appends/suites_test.go b/tools/integration_tests/rapid_appends/suites_test.go new file mode 100644 index 0000000000..5414c9e470 --- /dev/null +++ b/tools/integration_tests/rapid_appends/suites_test.go @@ -0,0 +1,206 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rapid_appends + +import ( + "log" + "os" + "path" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +// Struct to store the details of a mount point +type mountPoint struct { + rootDir string // Root directory of the test folder, which contains mnt and gcsfuse.log. + mntDir string // Directory where the GCS bucket is mounted. This is 'mnt' inside rootDir. + testDirPath string // Path to the 'RapidAppendsTest' directory inside mntDir. + logFilePath string // Path to the GCSFuse log file. This is gcsfuse.log inside rootDir. +} + +var ( + // TODO(b/432179045): `--write-global-max-blocks=-1` is needed right now because of a bug in global semaphore release. + secondaryMountFlags = []string{"--write-experimental-enable-rapid-appends=true", "--write-global-max-blocks=-1"} +) + +// ////////////////////////////////////////////////////////////////////// +// Suite Setup and Teardown +// ////////////////////////////////////////////////////////////////////// + +// CommonAppendsSuite is the base suite for rapid appends tests. +// Adding any tests (.e. Test*() member functions) to this struct will add it +// for all structs that encapsulate it, e.g. SingleMountAppendsSuite. +type CommonAppendsSuite struct { + suite.Suite + primaryMount mountPoint + fileName string + fileContent string + isSyncNeededAfterAppend bool + appendMountPath string +} + +func (t *CommonAppendsSuite) SetupSuite() { + t.primaryMount.setupTestDir() +} + +func (t *CommonAppendsSuite) TearDownSuite() { + t.tearDownMount(&t.primaryMount) +} + +// SingleMountAppendsSuite is the suite for rapid appends tests with a single mount, +// i.e. primary mount. +// Adding any tests (.e. Test*() member functions) to this struct will run +// only for single-mount. +type SingleMountAppendsSuite struct { + CommonAppendsSuite +} + +func (t *SingleMountAppendsSuite) SetupSuite() { + t.CommonAppendsSuite.SetupSuite() + // Set up appends to work through secondary mount. + t.appendMountPath = t.primaryMount.testDirPath + // fsync is not needed after append if append is from the same mount point as the read mount point. + t.isSyncNeededAfterAppend = false +} + +func (t *SingleMountAppendsSuite) TearDownSuite() { + t.CommonAppendsSuite.TearDownSuite() +} + +// DualMountAppendsSuite is the suite for rapid appends tests with two mounts, +// primary for reading and secondary for writing/appending. +// Adding any tests (.e. Test*() member functions) to this struct will run +// only for dual-mount. +type DualMountAppendsSuite struct { + CommonAppendsSuite + secondaryMount mountPoint +} + +func (t *DualMountAppendsSuite) SetupSuite() { + t.CommonAppendsSuite.SetupSuite() + t.secondaryMount.setupTestDir() + t.mountSecondaryMount(secondaryMountFlags) + + t.appendMountPath = t.secondaryMount.testDirPath + // fsync is needed after append if append is from a different mount point (secondary mount) from the read mount point (primary mount). + t.isSyncNeededAfterAppend = true +} + +func (t *DualMountAppendsSuite) TearDownSuite() { + t.unmountSecondaryMount() + t.tearDownMount(&t.secondaryMount) + t.CommonAppendsSuite.TearDownSuite() +} + +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func (mnt *mountPoint) setupTestDir() { + setup.SetUpTestDirForTestBucketFlag() + mnt.rootDir = setup.TestDir() + mnt.mntDir = setup.MntDir() + mnt.logFilePath = setup.LogFile() + mnt.testDirPath = path.Join(setup.MntDir(), testDirName) +} + +func (t *CommonAppendsSuite) tearDownMount(mnt *mountPoint) { + if t.T().Failed() { + setup.SetLogFile(mnt.logFilePath) + log.Printf("Saving mnt at %q mount log file %q ...", t.primaryMount.mntDir, t.primaryMount.logFilePath) + setup.SaveGCSFuseLogFileInCaseOfFailure(t.T()) + } +} + +func (t *CommonAppendsSuite) deleteUnfinalizedObject() { + if t.fileName != "" { + err := os.Remove(path.Join(t.primaryMount.testDirPath, t.fileName)) + require.NoError(t.T(), err) + t.fileName = "" + } +} + +func (t *CommonAppendsSuite) createUnfinalizedObject() { + t.fileName = fileNamePrefix + setup.GenerateRandomString(5) + // Create unfinalized object. + t.fileContent = setup.GenerateRandomString(unfinalizedObjectSize) + client.CreateUnfinalizedObject(ctx, t.T(), storageClient, path.Join(testDirName, t.fileName), t.fileContent) +} + +func (t *CommonAppendsSuite) mountPrimaryMount(flags []string) { + // Create primary mountpoint. + setup.SetMntDir(t.primaryMount.mntDir) + setup.SetLogFile(t.primaryMount.logFilePath) + err := static_mounting.MountGcsfuseWithStaticMounting(flags) + require.NoError(t.T(), err, "Unable to mount gcsfuse with flags %v: %v", flags, err) + setup.SetupTestDirectory(testDirName) +} + +func (t *CommonAppendsSuite) unmountPrimaryMount() { + setup.UnmountGCSFuse(t.primaryMount.mntDir) +} + +func (t *DualMountAppendsSuite) mountSecondaryMount(flags []string) { + // Create secondary mountpoint. + setup.SetMntDir(t.secondaryMount.mntDir) + setup.SetLogFile(t.secondaryMount.logFilePath) + err := static_mounting.MountGcsfuseWithStaticMounting(flags) + require.NoError(t.T(), err, "Unable to mount gcsfuse with flags %v: %v", flags, err) + //setup.SetupTestDirectory(testDirName) + t.secondaryMount.testDirPath = setup.SetupTestDirectory(testDirName) +} + +func (t *DualMountAppendsSuite) unmountSecondaryMount() { + setup.UnmountGCSFuse(t.secondaryMount.mntDir) +} + +// appendToFile appends the given "appendContent" to the given file. +func (t *CommonAppendsSuite) appendToFile(file *os.File, appendContent string) { + t.T().Helper() + n, err := file.WriteString(appendContent) + assert.NoError(t.T(), err) + assert.Equal(t.T(), len(appendContent), n) + t.fileContent += appendContent + if t.isSyncNeededAfterAppend { + operations.SyncFile(file, t.T()) + } +} + +func getNewEmptyCacheDir(rootDir string) string { + cacheDirPath, err := os.MkdirTemp(rootDir, "cache_dir_*") + if err != nil { + log.Fatalf("Failed to create temporary directory for cache dir for tests: %v", err) + } + return cacheDirPath +} + +//////////////////////////////////////////////////////////////////////// +// Test Functions (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestSingleMountAppendsSuite(t *testing.T) { + suite.Run(t, new(SingleMountAppendsSuite)) +} + +func TestDualMountAppendsSuite(t *testing.T) { + suite.Run(t, new(DualMountAppendsSuite)) +} From bb690df88fdd86a3b2f331c0a4a6750aeb8708f3 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 28 Jul 2025 15:09:57 +0530 Subject: [PATCH 0609/1298] chore: add separate comments for gemini review and gemini summary (#3582) --- ...uest-gemini-review-on-pr-read-for-review.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/request-gemini-review-on-pr-read-for-review.yml b/.github/workflows/request-gemini-review-on-pr-read-for-review.yml index 08700bcc1d..411d7528dd 100644 --- a/.github/workflows/request-gemini-review-on-pr-read-for-review.yml +++ b/.github/workflows/request-gemini-review-on-pr-read-for-review.yml @@ -30,10 +30,19 @@ jobs: permissions: pull-requests: write steps: - - name: Add Gemini review and summary comment + - name: Add Gemini review comment uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} - body: | - /gemini summary - /gemini review + body: "/gemini review" + add-gemini-comment: + if: github.base_ref == 'master' + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - name: Add Gemini summary comment + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + body: "/gemini summary" From 860ba33c614654afe6c143c9c9459f184cd73dd2 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 28 Jul 2025 16:35:14 +0530 Subject: [PATCH 0610/1298] refactor: removing cancelled block state (#3597) --- internal/block/prefetch_block.go | 7 +++---- internal/block/prefetch_block_test.go | 5 ----- internal/bufferedread/buffered_reader_test.go | 2 +- internal/bufferedread/download_task.go | 2 +- internal/bufferedread/download_task_test.go | 8 ++++---- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/internal/block/prefetch_block.go b/internal/block/prefetch_block.go index ebc1a2dff9..6ee6d26478 100644 --- a/internal/block/prefetch_block.go +++ b/internal/block/prefetch_block.go @@ -33,10 +33,9 @@ type BlockStatus struct { type BlockState int const ( - BlockStateInProgress BlockState = iota // Download of this block is in progress - BlockStateDownloaded // Download of this block is complete - BlockStateDownloadFailed // Download of this block has failed - BlockStateDownloadCancelled // Download of this block has been cancelled + BlockStateInProgress BlockState = iota // Download of this block is in progress + BlockStateDownloaded // Download of this block is complete + BlockStateDownloadFailed // Download of this block has failed ) type PrefetchBlock interface { diff --git a/internal/block/prefetch_block_test.go b/internal/block/prefetch_block_test.go index 9f61458517..b676f30588 100644 --- a/internal/block/prefetch_block_test.go +++ b/internal/block/prefetch_block_test.go @@ -213,11 +213,6 @@ func (testSuite *PrefetchMemoryBlockTest) TestAwaitReadyNotifyVariants() { notifyStatus: BlockStatus{State: BlockStateDownloadFailed}, wantStatus: BlockStatus{State: BlockStateDownloadFailed}, }, - { - name: "AfterNotifyCancelled", - notifyStatus: BlockStatus{State: BlockStateDownloadCancelled}, - wantStatus: BlockStatus{State: BlockStateDownloadCancelled}, - }, } for _, tt := range tests { diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index ef7042828e..421043b038 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -164,7 +164,7 @@ func (t *BufferedReaderTest) TestDestroyAwaitReadyError() { cancel: func() {}, }) - b.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadCancelled, Err: errors.New("test error")}) + b.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadFailed, Err: errors.New("test error")}) reader.Destroy() assert.Nil(t.T(), reader.cancelFunc) diff --git a/internal/bufferedread/download_task.go b/internal/bufferedread/download_task.go index afeea72876..692a0a59cd 100644 --- a/internal/bufferedread/download_task.go +++ b/internal/bufferedread/download_task.go @@ -71,7 +71,7 @@ func (p *DownloadTask) Execute() { p.block.NotifyReady(block.BlockStatus{State: block.BlockStateDownloaded}) } else if errors.Is(err, context.Canceled) && p.ctx.Err() == context.Canceled { logger.Tracef("Download: -> block (%s, %v) cancelled: %v.", p.object.Name, blockId, err) - p.block.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadCancelled}) + p.block.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadFailed, Err: err}) } else { logger.Errorf("Download: -> block (%s, %v) failed: %v.", p.object.Name, blockId, err) p.block.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadFailed, Err: err}) diff --git a/internal/bufferedread/download_task_test.go b/internal/bufferedread/download_task_test.go index 3b4660924f..11be9171c3 100644 --- a/internal/bufferedread/download_task_test.go +++ b/internal/bufferedread/download_task_test.go @@ -184,8 +184,8 @@ func (dts *DownloadTaskTestSuite) TestExecuteContextCancelledWhileReaderCreation defer cancelFunc() status, err := downloadBlock.AwaitReady(ctx) assert.NoError(dts.T(), err) - assert.Equal(dts.T(), block.BlockStateDownloadCancelled, status.State) - assert.NoError(dts.T(), status.Err) + assert.Equal(dts.T(), block.BlockStateDownloadFailed, status.State) + assert.ErrorIs(dts.T(), status.Err, context.Canceled) } // ctxCancelledReader is a mock reader that simulates a context cancellation error while reading. @@ -228,6 +228,6 @@ func (dts *DownloadTaskTestSuite) TestExecuteContextCancelledWhileReadingFromRea ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) defer cancelFunc() status, err := downloadBlock.AwaitReady(ctx) - assert.Equal(dts.T(), block.BlockStatus{State: block.BlockStateDownloadCancelled}, status) - assert.NoError(dts.T(), err) + assert.Equal(dts.T(), block.BlockStateDownloadFailed, status.State) + assert.ErrorIs(dts.T(), status.Err, context.Canceled) } From 73071de6f67b682e796dcae4e218995bf26ec2d4 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 29 Jul 2025 08:40:37 +0530 Subject: [PATCH 0611/1298] chore(deps): Upgrade direct dependencies (#3567) * Upgrade dependency except gax * Upgrade more libs * Upgrade more deps * Upgrade more deps * Upgrade more deps * Upgrade more deps * Upgrade more deps * Upgrade more deps * Upgrade more deps * More deps upgrade --- go.mod | 66 ++++++++++++++------------- go.sum | 139 ++++++++++++++++++++++++++++++--------------------------- 2 files changed, 106 insertions(+), 99 deletions(-) diff --git a/go.mod b/go.mod index 6cdf4b082a..08205beb05 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,17 @@ module github.com/googlecloudplatform/gcsfuse/v3 go 1.24.0 require ( - cloud.google.com/go/auth v0.16.1 + cloud.google.com/go/auth v0.16.3 cloud.google.com/go/auth/oauth2adapt v0.2.8 cloud.google.com/go/compute/metadata v0.7.0 cloud.google.com/go/iam v1.5.2 - cloud.google.com/go/profiler v0.4.2 - cloud.google.com/go/secretmanager v1.14.7 + cloud.google.com/go/profiler v0.4.3 + cloud.google.com/go/secretmanager v1.15.0 cloud.google.com/go/storage v1.55.0 - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0 - github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.28.0 + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.29.0 github.com/fsouza/fake-gcs-server v1.52.2 - github.com/go-viper/mapstructure/v2 v2.3.0 + github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.14.2 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec @@ -27,28 +27,28 @@ require ( github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_model v0.6.2 - github.com/prometheus/common v0.64.0 + github.com/prometheus/common v0.65.0 github.com/spf13/cobra v1.9.1 - github.com/spf13/pflag v1.0.6 + github.com/spf13/pflag v1.0.7 github.com/spf13/viper v1.20.1 github.com/stretchr/testify v1.10.0 go.opencensus.io v0.24.0 - go.opentelemetry.io/contrib/detectors/gcp v1.36.0 - go.opentelemetry.io/otel v1.36.0 - go.opentelemetry.io/otel/exporters/prometheus v0.58.0 - go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 - go.opentelemetry.io/otel/metric v1.36.0 - go.opentelemetry.io/otel/sdk v1.36.0 - go.opentelemetry.io/otel/sdk/metric v1.36.0 - go.opentelemetry.io/otel/trace v1.36.0 - golang.org/x/net v0.41.0 + go.opentelemetry.io/contrib/detectors/gcp v1.37.0 + go.opentelemetry.io/otel v1.37.0 + go.opentelemetry.io/otel/exporters/prometheus v0.59.1 + go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 + go.opentelemetry.io/otel/metric v1.37.0 + go.opentelemetry.io/otel/sdk v1.37.0 + go.opentelemetry.io/otel/sdk/metric v1.37.0 + go.opentelemetry.io/otel/trace v1.37.0 + golang.org/x/net v0.42.0 golang.org/x/oauth2 v0.30.0 - golang.org/x/sync v0.15.0 - golang.org/x/sys v0.33.0 - golang.org/x/text v0.26.0 - golang.org/x/time v0.11.0 - google.golang.org/api v0.235.0 - google.golang.org/grpc v1.72.2 + golang.org/x/sync v0.16.0 + golang.org/x/sys v0.34.0 + golang.org/x/text v0.27.0 + golang.org/x/time v0.12.0 + google.golang.org/api v0.239.0 + google.golang.org/grpc v1.74.2 google.golang.org/protobuf v1.36.6 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 @@ -56,13 +56,13 @@ require ( require ( cel.dev/expr v0.24.0 // indirect - cloud.google.com/go v0.121.2 // indirect + cloud.google.com/go v0.121.4 // indirect cloud.google.com/go/longrunning v0.6.7 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect cloud.google.com/go/pubsub v1.49.0 // indirect cloud.google.com/go/trace v1.11.6 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.28.0 // indirect - github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect @@ -75,19 +75,21 @@ require ( github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect - github.com/google/pprof v0.0.0-20250501235452-c0086092b71a // indirect + github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 // indirect github.com/google/renameio/v2 v2.0.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/xattr v0.4.10 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/otlptranslator v0.0.0-20250717125610-8549f4ab4f8f // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/sagikazarmark/locafero v0.9.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.14.0 // indirect @@ -100,8 +102,8 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.39.0 // indirect - google.golang.org/genproto v0.0.0-20250528174236-200df99c418a // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect + golang.org/x/crypto v0.40.0 // indirect + google.golang.org/genproto v0.0.0-20250721164621-a45f3dfb1074 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 // indirect ) diff --git a/go.sum b/go.sum index 433d6381db..70bdd56414 100644 --- a/go.sum +++ b/go.sum @@ -1,10 +1,10 @@ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.121.2 h1:v2qQpN6Dx9x2NmwrqlesOt3Ys4ol5/lFZ6Mg1B7OJCg= -cloud.google.com/go v0.121.2/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw= -cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU= -cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI= +cloud.google.com/go v0.121.4 h1:cVvUiY0sX0xwyxPwdSU2KsF9knOVmtRyAMt8xou0iTs= +cloud.google.com/go v0.121.4/go.mod h1:XEBchUiHFJbz4lKBZwYBDHV/rSyfFktk737TLDU089s= +cloud.google.com/go/auth v0.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc= +cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= @@ -19,27 +19,27 @@ cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFs cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY= cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM= cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U= -cloud.google.com/go/profiler v0.4.2 h1:KojCmZ+bEPIQrd7bo2UFvZ2xUPLHl55KzHl7iaR4V2I= -cloud.google.com/go/profiler v0.4.2/go.mod h1:7GcWzs9deJHHdJ5J9V1DzKQ9JoIoTGhezwlLbwkOoCs= +cloud.google.com/go/profiler v0.4.3 h1:IY3QNKlr8VbXwGWHcZbJQsMA/83ZTH6uAHf8jYyj7OI= +cloud.google.com/go/profiler v0.4.3/go.mod h1:3xFodugWfPIQZWFcXdUmfa+yTiiyQ8fWrdT+d2Sg4J0= cloud.google.com/go/pubsub v1.49.0 h1:5054IkbslnrMCgA2MAEPcsN3Ky+AyMpEZcii/DoySPo= cloud.google.com/go/pubsub v1.49.0/go.mod h1:K1FswTWP+C1tI/nfi3HQecoVeFvL4HUOB1tdaNXKhUY= -cloud.google.com/go/secretmanager v1.14.7 h1:VkscIRzj7GcmZyO4z9y1EH7Xf81PcoiAo7MtlD+0O80= -cloud.google.com/go/secretmanager v1.14.7/go.mod h1:uRuB4F6NTFbg0vLQ6HsT7PSsfbY7FqHbtJP1J94qxGc= +cloud.google.com/go/secretmanager v1.15.0 h1:RtkCMgTpaBMbzozcRUGfZe46jb9a3qh5EdEtVRUATF8= +cloud.google.com/go/secretmanager v1.15.0/go.mod h1:1hQSAhKK7FldiYw//wbR/XPfPc08eQ81oBsnRUHEvUc= cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0= cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.28.0 h1:VaFXBL0NJpiFBtw4aVJpKHeKULVTcHpD+/G0ibZkcBw= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.28.0/go.mod h1:JXkPazkEc/dZTHzOlzv2vT1DlpWSTbSLmu/1KY6Ly0I= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0 h1:QFgWzcdmJlgEAwJz/zePYVJQxfoJGRtgIqZfIUFg5oQ= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.52.0/go.mod h1:ayYHuYU7iNcNtEs1K9k6D/Bju7u1VEHMQm5qQ1n3GtM= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.28.0 h1:RC78FvsZ8rLRLgVQuw1jMJ8d6t38QgOv3hDoUVGD50U= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.28.0/go.mod h1:03+QlJ+6zSrBaVaZ9K87fzUyKBDcAh0X1n1Vxq3XAjc= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.52.0 h1:0l8ynskVvq1dvIn5vJbFMf/a/3TqFpRmCMrruFbzlvk= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.52.0/go.mod h1:f/ad5NuHnYz8AOZGuR0cY+l36oSCstdxD73YlIchr6I= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0 h1:wbMd4eG/fOhsCa6+IP8uEDvWF5vl7rNoUWmP5f72Tbs= -github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.52.0/go.mod h1:gdIm9TxRk5soClCwuB0FtdXsbqtw0aqPwBEurK9tPkw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 h1:owcC2UnmsZycprQ5RfRgjydWhuoxg71LUfyiQdijZuM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0/go.mod h1:ZPpqegjbE99EPKsu3iUWV22A04wzGPcAY/ziSIQEEgs= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.29.0 h1:YVtMlmfRUTaWs3+1acwMBp7rBUo6zrxl6Kn13/R9YW4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.29.0/go.mod h1:rKOFVIPbNs2wZeh7ZeQ0D9p/XLgbNiTr5m7x6KuAshk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0 h1:4LP6hvB4I5ouTbGgWtixJhgED6xdf67twf9PoY96Tbg= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.53.0/go.mod h1:jUZ5LYlw40WMd07qxcQJD5M40aUxrfwqQX1g7zxYnrQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 h1:Ron4zCA/yk6U7WOBXhTJcDpsUBG9npumK6xw2auFltQ= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -85,8 +85,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= -github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -94,8 +94,8 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= @@ -117,8 +117,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= -github.com/google/pprof v0.0.0-20250501235452-c0086092b71a h1:rDA3FfmxwXR+BVKKdz55WwMJ1pD2hJQNW31d+l3mPk4= -github.com/google/pprof v0.0.0-20250501235452-c0086092b71a/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= +github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5 h1:xhMrHhTJ6zxu3gA4enFM9MLn9AY7613teCdFnlUVbSQ= +github.com/google/pprof v0.0.0-20250630185457-6e76a2b096b5/go.mod h1:5hDyRhoBCxViHszMt12TnOpEI4VVi+U8Gm9iphldiMA= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= @@ -134,6 +134,8 @@ github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyE github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc h1:GN2Lv3MGO7AS6PrRoT6yV5+wkrOpcszoIsO4+4ds248= +github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc/go.mod h1:+JKpmjMGhpgPL+rXZ5nsZieVzvarn86asRlBg4uNGnk= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= @@ -186,10 +188,12 @@ github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= -github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/otlptranslator v0.0.0-20250717125610-8549f4ab4f8f h1:QQB6SuvGZjK8kdc2YaLJpYhV8fxauOsjE6jgcL6YJ8Q= +github.com/prometheus/otlptranslator v0.0.0-20250717125610-8549f4ab4f8f/go.mod h1:P8AwMgdD7XEr6QRUJ2QWLpiAZTgTE2UYgjlu3svompI= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= @@ -205,8 +209,9 @@ github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= @@ -231,36 +236,36 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= -go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/detectors/gcp v1.37.0 h1:B+WbN9RPsvobe6q4vP6KgM8/9plR/HNjgGBrfcOlweA= +go.opentelemetry.io/contrib/detectors/gcp v1.37.0/go.mod h1:K5zQ3TT7p2ru9Qkzk0bKtCql0RGkPj9pRjpXgZJZ+rU= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= -go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= -go.opentelemetry.io/otel/exporters/prometheus v0.58.0 h1:CJAxWKFIqdBennqxJyOgnt5LqkeFRT+Mz3Yjz3hL+h8= -go.opentelemetry.io/otel/exporters/prometheus v0.58.0/go.mod h1:7qo/4CLI+zYSNbv0GMNquzuss2FVZo3OYrGh96n4HNc= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/prometheus v0.59.1 h1:HcpSkTkJbggT8bjYP+BjyqPWlD17BH9C5CYNKeDzmcA= +go.opentelemetry.io/otel/exporters/prometheus v0.59.1/go.mod h1:0FJL+gjuUoM07xzik3KPBaN+nz/CoB15kV6WLMiXZag= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0 h1:G8Xec/SgZQricwWBJF/mHZc7A02YHedfFDENwJEdRA0= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.36.0/go.mod h1:PD57idA/AiFD5aqoxGxCvT/ILJPeHy3MjqU/NS7KogY= -go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= -go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= -go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= -go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= -go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= -go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= -go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= -go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 h1:SNhVp/9q4Go/XHBkQ1/d5u9P/U+L1yaGPoi0x+mStaI= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0/go.mod h1:tx8OOlGH6R4kLV67YaYO44GFXloEjGPZuMjEkaaqIp4= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= -golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -271,55 +276,55 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= -golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.235.0 h1:C3MkpQSRxS1Jy6AkzTGKKrpSCOd2WOGrezZ+icKSkKo= -google.golang.org/api v0.235.0/go.mod h1:QpeJkemzkFKe5VCE/PMv7GsUfn9ZF+u+q1Q7w6ckxTg= +google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo= +google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20250528174236-200df99c418a h1:KXuwdBmgjb4T3l4ZzXhP6HxxFKXD9FcK5/8qfJI4WwU= -google.golang.org/genproto v0.0.0-20250528174236-200df99c418a/go.mod h1:Nlk93rrS2X7rV8hiC2gh2A/AJspZhElz9Oh2KGsjLEY= -google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto v0.0.0-20250721164621-a45f3dfb1074 h1:OC4JjCnGdf5dQ5lMsq3KOGmd0xFXTeeo4h8QFoiLQhA= +google.golang.org/genproto v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:ZIjaIRmV0lzMh6VMUdtRvj3TTfpe0uA3cHt3skrCdSQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 h1:iOye66xuaAK0WnkPuhQPUFy8eJcmwUXqGGP3om6IxX8= +google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79/go.mod h1:HKJDgKsFUnv5VAGeQjz8kxcgDP0HoE0iZNp0OdZNlhE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 h1:1ZwqphdOdWYXsUHgMpU/101nCtf/kSp9hOrcvFsnl10= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8= -google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= From bcd4ab66b58b41f140914e27511ad0a5afdfb92d Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 29 Jul 2025 17:51:33 +0530 Subject: [PATCH 0612/1298] chore: fix workflow request-gemini-review-on-pr-read-for-review (Dont Review) (#3600) * test change * fix gemini github action workflow config * Revert "test change" This reverts commit b537f574eaf8960649265b2427e2ead02c02c0a5. --- .../workflows/request-gemini-review-on-pr-read-for-review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/request-gemini-review-on-pr-read-for-review.yml b/.github/workflows/request-gemini-review-on-pr-read-for-review.yml index 411d7528dd..5aa07ca21b 100644 --- a/.github/workflows/request-gemini-review-on-pr-read-for-review.yml +++ b/.github/workflows/request-gemini-review-on-pr-read-for-review.yml @@ -24,7 +24,7 @@ on: - ready_for_review jobs: - add-gemini-comment: + add-gemini-review: if: github.base_ref == 'master' runs-on: ubuntu-latest permissions: @@ -35,7 +35,7 @@ jobs: with: issue-number: ${{ github.event.pull_request.number }} body: "/gemini review" - add-gemini-comment: + add-gemini-summary: if: github.base_ref == 'master' runs-on: ubuntu-latest permissions: From c6c1135f48676e2b0a38f2983c80615c2c894651 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 30 Jul 2025 09:40:25 +0530 Subject: [PATCH 0613/1298] chore(flags): Add flag-name validations (#3604) * Add flag-name validations Ensure that flag-names conform to the following rules: * It's not empty * It only consists of lower-case characters * It starts and ends with alphabets. * It only has hyphens or underscores as separators. * Update tools/config-gen/parser_test.go Co-authored-by: Prince Kumar --- tools/config-gen/parser.go | 18 ++++++++++++++ tools/config-gen/parser_test.go | 43 +++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 tools/config-gen/parser_test.go diff --git a/tools/config-gen/parser.go b/tools/config-gen/parser.go index d1b4db647d..825ed145c5 100644 --- a/tools/config-gen/parser.go +++ b/tools/config-gen/parser.go @@ -20,6 +20,7 @@ import ( "bytes" "fmt" "os" + "regexp" "slices" "gopkg.in/yaml.v3" @@ -55,7 +56,24 @@ func parseParamsConfig() ([]Param, error) { return paramsConfig, nil } +func checkFlagName(name string) error { + if name == "" { + return fmt.Errorf("flag-name cannot be empty") + } + + // A valid name should contain only lower-case characters with hyphens as + // separators. It must start and end with an alphabet. + regex := `^[a-z]+([-_][a-z]+)*$` + if matched, _ := regexp.MatchString(regex, name); !matched { + return fmt.Errorf("flag-name %q does not conform to the regex: %s", name, regex) + } + return nil +} + func validateParam(param Param) error { + if err := checkFlagName(param.FlagName); err != nil { + return err + } if param.IsDeprecated && param.DeprecationWarning == "" { return fmt.Errorf("param %s is marked deprecated but deprecation-warning is not set", param.FlagName) } diff --git a/tools/config-gen/parser_test.go b/tools/config-gen/parser_test.go new file mode 100644 index 0000000000..0ba9bcee2a --- /dev/null +++ b/tools/config-gen/parser_test.go @@ -0,0 +1,43 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCheckFlagName_Valid(t *testing.T) { + validNames := []string{"a", "abc", "ab-c", "ab-c-d", "a_b"} + + for _, name := range validNames { + t.Run(name, func(t *testing.T) { + assert.NoError(t, checkFlagName(name)) + }) + } +} + +func TestCheckFlagName_Invalid(t *testing.T) { + invalidNames := []string{"", "a-", "-a", "a--b", "a-b-", "A-b", "a.b", "1-a"} + + for _, name := range invalidNames { + t.Run(name, func(t *testing.T) { + assert.Error(t, checkFlagName(name)) + }) + } +} From 81d39a13eedef980fc04acd962e0d3f83b112dcb Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 30 Jul 2025 10:00:38 +0530 Subject: [PATCH 0614/1298] feat(bufferedread): adding mount config to control buffered-read feature (#3598) * Adding required unit tests * review comments. * Update cfg/params.yaml * fixing formatting test --------- Co-authored-by: Kislay Kishore --- cfg/config.go | 60 +++++++++++++++++++++++ cfg/params.yaml | 47 ++++++++++++++++++ cfg/validate.go | 28 +++++++++++ cfg/validate_test.go | 84 ++++++++++++++++++++++++++++++++ cmd/config_validation_test.go | 47 ++++++++++++++++++ cmd/root_test.go | 89 ++++++++++++++++++++++++++++++++++ cmd/testdata/valid_config.yaml | 5 ++ 7 files changed, 360 insertions(+) diff --git a/cfg/config.go b/cfg/config.go index 77ce2e9a92..a5b8217e90 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -258,7 +258,17 @@ type ProfilingConfig struct { } type ReadConfig struct { + BlockSizeMb int64 `yaml:"block-size-mb"` + + EnableBufferedRead bool `yaml:"enable-buffered-read"` + + GlobalMaxBlocks int64 `yaml:"global-max-blocks"` + InactiveStreamTimeout time.Duration `yaml:"inactive-stream-timeout"` + + MaxBlocksPerHandle int64 `yaml:"max-blocks-per-handle"` + + StartBlocksPerHandle int64 `yaml:"start-blocks-per-handle"` } type ReadStallGcsRetriesConfig struct { @@ -367,6 +377,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } + flagSet.BoolP("enable-buffered-read", "", false, "When enabled, read starts using buffer to prefetch (asynchronous and in parallel) data from GCS. This improves performance for large file sequential reads. Note: Enabling this flag can increase the memory usage significantly.") + + if err := flagSet.MarkHidden("enable-buffered-read"); err != nil { + return err + } + flagSet.BoolP("enable-cloud-profiling", "", false, "Enables cloud profiling, by default disabled.") if err := flagSet.MarkHidden("enable-cloud-profiling"); err != nil { @@ -601,12 +617,30 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.IntP("prometheus-port", "", 0, "Expose Prometheus metrics endpoint on this port and a path of /metrics.") + flagSet.IntP("read-block-size-mb", "", 16, "Specifies the block size for buffered reads. The value should be more than 0. This is used to read data in chunks from GCS.") + + if err := flagSet.MarkHidden("read-block-size-mb"); err != nil { + return err + } + + flagSet.IntP("read-global-max-blocks", "", 20, "Specifies the maximum number of blocks available for buffered reads across all file-handles. The value should be >= 0 or -1 (for infinite blocks). A value of 0 disables buffered reads.") + + if err := flagSet.MarkHidden("read-global-max-blocks"); err != nil { + return err + } + flagSet.DurationP("read-inactive-stream-timeout", "", 10000000000*time.Nanosecond, "Duration of inactivity after which an open GCS read stream is automatically closed. This helps conserve resources when a file handle remains open without active Read calls. A value of '0s' disables this timeout.") if err := flagSet.MarkHidden("read-inactive-stream-timeout"); err != nil { return err } + flagSet.IntP("read-max-blocks-per-handle", "", 20, "Specifies the maximum number of blocks to be used by a single file handle for buffered reads. The value should be >= 0 or -1 (for infinite blocks). A value of 0 disables buffered reads.") + + if err := flagSet.MarkHidden("read-max-blocks-per-handle"); err != nil { + return err + } + flagSet.DurationP("read-stall-initial-req-timeout", "", 20000000000*time.Nanosecond, "Initial value of the read-request dynamic timeout.") if err := flagSet.MarkHidden("read-stall-initial-req-timeout"); err != nil { @@ -637,6 +671,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } + flagSet.IntP("read-start-blocks-per-handle", "", 1, "Specifies the number of blocks to be prefetched on the first read.") + + if err := flagSet.MarkHidden("read-start-blocks-per-handle"); err != nil { + return err + } + flagSet.IntP("rename-dir-limit", "", 0, "Allow rename a directory containing fewer descendants than this limit.") flagSet.Float64P("retry-multiplier", "", 2, "Param for exponential backoff algorithm, which is used to increase waiting time b/w two consecutive retries.") @@ -772,6 +812,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("read.enable-buffered-read", flagSet.Lookup("enable-buffered-read")); err != nil { + return err + } + if err := v.BindPFlag("profiling.enabled", flagSet.Lookup("enable-cloud-profiling")); err != nil { return err } @@ -1012,10 +1056,22 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("read.block-size-mb", flagSet.Lookup("read-block-size-mb")); err != nil { + return err + } + + if err := v.BindPFlag("read.global-max-blocks", flagSet.Lookup("read-global-max-blocks")); err != nil { + return err + } + if err := v.BindPFlag("read.inactive-stream-timeout", flagSet.Lookup("read-inactive-stream-timeout")); err != nil { return err } + if err := v.BindPFlag("read.max-blocks-per-handle", flagSet.Lookup("read-max-blocks-per-handle")); err != nil { + return err + } + if err := v.BindPFlag("gcs-retries.read-stall.initial-req-timeout", flagSet.Lookup("read-stall-initial-req-timeout")); err != nil { return err } @@ -1036,6 +1092,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("read.start-blocks-per-handle", flagSet.Lookup("read-start-blocks-per-handle")); err != nil { + return err + } + if err := v.BindPFlag("file-system.rename-dir-limit", flagSet.Lookup("rename-dir-limit")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 2c9143cab0..54bf35ba7c 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -736,6 +736,35 @@ default: false hide-flag: true +- config-path: "read.block-size-mb" + flag-name: "read-block-size-mb" + type: "int" + usage: >- + Specifies the block size for buffered reads. The value should be more than + 0. This is used to read data in chunks from GCS. + default: 16 + hide-flag: true + +- config-path: "read.enable-buffered-read" + flag-name: "enable-buffered-read" + type: "bool" + usage: >- + When enabled, read starts using buffer to prefetch (asynchronous and in parallel) + data from GCS. This improves performance for large file sequential reads. + Note: Enabling this flag can increase the memory usage significantly. + default: false + hide-flag: true + +- config-path: "read.global-max-blocks" + flag-name: "read-global-max-blocks" + type: "int" + usage: >- + Specifies the maximum number of blocks available for buffered reads across all file-handles. + The value should be >= 0 or -1 (for infinite blocks). + A value of 0 disables buffered reads. + default: 20 + hide-flag: true + - config-path: "read.inactive-stream-timeout" flag-name: "read-inactive-stream-timeout" type: "duration" @@ -746,6 +775,24 @@ default: "10s" hide-flag: true +- config-path: "read.max-blocks-per-handle" + flag-name: "read-max-blocks-per-handle" + type: "int" + usage: >- + Specifies the maximum number of blocks to be used by a single file handle for + buffered reads. The value should be >= 0 or -1 (for infinite blocks). + A value of 0 disables buffered reads. + default: 20 + hide-flag: true + +- config-path: "read.start-blocks-per-handle" + flag-name: "read-start-blocks-per-handle" + type: "int" + usage: >- + Specifies the number of blocks to be prefetched on the first read. + default: 1 + hide-flag: true + - config-path: "write.block-size-mb" flag-name: "write-block-size-mb" type: "int" diff --git a/cfg/validate.go b/cfg/validate.go index 671980b345..86999cc795 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -215,6 +215,30 @@ func isValidChunkTransferTimeoutForRetriesConfig(chunkTransferTimeoutSecs int64) return nil } +func isValidBufferedReadConfig(rc *ReadConfig) error { + if !rc.EnableBufferedRead { + return nil + } + + if rc.BlockSizeMb <= 0 || rc.BlockSizeMb > util.MaxMiBsInInt64 { + return fmt.Errorf("invalid value of read-block-size-mb; can't be less than 1 or more than %d", util.MaxMiBsInInt64) + } + + if rc.GlobalMaxBlocks < -1 { + return fmt.Errorf("invalid value of read-global-max-blocks: %d; should be >=0 or -1 (for infinite)", rc.GlobalMaxBlocks) + } + + if rc.StartBlocksPerHandle < 1 && rc.StartBlocksPerHandle != -1 { + return fmt.Errorf("invalid value of read-start-blocks-per-handle: %d; should be >=1 or -1 (for infinite)", rc.StartBlocksPerHandle) + } + + if rc.MaxBlocksPerHandle < 1 && rc.MaxBlocksPerHandle != -1 { + return fmt.Errorf("invalid value of read-max-blocks-per-handle: %d; should be >=1 or -1 (for infinite)", rc.MaxBlocksPerHandle) + } + + return nil +} + // ValidateConfig returns a non-nil error if the config is invalid. func ValidateConfig(v isSet, config *Config) error { var err error @@ -271,5 +295,9 @@ func ValidateConfig(v isSet, config *Config) error { return fmt.Errorf("error parsing parallel download config: %w", err) } + if err = isValidBufferedReadConfig(&config.Read); err != nil { + return fmt.Errorf("error parsing buffered read config: %w", err) + } + return nil } diff --git a/cfg/validate_test.go b/cfg/validate_test.go index 6922a2ec54..340dea6ba7 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -526,6 +526,90 @@ func Test_isValidWriteStreamingConfig_ErrorScenarios(t *testing.T) { } } +func Test_isValidBufferedReadConfig_ErrorScenarios(t *testing.T) { + var testCases = []struct { + testName string + read ReadConfig + }{ + {"negative_block_size", ReadConfig{ + BlockSizeMb: -1, + EnableBufferedRead: true, + GlobalMaxBlocks: -1, + MaxBlocksPerHandle: -1, + StartBlocksPerHandle: 1, + }}, + {"zero_block_size", ReadConfig{ + BlockSizeMb: 0, + EnableBufferedRead: true, + GlobalMaxBlocks: -1, + MaxBlocksPerHandle: -1, + StartBlocksPerHandle: 1, + }}, + {"negative_global_max_blocks", ReadConfig{ + BlockSizeMb: 16, + EnableBufferedRead: true, + GlobalMaxBlocks: -2, + MaxBlocksPerHandle: -1, + StartBlocksPerHandle: 1, + }}, + {"negative_max_blocks_per_handle", ReadConfig{ + BlockSizeMb: 16, + EnableBufferedRead: true, + GlobalMaxBlocks: -1, + MaxBlocksPerHandle: -2, + StartBlocksPerHandle: 1, + }}, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + assert.Error(t, isValidBufferedReadConfig(&tc.read)) + }) + } +} + +func Test_isValidBufferedReadConfig_ValidScenarios(t *testing.T) { + var testCases = []struct { + testName string + read ReadConfig + }{ + {"valid_config_1", ReadConfig{ + BlockSizeMb: 16, + EnableBufferedRead: true, + GlobalMaxBlocks: -1, + MaxBlocksPerHandle: -1, + StartBlocksPerHandle: 1, + }}, + {"valid_config_2", ReadConfig{ + BlockSizeMb: 16, + EnableBufferedRead: true, + GlobalMaxBlocks: 10, + MaxBlocksPerHandle: -1, + StartBlocksPerHandle: 1, + }}, + {"valid_config_3", ReadConfig{ + BlockSizeMb: 16, + EnableBufferedRead: true, + GlobalMaxBlocks: 10, + MaxBlocksPerHandle: 5, + StartBlocksPerHandle: 1, + }}, + {"valid_config_4", ReadConfig{ + BlockSizeMb: 16, + EnableBufferedRead: false, + GlobalMaxBlocks: 10, + MaxBlocksPerHandle: 5, + StartBlocksPerHandle: 10, + }}, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + assert.NoError(t, isValidBufferedReadConfig(&tc.read)) + }) + } +} + func Test_isValidWriteStreamingConfig_SuccessScenarios(t *testing.T) { var testCases = []struct { testName string diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 78e86aa539..5c7303f6d9 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -223,6 +223,53 @@ func TestValidateConfigFile_WriteConfig(t *testing.T) { } } +func TestValidateConfigFile_ReadConfig(t *testing.T) { + testCases := []struct { + name string + configFile string + expectedConfig *cfg.Config + }{ + { + name: "Empty config file [default values].", + configFile: "testdata/empty_file.yaml", + expectedConfig: &cfg.Config{ + Read: cfg.ReadConfig{ + InactiveStreamTimeout: 10 * time.Second, + BlockSizeMb: 16, + EnableBufferedRead: false, + GlobalMaxBlocks: 20, + MaxBlocksPerHandle: 20, + StartBlocksPerHandle: 1, + }, + }, + }, + { + name: "Valid config file.", + configFile: "testdata/valid_config.yaml", + expectedConfig: &cfg.Config{ + Read: cfg.ReadConfig{ + InactiveStreamTimeout: 10 * time.Second, + BlockSizeMb: 16, + EnableBufferedRead: true, + MaxBlocksPerHandle: 20, + GlobalMaxBlocks: 40, + StartBlocksPerHandle: 4, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + gotConfig, err := getConfigObjectWithConfigFile(t, tc.configFile) + + if assert.NoError(t, err) { + assert.EqualValues(t, tc.expectedConfig.Read, gotConfig.Read) + } + }) + } +} + func TestValidateConfigFile_InvalidConfigThrowsError(t *testing.T) { testCases := []struct { name string diff --git a/cmd/root_test.go b/cmd/root_test.go index 7869a07f30..4dc5a93a76 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -392,6 +392,95 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { } } +func TestArgsParsing_ReadConfigFlags(t *testing.T) { + tests := []struct { + name string + args []string + expectedReadBlockSizeMB int64 + expectedReadGlobalMaxBlocks int64 + expectedReadMaxBlocksPerHandle int64 + expectedReadStartBlocksPerHandle int64 + }{ + { + name: "Test default flags.", + args: []string{"gcsfuse", "abc", "pqr"}, + expectedReadBlockSizeMB: 16, + expectedReadGlobalMaxBlocks: 20, + expectedReadMaxBlocksPerHandle: 20, + expectedReadStartBlocksPerHandle: 1, + }, + { + name: "Test enable buffered read flag true.", + args: []string{"gcsfuse", "--enable-buffered-read", "abc", "pqr"}, + expectedReadBlockSizeMB: 16, + expectedReadGlobalMaxBlocks: 20, + expectedReadMaxBlocksPerHandle: 20, + expectedReadStartBlocksPerHandle: 1, + }, + { + name: "Test enable buffered read flag false.", + args: []string{"gcsfuse", "--enable-buffered-read=false", "abc", "pqr"}, + expectedReadBlockSizeMB: 16, + expectedReadGlobalMaxBlocks: 20, + expectedReadMaxBlocksPerHandle: 20, + expectedReadStartBlocksPerHandle: 1, + }, + { + name: "Test positive read-block-size-mb flag.", + args: []string{"gcsfuse", "--read-block-size-mb=10", "abc", "pqr"}, + expectedReadBlockSizeMB: 10, + expectedReadGlobalMaxBlocks: 20, + expectedReadMaxBlocksPerHandle: 20, + expectedReadStartBlocksPerHandle: 1, + }, + { + name: "Test positive read-global-max-blocks flag.", + args: []string{"gcsfuse", "--read-global-max-blocks=10", "abc", "pqr"}, + expectedReadBlockSizeMB: 16, + expectedReadGlobalMaxBlocks: 10, + expectedReadMaxBlocksPerHandle: 20, + expectedReadStartBlocksPerHandle: 1, + }, + { + name: "Test positive read-max-blocks-per-handle flag.", + args: []string{"gcsfuse", "--read-max-blocks-per-handle=10", "abc", "pqr"}, + expectedReadBlockSizeMB: 16, + expectedReadGlobalMaxBlocks: 20, + expectedReadMaxBlocksPerHandle: 10, + expectedReadStartBlocksPerHandle: 1, + }, + { + name: "Test positive read-start-blocks-per-handle flag.", + args: []string{"gcsfuse", "--read-start-blocks-per-handle=10", "abc", "pqr"}, + expectedReadBlockSizeMB: 16, + expectedReadGlobalMaxBlocks: 20, + expectedReadMaxBlocksPerHandle: 20, + expectedReadStartBlocksPerHandle: 10, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + var rc cfg.ReadConfig + cmd, err := newRootCmd(func(cfg *cfg.Config, _, _ string) error { + rc = cfg.Read + return nil + }) + require.Nil(t, err) + cmd.SetArgs(convertToPosixArgs(tc.args, cmd)) + + err = cmd.Execute() + + if assert.NoError(t, err) { + assert.Equal(t, tc.expectedReadBlockSizeMB, rc.BlockSizeMb) + assert.Equal(t, tc.expectedReadGlobalMaxBlocks, rc.GlobalMaxBlocks) + assert.Equal(t, tc.expectedReadMaxBlocksPerHandle, rc.MaxBlocksPerHandle) + assert.Equal(t, tc.expectedReadStartBlocksPerHandle, rc.StartBlocksPerHandle) + } + }) + } +} + func TestArgsParsing_FileCacheFlags(t *testing.T) { tests := []struct { name string diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 6dd371da62..a265082f9c 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -1,6 +1,11 @@ app-name: hello read: inactive-stream-timeout: 10s + enable-buffered-read: true + global-max-blocks: 40 + block-size-mb: 16 + start-blocks-per-handle: 4 + max-blocks-per-handle: 20 write: create-empty-file: true enable-streaming-writes: true From 25d51756b7db0c79fc9c9c626c5495317826d9d8 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 30 Jul 2025 10:56:09 +0530 Subject: [PATCH 0615/1298] ci: fail fast in utility CreateDirectoryWithNFiles (#3605) In this utility, don't proceed with file creation, if the parent directory failed to create. And similarly, don't try to close file handle if file creation itself failed first. These are needed to avoid - confusing and miseerror error logs - run time wasted in dependent operations for failed operations --- tools/integration_tests/util/operations/dir_operations.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/util/operations/dir_operations.go b/tools/integration_tests/util/operations/dir_operations.go index c4c0c81019..3bf8e4ad23 100644 --- a/tools/integration_tests/util/operations/dir_operations.go +++ b/tools/integration_tests/util/operations/dir_operations.go @@ -17,7 +17,9 @@ package operations import ( "bytes" + "errors" "fmt" + "io/fs" "log" "os" "os/exec" @@ -92,8 +94,8 @@ func RenameDir(dirName string, newDirName string) (err error) { func CreateDirectoryWithNFiles(numberOfFiles int, dirPath string, prefix string, t *testing.T) { err := os.Mkdir(dirPath, FilePermission_0777) - if err != nil && !strings.Contains(err.Error(), "file exists") { - t.Errorf("Error in creating directory: %v", err) + if err != nil && !errors.Is(err, fs.ErrExist) { + t.Fatalf("Error in creating directory %q: %v", dirPath, err) } for i := 1; i <= numberOfFiles; i++ { @@ -102,7 +104,7 @@ func CreateDirectoryWithNFiles(numberOfFiles int, dirPath string, prefix string, filePath := path.Join(dirPath, prefix+strconv.Itoa(i)) file, err := os.Create(filePath) if err != nil { - t.Errorf("Create file at %q: %v", dirPath, err) + t.Fatalf("Failed to create file %q: %v", filePath, err) } // Closing file at the end. From 72d85da9151cd23d1236a69918ab29494e7c3610 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 30 Jul 2025 11:08:42 +0530 Subject: [PATCH 0616/1298] fix: fail gcsfuse e2e tests faster if testBucket argument is improper (#3606) * fix: fail gcsfuse e2e tests if testBucket argument is improper * address gemini comment --- tools/integration_tests/util/setup/setup.go | 23 +++++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 842189efa4..4bc61c1535 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -50,14 +50,15 @@ var gcsfusePreBuiltDir = flag.String("gcsfuse_prebuilt_dir", "", "Path to the pr var profileLabelForMountedDirTest = flag.String("profile_label", "", "To pass profile-label for the cloud-profile test.") const ( - FilePermission_0600 = 0600 - DirPermission_0755 = 0755 - Charset = "abcdefghijklmnopqrstuvwxyz0123456789" - PathEnvVariable = "PATH" - GCSFuseLogFilePrefix = "gcsfuse-failed-integration-test-logs-" - ProxyServerLogFilePrefix = "proxy-server-failed-integration-test-logs-" - zoneMatcherRegex = "^[a-z]+-[a-z0-9]+-[a-z]$" - regionMatcherRegex = "^[a-z]+-[a-z0-9]+$" + FilePermission_0600 = 0600 + DirPermission_0755 = 0755 + Charset = "abcdefghijklmnopqrstuvwxyz0123456789" + PathEnvVariable = "PATH" + GCSFuseLogFilePrefix = "gcsfuse-failed-integration-test-logs-" + ProxyServerLogFilePrefix = "proxy-server-failed-integration-test-logs-" + zoneMatcherRegex = "^[a-z]+-[a-z0-9]+-[a-z]$" + regionMatcherRegex = "^[a-z]+-[a-z0-9]+$" + unsupportedCharactersInTestBucket = " /" ) var ( @@ -398,9 +399,13 @@ func RunTestsForMountedDirectoryFlag(m *testing.M) { } func SetUpTestDirForTestBucketFlag() { - if TestBucket() == "" { + testBucketName := TestBucket() + if testBucketName == "" { log.Fatal("Not running TestBucket tests as --testBucket flag is not set.") } + if strings.ContainsAny(testBucketName, unsupportedCharactersInTestBucket) { + log.Fatalf("Passed testBucket %q contains one or more of the following unsupported character(s): %q", testBucketName, unsupportedCharactersInTestBucket) + } if err := SetUpTestDir(); err != nil { log.Printf("setUpTestDir: %v\n", err) os.Exit(1) From 94da0c5ae52ce04ee39356bccf3fe881099ece45 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:16:28 +0530 Subject: [PATCH 0617/1298] feat(Migrate Auth TPC): Add auth option while creating HTTP/gRPC client (#3570) * adding token source function and unit tests * review comment from gemini * rebase * adding auth option while creating client * review comment and enable tests with new flow * rebase * pass context from parent function * adding unit tests * adding unit tests * minor update * fix unit test * minor update * remove unnecessary changes * adding small comment * pass key file as argument * specific return error * rename the test --- cmd/legacy_main.go | 1 + internal/storage/storage_handle.go | 43 +++- internal/storage/storage_handle_test.go | 188 +++++++++++++++--- .../storageutil/auth_client_option_test.go | 4 +- internal/storage/storageutil/client.go | 17 +- internal/storage/storageutil/client_test.go | 35 ++-- .../storageutil/control_client_test.go | 4 +- internal/storage/storageutil/test_util.go | 10 +- 8 files changed, 229 insertions(+), 73 deletions(-) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 6c83f1953e..17126c5d64 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -141,6 +141,7 @@ func createStorageHandle(newConfig *cfg.Config, userAgent string, metricHandle m ExperimentalEnableJsonRead: newConfig.GcsConnection.ExperimentalEnableJsonRead, GrpcConnPoolSize: int(newConfig.GcsConnection.GrpcConnPoolSize), EnableHNS: newConfig.EnableHns, + EnableGoogleLibAuth: newConfig.EnableGoogleLibAuth, ReadStallRetryConfig: newConfig.GcsRetries.ReadStall, MetricHandle: metricHandle, } diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index fe4031c494..6be4c5ab09 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -31,6 +31,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "golang.org/x/net/context" + "golang.org/x/oauth2" option "google.golang.org/api/option" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" @@ -79,36 +80,48 @@ func (pd *gRPCDirectPathDetector) isDirectPathPossible(ctx context.Context, buck } // Return clientOpts for both gRPC client and control client. -func createClientOptionForGRPCClient(clientConfig *storageutil.StorageClientConfig, enableBidiConfig bool) (clientOpts []option.ClientOption, err error) { - // Add Custom endpoint option. +func createClientOptionForGRPCClient(ctx context.Context, clientConfig *storageutil.StorageClientConfig, enableBidiConfig bool) (clientOpts []option.ClientOption, err error) { + // Add custom endpoint if provided. if clientConfig.CustomEndpoint != "" { clientOpts = append(clientOpts, option.WithEndpoint(storageutil.StripScheme(clientConfig.CustomEndpoint))) + // TODO(b/390799251): Check if this line can be merged with below anonymousAccess check. if clientConfig.AnonymousAccess { clientOpts = append(clientOpts, option.WithGRPCDialOption(grpc.WithTransportCredentials(insecure.NewCredentials()))) } } + // Configure authentication. if clientConfig.AnonymousAccess { clientOpts = append(clientOpts, option.WithoutAuthentication()) + } else if clientConfig.EnableGoogleLibAuth { + var authOpts []option.ClientOption + authOpts, _, err = storageutil.GetClientAuthOptionsAndToken(ctx, clientConfig) + if err != nil { + return nil, fmt.Errorf("failed to get client auth options and token: %w", err) + } + clientOpts = append(clientOpts, authOpts...) } else { - tokenSrc, tokenCreationErr := storageutil.CreateTokenSource(clientConfig) - if tokenCreationErr != nil { - err = fmt.Errorf("while fetching tokenSource: %w", tokenCreationErr) - return + var tokenSrc oauth2.TokenSource + tokenSrc, err = storageutil.CreateTokenSource(clientConfig) + if err != nil { + return nil, fmt.Errorf("while fetching token source: %w", err) } clientOpts = append(clientOpts, option.WithTokenSource(tokenSrc)) } + // Additional client options. if enableBidiConfig { clientOpts = append(clientOpts, experimental.WithGRPCBidiReads()) } + clientOpts = append(clientOpts, option.WithGRPCConnectionPool(clientConfig.GrpcConnPoolSize)) clientOpts = append(clientOpts, option.WithUserAgent(clientConfig.UserAgent)) // Turning off the go-sdk metrics exporter to prevent any problems. // TODO (kislaykishore) - to revisit here for monitoring support. clientOpts = append(clientOpts, storage.WithDisabledClientMetrics()) - return + + return clientOpts, nil } func setRetryConfig(ctx context.Context, sc *storage.Client, clientConfig *storageutil.StorageClientConfig) { @@ -149,7 +162,7 @@ func createGRPCClientHandle(ctx context.Context, clientConfig *storageutil.Stora } var clientOpts []option.ClientOption - clientOpts, err = createClientOptionForGRPCClient(clientConfig, enableBidiConfig) + clientOpts, err = createClientOptionForGRPCClient(ctx, clientConfig, enableBidiConfig) if err != nil { return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) } @@ -171,10 +184,20 @@ func createGRPCClientHandle(ctx context.Context, clientConfig *storageutil.Stora func createHTTPClientHandle(ctx context.Context, clientConfig *storageutil.StorageClientConfig) (sc *storage.Client, err error) { var clientOpts []option.ClientOption + var tokenSrc oauth2.TokenSource = nil + + if clientConfig.EnableGoogleLibAuth { + var authOpts []option.ClientOption + authOpts, tokenSrc, err = storageutil.GetClientAuthOptionsAndToken(ctx, clientConfig) + if err != nil { + return nil, fmt.Errorf("failed to get client auth options and token: %w", err) + } + clientOpts = append(clientOpts, authOpts...) + } // Add WithHttpClient option. var httpClient *http.Client - httpClient, err = storageutil.CreateHttpClient(clientConfig) + httpClient, err = storageutil.CreateHttpClient(clientConfig, tokenSrc) if err != nil { err = fmt.Errorf("while creating http endpoint: %w", err) return @@ -272,7 +295,7 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien // Control-client is needed for folder APIs and for getting storage-layout of the bucket. // GetStorageLayout API is not supported for storage-testbench, which are identified by custom-endpoint containing localhost. if clientConfig.EnableHNS && !strings.Contains(clientConfig.CustomEndpoint, "localhost") { - clientOpts, err = createClientOptionForGRPCClient(&clientConfig, false) + clientOpts, err = createClientOptionForGRPCClient(ctx, &clientConfig, false) if err != nil { return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) } diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index b7bca500af..b778016281 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -34,6 +34,8 @@ import ( const invalidBucketName string = "will-not-be-present-in-fake-server" const projectID string = "valid-project-id" +var keyFile = "storageutil/testdata/key.json" + type StorageHandleTest struct { suite.Suite fakeStorage FakeStorage @@ -123,7 +125,7 @@ func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketDoesNotExistWithNo } func (testSuite *StorageHandleTest) TestNewStorageHandleHttp2Disabled() { - sc := storageutil.GetDefaultStorageClientConfig() // by default http1 enabled + sc := storageutil.GetDefaultStorageClientConfig(keyFile) // by default http1 enabled handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") @@ -132,7 +134,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleHttp2Disabled() { } func (testSuite *StorageHandleTest) TestNewStorageHandleHttp2EnabledAndAuthEnabled() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ClientProtocol = cfg.HTTP2 sc.AnonymousAccess = false @@ -143,7 +145,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleHttp2EnabledAndAuthEnabl } func (testSuite *StorageHandleTest) TestNewStorageHandleWithZeroMaxConnsPerHost() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.MaxConnsPerHost = 0 handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") @@ -153,7 +155,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithZeroMaxConnsPerHost( } func (testSuite *StorageHandleTest) TestNewStorageHandleWhenUserAgentIsSet() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.UserAgent = "gcsfuse/unknown (Go version go1.20-pre3 cl/474093167 +a813be86df) appName (GPN:Gcsfuse-DLC)" handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") @@ -165,7 +167,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenUserAgentIsSet() { func (testSuite *StorageHandleTest) TestNewStorageHandleWithCustomEndpointAndAuthEnabled() { url, err := url.Parse(storageutil.CustomEndpoint) assert.Nil(testSuite.T(), err) - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.CustomEndpoint = url.String() sc.AnonymousAccess = false @@ -177,7 +179,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithCustomEndpointAndAut // This will fail while fetching the token-source, since key-file doesn't exist. func (testSuite *StorageHandleTest) TestNewStorageHandleWhenCustomEndpointIsNilAndAuthEnabled() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.CustomEndpoint = "" sc.AnonymousAccess = false @@ -187,9 +189,10 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenCustomEndpointIsNilA assert.NotNil(testSuite.T(), handleCreated) } -func (testSuite *StorageHandleTest) TestNewStorageHandleWhenKeyFileIsEmpty() { - sc := storageutil.GetDefaultStorageClientConfig() +func (testSuite *StorageHandleTest) TestNewStorageHandleWhenAnonymousAccessTrue() { + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.KeyFile = "" + sc.AnonymousAccess = true handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") @@ -198,7 +201,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenKeyFileIsEmpty() { } func (testSuite *StorageHandleTest) TestNewStorageHandleWhenReuseTokenUrlFalse() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ReuseTokenFromUrl = false handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") @@ -208,7 +211,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenReuseTokenUrlFalse() } func (testSuite *StorageHandleTest) TestNewStorageHandleWhenTokenUrlIsSet() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.TokenUrl = storageutil.CustomTokenUrl handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") @@ -218,7 +221,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenTokenUrlIsSet() { } func (testSuite *StorageHandleTest) TestNewStorageHandleWhenJsonReadEnabled() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ExperimentalEnableJsonRead = true handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") @@ -228,7 +231,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenJsonReadEnabled() { } func (testSuite *StorageHandleTest) TestNewStorageHandleWithBillingProject() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.EnableHNS = true handleCreated, err := NewStorageHandle(testSuite.ctx, sc, projectID) @@ -281,7 +284,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleDirectPathDetector() { for _, tc := range testCases { testSuite.Run(tc.name, func() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ExperimentalEnableJsonRead = true sc.ClientProtocol = tc.clientProtocol @@ -298,7 +301,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleDirectPathDetector() { } func (testSuite *StorageHandleTest) TestCreateGRPCClientHandle() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ClientProtocol = cfg.GRPC storageClient, err := createGRPCClientHandle(testSuite.ctx, &sc, false) @@ -308,7 +311,7 @@ func (testSuite *StorageHandleTest) TestCreateGRPCClientHandle() { } func (testSuite *StorageHandleTest) TestCreateGRPCClientHandleWithBidiConfig() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ClientProtocol = cfg.GRPC storageClient, err := createGRPCClientHandle(testSuite.ctx, &sc, true) @@ -318,7 +321,7 @@ func (testSuite *StorageHandleTest) TestCreateGRPCClientHandleWithBidiConfig() { } func (testSuite *StorageHandleTest) TestCreateHTTPClientHandle() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) storageClient, err := createHTTPClientHandle(testSuite.ctx, &sc) @@ -327,7 +330,7 @@ func (testSuite *StorageHandleTest) TestCreateHTTPClientHandle() { } func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientProtocol() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ClientProtocol = cfg.GRPC storageClient, err := NewStorageHandle(testSuite.ctx, sc, "") @@ -353,7 +356,7 @@ func (testSuite *StorageHandleTest) TestCreateHTTPClientHandle_WithReadStallRetr for _, tc := range testCases { testSuite.Run(tc.name, func() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ReadStallRetryConfig.Enable = tc.enableReadStallRetry storageClient, err := createHTTPClientHandle(testSuite.ctx, &sc) @@ -381,7 +384,7 @@ func (testSuite *StorageHandleTest) TestCreateHTTPClientHandle_ReadStallInitialR for _, tc := range testCases { testSuite.Run(tc.name, func() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ReadStallRetryConfig.Enable = true sc.ReadStallRetryConfig.InitialReqTimeout = tc.initialReqTimeout @@ -410,7 +413,7 @@ func (testSuite *StorageHandleTest) TestCreateHTTPClientHandle_ReadStallMinReqTi for _, tc := range testCases { testSuite.Run(tc.name, func() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ReadStallRetryConfig.Enable = true sc.ReadStallRetryConfig.MinReqTimeout = tc.minReqTimeout @@ -447,7 +450,7 @@ func (testSuite *StorageHandleTest) TestCreateHTTPClientHandle_ReadStallReqIncre for _, tc := range testCases { testSuite.Run(tc.name, func() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ReadStallRetryConfig.Enable = true sc.ReadStallRetryConfig.ReqIncreaseRate = tc.reqIncreaseRate @@ -498,7 +501,7 @@ func (testSuite *StorageHandleTest) TestCreateHTTPClientHandle_ReadStallReqTarge for _, tc := range testCases { testSuite.Run(tc.name, func() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.ReadStallRetryConfig.Enable = true sc.ReadStallRetryConfig.ReqTargetPercentile = tc.reqTargetPercentile @@ -515,7 +518,7 @@ func (testSuite *StorageHandleTest) TestCreateHTTPClientHandle_ReadStallReqTarge } func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustomEndpointNilAndAuthEnabled() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.CustomEndpoint = "" sc.AnonymousAccess = false sc.ClientProtocol = cfg.GRPC @@ -529,7 +532,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustom func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustomEndpointAndAuthEnabled() { url, err := url.Parse(storageutil.CustomEndpoint) assert.Nil(testSuite.T(), err) - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.CustomEndpoint = url.String() sc.AnonymousAccess = false sc.ClientProtocol = cfg.GRPC @@ -545,7 +548,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustom func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustomEndpointAndAuthDisabled() { url, err := url.Parse(storageutil.CustomEndpoint) assert.Nil(testSuite.T(), err) - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.CustomEndpoint = url.String() sc.ClientProtocol = cfg.GRPC sc.TokenUrl = storageutil.CustomTokenUrl @@ -558,7 +561,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithGRPCClientWithCustom } func (testSuite *StorageHandleTest) TestCreateStorageHandleWithEnableHNSTrue() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.EnableHNS = true sh, err := NewStorageHandle(testSuite.ctx, sc, "") @@ -570,7 +573,7 @@ func (testSuite *StorageHandleTest) TestCreateStorageHandleWithEnableHNSTrue() { func (testSuite *StorageHandleTest) TestNewStorageHandleWithCustomEndpointAndEnableHNSTrue() { url, err := url.Parse(storageutil.CustomEndpoint) require.NoError(testSuite.T(), err) - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.CustomEndpoint = url.String() sc.EnableHNS = true @@ -581,16 +584,141 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithCustomEndpointAndEna } func (testSuite *StorageHandleTest) TestCreateClientOptionForGRPCClient() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) + + clientOption, err := createClientOptionForGRPCClient(context.TODO(), &sc, false) + + assert.Nil(testSuite.T(), err) + assert.NotNil(testSuite.T(), clientOption) +} - clientOption, err := createClientOptionForGRPCClient(&sc, false) +func (testSuite *StorageHandleTest) Test_CreateClientOptionForGRPCClient_WithoutGoogleLibAuth() { + sc := storageutil.GetDefaultStorageClientConfig(keyFile) + sc.EnableGoogleLibAuth = false + + clientOption, err := createClientOptionForGRPCClient(context.TODO(), &sc, false) assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), clientOption) } +func (testSuite *StorageHandleTest) Test_CreateHTTPClientHandle_WithoutGoogleLibAuth() { + sc := storageutil.GetDefaultStorageClientConfig(keyFile) + sc.EnableGoogleLibAuth = false + + httpClient, err := createHTTPClientHandle(context.TODO(), &sc) + + assert.Nil(testSuite.T(), err) + assert.NotNil(testSuite.T(), httpClient) +} + +func (testSuite *StorageHandleTest) Test_CreateClientOptionForGRPCClient_AuthFailures() { + tests := []struct { + name string + modifyConfig func(sc *storageutil.StorageClientConfig) + expectError bool + expectNilOpts bool + }{ + { + name: "Invalid token URL with google lib auth", + modifyConfig: func(sc *storageutil.StorageClientConfig) { + sc.TokenUrl = ":" + sc.KeyFile = "" + sc.EnableGoogleLibAuth = true + }, + }, + { + name: "Invalid token URL without google lib auth", + modifyConfig: func(sc *storageutil.StorageClientConfig) { + sc.TokenUrl = ":" + sc.KeyFile = "" + sc.EnableGoogleLibAuth = false + }, + }, + { + name: "Invalid key file path with google lib auth", + modifyConfig: func(sc *storageutil.StorageClientConfig) { + sc.KeyFile = "incorrect_path" + sc.EnableGoogleLibAuth = true + }, + }, + { + name: "Invalid key file path without google lib auth", + modifyConfig: func(sc *storageutil.StorageClientConfig) { + sc.KeyFile = "incorrect_path" + sc.EnableGoogleLibAuth = false + }, + }, + } + + for _, tt := range tests { + testSuite.T().Run(tt.name, func(t *testing.T) { + sc := storageutil.GetDefaultStorageClientConfig(keyFile) + sc.ClientProtocol = cfg.GRPC + tt.modifyConfig(&sc) + + clientOption, err := createClientOptionForGRPCClient(context.TODO(), &sc, false) + + assert.Error(t, err) + assert.Nil(t, clientOption) + }) + } +} + +func (testSuite *StorageHandleTest) Test_CreateHTTPClientHandle_AuthFailures() { + tests := []struct { + name string + modifyConfig func(sc *storageutil.StorageClientConfig) + expectError bool + expectNilOpts bool + }{ + { + name: "Invalid token URL with google lib auth", + modifyConfig: func(sc *storageutil.StorageClientConfig) { + sc.TokenUrl = ":" + sc.KeyFile = "" + sc.EnableGoogleLibAuth = true + }, + }, + { + name: "Invalid token URL without google lib auth", + modifyConfig: func(sc *storageutil.StorageClientConfig) { + sc.TokenUrl = ":" + sc.KeyFile = "" + sc.EnableGoogleLibAuth = false + }, + }, + { + name: "Invalid key file path with google lib auth", + modifyConfig: func(sc *storageutil.StorageClientConfig) { + sc.KeyFile = "incorrect_path" + sc.EnableGoogleLibAuth = true + }, + }, + { + name: "Invalid Key File Path without google lib auth", + modifyConfig: func(sc *storageutil.StorageClientConfig) { + sc.KeyFile = "incorrect_path" + sc.EnableGoogleLibAuth = false + }, + }, + } + + for _, tt := range tests { + testSuite.T().Run(tt.name, func(t *testing.T) { + sc := storageutil.GetDefaultStorageClientConfig(keyFile) + tt.modifyConfig(&sc) + + httpClient, err := createHTTPClientHandle(context.TODO(), &sc) + + assert.Error(t, err) + assert.Nil(t, httpClient) + }) + } +} + func (testSuite *StorageHandleTest) TestNewStorageHandleWithMaxRetryAttemptsNotZero() { - sc := storageutil.GetDefaultStorageClientConfig() + sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.MaxRetryAttempts = 100 handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") diff --git a/internal/storage/storageutil/auth_client_option_test.go b/internal/storage/storageutil/auth_client_option_test.go index e28ff4b822..9b2c295ecf 100644 --- a/internal/storage/storageutil/auth_client_option_test.go +++ b/internal/storage/storageutil/auth_client_option_test.go @@ -29,7 +29,7 @@ import ( // Tests //////////////////////////////////////////////////////////////////////// -func Test_GetClientAuthOptionsAndToken_TokenUrlPreferredSuccess(t *testing.T) { +func Test_GetClientAuthOptionsAndToken_TokenUrlSuccess(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintln(w, `{"access_token":"dummy-token","token_type":"Bearer"}`) @@ -48,7 +48,7 @@ func Test_GetClientAuthOptionsAndToken_TokenUrlPreferredSuccess(t *testing.T) { assert.Len(t, clientOpts, 1) // Only tokenSource option attached } -func Test_GetClientAuthOptionsAndToken_TokenUrlPreferredError(t *testing.T) { +func Test_GetClientAuthOptionsAndToken_TokenUrlError(t *testing.T) { config := &StorageClientConfig{TokenUrl: ":"} clientOpts, tokenSrc, err := GetClientAuthOptionsAndToken(context.TODO(), config) diff --git a/internal/storage/storageutil/client.go b/internal/storage/storageutil/client.go index 313b282d32..6fb6eb8a77 100644 --- a/internal/storage/storageutil/client.go +++ b/internal/storage/storageutil/client.go @@ -56,13 +56,15 @@ type StorageClientConfig struct { // Enabling new API flow for HNS bucket. EnableHNS bool + // EnableGoogleLibAuth indicates whether to use the google library authentication flow + EnableGoogleLibAuth bool ReadStallRetryConfig cfg.ReadStallGcsRetriesConfig MetricHandle metrics.MetricHandle } -func CreateHttpClient(storageClientConfig *StorageClientConfig) (httpClient *http.Client, err error) { +func CreateHttpClient(storageClientConfig *StorageClientConfig, tokenSrc oauth2.TokenSource) (httpClient *http.Client, err error) { var transport *http.Transport // Using http1 makes the client more performant. if storageClientConfig.ClientProtocol == cfg.HTTP1 { @@ -97,11 +99,14 @@ func CreateHttpClient(storageClientConfig *StorageClientConfig) (httpClient *htt Timeout: storageClientConfig.HttpClientTimeout, } } else { - var tokenSrc oauth2.TokenSource - tokenSrc, err = CreateTokenSource(storageClientConfig) - if err != nil { - err = fmt.Errorf("while fetching tokenSource: %w", err) - return + if tokenSrc == nil { + // CreateTokenSource only if tokenSrc is nil, which means it wasn't provided externally. + // This indicates the EnableGoogleLibAuth flag is disabled. + tokenSrc, err = CreateTokenSource(storageClientConfig) + if err != nil { + err = fmt.Errorf("while fetching tokenSource: %w", err) + return nil, err + } } // Custom http client for Go Client. diff --git a/internal/storage/storageutil/client_test.go b/internal/storage/storageutil/client_test.go index 667e39fdea..78aac1574c 100644 --- a/internal/storage/storageutil/client_test.go +++ b/internal/storage/storageutil/client_test.go @@ -21,6 +21,8 @@ import ( "github.com/stretchr/testify/suite" ) +var keyFile = "testdata/key.json" + func TestClient(t *testing.T) { suite.Run(t, new(clientTest)) } @@ -32,9 +34,9 @@ type clientTest struct { // Tests func (t *clientTest) TestCreateHttpClientWithHttp1() { - sc := GetDefaultStorageClientConfig() // By default http1 enabled + sc := GetDefaultStorageClientConfig(keyFile) // By default http1 enabled - httpClient, err := CreateHttpClient(&sc) + httpClient, err := CreateHttpClient(&sc, nil) assert.NoError(t.T(), err) assert.NotNil(t.T(), httpClient) @@ -42,9 +44,9 @@ func (t *clientTest) TestCreateHttpClientWithHttp1() { } func (t *clientTest) TestCreateHttpClientWithHttp2() { - sc := GetDefaultStorageClientConfig() + sc := GetDefaultStorageClientConfig(keyFile) - httpClient, err := CreateHttpClient(&sc) + httpClient, err := CreateHttpClient(&sc, nil) assert.NoError(t.T(), err) assert.NotNil(t.T(), httpClient) @@ -52,36 +54,33 @@ func (t *clientTest) TestCreateHttpClientWithHttp2() { } func (t *clientTest) TestCreateHttpClientWithHttp1AndAuthEnabled() { - sc := GetDefaultStorageClientConfig() // By default http1 enabled + sc := GetDefaultStorageClientConfig(keyFile) // By default http1 enabled sc.AnonymousAccess = false // Act: this method add tokenSource and clientOptions. - httpClient, err := CreateHttpClient(&sc) + httpClient, err := CreateHttpClient(&sc, nil) - assert.Error(t.T(), err) - assert.ErrorContains(t.T(), err, "no such file or directory") - assert.Nil(t.T(), httpClient) + assert.NoError(t.T(), err) + assert.NotNil(t.T(), httpClient) } func (t *clientTest) TestCreateHttpClientWithHttp2AndAuthEnabled() { - sc := GetDefaultStorageClientConfig() + sc := GetDefaultStorageClientConfig(keyFile) sc.AnonymousAccess = false // Act: this method add tokenSource and clientOptions. - httpClient, err := CreateHttpClient(&sc) + httpClient, err := CreateHttpClient(&sc, nil) - assert.Error(t.T(), err) - assert.ErrorContains(t.T(), err, "no such file or directory") - assert.Nil(t.T(), httpClient) + assert.NoError(t.T(), err) + assert.NotNil(t.T(), httpClient) } func (t *clientTest) TestCreateTokenSrc() { - sc := GetDefaultStorageClientConfig() + sc := GetDefaultStorageClientConfig(keyFile) tokenSrc, err := CreateTokenSource(&sc) - assert.Error(t.T(), err) - assert.ErrorContains(t.T(), err, "no such file or directory") - assert.NotEqual(t.T(), nil, &tokenSrc) + assert.NoError(t.T(), err) + assert.NotNil(t.T(), &tokenSrc) } func (t *clientTest) TestStripScheme() { diff --git a/internal/storage/storageutil/control_client_test.go b/internal/storage/storageutil/control_client_test.go index fe5ff47a6a..cb9bdffa50 100644 --- a/internal/storage/storageutil/control_client_test.go +++ b/internal/storage/storageutil/control_client_test.go @@ -38,7 +38,7 @@ func (testSuite *ControlClientTest) TearDownTest() { } func (testSuite *ControlClientTest) TestStorageControlClientRetryOptions() { - clientConfig := GetDefaultStorageClientConfig() + clientConfig := GetDefaultStorageClientConfig(keyFile) gaxOpts := storageControlClientRetryOptions(&clientConfig) @@ -48,7 +48,7 @@ func (testSuite *ControlClientTest) TestStorageControlClientRetryOptions() { func (testSuite *ControlClientTest) TestStorageControlClient() { var clientOpts []option.ClientOption clientOpts = append(clientOpts, option.WithoutAuthentication()) - clientConfig := GetDefaultStorageClientConfig() + clientConfig := GetDefaultStorageClientConfig(keyFile) controlClient, err := CreateGRPCControlClient(context.Background(), clientOpts, &clientConfig) diff --git a/internal/storage/storageutil/test_util.go b/internal/storage/storageutil/test_util.go index 0c935b8a1f..7e1c0084b4 100644 --- a/internal/storage/storageutil/test_util.go +++ b/internal/storage/storageutil/test_util.go @@ -21,11 +21,10 @@ import ( ) const CustomEndpoint = "https://localhost:9000" -const DummyKeyFile = "test/test_creds.json" const CustomTokenUrl = "http://custom-token-url" // GetDefaultStorageClientConfig is only for test. -func GetDefaultStorageClientConfig() (clientConfig StorageClientConfig) { +func GetDefaultStorageClientConfig(keyFile string) (clientConfig StorageClientConfig) { return StorageClientConfig{ ClientProtocol: cfg.HTTP1, MaxConnsPerHost: 10, @@ -36,12 +35,13 @@ func GetDefaultStorageClientConfig() (clientConfig StorageClientConfig) { RetryMultiplier: 2, UserAgent: "gcsfuse/unknown (Go version go1.20-pre3 cl/474093167 +a813be86df) (GCP:gcsfuse)", CustomEndpoint: "", - KeyFile: DummyKeyFile, + KeyFile: keyFile, TokenUrl: "", ReuseTokenFromUrl: true, ExperimentalEnableJsonRead: false, - AnonymousAccess: true, - EnableHNS: false, + AnonymousAccess: false, + EnableHNS: true, + EnableGoogleLibAuth: true, ReadStallRetryConfig: cfg.ReadStallGcsRetriesConfig{ Enable: false, InitialReqTimeout: 20 * time.Second, From 255f130e43de690669a9ee964b67c18e1c908e48 Mon Sep 17 00:00:00 2001 From: vadlakondaswetha <101323867+vadlakondaswetha@users.noreply.github.com> Date: Wed, 30 Jul 2025 16:03:48 +0530 Subject: [PATCH 0618/1298] style: remove unused code - part3 and adding lint check (#3586) * removing unused code * fixing the test * removing unused code and adding lint check --- .github/workflows/ci.yml | 2 +- cfg/optimize.go | 18 +++++++++--------- cfg/optimize_test.go | 30 ++++++++++++------------------ cmd/root.go | 5 +---- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe498f3449..6c6a5c8c53 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,6 +92,6 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@032fa5c5e48499f06cf9d32c02149bfac1284239 with: - args: -E=goimports --timeout 2m0s + args: -E=goimports,unused --timeout 2m0s only-new-issues: true diff --git a/cfg/optimize.go b/cfg/optimize.go index 682c3633ad..bc93d20b35 100644 --- a/cfg/optimize.go +++ b/cfg/optimize.go @@ -160,10 +160,10 @@ func getMachineType(isSet isValueSet) (string, error) { return "", fmt.Errorf("failed to get machine type from any metadata endpoint after retries") } -func applyMachineTypeOptimizations(config *optimizationConfig, cfg *Config, isSet isValueSet) ([]string, error) { +func applyMachineTypeOptimizations(config *optimizationConfig, cfg *Config, isSet isValueSet) []string { currentMachineType, err := getMachineType(isSet) if err != nil { - return nil, nil // Non-fatal error, continue with default settings. + return nil // Non-fatal error, continue with default settings. } var optimizedFlags []string @@ -176,7 +176,7 @@ func applyMachineTypeOptimizations(config *optimizationConfig, cfg *Config, isSe // If no matching machine type is found, return. if mtIndex == -1 { - return optimizedFlags, nil + return optimizedFlags } mt := &config.machineTypes[mtIndex] @@ -187,7 +187,7 @@ func applyMachineTypeOptimizations(config *optimizationConfig, cfg *Config, isSe // If no matching flag override set is found, return. if flgOverrideSetIndex == -1 { - return optimizedFlags, nil + return optimizedFlags } flgOverrideSet := &config.flagOverrideSets[flgOverrideSetIndex] @@ -198,17 +198,17 @@ func applyMachineTypeOptimizations(config *optimizationConfig, cfg *Config, isSe optimizedFlags = append(optimizedFlags, flag) } } - return optimizedFlags, nil + return optimizedFlags } // Optimize applies machine-type specific optimizations. -func Optimize(cfg *Config, isSet isValueSet) ([]string, error) { +func Optimize(cfg *Config, isSet isValueSet) []string { // Check if disable-autoconfig is set to true. if isSet.GetBool("disable-autoconfig") { - return nil, nil + return nil } - optimizedFlags, err := applyMachineTypeOptimizations(&defaultOptimizationConfig, cfg, isSet) - return optimizedFlags, err + optimizedFlags := applyMachineTypeOptimizations(&defaultOptimizationConfig, cfg, isSet) + return optimizedFlags } // convertToCamelCase converts a string from snake-case to CamelCase. diff --git a/cfg/optimize_test.go b/cfg/optimize_test.go index 272a581a01..6eedcccc71 100644 --- a/cfg/optimize_test.go +++ b/cfg/optimize_test.go @@ -162,9 +162,8 @@ func TestOptimize_DisableAutoConfig(t *testing.T) { cfg := &Config{} isSet := &mockIsValueSet{setFlags: map[string]bool{"disable-autoconfig": true}, boolFlags: map[string]bool{"disable-autoconfig": true}} - _, err := Optimize(cfg, isSet) + _ = Optimize(cfg, isSet) - require.NoError(t, err) assert.False(t, cfg.Write.EnableStreamingWrites) assert.EqualValues(t, 0, cfg.MetadataCache.NegativeTtlSecs) assert.EqualValues(t, 0, cfg.MetadataCache.TtlSecs) @@ -187,9 +186,8 @@ func TestApplyMachineTypeOptimizations_MatchingMachineType(t *testing.T) { cfg := &Config{} isSet := &mockIsValueSet{setFlags: map[string]bool{}} - optimizedFlags, err := applyMachineTypeOptimizations(&config, cfg, isSet) + optimizedFlags := applyMachineTypeOptimizations(&config, cfg, isSet) - require.NoError(t, err) assert.NotEmpty(t, optimizedFlags) assert.EqualValues(t, 0, cfg.MetadataCache.NegativeTtlSecs) assert.EqualValues(t, -1, cfg.MetadataCache.TtlSecs) @@ -212,9 +210,8 @@ func TestApplyMachineTypeOptimizations_NonMatchingMachineType(t *testing.T) { cfg := &Config{} isSet := &mockIsValueSet{setFlags: map[string]bool{}} - optimizedFlags, err := applyMachineTypeOptimizations(&config, cfg, isSet) + optimizedFlags := applyMachineTypeOptimizations(&config, cfg, isSet) - require.NoError(t, err) assert.Empty(t, optimizedFlags) assert.False(t, cfg.Write.EnableStreamingWrites) } @@ -234,9 +231,8 @@ func TestApplyMachineTypeOptimizations_UserSetFlag(t *testing.T) { // Simulate setting config value by user cfg.FileSystem.RenameDirLimit = 10000 - optimizedFlags, err := applyMachineTypeOptimizations(&config, cfg, isSet) + optimizedFlags := applyMachineTypeOptimizations(&config, cfg, isSet) - require.NoError(t, err) assert.NotEmpty(t, optimizedFlags) assert.EqualValues(t, 0, cfg.MetadataCache.NegativeTtlSecs) assert.EqualValues(t, -1, cfg.MetadataCache.TtlSecs) @@ -267,9 +263,9 @@ func TestApplyMachineTypeOptimizations_MissingFlagOverrideSet(t *testing.T) { cfg := &Config{} isSet := &mockIsValueSet{setFlags: map[string]bool{}} - _, err := applyMachineTypeOptimizations(&config, cfg, isSet) + optimizedFlags := applyMachineTypeOptimizations(&config, cfg, isSet) - require.NoError(t, err) + require.Empty(t, optimizedFlags) } func TestApplyMachineTypeOptimizations_GetMachineTypeError(t *testing.T) { @@ -285,9 +281,9 @@ func TestApplyMachineTypeOptimizations_GetMachineTypeError(t *testing.T) { cfg := &Config{} isSet := &mockIsValueSet{setFlags: map[string]bool{}} - _, err := applyMachineTypeOptimizations(&config, cfg, isSet) + optimizedFlags := applyMachineTypeOptimizations(&config, cfg, isSet) - assert.NoError(t, err) + assert.Empty(t, optimizedFlags) } func TestApplyMachineTypeOptimizations_NoError(t *testing.T) { @@ -303,9 +299,9 @@ func TestApplyMachineTypeOptimizations_NoError(t *testing.T) { cfg := &Config{} isSet := &mockIsValueSet{setFlags: map[string]bool{}} - _, err := applyMachineTypeOptimizations(&config, cfg, isSet) + optimizedFlags := applyMachineTypeOptimizations(&config, cfg, isSet) - assert.NoError(t, err) + assert.NotEmpty(t, optimizedFlags) } func TestSetFlagValue_Bool(t *testing.T) { @@ -370,9 +366,8 @@ func TestApplyMachineTypeOptimizations_NoMachineTypes(t *testing.T) { cfg := &Config{} isSet := &mockIsValueSet{setFlags: map[string]bool{}} - _, err := applyMachineTypeOptimizations(&config, cfg, isSet) + _ = applyMachineTypeOptimizations(&config, cfg, isSet) - require.NoError(t, err) // Check that no optimizations were applied as no machine mapping is set. assert.False(t, cfg.Write.EnableStreamingWrites) } @@ -389,9 +384,8 @@ func TestOptimize_Success(t *testing.T) { cfg := &Config{} isSet := &mockIsValueSet{setFlags: map[string]bool{}} - optimizedFlags, err := Optimize(cfg, isSet) + optimizedFlags := Optimize(cfg, isSet) - require.NoError(t, err) assert.True(t, isFlagPresent(optimizedFlags, "write.global-max-blocks")) assert.EqualValues(t, 1600, cfg.Write.GlobalMaxBlocks) assert.True(t, isFlagPresent(optimizedFlags, "metadata-cache.negative-ttl-secs")) diff --git a/cmd/root.go b/cmd/root.go index 43ee03830e..a64dd562e8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -87,10 +87,7 @@ of Cloud Storage FUSE, see https://cloud.google.com/storage/docs/gcs-fuse.`, return } - var optimizedFlags []string - if optimizedFlags, cfgErr = cfg.Optimize(&configObj, v); cfgErr != nil { - return - } + optimizedFlags := cfg.Optimize(&configObj, v) if cfgErr = cfg.Rationalize(v, &configObj, optimizedFlags); cfgErr != nil { return From 005112cd8db8e368d538d37dcba90e4723d53f8a Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Wed, 30 Jul 2025 16:44:01 +0530 Subject: [PATCH 0619/1298] perf(metrics): Auto-generate metrics code to be more efficient (#3599) * Add support to auto-generate metrics code. We are not using the efficient metrics as yet. In a subsequent PR, we will fix the existing tests to work with this implementation, and then make changes to use this implementation. To demonstrate the code generation, we are using only two metrics --- main.go | 1 + optimizedmetrics/metric_handle.go | 33 +++ optimizedmetrics/metrics.yaml | 54 ++++ optimizedmetrics/noop_metrics.go | 36 +++ optimizedmetrics/otel_metrics.go | 141 ++++++++++ tools/metrics-gen/main.go | 398 ++++++++++++++++++++++++++++ tools/metrics-gen/metric_handle.tpl | 40 +++ tools/metrics-gen/noop_metrics.tpl | 40 +++ tools/metrics-gen/otel_metrics.tpl | 171 ++++++++++++ 9 files changed, 914 insertions(+) create mode 100644 optimizedmetrics/metric_handle.go create mode 100644 optimizedmetrics/metrics.yaml create mode 100644 optimizedmetrics/noop_metrics.go create mode 100644 optimizedmetrics/otel_metrics.go create mode 100644 tools/metrics-gen/main.go create mode 100644 tools/metrics-gen/metric_handle.tpl create mode 100644 tools/metrics-gen/noop_metrics.tpl create mode 100644 tools/metrics-gen/otel_metrics.tpl diff --git a/main.go b/main.go index 77fdf68603..aca1052744 100644 --- a/main.go +++ b/main.go @@ -40,6 +40,7 @@ func logPanic() { // Refer https://go.dev/blog/generate for details. // //go:generate go run -C tools/config-gen . --paramsFile=../../cfg/params.yaml --outDir=../../cfg --templateDir=templates +//go:generate go run -C tools/metrics-gen . --input=../../optimizedmetrics/metrics.yaml --outDir=../../optimizedmetrics func main() { // Common configuration for all commands defer logPanic() diff --git a/optimizedmetrics/metric_handle.go b/optimizedmetrics/metric_handle.go new file mode 100644 index 0000000000..40dda928cf --- /dev/null +++ b/optimizedmetrics/metric_handle.go @@ -0,0 +1,33 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** +package optimizedmetrics + +import ( + "context" + "time" +) + +// MetricHandle provides an interface for recording metrics. +// The methods of this interface are auto-generated from metrics.yaml. +// Each method corresponds to a metric defined in metrics.yaml. +type MetricHandle interface { + // FileCacheReadBytesCount - The cumulative number of bytes read from file cache along with read type - Sequential/Random + FileCacheReadBytesCount( + inc int64, readType string) + // FileCacheReadLatencies - The cumulative distribution of the file cache read latencies along with cache hit - true/false. + FileCacheReadLatencies( + ctx context.Context, duration time.Duration, cacheHit bool) +} diff --git a/optimizedmetrics/metrics.yaml b/optimizedmetrics/metrics.yaml new file mode 100644 index 0000000000..30b7dadacc --- /dev/null +++ b/optimizedmetrics/metrics.yaml @@ -0,0 +1,54 @@ +- metric-name: "file_cache/read_bytes_count" + description: "The cumulative number of bytes read from file cache along with read type - Sequential/Random" + unit: "By" + type: "int_counter" + attributes: + - attribute-name: read_type + attribute-type: string + values: + - "Parallel" + - "Random" + - "Sequential" + +- metric-name: "file_cache/read_latencies" + description: "The cumulative distribution of the file cache read latencies along with cache hit - true/false." + type: "int_histogram" + unit: "us" + boundaries: + - 1 + - 2 + - 3 + - 4 + - 5 + - 6 + - 8 + - 10 + - 13 + - 16 + - 20 + - 25 + - 30 + - 40 + - 50 + - 65 + - 80 + - 100 + - 130 + - 160 + - 200 + - 250 + - 300 + - 400 + - 500 + - 650 + - 800 + - 1000 + - 2000 + - 5000 + - 10000 + - 20000 + - 50000 + - 100000 + attributes: + - attribute-name: cache_hit + attribute-type: bool diff --git a/optimizedmetrics/noop_metrics.go b/optimizedmetrics/noop_metrics.go new file mode 100644 index 0000000000..b8a12a1692 --- /dev/null +++ b/optimizedmetrics/noop_metrics.go @@ -0,0 +1,36 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** +package optimizedmetrics + +import ( + "context" + "time" +) + +type noopMetrics struct{} + +func (*noopMetrics) FileCacheReadBytesCount( + inc int64, readType string) { +} + +func (*noopMetrics) FileCacheReadLatencies( + ctx context.Context, duration time.Duration, cacheHit bool) { +} + +func NewNoopMetrics() MetricHandle { + var n noopMetrics + return &n +} diff --git a/optimizedmetrics/otel_metrics.go b/optimizedmetrics/otel_metrics.go new file mode 100644 index 0000000000..d24bb9b553 --- /dev/null +++ b/optimizedmetrics/otel_metrics.go @@ -0,0 +1,141 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** + +package optimizedmetrics + +import ( + "context" + "errors" + "sync" + "sync/atomic" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" +) + +var ( + fileCacheReadBytesCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) + fileCacheReadBytesCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) + fileCacheReadBytesCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) + fileCacheReadLatenciesCacheHitTrueAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true))) + fileCacheReadLatenciesCacheHitFalseAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false))) +) + +type histogramRecord struct { + ctx context.Context + instrument metric.Int64Histogram + value int64 + attributes metric.RecordOption +} + +type otelMetrics struct { + ch chan histogramRecord + wg *sync.WaitGroup + fileCacheReadBytesCountReadTypeParallelAtomic *atomic.Int64 + fileCacheReadBytesCountReadTypeRandomAtomic *atomic.Int64 + fileCacheReadBytesCountReadTypeSequentialAtomic *atomic.Int64 + fileCacheReadLatencies metric.Int64Histogram +} + +func (o *otelMetrics) FileCacheReadBytesCount( + inc int64, readType string) { + switch readType { + case "Parallel": + o.fileCacheReadBytesCountReadTypeParallelAtomic.Add(inc) + case "Random": + o.fileCacheReadBytesCountReadTypeRandomAtomic.Add(inc) + case "Sequential": + o.fileCacheReadBytesCountReadTypeSequentialAtomic.Add(inc) + } + +} + +func (o *otelMetrics) FileCacheReadLatencies( + ctx context.Context, latency time.Duration, cacheHit bool) { + var record histogramRecord + switch cacheHit { + case true: + record = histogramRecord{ctx: ctx, instrument: o.fileCacheReadLatencies, value: latency.Microseconds(), attributes: fileCacheReadLatenciesCacheHitTrueAttrSet} + case false: + record = histogramRecord{ctx: ctx, instrument: o.fileCacheReadLatencies, value: latency.Microseconds(), attributes: fileCacheReadLatenciesCacheHitFalseAttrSet} + } + + select { + case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + default: // Unblock writes to channel if it's full. + } +} + +func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetrics, error) { + ch := make(chan histogramRecord, bufferSize) + var wg sync.WaitGroup + for range workers { + wg.Add(1) + go func() { + defer wg.Done() + for record := range ch { + record.instrument.Record(record.ctx, record.value, record.attributes) + } + }() + } + meter := otel.Meter("gcsfuse") + var fileCacheReadBytesCountReadTypeParallelAtomic, + fileCacheReadBytesCountReadTypeRandomAtomic, + fileCacheReadBytesCountReadTypeSequentialAtomic atomic.Int64 + + _, err0 := meter.Int64ObservableCounter("file_cache/read_bytes_count", + metric.WithDescription("The cumulative number of bytes read from file cache along with read type - Sequential/Random"), + metric.WithUnit("By"), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &fileCacheReadBytesCountReadTypeParallelAtomic, fileCacheReadBytesCountReadTypeParallelAttrSet) + conditionallyObserve(obsrv, &fileCacheReadBytesCountReadTypeRandomAtomic, fileCacheReadBytesCountReadTypeRandomAttrSet) + conditionallyObserve(obsrv, &fileCacheReadBytesCountReadTypeSequentialAtomic, fileCacheReadBytesCountReadTypeSequentialAttrSet) + return nil + })) + + fileCacheReadLatencies, err1 := meter.Int64Histogram("file_cache/read_latencies", + metric.WithDescription("The cumulative distribution of the file cache read latencies along with cache hit - true/false."), + metric.WithUnit("us"), + metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) + + errs := []error{err0, err1} + if err := errors.Join(errs...); err != nil { + return nil, err + } + + return &otelMetrics{ + ch: ch, + wg: &wg, + fileCacheReadBytesCountReadTypeParallelAtomic: &fileCacheReadBytesCountReadTypeParallelAtomic, + fileCacheReadBytesCountReadTypeRandomAtomic: &fileCacheReadBytesCountReadTypeRandomAtomic, + fileCacheReadBytesCountReadTypeSequentialAtomic: &fileCacheReadBytesCountReadTypeSequentialAtomic, + fileCacheReadLatencies: fileCacheReadLatencies, + }, nil +} + +func (o *otelMetrics) Close() { + close(o.ch) + o.wg.Wait() +} + +func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, attrSet metric.ObserveOption) { + if val := counter.Load(); val > 0 { + obsrv.Observe(val, attrSet) + } + +} diff --git a/tools/metrics-gen/main.go b/tools/metrics-gen/main.go new file mode 100644 index 0000000000..6b959016cb --- /dev/null +++ b/tools/metrics-gen/main.go @@ -0,0 +1,398 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bytes" + "flag" + "fmt" + "log" + "os" + "sort" + "strconv" + "strings" + "text/template" // NOLINT + + "gopkg.in/yaml.v3" +) + +// Data structures to parse metrics.yaml +type Metric struct { + Name string `yaml:"metric-name"` + Description string `yaml:"description"` + Type string `yaml:"type"` + Unit string `yaml:"unit"` + Attributes []Attribute `yaml:"attributes"` + Boundaries []int64 `yaml:"boundaries"` +} + +type Attribute struct { + Name string `yaml:"attribute-name"` + Type string `yaml:"attribute-type"` + Values []string `yaml:"values"` +} + +// AttrValuePair is a helper struct for generating combinations. +type AttrValuePair struct { + Name string + Type string + Value string // "true"/"false" for bools +} + +// AttrCombination is a list of AttrValuePairs. +type AttrCombination []AttrValuePair + +// Data structure to pass to the template. +type TemplateData struct { + Metrics []Metric + AttrCombinations map[string][]AttrCombination +} + +// Helper functions for the template. +var funcMap = template.FuncMap{ + "toPascal": toPascal, + "toCamel": toCamel, + "getVarName": getVarName, + "getAtomicName": getAtomicName, + "getGoType": getGoType, + "getUnitMethod": getUnitMethod, + "joinInts": joinInts, + "isCounter": func(m Metric) bool { return m.Type == "int_counter" }, + "isHistogram": func(m Metric) bool { return m.Type == "int_histogram" }, + "buildSwitches": buildSwitches, +} + +func toPascal(s string) string { + s = strings.ReplaceAll(s, "::", "-") + s = strings.ReplaceAll(s, "/", "-") + s = strings.ReplaceAll(s, "_", "-") + parts := strings.Split(s, "-") + for i, p := range parts { + if len(p) > 0 { + parts[i] = strings.ToUpper(p[:1]) + p[1:] + } + } + return strings.Join(parts, "") +} + +func toCamel(s string) string { + pascal := toPascal(s) + if len(pascal) > 0 { + return strings.ToLower(pascal[:1]) + pascal[1:] + } + return "" +} + +func getVarName(metricName string, combo AttrCombination) string { + var parts []string + parts = append(parts, toCamel(metricName)) + for _, pair := range combo { + parts = append(parts, toPascal(pair.Name)) + parts = append(parts, toPascal(pair.Value)) + } + parts = append(parts, "AttrSet") + return strings.Join(parts, "") +} + +func getAtomicName(metricName string, combo AttrCombination) string { + var parts []string + parts = append(parts, toCamel(metricName)) + for _, pair := range combo { + parts = append(parts, toPascal(pair.Name)) + parts = append(parts, toPascal(pair.Value)) + } + parts = append(parts, "Atomic") + return strings.Join(parts, "") +} + +func getGoType(t string) string { + switch t { + case "string": + return "string" + case "bool": + return "bool" + default: + return "interface{}" + } +} + +func getUnitMethod(unit string) string { + switch unit { + case "us": + return ".Microseconds()" + case "ms": + return ".Milliseconds()" + case "s": + return ".Seconds()" + default: + // Assumes the value is already in the correct unit if not time-based. + return "" + } +} + +func joinInts(nums []int64) string { + var s []string + for _, n := range nums { + s = append(s, strconv.FormatInt(n, 10)) + } + return strings.Join(s, ", ") +} + +// generateCombinations creates all possible combinations of attribute values. +func generateCombinations(attributes []Attribute) []AttrCombination { + if len(attributes) == 0 { + return []AttrCombination{{}} + } + + firstAttr := attributes[0] + remainingAttrs := attributes[1:] + combsOfRest := generateCombinations(remainingAttrs) + + var firstAttrValues []AttrValuePair + if firstAttr.Type == "string" { + for _, v := range firstAttr.Values { + firstAttrValues = append(firstAttrValues, AttrValuePair{Name: firstAttr.Name, Type: "string", Value: v}) + } + } else if firstAttr.Type == "bool" { + firstAttrValues = append(firstAttrValues, AttrValuePair{Name: firstAttr.Name, Type: "bool", Value: "true"}) + firstAttrValues = append(firstAttrValues, AttrValuePair{Name: firstAttr.Name, Type: "bool", Value: "false"}) + } + + var result []AttrCombination + for _, val := range firstAttrValues { + for _, comb := range combsOfRest { + newComb := append(AttrCombination{val}, comb...) + result = append(result, newComb) + } + } + return result +} + +func validateMetric(m Metric) error { + if m.Name == "" { + return fmt.Errorf("metric-name is required") + } + if m.Description == "" { + return fmt.Errorf("description is required for metric %q", m.Name) + } + if m.Type != "int_counter" && m.Type != "int_histogram" { + return fmt.Errorf("type for metric %q must be 'int_counter' or 'int_histogram', got %q", m.Name, m.Type) + } + + if m.Type == "int_histogram" { + if len(m.Boundaries) == 0 { + return fmt.Errorf("boundaries are required for histogram metric %q", m.Name) + } + } else { // int_counter + if len(m.Boundaries) > 0 { + return fmt.Errorf("boundaries should not be present for counter metric %q", m.Name) + } + } + + for _, a := range m.Attributes { + if a.Name == "" { + return fmt.Errorf("attribute-name is required for an attribute in metric %q", m.Name) + } + if a.Type != "string" && a.Type != "bool" { + return fmt.Errorf("attribute-type for attribute %q in metric %q must be 'string' or 'bool', got %q", a.Name, m.Name, a.Type) + } + + if a.Type == "string" { + if len(a.Values) == 0 { + return fmt.Errorf("values are required for string attribute %q in metric %q", a.Name, m.Name) + } + } + if a.Type == "bool" && len(a.Values) != 0 { + return fmt.Errorf("values should not be present for bool attribute %q in metric %q", a.Name, m.Name) + } + } + return nil +} + +func validateForDuplicates(metrics []Metric) error { + names := make(map[string]bool) + for _, m := range metrics { + if names[m.Name] { + return fmt.Errorf("duplicate metric-name: %q", m.Name) + } + names[m.Name] = true + } + return nil +} + +func validateSortOrder(metrics []Metric) error { + for i := 1; i < len(metrics); i++ { + if metrics[i-1].Name > metrics[i].Name { + return fmt.Errorf("metrics are not sorted by name. %q should come before %q", metrics[i].Name, metrics[i-1].Name) + } + } + return nil +} + +func validateAttributeSortOrder(metrics []Metric) error { + for _, m := range metrics { + for i := 1; i < len(m.Attributes); i++ { + if m.Attributes[i-1].Name > m.Attributes[i].Name { + return fmt.Errorf("attributes for metric %q are not sorted by name. %q should come before %q", m.Name, m.Attributes[i].Name, m.Attributes[i-1].Name) + } + } + } + return nil +} + +// validateMetrics checks for correctness of the metric definitions. +func validateMetrics(metrics []Metric) error { + if err := validateForDuplicates(metrics); err != nil { + return err + } + if err := validateSortOrder(metrics); err != nil { + return err + } + if err := validateAttributeSortOrder(metrics); err != nil { + return err + } + for _, m := range metrics { + if err := validateMetric(m); err != nil { + return err + } + } + return nil +} + +// buildSwitches generates the nested switch statement code for a metric method. +func buildSwitches(metric Metric) string { + var builder strings.Builder + var recorder func(level int, combo AttrCombination) + + recorder = func(level int, combo AttrCombination) { + if level == len(metric.Attributes) { + // Base case: record the metric + indent := strings.Repeat("\t", level+1) + if metric.Type == "int_counter" { + atomicName := getAtomicName(metric.Name, combo) + builder.WriteString(fmt.Sprintf("%so.%s.Add(inc)\n", indent, atomicName)) + } else { // histogram + varName := getVarName(metric.Name, combo) + unitMethod := getUnitMethod(metric.Unit) + builder.WriteString(fmt.Sprintf("%srecord = histogramRecord{ctx: ctx,instrument: o.%s, value: latency%s, attributes: %s}\n", indent, toCamel(metric.Name), unitMethod, varName)) + } + return + } + + attr := metric.Attributes[level] + indent := strings.Repeat("\t", level+1) + builder.WriteString(fmt.Sprintf("%sswitch %s {\n", indent, toCamel(attr.Name))) + + var values []string + if attr.Type == "string" { + values = attr.Values + } else { // bool + values = []string{"true", "false"} + } + + for _, val := range values { + caseVal := val + if attr.Type == "string" { + caseVal = `"` + val + `"` + } + builder.WriteString(fmt.Sprintf("%scase %s:\n", strings.Repeat("\t", level+2), caseVal)) + currentCombo := append(combo, AttrValuePair{Name: attr.Name, Type: attr.Type, Value: val}) + recorder(level+1, currentCombo) + } + builder.WriteString(fmt.Sprintf("%s}\n", indent)) + } + + if len(metric.Attributes) == 0 { + if metric.Type == "int_histogram" { + unitMethod := getUnitMethod(metric.Unit) + builder.WriteString(fmt.Sprintf("\trecord = histogramRecord{instrument: o.%s, value: latency%s}\n", toCamel(metric.Name), unitMethod)) + } else if metric.Type == "int_counter" { + atomicName := getAtomicName(metric.Name, AttrCombination{}) + builder.WriteString(fmt.Sprintf("\to.%s.Add(inc)\n", atomicName)) + } + } else { + recorder(0, AttrCombination{}) + } + + return builder.String() +} + +func main() { + inputFile := flag.String("input", "metrics.yaml", "Input YAML file") + outputDir := flag.String("outDir", ".", "Output directory to dump artifacts.") + flag.Parse() + + yamlFile, err := os.ReadFile(*inputFile) + if err != nil { + log.Fatalf("error reading yaml file: %v", err) + } + + var metrics []Metric + err = yaml.Unmarshal(yamlFile, &metrics) + if err != nil { + log.Fatalf("error unmarshalling yaml: %v", err) + } + + // Validate metrics + if err := validateMetrics(metrics); err != nil { + log.Fatalf("invalid metrics.yaml: %v", err) + } + + // Sort attributes and their string values for deterministic output + for i := range metrics { + sort.Slice(metrics[i].Attributes, func(k, j int) bool { + return metrics[i].Attributes[k].Name < metrics[i].Attributes[j].Name + }) + for j := range metrics[i].Attributes { + if metrics[i].Attributes[j].Type == "string" { + sort.Strings(metrics[i].Attributes[j].Values) + } + } + } + + attrCombinations := make(map[string][]AttrCombination) + for _, m := range metrics { + attrCombinations[m.Name] = generateCombinations(m.Attributes) + } + + // Create the directory if it doesn't exist + if err := os.MkdirAll(*outputDir, 0755); err != nil { + log.Fatalf("error creating output directory: %v", err) + } + data := TemplateData{ + Metrics: metrics, + AttrCombinations: attrCombinations, + } + createFile(&data, fmt.Sprintf("%s/metric_handle.go", *outputDir), "metric_handle.tpl") + createFile(&data, fmt.Sprintf("%s/noop_metrics.go", *outputDir), "noop_metrics.tpl") + createFile(&data, fmt.Sprintf("%s/otel_metrics.go", *outputDir), "otel_metrics.tpl") + +} + +func createFile(data *TemplateData, fName string, templateName string) { + tmpl, err := template.New(templateName).Funcs(funcMap).ParseFiles(templateName) + if err != nil { + log.Fatalf("error parsing template: %v", err) + } + var buf bytes.Buffer + err = tmpl.Execute(&buf, data) + if err != nil { + log.Fatalf("error executing template: %v", err) + } + + if err := os.WriteFile(fName, buf.Bytes(), 0644); err != nil { + log.Fatalf("error writing output file: %v", err) + } +} diff --git a/tools/metrics-gen/metric_handle.tpl b/tools/metrics-gen/metric_handle.tpl new file mode 100644 index 0000000000..b1bdef2d45 --- /dev/null +++ b/tools/metrics-gen/metric_handle.tpl @@ -0,0 +1,40 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** +package optimizedmetrics + +import ( + "context" + "time" +) + +// MetricHandle provides an interface for recording metrics. +// The methods of this interface are auto-generated from metrics.yaml. +// Each method corresponds to a metric defined in metrics.yaml. +type MetricHandle interface { +{{- range .Metrics}} + // {{toPascal .Name}} - {{.Description}} + {{toPascal .Name}}( + {{- if isCounter . }} + inc int64 + {{- else }} + ctx context.Context, duration time.Duration + {{- end }} + {{- if .Attributes}}, {{end}} + {{- range $i, $attr := .Attributes -}} + {{if $i}}, {{end}}{{toCamel $attr.Name}} {{getGoType $attr.Type}} + {{- end }}) +{{- end}} +} diff --git a/tools/metrics-gen/noop_metrics.tpl b/tools/metrics-gen/noop_metrics.tpl new file mode 100644 index 0000000000..0e4356de6e --- /dev/null +++ b/tools/metrics-gen/noop_metrics.tpl @@ -0,0 +1,40 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** +package optimizedmetrics + +import ( + "context" + "time" +) + +type noopMetrics struct {} +{{- range .Metrics}} + func (*noopMetrics) {{toPascal .Name}}( + {{- if isCounter . }} + inc int64 + {{- else }} + ctx context.Context, duration time.Duration + {{- end }} + {{- if .Attributes}}, {{end}} + {{- range $i, $attr := .Attributes -}} + {{if $i}}, {{end}}{{toCamel $attr.Name}} {{getGoType $attr.Type}} + {{- end }}){} +{{end}} + +func NewNoopMetrics() MetricHandle { + var n noopMetrics + return &n +} diff --git a/tools/metrics-gen/otel_metrics.tpl b/tools/metrics-gen/otel_metrics.tpl new file mode 100644 index 0000000000..b2799adbf1 --- /dev/null +++ b/tools/metrics-gen/otel_metrics.tpl @@ -0,0 +1,171 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** + +package optimizedmetrics + +import ( + "context" + "errors" + "sync" + "sync/atomic" + "time" + + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/metric" +) + +var ( +{{- range $metric := .Metrics -}} +{{- if .Attributes}} +{{- range $combination := (index $.AttrCombinations $metric.Name)}} + {{getVarName $metric.Name $combination}} = metric.WithAttributeSet(attribute.NewSet( + {{- range $pair := $combination -}} + attribute.{{if eq $pair.Type "string"}}String{{else}}Bool{{end}}("{{$pair.Name}}", {{if eq $pair.Type "string"}}"{{$pair.Value}}"{{else}}{{$pair.Value}}{{end}}), + {{- end -}} + )) +{{- end -}} +{{- end -}} +{{- end -}} +) + +type histogramRecord struct { + ctx context.Context + instrument metric.Int64Histogram + value int64 + attributes metric.RecordOption +} + +type otelMetrics struct { + ch chan histogramRecord + wg *sync.WaitGroup + {{- range $metric := .Metrics}} + {{- if isCounter $metric}} + {{- range $combination := (index $.AttrCombinations $metric.Name)}} + {{getAtomicName $metric.Name $combination}} *atomic.Int64 + {{- end}} + {{- end}} + {{- end}} + {{- range $metric := .Metrics}} + {{- if isHistogram $metric}} + {{toCamel $metric.Name}} metric.Int64Histogram + {{- end}} + {{- end}} +} + +{{range .Metrics}} +func (o *otelMetrics) {{toPascal .Name}}( + {{- if isCounter . }} + inc int64 + {{- else }} + ctx context.Context, latency time.Duration + {{- end }} + {{- if .Attributes}}, {{end}} + {{- range $i, $attr := .Attributes -}} + {{if $i}}, {{end}}{{toCamel $attr.Name}} {{getGoType $attr.Type}} + {{- end }}) { +{{- if isCounter . }} + {{buildSwitches .}} +{{- else }} + var record histogramRecord + {{buildSwitches .}} + select { + case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + default: // Unblock writes to channel if it's full. + } + {{- end}} +} +{{end}} + +func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetrics, error) { + ch := make(chan histogramRecord, bufferSize) + var wg sync.WaitGroup + for range workers { + wg.Add(1) + go func() { + defer wg.Done() + for record := range ch { + record.instrument.Record(record.ctx, record.value, record.attributes) + } + }() + } + meter := otel.Meter("gcsfuse") +{{- range $metric := .Metrics}} + {{- if isCounter $metric}} + var {{range $i, $combination := (index $.AttrCombinations $metric.Name)}}{{if $i}}, + {{end}}{{getAtomicName $metric.Name $combination}}{{end}} atomic.Int64 + {{- end}} + +{{end}} + +{{- range $i, $metric := .Metrics}} + {{- if isCounter $metric}} + _, err{{$i}} := meter.Int64ObservableCounter("{{$metric.Name}}", + metric.WithDescription("{{.Description}}"), + metric.WithUnit("{{.Unit}}"), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + {{- range $combination := (index $.AttrCombinations $metric.Name)}} + conditionallyObserve(obsrv, &{{getAtomicName $metric.Name $combination}}, {{getVarName $metric.Name $combination}}) + {{- end}} + return nil + })) + {{- else}} + {{toCamel $metric.Name}}, err{{$i}} := meter.Int64Histogram("{{$metric.Name}}", + metric.WithDescription("{{.Description}}"), + metric.WithUnit("{{.Unit}}"), + {{- if .Boundaries}} + metric.WithExplicitBucketBoundaries({{joinInts .Boundaries}})) + {{- else}} + ) + {{- end}} + {{- end}} +{{end}} + + errs := []error{ + {{- range $i, $metric := .Metrics -}} + {{if $i}}, {{end}}err{{$i}} + {{- end -}} + } + if err := errors.Join(errs...); err != nil { + return nil, err + } + + return &otelMetrics{ + ch : ch, + wg: &wg, + {{- range $metric := .Metrics}} + {{- if isCounter $metric}} + {{- range $combination := (index $.AttrCombinations $metric.Name)}} + {{getAtomicName $metric.Name $combination}}: &{{getAtomicName $metric.Name $combination}}, + {{- end}} + {{- else}} + {{toCamel $metric.Name}}: {{toCamel $metric.Name}}, + {{- end}} + {{- end}} + }, nil +} + +func (o *otelMetrics) Close() { + close(o.ch) + o.wg.Wait() +} + +func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, attrSet metric.ObserveOption) { + if val := counter.Load(); val > 0 { + obsrv.Observe(val, attrSet) + } + +} From 967a47d006685d05fe40f50f8610ee276d2e74cf Mon Sep 17 00:00:00 2001 From: Aditi Mittal <96827030+aditimittal2003@users.noreply.github.com> Date: Wed, 30 Jul 2025 12:48:16 +0000 Subject: [PATCH 0620/1298] test(dentry_cache): add integration tests for delete operations (#3594) * add delete file test --- .../dentry_cache/delete_operation_test.go | 80 +++++++++++++++++++ .../dentry_cache/notifier_test.go | 21 +++++ .../dentry_cache/stat_test.go | 25 +++++- .../run_tests_mounted_directory.sh | 20 ++++- 4 files changed, 141 insertions(+), 5 deletions(-) create mode 100644 tools/integration_tests/dentry_cache/delete_operation_test.go diff --git a/tools/integration_tests/dentry_cache/delete_operation_test.go b/tools/integration_tests/dentry_cache/delete_operation_test.go new file mode 100644 index 0000000000..3fed3d17e2 --- /dev/null +++ b/tools/integration_tests/dentry_cache/delete_operation_test.go @@ -0,0 +1,80 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dentry_cache + +import ( + "log" + "os" + "path" + "testing" + + "cloud.google.com/go/storage" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type deleteOperationTest struct { + flags []string +} + +func (s *deleteOperationTest) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(s.flags, testDirName) +} + +func (s *deleteOperationTest) Teardown(t *testing.T) { + if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory + setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) + setup.UnmountGCSFuseAndDeleteLogFile(rootDir) + } +} + +func (s *deleteOperationTest) TestDeleteFileWhenFileIsClobbered(t *testing.T) { + // Create a file with initial content directly in GCS. + filePath := path.Join(setup.MntDir(), testDirName, testFileName) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + // Stat file to cache the entry + _, err := os.Stat(filePath) + require.Nil(t, err) + // Modify the object on GCS. + objectName := path.Join(testDirName, testFileName) + smallContent, err := operations.GenerateRandomData(updatedContentSize) + require.Nil(t, err) + require.Nil(t, client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) + + // Deleting the file should not give error + err = os.Remove(filePath) + + assert.Nil(t, err) +} + +func TestDeleteOperationTest(t *testing.T) { + ts := &deleteOperationTest{} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + // Setup flags and run tests. + ts.flags = []string{"--implicit-dirs", "--experimental-enable-dentry-cache", "--metadata-cache-ttl-secs=1000"} + log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) +} diff --git a/tools/integration_tests/dentry_cache/notifier_test.go b/tools/integration_tests/dentry_cache/notifier_test.go index f4bd5f6771..ce231f17f8 100644 --- a/tools/integration_tests/dentry_cache/notifier_test.go +++ b/tools/integration_tests/dentry_cache/notifier_test.go @@ -95,6 +95,27 @@ func (s *notifierTest) TestReadFileWithDentryCacheEnabled(t *testing.T) { assert.Nil(t, err) } +func (s *notifierTest) TestDeleteFileWithDentryCacheEnabled(t *testing.T) { + // Create a file with initial content directly in GCS. + filePath := path.Join(setup.MntDir(), testDirName, testFileName) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + // Stat file to cache the entry + _, err := os.Stat(filePath) + require.Nil(t, err) + // Delete the object directly from GCS. + objectName := path.Join(testDirName, testFileName) + require.Nil(t, client.DeleteObjectOnGCS(ctx, storageClient, objectName)) + // 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(t, err) + + // Stat again, it should give error as entry does not exist. + _, err = os.Stat(filePath) + + assert.NotNil(t, err) +} + func TestNotifierTest(t *testing.T) { ts := ¬ifierTest{} diff --git a/tools/integration_tests/dentry_cache/stat_test.go b/tools/integration_tests/dentry_cache/stat_test.go index db02e6ff24..8a9043d6fb 100644 --- a/tools/integration_tests/dentry_cache/stat_test.go +++ b/tools/integration_tests/dentry_cache/stat_test.go @@ -64,7 +64,7 @@ func (s *statWithDentryCacheEnabledTest) TestStatWithDentryCacheEnabled(t *testi assert.Nil(t, err) assert.Equal(t, int64(initialContentSize), fileInfo.Size()) - // Wait until entry expires in cache. + // Wait for a period more than the timeout (1 second), so that entry expires in cache. time.Sleep(1100 * time.Millisecond) // Stat again, it should give updated attributes. fileInfo, err = os.Stat(filePath) @@ -72,6 +72,29 @@ func (s *statWithDentryCacheEnabledTest) TestStatWithDentryCacheEnabled(t *testi assert.Equal(t, int64(updatedContentSize), fileInfo.Size()) } +func (s *statWithDentryCacheEnabledTest) TestStatWhenFileIsDeletedDirectlyFromGCS(t *testing.T) { + // Create a file with initial content directly in GCS. + filePath := path.Join(setup.MntDir(), testDirName, testFileName) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + // Stat file to cache the entry + _, err := os.Stat(filePath) + require.Nil(t, err) + // Delete the object directly from GCS. + objectName := path.Join(testDirName, testFileName) + require.Nil(t, client.DeleteObjectOnGCS(ctx, storageClient, objectName)) + + // Stat again, it should give old cached attributes rather than giving not found error. + fileInfo, err := os.Stat(filePath) + + assert.Nil(t, err) + assert.Equal(t, int64(initialContentSize), fileInfo.Size()) + // Wait for a period more than the timeout (1 second), so that entry expires in cache. + time.Sleep(1100 * time.Millisecond) + // Stat again, it should give error as file does not exist. + _, err = os.Stat(filePath) + assert.NotNil(t, err) +} + func TestStatWithDentryCacheEnabledTest(t *testing.T) { ts := &statWithDentryCacheEnabledTest{} diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index 870e9ecb20..4ea62c88f1 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -682,18 +682,30 @@ rm -rf $log_dir # Test package: dentry_cache # Run stat with dentry cache enabled -test_case="TestStatWithDentryCacheEnabledTest/TestStatWithDentryCacheEnabled" -gcsfuse --implicit-dirs --experimental-enable-dentry-cache --metadata-cache-ttl-secs=1 "$TEST_BUCKET_NAME" "$MOUNT_DIR" -GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/dentry_cache/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run $test_case -sudo umount "$MOUNT_DIR" +test_cases=( +"TestStatWithDentryCacheEnabledTest/TestStatWithDentryCacheEnabled" +"TestStatWithDentryCacheEnabledTest/TestStatWhenFileIsDeletedDirectlyFromGCS" +) +for test_case in "${test_cases[@]}"; do + gcsfuse --implicit-dirs --experimental-enable-dentry-cache --metadata-cache-ttl-secs=1 "$TEST_BUCKET_NAME" "$MOUNT_DIR" + GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/dentry_cache/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run $test_case + sudo umount "$MOUNT_DIR" +done # Run notifier tests test_cases=( "TestNotifierTest/TestReadFileWithDentryCacheEnabled" "TestNotifierTest/TestWriteFileWithDentryCacheEnabled" + "TestNotifierTest/TestDeleteFileWithDentryCacheEnabled" ) for test_case in "${test_cases[@]}"; do gcsfuse --implicit-dirs --experimental-enable-dentry-cache --metadata-cache-ttl-secs=1000 "$TEST_BUCKET_NAME" "$MOUNT_DIR" GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/dentry_cache/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run $test_case sudo umount "$MOUNT_DIR" done + +# Run delete operation tests when dentry cache is enabled +test_case="TestDeleteOperationTest/TestDeleteFileWhenFileIsClobbered" +gcsfuse --implicit-dirs --experimental-enable-dentry-cache --metadata-cache-ttl-secs=1000 "$TEST_BUCKET_NAME" "$MOUNT_DIR" +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/dentry_cache/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run $test_case +sudo umount "$MOUNT_DIR" From 5a62019fd454af517d5e36deb3fbb2d998a28719 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Thu, 31 Jul 2025 09:59:10 +0530 Subject: [PATCH 0621/1298] test(rapid appends): test size visible to kernel on appends (#3608) * ft for size visible to kernel * close the append session * refactor --- .../rapid_appends/appends_test.go | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tools/integration_tests/rapid_appends/appends_test.go b/tools/integration_tests/rapid_appends/appends_test.go index 11154f4bb1..90b43cb99d 100644 --- a/tools/integration_tests/rapid_appends/appends_test.go +++ b/tools/integration_tests/rapid_appends/appends_test.go @@ -19,6 +19,7 @@ import ( "os" "path" "syscall" + "time" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" @@ -332,3 +333,54 @@ func (t *SingleMountAppendsSuite) TestFallbackHappensWhenNonAppendHandleDoesFirs }() } } + +func (t *SingleMountAppendsSuite) TestKernelShouldSeeUpdatedSizeOnAppends() { + const initialContent = "dummy content" + flags := []string{"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"} + log.Printf("Running test with flags: %v", flags) + + testCases := []struct { + name string + expireCache bool + }{ + { + name: "validStatCache", + expireCache: false, + }, + { + name: "expiredStatCache", + expireCache: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.mountPrimaryMount(flags) + defer t.unmountPrimaryMount() + + // Initially create an unfinalized object. + t.createUnfinalizedObject() + defer t.deleteUnfinalizedObject() + + filePath := path.Join(t.primaryMount.testDirPath, t.fileName) + + // Append to the unfinalized object and close the file handle. + appendFileHandle := operations.OpenFileInMode(t.T(), filePath, os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + n, err := appendFileHandle.Write([]byte(initialContent)) + require.NoError(t.T(), err) + require.NotZero(t.T(), n) + appendFileHandle.Close() + + // Expire stat cache if required by the test case. By default, stat cache ttl is 1 sec. + if tc.expireCache { + time.Sleep(time.Second) + } + + // stat() the file to assert on the file size as viewed by the kernel. + expectedFileSize := int64(unfinalizedObjectSize + len(initialContent)) + fileInfo, err := operations.StatFile(filePath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), expectedFileSize, (*fileInfo).Size()) + }) + } +} From f58d837581a6eb6e7d95f979288a60a9986d9b28 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 31 Jul 2025 10:03:48 +0530 Subject: [PATCH 0622/1298] docs: semantic doc update to fix concurrent write documentation (#3613) --- docs/semantics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/semantics.md b/docs/semantics.md index 8b61ccfe74..90b60b4842 100644 --- a/docs/semantics.md +++ b/docs/semantics.md @@ -123,7 +123,7 @@ Close and fsync create a new generation of the object before returning, as long Examples: - Machine A opens a file and writes then successfully closes or syncs it, and the file was not concurrently unlinked from the point of view of A. Machine B then opens the file after machine A finishes closing or syncing. Machine B will observe a version of the file at least as new as the one created by machine A. -- Machine A and B both open the same file, which contains the text ‘ABC’. Machine A modifies the file to ‘ABC-123’ and closes/syncs the file which gets written back to Cloud Storage. After, Machine B, which still has the file open, instead modifies the file to ‘ABC-XYZ’, and saves and closes the file. As the last writer wins, the current state of the file will read ‘ABC-XYZ’. +- Machine A and B both open the same file, which contains the text ‘ABC’. Machine A modifies the file to ‘ABC-123’ and closes/syncs the file which gets written back to Cloud Storage. Afterward, Machine B, which still has the file open, modifies its local copy to ‘ABC-XYZ’, then tries to save and close the file. Since the first writer wins, the final state of the file in the cloud storage will be 'ABC-123'. Consequently, Machine B's file descriptor will receive an ESTALE error. ### Stale File Handle Errors From 1a343335c808dbc3a17c5be3cb8735d151f948c7 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 31 Jul 2025 11:39:03 +0530 Subject: [PATCH 0623/1298] feat(bufferedread): Implement ReadAt method of Buffered Reader (#3602) * Implementation * Add ReadAt method and unit tests * Fix Lint * Addressing comments * Add new method isRandom * Add Comment --- internal/block/prefetch_block.go | 1 - internal/bufferedread/buffered_reader.go | 216 ++++++++ internal/bufferedread/buffered_reader_test.go | 513 ++++++++++++++++-- internal/bufferedread/download_task.go | 1 - 4 files changed, 693 insertions(+), 38 deletions(-) diff --git a/internal/block/prefetch_block.go b/internal/block/prefetch_block.go index 6ee6d26478..4ac1711f75 100644 --- a/internal/block/prefetch_block.go +++ b/internal/block/prefetch_block.go @@ -63,7 +63,6 @@ type PrefetchBlock interface { // The value indicates the status of the block: // - BlockStatusDownloaded: Download of this block is complete. // - BlockStatusDownloadFailed: Download of this block has failed. - // - BlockStatusDownloadCancelled: Download of this block has been cancelled. NotifyReady(val BlockStatus) } diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index 2322da55d1..f7b6b0c1f8 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -18,7 +18,10 @@ import ( "context" "errors" "fmt" + "io" + "time" + "github.com/google/uuid" "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/block" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" @@ -42,6 +45,8 @@ type BufferedReadConfig struct { RandomReadsThreshold int64 // Number of random reads after which the reader falls back to GCS reader. } +const MiB = 1 << 20 + // blockQueueEntry holds a data block with a function // to cancel its in-flight download. type blockQueueEntry struct { @@ -103,6 +108,212 @@ func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *Buffere return reader, nil } +// handleRandomRead detects and handles random read patterns. A read is considered +// random if the requested offset is outside the currently prefetched window. +// If the number of detected random reads exceeds a configured threshold, it +// returns a gcsx.FallbackToAnotherReader error to signal that a simpler GCS +// reader should be used. +func (p *BufferedReader) handleRandomRead(offset int64) error { + // Exit early if we have already decided to fall back to a GCS reader. This + // avoids re-evaluating the read pattern on every call when the random read + // threshold has been met. + if p.randomSeekCount > p.config.RandomReadsThreshold { + return gcsx.FallbackToAnotherReader + } + + if !p.isRandomSeek(offset) { + return nil + } + + p.randomSeekCount++ + + // When a random seek is detected, the prefetched blocks in the queue become + // irrelevant. We must clear the queue, cancel any ongoing downloads, and + // release the blocks back to the pool. + for !p.blockQueue.IsEmpty() { + entry := p.blockQueue.Pop() + entry.cancel() + if _, waitErr := entry.block.AwaitReady(context.Background()); waitErr != nil { + logger.Warnf("handleRandomRead: AwaitReady error during discard (offset=%d): %v", offset, waitErr) + } + p.blockPool.Release(entry.block) + } + + if p.randomSeekCount > p.config.RandomReadsThreshold { + logger.Tracef("Too many random reads for object %q (count: %d, threshold: %d); falling back to GCS reader.", + p.object.Name, p.randomSeekCount, p.config.RandomReadsThreshold) + return gcsx.FallbackToAnotherReader + } + + return nil +} + +// isRandomSeek checks if the read for the given offset is random or not. +func (p *BufferedReader) isRandomSeek(offset int64) bool { + if p.blockQueue.IsEmpty() { + return offset != 0 + } + + start := p.blockQueue.Peek().block.AbsStartOff() + end := start + int64(p.blockQueue.Len())*p.config.PrefetchBlockSizeBytes + if offset < start || offset >= end { + return true + } + + return false +} + +// prepareQueueForOffset cleans the head of the block queue by discarding any +// blocks that are no longer relevant for the given read offset. This occurs on +// seeks (both forward and backward) that land outside the current block. +// For each discarded block, its download is cancelled, and it is returned to +// the block pool. +func (p *BufferedReader) prepareQueueForOffset(offset int64) { + for !p.blockQueue.IsEmpty() { + entry := p.blockQueue.Peek() + block := entry.block + blockStart := block.AbsStartOff() + blockEnd := blockStart + block.Cap() + + if offset < blockStart || offset >= blockEnd { + // Offset is either before or beyond this block – discard. + p.blockQueue.Pop() + entry.cancel() + + if _, waitErr := block.AwaitReady(context.Background()); waitErr != nil { + logger.Warnf("prepareQueueForOffset: AwaitReady error during discard (offset=%d): %v", offset, waitErr) + } + + p.blockPool.Release(block) + } else { + break + } + } +} + +// ReadAt reads data from the GCS object into the provided buffer starting at +// the given offset. It implements the gcsx.Reader interface. +// +// The read is satisfied by reading from in-memory blocks that are prefetched +// in the background. The core logic is as follows: +// 1. Detect if the read pattern is random. If so, and if the random read +// threshold is exceeded, it returns a FallbackToAnotherReader error. +// 2. Prepare the internal block queue by discarding any stale blocks from the +// head of the queue that are before the requested offset. +// 3. If the queue becomes empty (e.g., on a fresh read or a large seek), it +// initiates a "fresh start" to prefetch blocks starting from the current +// offset. +// 4. It then enters a loop to fill the destination buffer: +// a. It waits for the block at the head of the queue to be downloaded. +// b. If the download failed or was cancelled, it returns an appropriate error. +// c. If successful, it copies data from the downloaded block into the buffer. +// d. If a block is fully consumed, it is removed from the queue, and a new +// prefetch operation is triggered to keep the pipeline full. +// 5. The loop continues until the buffer is full, the end of the file is +// reached, or an error occurs. +func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) (gcsx.ReaderResponse, error) { + resp := gcsx.ReaderResponse{DataBuf: inputBuf} + reqID := uuid.New() + start := time.Now() + initOff := off + blockIdx := initOff / p.config.PrefetchBlockSizeBytes + + var bytesRead int + var err error + + logger.Tracef("%.13v <- ReadAt(offset=%d, len=%d, blockIdx=%d)", reqID, off, len(inputBuf), blockIdx) + + if off >= int64(p.object.Size) { + err = io.EOF + return resp, err + } + + if len(inputBuf) == 0 { + return resp, nil + } + + defer func() { + dur := time.Since(start) + if err != nil && err != io.EOF { + logger.Errorf("%.13v -> ReadAt failed (offset=%d, len=%d, blockIdx=%d): err: %v (%v)", reqID, initOff, len(inputBuf), blockIdx, err, dur) + } else { + logger.Tracef("%.13v -> ReadAt OK (offset=%d, len=%d, read=%d) (%v)", reqID, initOff, len(inputBuf), bytesRead, dur) + } + }() + + if err = p.handleRandomRead(off); err != nil { + err = fmt.Errorf("ReadAt: handleRandomRead failed: %w", err) + return resp, err + } + + prefetchTriggered := false + + for bytesRead < len(inputBuf) { + p.prepareQueueForOffset(off) + + if p.blockQueue.IsEmpty() { + if err = p.freshStart(off); err != nil { + err = fmt.Errorf("ReadAt: freshStart failed: %w", err) + break + } + prefetchTriggered = true + } + + entry := p.blockQueue.Peek() + blk := entry.block + + status, waitErr := blk.AwaitReady(ctx) + if waitErr != nil { + err = fmt.Errorf("ReadAt: AwaitReady failed: %w", waitErr) + break + } + + if status.State != block.BlockStateDownloaded { + p.blockQueue.Pop() + p.blockPool.Release(blk) + entry.cancel() + + switch status.State { + case block.BlockStateDownloadFailed: + err = fmt.Errorf("ReadAt: download failed: %w", status.Err) + default: + err = fmt.Errorf("ReadAt: unexpected block state: %d", status.State) + } + break + } + + relOff := off - blk.AbsStartOff() + n, readErr := blk.ReadAt(inputBuf[bytesRead:], relOff) + bytesRead += n + off += int64(n) + + if readErr != nil && !errors.Is(readErr, io.EOF) { + err = fmt.Errorf("ReadAt: block.ReadAt failed: %w", readErr) + break + } + + if off >= int64(p.object.Size) { + err = io.EOF + break + } + + if off >= blk.AbsStartOff()+blk.Size() { + p.blockQueue.Pop() + p.blockPool.Release(blk) + + if !prefetchTriggered { + prefetchTriggered = true + if pfErr := p.prefetch(); pfErr != nil { + logger.Warnf("Prefetch failed: %v", pfErr) + } + } + } + } + + resp.Size = bytesRead + return resp, err +} + // prefetch schedules the next set of blocks for prefetching starting from // the nextBlockIndexToPrefetch. func (p *BufferedReader) prefetch() error { @@ -230,6 +441,11 @@ func (p *BufferedReader) CheckInvariants() { panic(fmt.Sprintf("BufferedReader: PrefetchBlockSizeBytes must be positive, but is %d", p.config.PrefetchBlockSizeBytes)) } + // The prefetch block size must be at least 1 MiB. + if p.config.PrefetchBlockSizeBytes < MiB { + panic(fmt.Sprintf("BufferedReader: PrefetchBlockSizeBytes must be at least 1 MiB, but is %d", p.config.PrefetchBlockSizeBytes)) + } + // The number of items in the blockQueue should not exceed MaxPrefetchBlockCnt. if int64(p.blockQueue.Len()) > p.config.MaxPrefetchBlockCnt { panic(fmt.Sprintf("BufferedReader: blockQueue length %d exceeds limit %d", p.blockQueue.Len(), p.config.MaxPrefetchBlockCnt)) diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 421043b038..d86585703a 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" @@ -59,12 +60,13 @@ type BufferedReaderTest struct { // Helpers //////////////////////////////////////////////////////////////////////// -// createFakeReader returns a FakeReader with deterministic, non-zero content. -func createFakeReader(t *testing.T, size int) *fake.FakeReader { +// createFakeReaderWithOffset returns a FakeReader with deterministic, non-zero content +// starting from a specific absolute offset. +func createFakeReaderWithOffset(t *testing.T, size int, startOffset int64) *fake.FakeReader { t.Helper() content := make([]byte, size) for i := range content { - content[i] = byte('A' + (i % 26)) // A-Z repeating pattern + content[i] = byte('A' + ((int(startOffset) + i) % 26)) // A-Z repeating pattern } return &fake.FakeReader{ ReadCloser: io.NopCloser(bytes.NewReader(content)), @@ -78,9 +80,16 @@ func assertBlockContent(t *testing.T, blk block.PrefetchBlock, expectedOffset in n, err := blk.ReadAt(buf, 0) require.NoError(t, err) require.Equal(t, length, n) - for i := 0; i < n; i++ { - expected := byte('A' + (i % 26)) - assert.Equalf(t, expected, buf[i], "Mismatch at offset %d", expectedOffset+int64(i)) + assertBufferContent(t, buf, expectedOffset) +} + +// assertBufferContent validates that a buffer's data matches the expected A-Z repeating pattern +// for a given absolute starting offset. +func assertBufferContent(t *testing.T, buf []byte, absStartOffset int64) { + t.Helper() + for i := 0; i < len(buf); i++ { + expected := byte('A' + ((int(absStartOffset) + i) % 26)) + assert.Equalf(t, expected, buf[i], "Mismatch at buffer index %d (absolute offset %d)", i, absStartOffset+int64(i)) } } @@ -221,7 +230,17 @@ func (t *BufferedReaderTest) TestCheckInvariantsPrefetchBlockSizeNotPositive() { } } +func (t *BufferedReaderTest) TestCheckInvariantsPrefetchBlockSizeTooSmall() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err, "NewBufferedReader should not return error") + + reader.config.PrefetchBlockSizeBytes = MiB - 1 + + assert.Panics(t.T(), func() { reader.CheckInvariants() }, "Should panic for block size less than 1 MiB") +} + func (t *BufferedReaderTest) TestCheckInvariantsNoPanic() { + t.config.PrefetchBlockSizeBytes = MiB reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err, "NewBufferedReader should not return error") @@ -241,10 +260,11 @@ func (t *BufferedReaderTest) TestScheduleNextBlock() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err) initialBlockCount := reader.blockQueue.Len() + startOffset := int64(0) t.bucket.On("NewReaderWithReadHandle", mock.Anything, - mock.AnythingOfType("*gcs.ReadObjectRequest"), - ).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(startOffset) }), + ).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), startOffset), nil).Once() err = reader.scheduleNextBlock(tc.urgent) @@ -266,10 +286,11 @@ func (t *BufferedReaderTest) TestScheduleNextBlockSuccessive() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err) initialBlockCount := reader.blockQueue.Len() + startOffset1 := int64(0) t.bucket.On("NewReaderWithReadHandle", mock.Anything, - mock.AnythingOfType("*gcs.ReadObjectRequest"), - ).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(startOffset1) }), + ).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), startOffset1), nil).Once() err = reader.scheduleNextBlock(false) require.NoError(t.T(), err) bqe1 := reader.blockQueue.Pop() @@ -279,10 +300,11 @@ func (t *BufferedReaderTest) TestScheduleNextBlockSuccessive() { assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) assert.Equal(t.T(), int64(0), bqe1.block.AbsStartOff()) assertBlockContent(t.T(), bqe1.block, bqe1.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) + startOffset2 := int64(testPrefetchBlockSizeBytes) t.bucket.On("NewReaderWithReadHandle", mock.Anything, - mock.AnythingOfType("*gcs.ReadObjectRequest"), - ).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(startOffset2) }), + ).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), startOffset2), nil).Once() err = reader.scheduleNextBlock(false) @@ -316,8 +338,8 @@ func (t *BufferedReaderTest) TestScheduleBlockWithIndex() { startOffset := tc.blockIndex * reader.config.PrefetchBlockSizeBytes t.bucket.On("NewReaderWithReadHandle", mock.Anything, - mock.AnythingOfType("*gcs.ReadObjectRequest"), - ).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(startOffset) }), + ).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), startOffset), nil).Once() b, err := reader.blockPool.Get() require.NoError(t.T(), err) @@ -341,9 +363,9 @@ func (t *BufferedReaderTest) TestFreshStart() { require.NoError(t.T(), err) currentOffset := int64(2048) // Start prefetching from offset 2048 (block 2). // freshStart schedules 1 urgent block and 2 initial prefetch blocks, totaling 3 blocks. - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 2048 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 2048), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 3072 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 3072), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 4096 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 4096), nil).Once() err = reader.freshStart(currentOffset) @@ -359,16 +381,19 @@ func (t *BufferedReaderTest) TestFreshStart() { status1, err1 := bqe1.block.AwaitReady(t.ctx) require.NoError(t.T(), err1) assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + assertBlockContent(t.T(), bqe1.block, bqe1.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) bqe2 := reader.blockQueue.Pop() assert.Equal(t.T(), int64(3072), bqe2.block.AbsStartOff()) status2, err2 := bqe2.block.AwaitReady(t.ctx) require.NoError(t.T(), err2) assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + assertBlockContent(t.T(), bqe2.block, bqe2.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) bqe3 := reader.blockQueue.Pop() assert.Equal(t.T(), int64(4096), bqe3.block.AbsStartOff()) status3, err3 := bqe3.block.AwaitReady(t.ctx) require.NoError(t.T(), err3) assert.Equal(t.T(), block.BlockStateDownloaded, status3.State) + assertBlockContent(t.T(), bqe3.block, bqe3.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) t.bucket.AssertExpectations(t.T()) } @@ -378,9 +403,9 @@ func (t *BufferedReaderTest) TestFreshStartWithNonBlockAlignedOffset() { currentOffset := int64(2500) // Start prefetching from offset 2500 (inside block 2). // freshStart should start prefetching from block 2. It schedules 1 urgent block // and 2 initial prefetch blocks, totaling 3 blocks. - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 2048 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 2048), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 3072 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 3072), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 4096 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 4096), nil).Once() err = reader.freshStart(currentOffset) @@ -396,16 +421,19 @@ func (t *BufferedReaderTest) TestFreshStartWithNonBlockAlignedOffset() { status1, err1 := bqe1.block.AwaitReady(t.ctx) require.NoError(t.T(), err1) assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + assertBlockContent(t.T(), bqe1.block, bqe1.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) bqe2 := reader.blockQueue.Pop() assert.Equal(t.T(), int64(3072), bqe2.block.AbsStartOff()) status2, err2 := bqe2.block.AwaitReady(t.ctx) require.NoError(t.T(), err2) assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + assertBlockContent(t.T(), bqe2.block, bqe2.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) bqe3 := reader.blockQueue.Pop() assert.Equal(t.T(), int64(4096), bqe3.block.AbsStartOff()) status3, err3 := bqe3.block.AwaitReady(t.ctx) require.NoError(t.T(), err3) assert.Equal(t.T(), block.BlockStateDownloaded, status3.State) + assertBlockContent(t.T(), bqe3.block, bqe3.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) t.bucket.AssertExpectations(t.T()) } @@ -416,9 +444,9 @@ func (t *BufferedReaderTest) TestFreshStartWhenInitialCountGreaterThanMax() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err) // freshStart schedules 1 urgent block and 2 prefetch blocks (InitialPrefetchBlockCnt capped by MaxPrefetchBlockCnt). - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), 1024), nil).Once() - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), 1024), nil).Once() - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), 1024), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), 1024, 0), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 1024 })).Return(createFakeReaderWithOffset(t.T(), 1024, 1024), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 2048 })).Return(createFakeReaderWithOffset(t.T(), 1024, 2048), nil).Once() err = reader.freshStart(0) @@ -433,14 +461,17 @@ func (t *BufferedReaderTest) TestFreshStartWhenInitialCountGreaterThanMax() { status1, err1 := bqe1.block.AwaitReady(t.ctx) require.NoError(t.T(), err1) assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + assertBlockContent(t.T(), bqe1.block, bqe1.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) bqe2 := reader.blockQueue.Pop() status2, err2 := bqe2.block.AwaitReady(t.ctx) require.NoError(t.T(), err2) assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + assertBlockContent(t.T(), bqe2.block, bqe2.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) bqe3 := reader.blockQueue.Pop() status3, err3 := bqe3.block.AwaitReady(t.ctx) require.NoError(t.T(), err3) assert.Equal(t.T(), block.BlockStateDownloaded, status3.State) + assertBlockContent(t.T(), bqe3.block, bqe3.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) t.bucket.AssertExpectations(t.T()) } @@ -449,10 +480,11 @@ func (t *BufferedReaderTest) TestFreshStartStopsAtObjectEnd() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err) currentOffset := int64(2048) // Start from block 2. - // freshStart schedules 1 urgent block (block 2) and 1 prefetch block (block 3). - // Prefetching stops because the object ends after block 3, totaling 2 blocks scheduled. - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + // freshStart schedules 1 urgent block (block 2) and 1 prefetch block (block 3 - partial). + // The object ends after block 3, so only these 2 blocks are scheduled. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 2*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 2*testPrefetchBlockSizeBytes), nil).Once() + partialBlockSize := int(int64(t.object.Size) - (3 * testPrefetchBlockSizeBytes)) + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 3*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), partialBlockSize, 3*testPrefetchBlockSizeBytes), nil).Once() err = reader.freshStart(currentOffset) @@ -468,20 +500,23 @@ func (t *BufferedReaderTest) TestFreshStartStopsAtObjectEnd() { status1, err1 := bqe1.block.AwaitReady(t.ctx) require.NoError(t.T(), err1) assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + assertBlockContent(t.T(), bqe1.block, bqe1.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) // Verify block 3. bqe2 := reader.blockQueue.Pop() assert.Equal(t.T(), int64(3072), bqe2.block.AbsStartOff()) status2, err2 := bqe2.block.AwaitReady(t.ctx) require.NoError(t.T(), err2) assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + // Assert content for the partial block. + assertBlockContent(t.T(), bqe2.block, bqe2.block.AbsStartOff(), partialBlockSize) t.bucket.AssertExpectations(t.T()) } func (t *BufferedReaderTest) TestPrefetch() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err) - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 0), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 1024 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 1024), nil).Once() err = reader.prefetch() @@ -496,10 +531,12 @@ func (t *BufferedReaderTest) TestPrefetch() { status1, err1 := bqe1.block.AwaitReady(t.ctx) require.NoError(t.T(), err1) assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + assertBlockContent(t.T(), bqe1.block, bqe1.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) bqe2 := reader.blockQueue.Pop() status2, err2 := bqe2.block.AwaitReady(t.ctx) require.NoError(t.T(), err2) assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + assertBlockContent(t.T(), bqe2.block, bqe2.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) t.bucket.AssertExpectations(t.T()) } @@ -509,7 +546,7 @@ func (t *BufferedReaderTest) TestPrefetchWithMultiplicativeIncrease() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err) // First prefetch schedules 1 block. - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 0), nil).Once() err = reader.prefetch() require.NoError(t.T(), err) // Wait for the first prefetch to complete and drain the queue. @@ -517,9 +554,10 @@ func (t *BufferedReaderTest) TestPrefetchWithMultiplicativeIncrease() { status1, err1 := bqe1.block.AwaitReady(t.ctx) require.NoError(t.T(), err1) assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + assertBlockContent(t.T(), bqe1.block, bqe1.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) // Second prefetch should schedule 2 blocks due to multiplicative increase. - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 1024 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 1024), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 2048 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 2048), nil).Once() err = reader.prefetch() @@ -534,10 +572,12 @@ func (t *BufferedReaderTest) TestPrefetchWithMultiplicativeIncrease() { status2, err2 := bqe2.block.AwaitReady(t.ctx) require.NoError(t.T(), err2) assert.Equal(t.T(), block.BlockStateDownloaded, status2.State) + assertBlockContent(t.T(), bqe2.block, bqe2.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) bqe3 := reader.blockQueue.Pop() status3, err3 := bqe3.block.AwaitReady(t.ctx) require.NoError(t.T(), err3) assert.Equal(t.T(), block.BlockStateDownloaded, status3.State) + assertBlockContent(t.T(), bqe3.block, bqe3.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) t.bucket.AssertExpectations(t.T()) } @@ -570,10 +610,9 @@ func (t *BufferedReaderTest) TestPrefetchWhenQueueIsPartiallyFull() { require.NoError(t.T(), err) reader.blockQueue.Push(&blockQueueEntry{block: b}) reader.blockQueue.Push(&blockQueueEntry{block: b}) - // blockCountToPrefetch = min(numPrefetchBlocks (2), availableSlots (2)) = 2. - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 0), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 1024 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 1024), nil).Once() err = reader.prefetch() @@ -593,10 +632,12 @@ func (t *BufferedReaderTest) TestPrefetchWhenQueueIsPartiallyFull() { status3, err3 := bqe3.block.AwaitReady(t.ctx) require.NoError(t.T(), err3) assert.Equal(t.T(), block.BlockStateDownloaded, status3.State) + assertBlockContent(t.T(), bqe3.block, bqe3.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) bqe4 := reader.blockQueue.Pop() status4, err4 := bqe4.block.AwaitReady(t.ctx) require.NoError(t.T(), err4) assert.Equal(t.T(), block.BlockStateDownloaded, status4.State) + assertBlockContent(t.T(), bqe4.block, bqe4.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) t.bucket.AssertExpectations(t.T()) } @@ -611,7 +652,7 @@ func (t *BufferedReaderTest) TestPrefetchLimitedByAvailableSlots() { reader.blockQueue.Push(&blockQueueEntry{block: b}) reader.blockQueue.Push(&blockQueueEntry{block: b}) // blockCountToPrefetch = min(numPrefetchBlocks (4), availableSlots (1)) = 1. - t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(createFakeReader(t.T(), int(testPrefetchBlockSizeBytes)), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 0), nil).Once() err = reader.prefetch() @@ -634,5 +675,405 @@ func (t *BufferedReaderTest) TestPrefetchLimitedByAvailableSlots() { status4, err4 := bqe4.block.AwaitReady(t.ctx) require.NoError(t.T(), err4) assert.Equal(t.T(), block.BlockStateDownloaded, status4.State) + assertBlockContent(t.T(), bqe4.block, bqe4.block.AbsStartOff(), int(testPrefetchBlockSizeBytes)) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestReadAtOffsetBeyondEOF() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + buf := make([]byte, 10) + + resp, err := reader.ReadAt(t.ctx, buf, int64(t.object.Size+1)) + + assert.ErrorIs(t.T(), err, io.EOF) + assert.Zero(t.T(), resp.Size) +} + +func (t *BufferedReaderTest) TestReadAtEmptyBuffer() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + buf := make([]byte, 0) + + resp, err := reader.ReadAt(t.ctx, buf, 0) + + assert.NoError(t.T(), err) + assert.Zero(t.T(), resp.Size) +} + +func (t *BufferedReaderTest) TestReadAtBackwardSeekIsRandomRead() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + // Perform a read that populates the prefetch queue. + // This is a random read since offset != 0 and queue is empty. + startOffset := int64(3072) // block 3 + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(startOffset) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), startOffset), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { + return r.Range.Start == uint64(startOffset+testPrefetchBlockSizeBytes) + })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), startOffset+testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { + return r.Range.Start == uint64(startOffset+2*testPrefetchBlockSizeBytes) + })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), startOffset+2*testPrefetchBlockSizeBytes), nil).Once() + _, err = reader.ReadAt(t.ctx, make([]byte, 10), startOffset) + require.NoError(t.T(), err) + assert.Equal(t.T(), int64(1), reader.randomSeekCount, "First read should be counted as random.") + require.Equal(t.T(), 3, reader.blockQueue.Len(), "Queue should be populated after first read.") + // Perform a backward seek, which is another random read. + // This should clear the existing queue and start a new prefetch. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 0), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 2*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 2*testPrefetchBlockSizeBytes), nil).Once() + buf := make([]byte, 1024) + + resp, err := reader.ReadAt(t.ctx, buf, 0) + + require.NoError(t.T(), err) + assert.Equal(t.T(), int(1024), resp.Size) + assert.Equal(t.T(), int64(2), reader.randomSeekCount, "Second read should be counted as random.") + assert.Equal(t.T(), 2, reader.blockQueue.Len(), "Queue should contain newly prefetched blocks.") + assertBufferContent(t.T(), buf, 0) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestReadAtForwardSeekDiscardsPreviousBlocks() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + var cancelCount int + addBlockToQueue := func(offset int64) { + b, poolErr := reader.blockPool.Get() + require.NoError(t.T(), poolErr) + require.NoError(t.T(), b.SetAbsStartOff(offset)) + _, writeErr := b.Write(make([]byte, testPrefetchBlockSizeBytes)) + require.NoError(t.T(), writeErr) + b.NotifyReady(block.BlockStatus{State: block.BlockStateDownloaded}) + reader.blockQueue.Push(&blockQueueEntry{ + block: b, + cancel: func() { cancelCount++ }, + }) + } + addBlockToQueue(0) // block 0 + addBlockToQueue(1024) // block 1 + addBlockToQueue(2048) // block 2 + // Manually update the reader's state to reflect the manually added blocks. + reader.nextBlockIndexToPrefetch = 3 + require.Equal(t.T(), 3, reader.blockQueue.Len()) + // Reading block 2 should trigger a prefetch for blocks 3 and 4. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 3*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 3*testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 4*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 4*testPrefetchBlockSizeBytes), nil).Once() + readOffset := int64(2048) + + // Read the entire block at offset 2048 to trigger the prefetch logic. + _, err = reader.ReadAt(t.ctx, make([]byte, 1024), readOffset) + + require.NoError(t.T(), err) + assert.Equal(t.T(), 2, cancelCount, "Expected 2 blocks to be discarded") + // The queue should now contain the two newly prefetched blocks. + require.Equal(t.T(), 2, reader.blockQueue.Len(), "Queue should contain the 2 newly prefetched blocks") + // Wait for the async prefetch tasks to complete to verify the mock calls. + bqe3 := reader.blockQueue.Pop() + bqe4 := reader.blockQueue.Pop() + _, err = bqe3.block.AwaitReady(t.ctx) + require.NoError(t.T(), err, "AwaitReady for block 3 failed") + _, err = bqe4.block.AwaitReady(t.ctx) + require.NoError(t.T(), err, "AwaitReady for block 4 failed") + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestReadAtInitialDownloadFails() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + downloadError := errors.New("gcs error") + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(nil, downloadError) + buf := make([]byte, 10) + + _, err = reader.ReadAt(t.ctx, buf, 0) + + assert.ErrorContains(t.T(), err, "download failed") + assert.ErrorIs(t.T(), err, downloadError) + // After the failed read, the other prefetched blocks should also have failed. + // We wait for them to finish to avoid a race condition and to verify their state. + require.Equal(t.T(), 2, reader.blockQueue.Len()) + bqe1 := reader.blockQueue.Pop() + status1, err1 := bqe1.block.AwaitReady(t.ctx) + require.NoError(t.T(), err1) + assert.Equal(t.T(), block.BlockStateDownloadFailed, status1.State) + assert.ErrorIs(t.T(), status1.Err, downloadError) + bqe2 := reader.blockQueue.Pop() + status2, err2 := bqe2.block.AwaitReady(t.ctx) + require.NoError(t.T(), err2) + assert.Equal(t.T(), block.BlockStateDownloadFailed, status2.State) + assert.ErrorIs(t.T(), status2.Err, downloadError) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestReadAtAwaitReadyCancelled() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + b, err := reader.blockPool.Get() + require.NoError(t.T(), err) + err = b.SetAbsStartOff(0) + require.NoError(t.T(), err) + reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + // Read with a cancelled context. + _, err = reader.ReadAt(ctx, make([]byte, 10), 0) + + assert.ErrorIs(t.T(), err, context.Canceled) +} + +func (t *BufferedReaderTest) TestReadAtBlockStateDownloadFailed() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + b, err := reader.blockPool.Get() + require.NoError(t.T(), err) + err = b.SetAbsStartOff(0) + require.NoError(t.T(), err) + downloadError := errors.New("simulated download error") + b.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadFailed, Err: downloadError}) + reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) + + // Read from a reader where the next block has failed to download. + _, err = reader.ReadAt(t.ctx, make([]byte, 10), 0) + + assert.ErrorIs(t.T(), err, downloadError) + status, err := b.AwaitReady(t.ctx) + require.NoError(t.T(), err) + assert.Equal(t.T(), block.BlockStateDownloadFailed, status.State) + assert.ErrorIs(t.T(), status.Err, downloadError) + assert.True(t.T(), reader.blockQueue.IsEmpty()) +} + +func (t *BufferedReaderTest) TestReadAtBlockDownloadCancelled() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + b, err := reader.blockPool.Get() + require.NoError(t.T(), err) + err = b.SetAbsStartOff(0) + require.NoError(t.T(), err) + b.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadFailed, Err: context.Canceled}) + reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) + + // Read from a reader where the next block download was cancelled. + _, err = reader.ReadAt(t.ctx, make([]byte, 10), 0) + + assert.ErrorIs(t.T(), err, context.Canceled) + status, err := b.AwaitReady(t.ctx) + require.NoError(t.T(), err) + assert.Equal(t.T(), block.BlockStateDownloadFailed, status.State) + assert.ErrorIs(t.T(), status.Err, context.Canceled) + assert.True(t.T(), reader.blockQueue.IsEmpty()) +} + +func (t *BufferedReaderTest) TestReadAtBlockStateUnexpected() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + b, err := reader.blockPool.Get() + require.NoError(t.T(), err) + err = b.SetAbsStartOff(0) + require.NoError(t.T(), err) + b.NotifyReady(block.BlockStatus{State: block.BlockStateInProgress}) + reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) + + // Read from a reader where the next block is in an unexpected state. + _, err = reader.ReadAt(t.ctx, make([]byte, 10), 0) + + assert.ErrorContains(t.T(), err, "unexpected block state") + status, err := b.AwaitReady(t.ctx) + require.NoError(t.T(), err) + assert.Equal(t.T(), block.BlockStateInProgress, status.State) + assert.Nil(t.T(), status.Err) + assert.True(t.T(), reader.blockQueue.IsEmpty()) +} + +func (t *BufferedReaderTest) TestReadAtFromDownloadedBlock() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + b, err := reader.blockPool.Get() + require.NoError(t.T(), err) + err = b.SetAbsStartOff(0) + require.NoError(t.T(), err) + content := []byte("abcdefghijk") + _, err = b.Write(content) + require.NoError(t.T(), err) + b.NotifyReady(block.BlockStatus{State: block.BlockStateDownloaded}) + reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) + buf := make([]byte, 5) + + // Read from a block that is already downloaded and in the queue. + resp, err := reader.ReadAt(t.ctx, buf, 0) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), 5, resp.Size) + assert.Equal(t.T(), "abcde", string(buf)) + assert.False(t.T(), reader.blockQueue.IsEmpty()) +} + +func (t *BufferedReaderTest) TestReadAtExactlyToEndOfFile() { + t.object.Size = uint64(testPrefetchBlockSizeBytes + 50) // 1 full block and 50 bytes + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 0), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), 50, testPrefetchBlockSizeBytes), nil).Once() + buf := make([]byte, t.object.Size) + + // Read the entire file. + resp, err := reader.ReadAt(t.ctx, buf, 0) + + assert.ErrorIs(t.T(), err, io.EOF) + assert.Equal(t.T(), int(t.object.Size), resp.Size) + assertBufferContent(t.T(), buf, 0) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestReadAtSucceedsWhenPrefetchFails() { + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + // Mock GCS reads where the initial read and first prefetch succeed, but the second prefetch fails. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 0), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), testPrefetchBlockSizeBytes), nil).Once() + prefetchError := errors.New("prefetch failed") + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 2*uint64(testPrefetchBlockSizeBytes) })).Return(nil, prefetchError).Once() + buf := make([]byte, testPrefetchBlockSizeBytes) + + // Read the first block. This should succeed, even though a background prefetch will fail. + resp, err := reader.ReadAt(t.ctx, buf, 0) + + require.NoError(t.T(), err) + assert.Equal(t.T(), int(testPrefetchBlockSizeBytes), resp.Size) + assertBufferContent(t.T(), buf, 0) + // After reading block 0, the queue should contain the successful and failed prefetched blocks. + require.Equal(t.T(), 2, reader.blockQueue.Len()) + // Wait for background downloads to complete to prevent a race condition. + bqe1 := reader.blockQueue.Pop() + status1, err1 := bqe1.block.AwaitReady(t.ctx) + require.NoError(t.T(), err1) + assert.Equal(t.T(), block.BlockStateDownloaded, status1.State) + bqe2 := reader.blockQueue.Pop() + status2, err2 := bqe2.block.AwaitReady(t.ctx) + require.NoError(t.T(), err2) + assert.Equal(t.T(), block.BlockStateDownloadFailed, status2.State) + assert.ErrorIs(t.T(), status2.Err, prefetchError) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestReadAtSpanningMultipleBlocks() { + // Read 2.5 blocks of data in a single ReadAt call. + readSize := 2560 + readOffset := int64(0) + t.object.Size = 3072 + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + buf := make([]byte, readSize) + // freshStart will be called, downloading block 0 (urgent) and + // prefetching blocks 1 and 2 (InitialPrefetchBlockCnt=2). + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { + return r.Range.Start == uint64(0*testPrefetchBlockSizeBytes) + })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 0*testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { + return r.Range.Start == uint64(1*testPrefetchBlockSizeBytes) + })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 1*testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { + return r.Range.Start == uint64(2*testPrefetchBlockSizeBytes) + })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 2*testPrefetchBlockSizeBytes), nil).Once() + + resp, err := reader.ReadAt(t.ctx, buf, readOffset) + + require.NoError(t.T(), err) + assert.Equal(t.T(), 2560, resp.Size) + assertBufferContent(t.T(), buf, readOffset) + assert.Equal(t.T(), 1, reader.blockQueue.Len(), "Block 2 should be left in the queue.") + assert.Equal(t.T(), int64(2048), reader.blockQueue.Peek().block.AbsStartOff()) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestReadAtSequentialReadAcrossBlocks() { + t.config.InitialPrefetchBlockCnt = 1 + t.config.PrefetchMultiplier = 2 + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + // Mock reads for all blocks that will be downloaded. + // First ReadAt(0) triggers freshStart, which downloads block 0 (urgent) and prefetches block 1. + // Second ReadAt(1024) consumes block 1 and triggers prefetch for blocks 2, 3. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { + return r.Range.Start == uint64(0*testPrefetchBlockSizeBytes) + })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 0*testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { + return r.Range.Start == uint64(1*testPrefetchBlockSizeBytes) + })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 1*testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { + return r.Range.Start == uint64(2*testPrefetchBlockSizeBytes) + })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 2*testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { + return r.Range.Start == uint64(3*testPrefetchBlockSizeBytes) + })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 3*testPrefetchBlockSizeBytes), nil).Once() + buf1 := make([]byte, testPrefetchBlockSizeBytes) + buf2 := make([]byte, testPrefetchBlockSizeBytes) + + // Perform two sequential reads. + _, err = reader.ReadAt(t.ctx, buf1, 0) + require.NoError(t.T(), err) + _, err = reader.ReadAt(t.ctx, buf2, testPrefetchBlockSizeBytes) + require.NoError(t.T(), err) + + assert.Equal(t.T(), int64(0), reader.randomSeekCount) + assertBufferContent(t.T(), buf1, 0) + assertBufferContent(t.T(), buf2, testPrefetchBlockSizeBytes) + // Wait for all background prefetches to complete before asserting mock expectations. + require.Equal(t.T(), 2, reader.blockQueue.Len()) + bqe1 := reader.blockQueue.Pop() + _, err = bqe1.block.AwaitReady(t.ctx) + require.NoError(t.T(), err) + bqe2 := reader.blockQueue.Pop() + _, err = bqe2.block.AwaitReady(t.ctx) + require.NoError(t.T(), err) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestReadAtFallsBackAfterRandomReads() { + t.config.RandomReadsThreshold = 2 + t.config.InitialPrefetchBlockCnt = 1 + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + buf := make([]byte, 10) + // Mock GCS calls for the first random read, which will download block 2 and prefetch block 3. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 2*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 2*testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 3*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 3*testPrefetchBlockSizeBytes), nil).Once() + // First random read should succeed. + _, err = reader.ReadAt(t.ctx, buf, 2*testPrefetchBlockSizeBytes) + require.NoError(t.T(), err, "Random read #1 should succeed") + // Mock GCS calls for the second random read, which will download block 5 and prefetch block 6. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 5*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 5*testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 6*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 6*testPrefetchBlockSizeBytes), nil).Once() + // Second random read should succeed. + _, err = reader.ReadAt(t.ctx, buf, 5*testPrefetchBlockSizeBytes) + require.NoError(t.T(), err, "Random read #2 should succeed") + + // The third random read should exceed the threshold and trigger the fallback. + _, err = reader.ReadAt(t.ctx, buf, 0) + + assert.ErrorIs(t.T(), err, gcsx.FallbackToAnotherReader, "Error should be FallbackToAnotherReader") + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestReadAtExceedsObjectSize() { + objectSize := uint64(1536) // 1.5 blocks + readOffset := int64(1024) + readSize := int(1024) // Tries to read 1024 bytes, but only 512 are available. + t.object.Size = objectSize + t.object.Generation = 12345 + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + buf := make([]byte, readSize) + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { + return r.Range.Start == uint64(testPrefetchBlockSizeBytes) && r.Range.Limit == objectSize + })).Return(createFakeReaderWithOffset(t.T(), 512, testPrefetchBlockSizeBytes), nil).Once() + + resp, err := reader.ReadAt(t.ctx, buf, readOffset) + + assert.ErrorIs(t.T(), err, io.EOF) + assert.Equal(t.T(), 512, resp.Size) + assertBufferContent(t.T(), buf[:resp.Size], readOffset) t.bucket.AssertExpectations(t.T()) } diff --git a/internal/bufferedread/download_task.go b/internal/bufferedread/download_task.go index 692a0a59cd..45114d29a4 100644 --- a/internal/bufferedread/download_task.go +++ b/internal/bufferedread/download_task.go @@ -58,7 +58,6 @@ func NewDownloadTask(ctx context.Context, object *gcs.MinObject, bucket gcs.Buck // download task. The status can be one of the following: // - BlockStatusDownloaded: The download was successful. // - BlockStatusDownloadFailed: The download failed due to an error. -// - BlockStatusDownloadCancelled: The download was cancelled due to context cancellation. func (p *DownloadTask) Execute() { startOff := p.block.AbsStartOff() blockId := startOff / p.block.Cap() From 061f2586278031153e3738ac69cc030f7a8bbad0 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 31 Jul 2025 12:26:37 +0530 Subject: [PATCH 0624/1298] perf(metrics): Add support for all metrics (#3611) Add support for all metrics --- optimizedmetrics/metric_handle.go | 40 +- optimizedmetrics/metrics.yaml | 164 +- optimizedmetrics/noop_metrics.go | 28 +- optimizedmetrics/otel_metrics.go | 4190 ++++++++++++++++++++++++++- tools/metrics-gen/metric_handle.tpl | 7 +- tools/metrics-gen/noop_metrics.tpl | 4 +- tools/metrics-gen/otel_metrics.tpl | 6 +- 7 files changed, 4402 insertions(+), 37 deletions(-) diff --git a/optimizedmetrics/metric_handle.go b/optimizedmetrics/metric_handle.go index 40dda928cf..59b131c899 100644 --- a/optimizedmetrics/metric_handle.go +++ b/optimizedmetrics/metric_handle.go @@ -25,9 +25,41 @@ import ( // Each method corresponds to a metric defined in metrics.yaml. type MetricHandle interface { // FileCacheReadBytesCount - The cumulative number of bytes read from file cache along with read type - Sequential/Random - FileCacheReadBytesCount( - inc int64, readType string) + FileCacheReadBytesCount(inc int64, readType string) + + // FileCacheReadCount - Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false + FileCacheReadCount(inc int64, cacheHit bool, readType string) + // FileCacheReadLatencies - The cumulative distribution of the file cache read latencies along with cache hit - true/false. - FileCacheReadLatencies( - ctx context.Context, duration time.Duration, cacheHit bool) + FileCacheReadLatencies(ctx context.Context, duration time.Duration, cacheHit bool) + + // FsOpsCount - The cumulative number of ops processed by the file system. + FsOpsCount(inc int64, fsOp string) + + // FsOpsErrorCount - The cumulative number of errors generated by file system operations. + FsOpsErrorCount(inc int64, fsErrorCategory string, fsOp string) + + // FsOpsLatency - The cumulative distribution of file system operation latencies + FsOpsLatency(ctx context.Context, duration time.Duration, fsOp string) + + // GcsDownloadBytesCount - The cumulative number of bytes downloaded from GCS along with type - Sequential/Random + GcsDownloadBytesCount(inc int64, readType string) + + // GcsReadBytesCount - The cumulative number of bytes read from GCS objects. + GcsReadBytesCount(inc int64) + + // GcsReadCount - Specifies the number of gcs reads made along with type - Sequential/Random + GcsReadCount(inc int64, readType string) + + // GcsReaderCount - The cumulative number of GCS object readers opened or closed. + GcsReaderCount(inc int64, ioMethod string) + + // GcsRequestCount - The cumulative number of GCS requests processed along with the GCS method. + GcsRequestCount(inc int64, gcsMethod string) + + // GcsRequestLatencies - The cumulative distribution of the GCS request latencies. + GcsRequestLatencies(ctx context.Context, duration time.Duration, gcsMethod string) + + // GcsRetryCount - The cumulative number of retry requests made to GCS. + GcsRetryCount(inc int64, retryErrorCategory string) } diff --git a/optimizedmetrics/metrics.yaml b/optimizedmetrics/metrics.yaml index 30b7dadacc..4509c8e4ef 100644 --- a/optimizedmetrics/metrics.yaml +++ b/optimizedmetrics/metrics.yaml @@ -5,16 +5,26 @@ attributes: - attribute-name: read_type attribute-type: string - values: + values: &read_types_list - "Parallel" - "Random" - "Sequential" +- metric-name: "file_cache/read_count" + description: "Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false" + type: "int_counter" + attributes: + - attribute-name: cache_hit + attribute-type: bool + - attribute-name: read_type + attribute-type: string + values: *read_types_list + - metric-name: "file_cache/read_latencies" description: "The cumulative distribution of the file cache read latencies along with cache hit - true/false." type: "int_histogram" unit: "us" - boundaries: + boundaries: &common_boundaries - 1 - 2 - 3 @@ -52,3 +62,153 @@ attributes: - attribute-name: cache_hit attribute-type: bool + +- metric-name: "fs/ops_count" + description: "The cumulative number of ops processed by the file system." + type: "int_counter" + attributes: + - attribute-name: fs_op + attribute-type: string + values: &fs_ops_list + - "BatchForget" + - "CreateFile" + - "CreateLink" + - "CreateSymlink" + - "Fallocate" + - "FlushFile" + - "ForgetInode" + - "GetInodeAttributes" + - "GetXattr" + - "ListXattr" + - "LookUpInode" + - "MkDir" + - "MkNode" + - "OpenDir" + - "OpenFile" + - "ReadDir" + - "ReadFile" + - "ReadSymlink" + - "ReleaseDirHandle" + - "ReleaseFileHandle" + - "RemoveXattr" + - "Rename" + - "RmDir" + - "SetInodeAttributes" + - "SetXattr" + - "StatFS" + - "SyncFS" + - "SyncFile" + - "Unlink" + - "WriteFile" + +- metric-name: "fs/ops_error_count" + description: "The cumulative number of errors generated by file system operations." + type: "int_counter" + attributes: + - attribute-name: fs_error_category + attribute-type: string + values: + - "DEVICE_ERROR" + - "DIR_NOT_EMPTY" + - "FILE_DIR_ERROR" + - "FILE_EXISTS" + - "INTERRUPT_ERROR" + - "INVALID_ARGUMENT" + - "INVALID_OPERATION" + - "IO_ERROR" + - "MISC_ERROR" + - "NETWORK_ERROR" + - "NOT_A_DIR" + - "NOT_IMPLEMENTED" + - "NO_FILE_OR_DIR" + - "PERM_ERROR" + - "PROCESS_RESOURCE_MGMT_ERROR" + - "TOO_MANY_OPEN_FILES" + - attribute-name: fs_op + attribute-type: string + values: *fs_ops_list + +- metric-name: "fs/ops_latency" + description: "The cumulative distribution of file system operation latencies" + type: "int_histogram" + unit: "us" + boundaries: *common_boundaries + attributes: + - attribute-name: fs_op + attribute-type: string + values: *fs_ops_list + +- metric-name: "gcs/download_bytes_count" + description: "The cumulative number of bytes downloaded from GCS along with type - Sequential/Random" + unit: "By" + type: "int_counter" + attributes: + - attribute-name: read_type + attribute-type: string + values: *read_types_list + +- metric-name: "gcs/read_bytes_count" + description: "The cumulative number of bytes read from GCS objects." + unit: "By" + type: "int_counter" + +- metric-name: "gcs/read_count" + description: "Specifies the number of gcs reads made along with type - Sequential/Random" + type: "int_counter" + attributes: + - attribute-name: read_type + attribute-type: string + values: *read_types_list + +- metric-name: "gcs/reader_count" + description: "The cumulative number of GCS object readers opened or closed." + type: "int_counter" + attributes: + - attribute-name: io_method + attribute-type: string + values: + - "closed" + - "opened" + +- metric-name: "gcs/request_count" + description: "The cumulative number of GCS requests processed along with the GCS method." + type: "int_counter" + attributes: + - attribute-name: gcs_method + attribute-type: string + values: &gcs_method_list + - "ComposeObjects" + - "CreateFolder" + - "CreateObjectChunkWriter" + - "DeleteFolder" + - "DeleteObject" + - "FinalizeUpload" + - "GetFolder" + - "ListObjects" + - "MoveObject" + - "MultiRangeDownloader::Add" + - "NewMultiRangeDownloader" + - "NewReader" + - "RenameFolder" + - "StatObject" + - "UpdateObject" + +- metric-name: "gcs/request_latencies" + description: "The cumulative distribution of the GCS request latencies." + type: "int_histogram" + unit: "ms" + boundaries: *common_boundaries + attributes: + - attribute-name: gcs_method + attribute-type: string + values: *gcs_method_list + +- metric-name: "gcs/retry_count" + description: "The cumulative number of retry requests made to GCS." + type: "int_counter" + attributes: + - attribute-name: retry_error_category + attribute-type: string + values: + - "OTHER_ERRORS" + - "STALLED_READ_REQUEST" diff --git a/optimizedmetrics/noop_metrics.go b/optimizedmetrics/noop_metrics.go index b8a12a1692..5b1424efc3 100644 --- a/optimizedmetrics/noop_metrics.go +++ b/optimizedmetrics/noop_metrics.go @@ -22,14 +22,34 @@ import ( type noopMetrics struct{} -func (*noopMetrics) FileCacheReadBytesCount( - inc int64, readType string) { +func (*noopMetrics) FileCacheReadBytesCount(inc int64, readType string) {} + +func (*noopMetrics) FileCacheReadCount(inc int64, cacheHit bool, readType string) {} + +func (*noopMetrics) FileCacheReadLatencies(ctx context.Context, duration time.Duration, cacheHit bool) { } -func (*noopMetrics) FileCacheReadLatencies( - ctx context.Context, duration time.Duration, cacheHit bool) { +func (*noopMetrics) FsOpsCount(inc int64, fsOp string) {} + +func (*noopMetrics) FsOpsErrorCount(inc int64, fsErrorCategory string, fsOp string) {} + +func (*noopMetrics) FsOpsLatency(ctx context.Context, duration time.Duration, fsOp string) {} + +func (*noopMetrics) GcsDownloadBytesCount(inc int64, readType string) {} + +func (*noopMetrics) GcsReadBytesCount(inc int64) {} + +func (*noopMetrics) GcsReadCount(inc int64, readType string) {} + +func (*noopMetrics) GcsReaderCount(inc int64, ioMethod string) {} + +func (*noopMetrics) GcsRequestCount(inc int64, gcsMethod string) {} + +func (*noopMetrics) GcsRequestLatencies(ctx context.Context, duration time.Duration, gcsMethod string) { } +func (*noopMetrics) GcsRetryCount(inc int64, retryErrorCategory string) {} + func NewNoopMetrics() MetricHandle { var n noopMetrics return &n diff --git a/optimizedmetrics/otel_metrics.go b/optimizedmetrics/otel_metrics.go index d24bb9b553..099e00c4ed 100644 --- a/optimizedmetrics/otel_metrics.go +++ b/optimizedmetrics/otel_metrics.go @@ -29,11 +29,597 @@ import ( ) var ( - fileCacheReadBytesCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) - fileCacheReadBytesCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) - fileCacheReadBytesCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) - fileCacheReadLatenciesCacheHitTrueAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true))) - fileCacheReadLatenciesCacheHitFalseAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false))) + fileCacheReadBytesCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) + fileCacheReadBytesCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) + fileCacheReadBytesCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) + fileCacheReadCountCacheHitTrueReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Parallel"))) + fileCacheReadCountCacheHitTrueReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Random"))) + fileCacheReadCountCacheHitTrueReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Sequential"))) + fileCacheReadCountCacheHitFalseReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Parallel"))) + fileCacheReadCountCacheHitFalseReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Random"))) + fileCacheReadCountCacheHitFalseReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Sequential"))) + fileCacheReadLatenciesCacheHitTrueAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true))) + fileCacheReadLatenciesCacheHitFalseAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false))) + fsOpsCountFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "BatchForget"))) + fsOpsCountFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateFile"))) + fsOpsCountFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateLink"))) + fsOpsCountFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateSymlink"))) + fsOpsCountFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Fallocate"))) + fsOpsCountFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "FlushFile"))) + fsOpsCountFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ForgetInode"))) + fsOpsCountFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsCountFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetXattr"))) + fsOpsCountFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ListXattr"))) + fsOpsCountFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "LookUpInode"))) + fsOpsCountFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkDir"))) + fsOpsCountFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkNode"))) + fsOpsCountFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenDir"))) + fsOpsCountFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenFile"))) + fsOpsCountFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadDir"))) + fsOpsCountFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadFile"))) + fsOpsCountFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadSymlink"))) + fsOpsCountFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsCountFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsCountFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RemoveXattr"))) + fsOpsCountFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Rename"))) + fsOpsCountFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RmDir"))) + fsOpsCountFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsCountFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetXattr"))) + fsOpsCountFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "StatFS"))) + fsOpsCountFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFS"))) + fsOpsCountFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFile"))) + fsOpsCountFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Unlink"))) + fsOpsCountFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "WriteFile"))) + fsOpsLatencyFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "BatchForget"))) + fsOpsLatencyFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateFile"))) + fsOpsLatencyFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateLink"))) + fsOpsLatencyFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateSymlink"))) + fsOpsLatencyFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Fallocate"))) + fsOpsLatencyFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "FlushFile"))) + fsOpsLatencyFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ForgetInode"))) + fsOpsLatencyFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsLatencyFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetXattr"))) + fsOpsLatencyFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ListXattr"))) + fsOpsLatencyFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "LookUpInode"))) + fsOpsLatencyFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkDir"))) + fsOpsLatencyFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkNode"))) + fsOpsLatencyFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenDir"))) + fsOpsLatencyFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenFile"))) + fsOpsLatencyFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadDir"))) + fsOpsLatencyFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadFile"))) + fsOpsLatencyFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadSymlink"))) + fsOpsLatencyFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsLatencyFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsLatencyFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RemoveXattr"))) + fsOpsLatencyFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Rename"))) + fsOpsLatencyFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RmDir"))) + fsOpsLatencyFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsLatencyFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetXattr"))) + fsOpsLatencyFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "StatFS"))) + fsOpsLatencyFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFS"))) + fsOpsLatencyFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFile"))) + fsOpsLatencyFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Unlink"))) + fsOpsLatencyFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "WriteFile"))) + gcsDownloadBytesCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) + gcsDownloadBytesCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) + gcsDownloadBytesCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) + gcsReadCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) + gcsReadCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) + gcsReadCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) + gcsReaderCountIoMethodClosedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("io_method", "closed"))) + gcsReaderCountIoMethodOpenedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("io_method", "opened"))) + gcsRequestCountGcsMethodComposeObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ComposeObjects"))) + gcsRequestCountGcsMethodCreateFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateFolder"))) + gcsRequestCountGcsMethodCreateObjectChunkWriterAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateObjectChunkWriter"))) + gcsRequestCountGcsMethodDeleteFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteFolder"))) + gcsRequestCountGcsMethodDeleteObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteObject"))) + gcsRequestCountGcsMethodFinalizeUploadAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload"))) + gcsRequestCountGcsMethodGetFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "GetFolder"))) + gcsRequestCountGcsMethodListObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ListObjects"))) + gcsRequestCountGcsMethodMoveObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MoveObject"))) + gcsRequestCountGcsMethodMultiRangeDownloaderAddAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MultiRangeDownloader::Add"))) + gcsRequestCountGcsMethodNewMultiRangeDownloaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewMultiRangeDownloader"))) + gcsRequestCountGcsMethodNewReaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewReader"))) + gcsRequestCountGcsMethodRenameFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "RenameFolder"))) + gcsRequestCountGcsMethodStatObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "StatObject"))) + gcsRequestCountGcsMethodUpdateObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "UpdateObject"))) + gcsRequestLatenciesGcsMethodComposeObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ComposeObjects"))) + gcsRequestLatenciesGcsMethodCreateFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateFolder"))) + gcsRequestLatenciesGcsMethodCreateObjectChunkWriterAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateObjectChunkWriter"))) + gcsRequestLatenciesGcsMethodDeleteFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteFolder"))) + gcsRequestLatenciesGcsMethodDeleteObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteObject"))) + gcsRequestLatenciesGcsMethodFinalizeUploadAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload"))) + gcsRequestLatenciesGcsMethodGetFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "GetFolder"))) + gcsRequestLatenciesGcsMethodListObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ListObjects"))) + gcsRequestLatenciesGcsMethodMoveObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MoveObject"))) + gcsRequestLatenciesGcsMethodMultiRangeDownloaderAddAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MultiRangeDownloader::Add"))) + gcsRequestLatenciesGcsMethodNewMultiRangeDownloaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewMultiRangeDownloader"))) + gcsRequestLatenciesGcsMethodNewReaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewReader"))) + gcsRequestLatenciesGcsMethodRenameFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "RenameFolder"))) + gcsRequestLatenciesGcsMethodStatObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "StatObject"))) + gcsRequestLatenciesGcsMethodUpdateObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "UpdateObject"))) + gcsRetryCountRetryErrorCategoryOTHERERRORSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS"))) + gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST"))) ) type histogramRecord struct { @@ -44,12 +630,556 @@ type histogramRecord struct { } type otelMetrics struct { - ch chan histogramRecord - wg *sync.WaitGroup - fileCacheReadBytesCountReadTypeParallelAtomic *atomic.Int64 - fileCacheReadBytesCountReadTypeRandomAtomic *atomic.Int64 - fileCacheReadBytesCountReadTypeSequentialAtomic *atomic.Int64 - fileCacheReadLatencies metric.Int64Histogram + ch chan histogramRecord + wg *sync.WaitGroup + fileCacheReadBytesCountReadTypeParallelAtomic *atomic.Int64 + fileCacheReadBytesCountReadTypeRandomAtomic *atomic.Int64 + fileCacheReadBytesCountReadTypeSequentialAtomic *atomic.Int64 + fileCacheReadCountCacheHitTrueReadTypeParallelAtomic *atomic.Int64 + fileCacheReadCountCacheHitTrueReadTypeRandomAtomic *atomic.Int64 + fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic *atomic.Int64 + fileCacheReadCountCacheHitFalseReadTypeParallelAtomic *atomic.Int64 + fileCacheReadCountCacheHitFalseReadTypeRandomAtomic *atomic.Int64 + fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic *atomic.Int64 + fsOpsCountFsOpBatchForgetAtomic *atomic.Int64 + fsOpsCountFsOpCreateFileAtomic *atomic.Int64 + fsOpsCountFsOpCreateLinkAtomic *atomic.Int64 + fsOpsCountFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsCountFsOpFallocateAtomic *atomic.Int64 + fsOpsCountFsOpFlushFileAtomic *atomic.Int64 + fsOpsCountFsOpForgetInodeAtomic *atomic.Int64 + fsOpsCountFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsCountFsOpGetXattrAtomic *atomic.Int64 + fsOpsCountFsOpListXattrAtomic *atomic.Int64 + fsOpsCountFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsCountFsOpMkDirAtomic *atomic.Int64 + fsOpsCountFsOpMkNodeAtomic *atomic.Int64 + fsOpsCountFsOpOpenDirAtomic *atomic.Int64 + fsOpsCountFsOpOpenFileAtomic *atomic.Int64 + fsOpsCountFsOpReadDirAtomic *atomic.Int64 + fsOpsCountFsOpReadFileAtomic *atomic.Int64 + fsOpsCountFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsCountFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsCountFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsCountFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsCountFsOpRenameAtomic *atomic.Int64 + fsOpsCountFsOpRmDirAtomic *atomic.Int64 + fsOpsCountFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsCountFsOpSetXattrAtomic *atomic.Int64 + fsOpsCountFsOpStatFSAtomic *atomic.Int64 + fsOpsCountFsOpSyncFSAtomic *atomic.Int64 + fsOpsCountFsOpSyncFileAtomic *atomic.Int64 + fsOpsCountFsOpUnlinkAtomic *atomic.Int64 + fsOpsCountFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic *atomic.Int64 + gcsDownloadBytesCountReadTypeParallelAtomic *atomic.Int64 + gcsDownloadBytesCountReadTypeRandomAtomic *atomic.Int64 + gcsDownloadBytesCountReadTypeSequentialAtomic *atomic.Int64 + gcsReadBytesCountAtomic *atomic.Int64 + gcsReadCountReadTypeParallelAtomic *atomic.Int64 + gcsReadCountReadTypeRandomAtomic *atomic.Int64 + gcsReadCountReadTypeSequentialAtomic *atomic.Int64 + gcsReaderCountIoMethodClosedAtomic *atomic.Int64 + gcsReaderCountIoMethodOpenedAtomic *atomic.Int64 + gcsRequestCountGcsMethodComposeObjectsAtomic *atomic.Int64 + gcsRequestCountGcsMethodCreateFolderAtomic *atomic.Int64 + gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic *atomic.Int64 + gcsRequestCountGcsMethodDeleteFolderAtomic *atomic.Int64 + gcsRequestCountGcsMethodDeleteObjectAtomic *atomic.Int64 + gcsRequestCountGcsMethodFinalizeUploadAtomic *atomic.Int64 + gcsRequestCountGcsMethodGetFolderAtomic *atomic.Int64 + gcsRequestCountGcsMethodListObjectsAtomic *atomic.Int64 + gcsRequestCountGcsMethodMoveObjectAtomic *atomic.Int64 + gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic *atomic.Int64 + gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic *atomic.Int64 + gcsRequestCountGcsMethodNewReaderAtomic *atomic.Int64 + gcsRequestCountGcsMethodRenameFolderAtomic *atomic.Int64 + gcsRequestCountGcsMethodStatObjectAtomic *atomic.Int64 + gcsRequestCountGcsMethodUpdateObjectAtomic *atomic.Int64 + gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic *atomic.Int64 + gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic *atomic.Int64 + fileCacheReadLatencies metric.Int64Histogram + fsOpsLatency metric.Int64Histogram + gcsRequestLatencies metric.Int64Histogram } func (o *otelMetrics) FileCacheReadBytesCount( @@ -65,6 +1195,31 @@ func (o *otelMetrics) FileCacheReadBytesCount( } +func (o *otelMetrics) FileCacheReadCount( + inc int64, cacheHit bool, readType string) { + switch cacheHit { + case true: + switch readType { + case "Parallel": + o.fileCacheReadCountCacheHitTrueReadTypeParallelAtomic.Add(inc) + case "Random": + o.fileCacheReadCountCacheHitTrueReadTypeRandomAtomic.Add(inc) + case "Sequential": + o.fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic.Add(inc) + } + case false: + switch readType { + case "Parallel": + o.fileCacheReadCountCacheHitFalseReadTypeParallelAtomic.Add(inc) + case "Random": + o.fileCacheReadCountCacheHitFalseReadTypeRandomAtomic.Add(inc) + case "Sequential": + o.fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic.Add(inc) + } + } + +} + func (o *otelMetrics) FileCacheReadLatencies( ctx context.Context, latency time.Duration, cacheHit bool) { var record histogramRecord @@ -81,6 +1236,1293 @@ func (o *otelMetrics) FileCacheReadLatencies( } } +func (o *otelMetrics) FsOpsCount( + inc int64, fsOp string) { + switch fsOp { + case "BatchForget": + o.fsOpsCountFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsCountFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsCountFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsCountFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsCountFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsCountFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsCountFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsCountFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsCountFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsCountFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsCountFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsCountFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsCountFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsCountFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsCountFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsCountFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsCountFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsCountFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsCountFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsCountFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsCountFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsCountFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsCountFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsCountFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsCountFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsCountFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsCountFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsCountFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsCountFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsCountFsOpWriteFileAtomic.Add(inc) + } + +} + +func (o *otelMetrics) FsOpsErrorCount( + inc int64, fsErrorCategory string, fsOp string) { + switch fsErrorCategory { + case "DEVICE_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic.Add(inc) + } + case "DIR_NOT_EMPTY": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic.Add(inc) + } + case "FILE_DIR_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic.Add(inc) + } + case "FILE_EXISTS": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic.Add(inc) + } + case "INTERRUPT_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic.Add(inc) + } + case "INVALID_ARGUMENT": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic.Add(inc) + } + case "INVALID_OPERATION": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic.Add(inc) + } + case "IO_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic.Add(inc) + } + case "MISC_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic.Add(inc) + } + case "NETWORK_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic.Add(inc) + } + case "NOT_A_DIR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic.Add(inc) + } + case "NOT_IMPLEMENTED": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic.Add(inc) + } + case "NO_FILE_OR_DIR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic.Add(inc) + } + case "PERM_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic.Add(inc) + } + case "PROCESS_RESOURCE_MGMT_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic.Add(inc) + } + case "TOO_MANY_OPEN_FILES": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic.Add(inc) + } + } + +} + +func (o *otelMetrics) FsOpsLatency( + ctx context.Context, latency time.Duration, fsOp string) { + var record histogramRecord + switch fsOp { + case "BatchForget": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpBatchForgetAttrSet} + case "CreateFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpCreateFileAttrSet} + case "CreateLink": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpCreateLinkAttrSet} + case "CreateSymlink": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpCreateSymlinkAttrSet} + case "Fallocate": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpFallocateAttrSet} + case "FlushFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpFlushFileAttrSet} + case "ForgetInode": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpForgetInodeAttrSet} + case "GetInodeAttributes": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpGetInodeAttributesAttrSet} + case "GetXattr": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpGetXattrAttrSet} + case "ListXattr": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpListXattrAttrSet} + case "LookUpInode": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpLookUpInodeAttrSet} + case "MkDir": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpMkDirAttrSet} + case "MkNode": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpMkNodeAttrSet} + case "OpenDir": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpOpenDirAttrSet} + case "OpenFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpOpenFileAttrSet} + case "ReadDir": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadDirAttrSet} + case "ReadFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadFileAttrSet} + case "ReadSymlink": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadSymlinkAttrSet} + case "ReleaseDirHandle": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReleaseDirHandleAttrSet} + case "ReleaseFileHandle": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReleaseFileHandleAttrSet} + case "RemoveXattr": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpRemoveXattrAttrSet} + case "Rename": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpRenameAttrSet} + case "RmDir": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpRmDirAttrSet} + case "SetInodeAttributes": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSetInodeAttributesAttrSet} + case "SetXattr": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSetXattrAttrSet} + case "StatFS": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpStatFSAttrSet} + case "SyncFS": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSyncFSAttrSet} + case "SyncFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSyncFileAttrSet} + case "Unlink": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpUnlinkAttrSet} + case "WriteFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpWriteFileAttrSet} + } + + select { + case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + default: // Unblock writes to channel if it's full. + } +} + +func (o *otelMetrics) GcsDownloadBytesCount( + inc int64, readType string) { + switch readType { + case "Parallel": + o.gcsDownloadBytesCountReadTypeParallelAtomic.Add(inc) + case "Random": + o.gcsDownloadBytesCountReadTypeRandomAtomic.Add(inc) + case "Sequential": + o.gcsDownloadBytesCountReadTypeSequentialAtomic.Add(inc) + } + +} + +func (o *otelMetrics) GcsReadBytesCount( + inc int64) { + o.gcsReadBytesCountAtomic.Add(inc) + +} + +func (o *otelMetrics) GcsReadCount( + inc int64, readType string) { + switch readType { + case "Parallel": + o.gcsReadCountReadTypeParallelAtomic.Add(inc) + case "Random": + o.gcsReadCountReadTypeRandomAtomic.Add(inc) + case "Sequential": + o.gcsReadCountReadTypeSequentialAtomic.Add(inc) + } + +} + +func (o *otelMetrics) GcsReaderCount( + inc int64, ioMethod string) { + switch ioMethod { + case "closed": + o.gcsReaderCountIoMethodClosedAtomic.Add(inc) + case "opened": + o.gcsReaderCountIoMethodOpenedAtomic.Add(inc) + } + +} + +func (o *otelMetrics) GcsRequestCount( + inc int64, gcsMethod string) { + switch gcsMethod { + case "ComposeObjects": + o.gcsRequestCountGcsMethodComposeObjectsAtomic.Add(inc) + case "CreateFolder": + o.gcsRequestCountGcsMethodCreateFolderAtomic.Add(inc) + case "CreateObjectChunkWriter": + o.gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic.Add(inc) + case "DeleteFolder": + o.gcsRequestCountGcsMethodDeleteFolderAtomic.Add(inc) + case "DeleteObject": + o.gcsRequestCountGcsMethodDeleteObjectAtomic.Add(inc) + case "FinalizeUpload": + o.gcsRequestCountGcsMethodFinalizeUploadAtomic.Add(inc) + case "GetFolder": + o.gcsRequestCountGcsMethodGetFolderAtomic.Add(inc) + case "ListObjects": + o.gcsRequestCountGcsMethodListObjectsAtomic.Add(inc) + case "MoveObject": + o.gcsRequestCountGcsMethodMoveObjectAtomic.Add(inc) + case "MultiRangeDownloader::Add": + o.gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic.Add(inc) + case "NewMultiRangeDownloader": + o.gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic.Add(inc) + case "NewReader": + o.gcsRequestCountGcsMethodNewReaderAtomic.Add(inc) + case "RenameFolder": + o.gcsRequestCountGcsMethodRenameFolderAtomic.Add(inc) + case "StatObject": + o.gcsRequestCountGcsMethodStatObjectAtomic.Add(inc) + case "UpdateObject": + o.gcsRequestCountGcsMethodUpdateObjectAtomic.Add(inc) + } + +} + +func (o *otelMetrics) GcsRequestLatencies( + ctx context.Context, latency time.Duration, gcsMethod string) { + var record histogramRecord + switch gcsMethod { + case "ComposeObjects": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodComposeObjectsAttrSet} + case "CreateFolder": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodCreateFolderAttrSet} + case "CreateObjectChunkWriter": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodCreateObjectChunkWriterAttrSet} + case "DeleteFolder": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodDeleteFolderAttrSet} + case "DeleteObject": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodDeleteObjectAttrSet} + case "FinalizeUpload": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodFinalizeUploadAttrSet} + case "GetFolder": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodGetFolderAttrSet} + case "ListObjects": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodListObjectsAttrSet} + case "MoveObject": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodMoveObjectAttrSet} + case "MultiRangeDownloader::Add": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodMultiRangeDownloaderAddAttrSet} + case "NewMultiRangeDownloader": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodNewMultiRangeDownloaderAttrSet} + case "NewReader": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodNewReaderAttrSet} + case "RenameFolder": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodRenameFolderAttrSet} + case "StatObject": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodStatObjectAttrSet} + case "UpdateObject": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodUpdateObjectAttrSet} + } + + select { + case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + default: // Unblock writes to channel if it's full. + } +} + +func (o *otelMetrics) GcsRetryCount( + inc int64, retryErrorCategory string) { + switch retryErrorCategory { + case "OTHER_ERRORS": + o.gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic.Add(inc) + case "STALLED_READ_REQUEST": + o.gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic.Add(inc) + } + +} + func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetrics, error) { ch := make(chan histogramRecord, bufferSize) var wg sync.WaitGroup @@ -98,6 +2540,557 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fileCacheReadBytesCountReadTypeRandomAtomic, fileCacheReadBytesCountReadTypeSequentialAtomic atomic.Int64 + var fileCacheReadCountCacheHitTrueReadTypeParallelAtomic, + fileCacheReadCountCacheHitTrueReadTypeRandomAtomic, + fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic, + fileCacheReadCountCacheHitFalseReadTypeParallelAtomic, + fileCacheReadCountCacheHitFalseReadTypeRandomAtomic, + fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic atomic.Int64 + + var fsOpsCountFsOpBatchForgetAtomic, + fsOpsCountFsOpCreateFileAtomic, + fsOpsCountFsOpCreateLinkAtomic, + fsOpsCountFsOpCreateSymlinkAtomic, + fsOpsCountFsOpFallocateAtomic, + fsOpsCountFsOpFlushFileAtomic, + fsOpsCountFsOpForgetInodeAtomic, + fsOpsCountFsOpGetInodeAttributesAtomic, + fsOpsCountFsOpGetXattrAtomic, + fsOpsCountFsOpListXattrAtomic, + fsOpsCountFsOpLookUpInodeAtomic, + fsOpsCountFsOpMkDirAtomic, + fsOpsCountFsOpMkNodeAtomic, + fsOpsCountFsOpOpenDirAtomic, + fsOpsCountFsOpOpenFileAtomic, + fsOpsCountFsOpReadDirAtomic, + fsOpsCountFsOpReadFileAtomic, + fsOpsCountFsOpReadSymlinkAtomic, + fsOpsCountFsOpReleaseDirHandleAtomic, + fsOpsCountFsOpReleaseFileHandleAtomic, + fsOpsCountFsOpRemoveXattrAtomic, + fsOpsCountFsOpRenameAtomic, + fsOpsCountFsOpRmDirAtomic, + fsOpsCountFsOpSetInodeAttributesAtomic, + fsOpsCountFsOpSetXattrAtomic, + fsOpsCountFsOpStatFSAtomic, + fsOpsCountFsOpSyncFSAtomic, + fsOpsCountFsOpSyncFileAtomic, + fsOpsCountFsOpUnlinkAtomic, + fsOpsCountFsOpWriteFileAtomic atomic.Int64 + + var fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic atomic.Int64 + + var gcsDownloadBytesCountReadTypeParallelAtomic, + gcsDownloadBytesCountReadTypeRandomAtomic, + gcsDownloadBytesCountReadTypeSequentialAtomic atomic.Int64 + + var gcsReadBytesCountAtomic atomic.Int64 + + var gcsReadCountReadTypeParallelAtomic, + gcsReadCountReadTypeRandomAtomic, + gcsReadCountReadTypeSequentialAtomic atomic.Int64 + + var gcsReaderCountIoMethodClosedAtomic, + gcsReaderCountIoMethodOpenedAtomic atomic.Int64 + + var gcsRequestCountGcsMethodComposeObjectsAtomic, + gcsRequestCountGcsMethodCreateFolderAtomic, + gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, + gcsRequestCountGcsMethodDeleteFolderAtomic, + gcsRequestCountGcsMethodDeleteObjectAtomic, + gcsRequestCountGcsMethodFinalizeUploadAtomic, + gcsRequestCountGcsMethodGetFolderAtomic, + gcsRequestCountGcsMethodListObjectsAtomic, + gcsRequestCountGcsMethodMoveObjectAtomic, + gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic, + gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic, + gcsRequestCountGcsMethodNewReaderAtomic, + gcsRequestCountGcsMethodRenameFolderAtomic, + gcsRequestCountGcsMethodStatObjectAtomic, + gcsRequestCountGcsMethodUpdateObjectAtomic atomic.Int64 + + var gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, + gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic atomic.Int64 + _, err0 := meter.Int64ObservableCounter("file_cache/read_bytes_count", metric.WithDescription("The cumulative number of bytes read from file cache along with read type - Sequential/Random"), metric.WithUnit("By"), @@ -108,12 +3101,627 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return nil })) - fileCacheReadLatencies, err1 := meter.Int64Histogram("file_cache/read_latencies", + _, err1 := meter.Int64ObservableCounter("file_cache/read_count", + metric.WithDescription("Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false"), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitTrueReadTypeParallelAtomic, fileCacheReadCountCacheHitTrueReadTypeParallelAttrSet) + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitTrueReadTypeRandomAtomic, fileCacheReadCountCacheHitTrueReadTypeRandomAttrSet) + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic, fileCacheReadCountCacheHitTrueReadTypeSequentialAttrSet) + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitFalseReadTypeParallelAtomic, fileCacheReadCountCacheHitFalseReadTypeParallelAttrSet) + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitFalseReadTypeRandomAtomic, fileCacheReadCountCacheHitFalseReadTypeRandomAttrSet) + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic, fileCacheReadCountCacheHitFalseReadTypeSequentialAttrSet) + return nil + })) + + fileCacheReadLatencies, err2 := meter.Int64Histogram("file_cache/read_latencies", metric.WithDescription("The cumulative distribution of the file cache read latencies along with cache hit - true/false."), metric.WithUnit("us"), metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) - errs := []error{err0, err1} + _, err3 := meter.Int64ObservableCounter("fs/ops_count", + metric.WithDescription("The cumulative number of ops processed by the file system."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &fsOpsCountFsOpBatchForgetAtomic, fsOpsCountFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpCreateFileAtomic, fsOpsCountFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpCreateLinkAtomic, fsOpsCountFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpCreateSymlinkAtomic, fsOpsCountFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpFallocateAtomic, fsOpsCountFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpFlushFileAtomic, fsOpsCountFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpForgetInodeAtomic, fsOpsCountFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpGetInodeAttributesAtomic, fsOpsCountFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpGetXattrAtomic, fsOpsCountFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpListXattrAtomic, fsOpsCountFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpLookUpInodeAtomic, fsOpsCountFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpMkDirAtomic, fsOpsCountFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpMkNodeAtomic, fsOpsCountFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpOpenDirAtomic, fsOpsCountFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpOpenFileAtomic, fsOpsCountFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpReadDirAtomic, fsOpsCountFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpReadFileAtomic, fsOpsCountFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpReadSymlinkAtomic, fsOpsCountFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpReleaseDirHandleAtomic, fsOpsCountFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpReleaseFileHandleAtomic, fsOpsCountFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpRemoveXattrAtomic, fsOpsCountFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpRenameAtomic, fsOpsCountFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpRmDirAtomic, fsOpsCountFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpSetInodeAttributesAtomic, fsOpsCountFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpSetXattrAtomic, fsOpsCountFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpStatFSAtomic, fsOpsCountFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpSyncFSAtomic, fsOpsCountFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpSyncFileAtomic, fsOpsCountFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpUnlinkAtomic, fsOpsCountFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpWriteFileAtomic, fsOpsCountFsOpWriteFileAttrSet) + return nil + })) + + _, err4 := meter.Int64ObservableCounter("fs/ops_error_count", + metric.WithDescription("The cumulative number of errors generated by file system operations."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAttrSet) + return nil + })) + + fsOpsLatency, err5 := meter.Int64Histogram("fs/ops_latency", + metric.WithDescription("The cumulative distribution of file system operation latencies"), + metric.WithUnit("us"), + metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) + + _, err6 := meter.Int64ObservableCounter("gcs/download_bytes_count", + metric.WithDescription("The cumulative number of bytes downloaded from GCS along with type - Sequential/Random"), + metric.WithUnit("By"), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsDownloadBytesCountReadTypeParallelAtomic, gcsDownloadBytesCountReadTypeParallelAttrSet) + conditionallyObserve(obsrv, &gcsDownloadBytesCountReadTypeRandomAtomic, gcsDownloadBytesCountReadTypeRandomAttrSet) + conditionallyObserve(obsrv, &gcsDownloadBytesCountReadTypeSequentialAtomic, gcsDownloadBytesCountReadTypeSequentialAttrSet) + return nil + })) + + _, err7 := meter.Int64ObservableCounter("gcs/read_bytes_count", + metric.WithDescription("The cumulative number of bytes read from GCS objects."), + metric.WithUnit("By"), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsReadBytesCountAtomic) + return nil + })) + + _, err8 := meter.Int64ObservableCounter("gcs/read_count", + metric.WithDescription("Specifies the number of gcs reads made along with type - Sequential/Random"), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsReadCountReadTypeParallelAtomic, gcsReadCountReadTypeParallelAttrSet) + conditionallyObserve(obsrv, &gcsReadCountReadTypeRandomAtomic, gcsReadCountReadTypeRandomAttrSet) + conditionallyObserve(obsrv, &gcsReadCountReadTypeSequentialAtomic, gcsReadCountReadTypeSequentialAttrSet) + return nil + })) + + _, err9 := meter.Int64ObservableCounter("gcs/reader_count", + metric.WithDescription("The cumulative number of GCS object readers opened or closed."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsReaderCountIoMethodClosedAtomic, gcsReaderCountIoMethodClosedAttrSet) + conditionallyObserve(obsrv, &gcsReaderCountIoMethodOpenedAtomic, gcsReaderCountIoMethodOpenedAttrSet) + return nil + })) + + _, err10 := meter.Int64ObservableCounter("gcs/request_count", + metric.WithDescription("The cumulative number of GCS requests processed along with the GCS method."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodComposeObjectsAtomic, gcsRequestCountGcsMethodComposeObjectsAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodCreateFolderAtomic, gcsRequestCountGcsMethodCreateFolderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, gcsRequestCountGcsMethodCreateObjectChunkWriterAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodDeleteFolderAtomic, gcsRequestCountGcsMethodDeleteFolderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodDeleteObjectAtomic, gcsRequestCountGcsMethodDeleteObjectAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodFinalizeUploadAtomic, gcsRequestCountGcsMethodFinalizeUploadAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodGetFolderAtomic, gcsRequestCountGcsMethodGetFolderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodListObjectsAtomic, gcsRequestCountGcsMethodListObjectsAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodMoveObjectAtomic, gcsRequestCountGcsMethodMoveObjectAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic, gcsRequestCountGcsMethodMultiRangeDownloaderAddAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic, gcsRequestCountGcsMethodNewMultiRangeDownloaderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodNewReaderAtomic, gcsRequestCountGcsMethodNewReaderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodRenameFolderAtomic, gcsRequestCountGcsMethodRenameFolderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodStatObjectAtomic, gcsRequestCountGcsMethodStatObjectAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodUpdateObjectAtomic, gcsRequestCountGcsMethodUpdateObjectAttrSet) + return nil + })) + + gcsRequestLatencies, err11 := meter.Int64Histogram("gcs/request_latencies", + metric.WithDescription("The cumulative distribution of the GCS request latencies."), + metric.WithUnit("ms"), + metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) + + _, err12 := meter.Int64ObservableCounter("gcs/retry_count", + metric.WithDescription("The cumulative number of retry requests made to GCS."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, gcsRetryCountRetryErrorCategoryOTHERERRORSAttrSet) + conditionallyObserve(obsrv, &gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic, gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAttrSet) + return nil + })) + + errs := []error{err0, err1, err2, err3, err4, err5, err6, err7, err8, err9, err10, err11, err12} if err := errors.Join(errs...); err != nil { return nil, err } @@ -121,10 +3729,554 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return &otelMetrics{ ch: ch, wg: &wg, - fileCacheReadBytesCountReadTypeParallelAtomic: &fileCacheReadBytesCountReadTypeParallelAtomic, - fileCacheReadBytesCountReadTypeRandomAtomic: &fileCacheReadBytesCountReadTypeRandomAtomic, - fileCacheReadBytesCountReadTypeSequentialAtomic: &fileCacheReadBytesCountReadTypeSequentialAtomic, - fileCacheReadLatencies: fileCacheReadLatencies, + fileCacheReadBytesCountReadTypeParallelAtomic: &fileCacheReadBytesCountReadTypeParallelAtomic, + fileCacheReadBytesCountReadTypeRandomAtomic: &fileCacheReadBytesCountReadTypeRandomAtomic, + fileCacheReadBytesCountReadTypeSequentialAtomic: &fileCacheReadBytesCountReadTypeSequentialAtomic, + fileCacheReadCountCacheHitTrueReadTypeParallelAtomic: &fileCacheReadCountCacheHitTrueReadTypeParallelAtomic, + fileCacheReadCountCacheHitTrueReadTypeRandomAtomic: &fileCacheReadCountCacheHitTrueReadTypeRandomAtomic, + fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic: &fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic, + fileCacheReadCountCacheHitFalseReadTypeParallelAtomic: &fileCacheReadCountCacheHitFalseReadTypeParallelAtomic, + fileCacheReadCountCacheHitFalseReadTypeRandomAtomic: &fileCacheReadCountCacheHitFalseReadTypeRandomAtomic, + fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic: &fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic, + fileCacheReadLatencies: fileCacheReadLatencies, + fsOpsCountFsOpBatchForgetAtomic: &fsOpsCountFsOpBatchForgetAtomic, + fsOpsCountFsOpCreateFileAtomic: &fsOpsCountFsOpCreateFileAtomic, + fsOpsCountFsOpCreateLinkAtomic: &fsOpsCountFsOpCreateLinkAtomic, + fsOpsCountFsOpCreateSymlinkAtomic: &fsOpsCountFsOpCreateSymlinkAtomic, + fsOpsCountFsOpFallocateAtomic: &fsOpsCountFsOpFallocateAtomic, + fsOpsCountFsOpFlushFileAtomic: &fsOpsCountFsOpFlushFileAtomic, + fsOpsCountFsOpForgetInodeAtomic: &fsOpsCountFsOpForgetInodeAtomic, + fsOpsCountFsOpGetInodeAttributesAtomic: &fsOpsCountFsOpGetInodeAttributesAtomic, + fsOpsCountFsOpGetXattrAtomic: &fsOpsCountFsOpGetXattrAtomic, + fsOpsCountFsOpListXattrAtomic: &fsOpsCountFsOpListXattrAtomic, + fsOpsCountFsOpLookUpInodeAtomic: &fsOpsCountFsOpLookUpInodeAtomic, + fsOpsCountFsOpMkDirAtomic: &fsOpsCountFsOpMkDirAtomic, + fsOpsCountFsOpMkNodeAtomic: &fsOpsCountFsOpMkNodeAtomic, + fsOpsCountFsOpOpenDirAtomic: &fsOpsCountFsOpOpenDirAtomic, + fsOpsCountFsOpOpenFileAtomic: &fsOpsCountFsOpOpenFileAtomic, + fsOpsCountFsOpReadDirAtomic: &fsOpsCountFsOpReadDirAtomic, + fsOpsCountFsOpReadFileAtomic: &fsOpsCountFsOpReadFileAtomic, + fsOpsCountFsOpReadSymlinkAtomic: &fsOpsCountFsOpReadSymlinkAtomic, + fsOpsCountFsOpReleaseDirHandleAtomic: &fsOpsCountFsOpReleaseDirHandleAtomic, + fsOpsCountFsOpReleaseFileHandleAtomic: &fsOpsCountFsOpReleaseFileHandleAtomic, + fsOpsCountFsOpRemoveXattrAtomic: &fsOpsCountFsOpRemoveXattrAtomic, + fsOpsCountFsOpRenameAtomic: &fsOpsCountFsOpRenameAtomic, + fsOpsCountFsOpRmDirAtomic: &fsOpsCountFsOpRmDirAtomic, + fsOpsCountFsOpSetInodeAttributesAtomic: &fsOpsCountFsOpSetInodeAttributesAtomic, + fsOpsCountFsOpSetXattrAtomic: &fsOpsCountFsOpSetXattrAtomic, + fsOpsCountFsOpStatFSAtomic: &fsOpsCountFsOpStatFSAtomic, + fsOpsCountFsOpSyncFSAtomic: &fsOpsCountFsOpSyncFSAtomic, + fsOpsCountFsOpSyncFileAtomic: &fsOpsCountFsOpSyncFileAtomic, + fsOpsCountFsOpUnlinkAtomic: &fsOpsCountFsOpUnlinkAtomic, + fsOpsCountFsOpWriteFileAtomic: &fsOpsCountFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic, + fsOpsLatency: fsOpsLatency, + gcsDownloadBytesCountReadTypeParallelAtomic: &gcsDownloadBytesCountReadTypeParallelAtomic, + gcsDownloadBytesCountReadTypeRandomAtomic: &gcsDownloadBytesCountReadTypeRandomAtomic, + gcsDownloadBytesCountReadTypeSequentialAtomic: &gcsDownloadBytesCountReadTypeSequentialAtomic, + gcsReadBytesCountAtomic: &gcsReadBytesCountAtomic, + gcsReadCountReadTypeParallelAtomic: &gcsReadCountReadTypeParallelAtomic, + gcsReadCountReadTypeRandomAtomic: &gcsReadCountReadTypeRandomAtomic, + gcsReadCountReadTypeSequentialAtomic: &gcsReadCountReadTypeSequentialAtomic, + gcsReaderCountIoMethodClosedAtomic: &gcsReaderCountIoMethodClosedAtomic, + gcsReaderCountIoMethodOpenedAtomic: &gcsReaderCountIoMethodOpenedAtomic, + gcsRequestCountGcsMethodComposeObjectsAtomic: &gcsRequestCountGcsMethodComposeObjectsAtomic, + gcsRequestCountGcsMethodCreateFolderAtomic: &gcsRequestCountGcsMethodCreateFolderAtomic, + gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic: &gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, + gcsRequestCountGcsMethodDeleteFolderAtomic: &gcsRequestCountGcsMethodDeleteFolderAtomic, + gcsRequestCountGcsMethodDeleteObjectAtomic: &gcsRequestCountGcsMethodDeleteObjectAtomic, + gcsRequestCountGcsMethodFinalizeUploadAtomic: &gcsRequestCountGcsMethodFinalizeUploadAtomic, + gcsRequestCountGcsMethodGetFolderAtomic: &gcsRequestCountGcsMethodGetFolderAtomic, + gcsRequestCountGcsMethodListObjectsAtomic: &gcsRequestCountGcsMethodListObjectsAtomic, + gcsRequestCountGcsMethodMoveObjectAtomic: &gcsRequestCountGcsMethodMoveObjectAtomic, + gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic: &gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic, + gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic: &gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic, + gcsRequestCountGcsMethodNewReaderAtomic: &gcsRequestCountGcsMethodNewReaderAtomic, + gcsRequestCountGcsMethodRenameFolderAtomic: &gcsRequestCountGcsMethodRenameFolderAtomic, + gcsRequestCountGcsMethodStatObjectAtomic: &gcsRequestCountGcsMethodStatObjectAtomic, + gcsRequestCountGcsMethodUpdateObjectAtomic: &gcsRequestCountGcsMethodUpdateObjectAtomic, + gcsRequestLatencies: gcsRequestLatencies, + gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic: &gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, + gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic: &gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic, }, nil } @@ -133,9 +4285,9 @@ func (o *otelMetrics) Close() { o.wg.Wait() } -func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, attrSet metric.ObserveOption) { +func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, obsrvOptions ...metric.ObserveOption) { if val := counter.Load(); val > 0 { - obsrv.Observe(val, attrSet) + obsrv.Observe(val, obsrvOptions...) } } diff --git a/tools/metrics-gen/metric_handle.tpl b/tools/metrics-gen/metric_handle.tpl index b1bdef2d45..2331450a23 100644 --- a/tools/metrics-gen/metric_handle.tpl +++ b/tools/metrics-gen/metric_handle.tpl @@ -27,14 +27,15 @@ type MetricHandle interface { {{- range .Metrics}} // {{toPascal .Name}} - {{.Description}} {{toPascal .Name}}( - {{- if isCounter . }} + {{- if isCounter . -}} inc int64 - {{- else }} + {{- else -}} ctx context.Context, duration time.Duration {{- end }} {{- if .Attributes}}, {{end}} {{- range $i, $attr := .Attributes -}} {{if $i}}, {{end}}{{toCamel $attr.Name}} {{getGoType $attr.Type}} {{- end }}) -{{- end}} + +{{end}} } diff --git a/tools/metrics-gen/noop_metrics.tpl b/tools/metrics-gen/noop_metrics.tpl index 0e4356de6e..ae9d870aa1 100644 --- a/tools/metrics-gen/noop_metrics.tpl +++ b/tools/metrics-gen/noop_metrics.tpl @@ -23,9 +23,9 @@ import ( type noopMetrics struct {} {{- range .Metrics}} func (*noopMetrics) {{toPascal .Name}}( - {{- if isCounter . }} + {{- if isCounter . -}} inc int64 - {{- else }} + {{- else -}} ctx context.Context, duration time.Duration {{- end }} {{- if .Attributes}}, {{end}} diff --git a/tools/metrics-gen/otel_metrics.tpl b/tools/metrics-gen/otel_metrics.tpl index b2799adbf1..368047d75d 100644 --- a/tools/metrics-gen/otel_metrics.tpl +++ b/tools/metrics-gen/otel_metrics.tpl @@ -118,7 +118,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr metric.WithUnit("{{.Unit}}"), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { {{- range $combination := (index $.AttrCombinations $metric.Name)}} - conditionallyObserve(obsrv, &{{getAtomicName $metric.Name $combination}}, {{getVarName $metric.Name $combination}}) + conditionallyObserve(obsrv, &{{getAtomicName $metric.Name $combination}}{{if $metric.Attributes}}, {{getVarName $metric.Name $combination}}{{end}}) {{- end}} return nil })) @@ -163,9 +163,9 @@ func (o *otelMetrics) Close() { o.wg.Wait() } -func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, attrSet metric.ObserveOption) { +func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, obsrvOptions ...metric.ObserveOption) { if val := counter.Load(); val > 0 { - obsrv.Observe(val, attrSet) + obsrv.Observe(val, obsrvOptions...) } } From 9eb0aae680d562668d9f9148dbfe9e1e1926a365 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 31 Jul 2025 13:39:47 +0530 Subject: [PATCH 0625/1298] perf: MRD improvements (#3525) * MRD improvements build fix * make go routine conditional * clean up code * fix UT * make debug bucket conditional * hardcode read handle * rebase changes * Update internal/gcsx/multi_range_downloader_wrapper.go --- cmd/mount.go | 1 + internal/fs/inode/file.go | 2 +- internal/gcsx/bucket_manager.go | 8 ++- .../gcsx/client_readers/gcs_reader_test.go | 4 +- .../client_readers/multi_range_reader_test.go | 13 ++--- internal/gcsx/client_readers/range_reader.go | 2 +- .../gcsx/multi_range_downloader_wrapper.go | 52 ++++++++++--------- .../multi_range_downloader_wrapper_test.go | 28 +++++++--- internal/gcsx/random_reader.go | 2 +- internal/gcsx/random_reader_stretchr_test.go | 16 +++--- internal/storage/bucket_handle.go | 17 ++---- internal/storage/bucket_handle_test.go | 22 -------- internal/storage/debug_bucket.go | 29 ++++++++--- internal/storage/gcs/request.go | 3 ++ 14 files changed, 106 insertions(+), 93 deletions(-) diff --git a/cmd/mount.go b/cmd/mount.go index 9d734bea28..5c81a58d0c 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -95,6 +95,7 @@ be interacting with the file system.`) StatCacheTTL: time.Duration(newConfig.MetadataCache.TtlSecs) * time.Second, NegativeStatCacheTTL: time.Duration(newConfig.MetadataCache.NegativeTtlSecs) * time.Second, EnableMonitoring: cfg.IsMetricsEnabled(&newConfig.Metrics), + LogSeverity: newConfig.Logging.Severity, AppendThreshold: 1 << 21, // 2 MiB, a total guess. ChunkTransferTimeoutSecs: newConfig.GcsRetries.ChunkTransferTimeoutSecs, TmpObjectPrefix: ".gcsfuse_tmp/", diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index c1492285ad..bd75eb2fca 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -162,7 +162,7 @@ func NewFileInode( globalMaxWriteBlocksSem: globalMaxBlocksSem, } var err error - f.MRDWrapper, err = gcsx.NewMultiRangeDownloaderWrapper(bucket, &minObj) + f.MRDWrapper, err = gcsx.NewMultiRangeDownloaderWrapper(bucket, &minObj, cfg) if err != nil { logger.Errorf("NewFileInode: Error in creating MRDWrapper %v", err) } diff --git a/internal/gcsx/bucket_manager.go b/internal/gcsx/bucket_manager.go index 052fa5b4a6..5f3dd44631 100644 --- a/internal/gcsx/bucket_manager.go +++ b/internal/gcsx/bucket_manager.go @@ -21,6 +21,7 @@ import ( "path" "time" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" "github.com/googlecloudplatform/gcsfuse/v3/internal/canned" @@ -45,6 +46,7 @@ type BucketConfig struct { // Config for TTL of entries for non-existing file in stat cache NegativeStatCacheTTL time.Duration EnableMonitoring bool + LogSeverity cfg.LogSeverity // Files backed by on object of length at least AppendThreshold that have // only been appended to (i.e. none of the object's contents have been @@ -178,8 +180,10 @@ func (bm *bucketManager) SetUpBucket( // Enable monitoring. b = monitor.NewMonitoringBucket(b, metricHandle) - // Enable gcs logs. - b = storage.NewDebugBucket(b) + if bm.config.LogSeverity == cfg.TraceLogSeverity { + // Enable gcs logs. + b = storage.NewDebugBucket(b) + } // Limit to a requested prefix of the bucket, if any. if bm.config.OnlyDir != "" { diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go index 5124946c4e..77c12beacc 100644 --- a/internal/gcsx/client_readers/gcs_reader_test.go +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -327,7 +327,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { t.gcsReader.expectedOffset.Store(0) t.object.Size = uint64(tc.dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) require.NoError(t.T(), err, "Error in creating MRDWrapper") t.gcsReader.mrr.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)) @@ -618,7 +618,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateZonalRandomReads() { t.object.Size = 20 * MiB t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.gcsReader.mrr.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(&fake.FakeReader{ReadCloser: getReadCloser(testContent)}, nil).Twice() diff --git a/internal/gcsx/client_readers/multi_range_reader_test.go b/internal/gcsx/client_readers/multi_range_reader_test.go index d22dabfede..ecb523cf66 100644 --- a/internal/gcsx/client_readers/multi_range_reader_test.go +++ b/internal/gcsx/client_readers/multi_range_reader_test.go @@ -21,6 +21,7 @@ import ( "testing" "time" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" @@ -97,7 +98,7 @@ func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_ReadFull() { t.multiRangeReader.isMRDInUse = false t.object.Size = uint64(tc.dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) require.NoError(t.T(), err, "Error in creating MRDWrapper") t.multiRangeReader.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) @@ -118,7 +119,7 @@ func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_TimeoutExceeded() { dataSize := 100 t.object.Size = uint64(dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) require.NoError(t.T(), err, "Error in creating MRDWrapper") t.multiRangeReader.mrdWrapper = &fakeMRDWrapper sleepTime := 10 * time.Millisecond @@ -130,7 +131,7 @@ func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_TimeoutExceeded() { _, err = t.multiRangeReader.readFromMultiRangeReader(t.ctx, buf, 0, int64(t.object.Size), timeout) assert.Error(t.T(), err) - assert.ErrorContains(t.T(), err, "Timeout") + assert.ErrorContains(t.T(), err, "timeout") } func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_TimeoutNotExceeded() { @@ -138,7 +139,7 @@ func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_TimeoutNotExceeded( dataSize := 100 t.object.Size = uint64(dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) require.NoError(t.T(), err, "Error in creating MRDWrapper") t.multiRangeReader.mrdWrapper = &fakeMRDWrapper sleepTime := 2 * time.Millisecond @@ -182,7 +183,7 @@ func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_ReadChunk() { for _, tc := range testCases { t.object.Size = uint64(tc.dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) require.NoError(t.T(), err, "Error in creating MRDWrapper") t.multiRangeReader.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) @@ -223,7 +224,7 @@ func (t *multiRangeReaderTest) Test_ReadAt_MRDRead() { t.multiRangeReader.isMRDInUse = false t.object.Size = uint64(tc.dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) require.NoError(t.T(), err, "Error in creating MRDWrapper") t.multiRangeReader.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index 57662b8ded..df0c1e6e3e 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -174,7 +174,7 @@ func (rr *RangeReader) readFromRangeReader(ctx context.Context, p []byte, offset // if the reader peters out early. That's fine, but it means we should // have hit the limit above. if rr.reader != nil { - err = fmt.Errorf("reader returned early by skipping %d bytes", rr.limit-rr.start) + err = fmt.Errorf("range reader returned early by skipping %d bytes", rr.limit-rr.start) return 0, err } diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go index 14726d8325..72b4334c53 100644 --- a/internal/gcsx/multi_range_downloader_wrapper.go +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -21,7 +21,7 @@ import ( "sync" "time" - "github.com/google/uuid" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/monitor" @@ -34,11 +34,11 @@ import ( // it's refcount reaches 0. const multiRangeDownloaderTimeout = 60 * time.Second -func NewMultiRangeDownloaderWrapper(bucket gcs.Bucket, object *gcs.MinObject) (MultiRangeDownloaderWrapper, error) { - return NewMultiRangeDownloaderWrapperWithClock(bucket, object, clock.RealClock{}) +func NewMultiRangeDownloaderWrapper(bucket gcs.Bucket, object *gcs.MinObject, config *cfg.Config) (MultiRangeDownloaderWrapper, error) { + return NewMultiRangeDownloaderWrapperWithClock(bucket, object, clock.RealClock{}, config) } -func NewMultiRangeDownloaderWrapperWithClock(bucket gcs.Bucket, object *gcs.MinObject, clock clock.Clock) (MultiRangeDownloaderWrapper, error) { +func NewMultiRangeDownloaderWrapperWithClock(bucket gcs.Bucket, object *gcs.MinObject, clock clock.Clock, config *cfg.Config) (MultiRangeDownloaderWrapper, error) { if object == nil { return MultiRangeDownloaderWrapper{}, fmt.Errorf("NewMultiRangeDownloaderWrapperWithClock: Missing MinObject") } @@ -48,6 +48,7 @@ func NewMultiRangeDownloaderWrapperWithClock(bucket gcs.Bucket, object *gcs.MinO clock: clock, bucket: bucket, object: object, + config: config, }, nil } @@ -73,9 +74,11 @@ type MultiRangeDownloaderWrapper struct { cancelCleanup context.CancelFunc // Used for waiting for timeout (helps us in mocking the functionality). clock clock.Clock + // GCSFuse mount config. + config *cfg.Config } -// Sets the gcs.MinObject stored in the wrapper to passed value, only if it's non nil. +// SetMinObject sets the gcs.MinObject stored in the wrapper to passed value, only if it's non nil. func (mrdWrapper *MultiRangeDownloaderWrapper) SetMinObject(minObj *gcs.MinObject) error { if minObj == nil { return fmt.Errorf("MultiRangeDownloaderWrapper::SetMinObject: Missing MinObject") @@ -84,19 +87,19 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) SetMinObject(minObj *gcs.MinObjec return nil } -// Returns the minObject stored in MultiRangeDownloaderWrapper. Used only for unit testing. +// GetMinObject returns the minObject stored in MultiRangeDownloaderWrapper. Used only for unit testing. func (mrdWrapper *MultiRangeDownloaderWrapper) GetMinObject() *gcs.MinObject { return mrdWrapper.object } -// Returns current refcount. +// GetRefCount returns current refcount. func (mrdWrapper *MultiRangeDownloaderWrapper) GetRefCount() int { mrdWrapper.mu.Lock() defer mrdWrapper.mu.Unlock() return mrdWrapper.refCount } -// Increment the refcount and cancel any running cleanup function. +// IncrementRefCount increments the refcount and cancel any running cleanup function. // This method should be called exactly once per user of this wrapper. // It has to be called before using the MultiRangeDownloader. func (mrdWrapper *MultiRangeDownloaderWrapper) IncrementRefCount() { @@ -110,7 +113,7 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) IncrementRefCount() { } } -// Decrement the refcount. In case refcount reaches 0, cleanup the MRD. +// DecrementRefCount decrements the refcount. In case refcount reaches 0, cleanup the MRD. // Returns error on invalid usage. // This method should be called exactly once per user of this wrapper // when MultiRangeDownloader is no longer needed & can be cleaned up. @@ -216,8 +219,6 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []b mu.Unlock() }() - requestId := uuid.New() - logger.Tracef("%.13v <- MultiRangeDownloader::Add (%s, [%d, %d))", requestId, mrdWrapper.object.Name, startOffset, endOffset) start := time.Now() mrdWrapper.Wrapped.Add(buffer, startOffset, endOffset-startOffset, func(offsetAddCallback int64, bytesReadAddCallback int64, e error) { defer func() { @@ -229,27 +230,30 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []b }() if e != nil && e != io.EOF { - e = fmt.Errorf("Error in Add Call: %w", e) + e = fmt.Errorf("error in Add call: %w", e) } }) - select { - case <-time.After(timeout): - err = fmt.Errorf("Timeout") - case <-ctx.Done(): - err = fmt.Errorf("Context Cancelled: %w", ctx.Err()) - case res := <-done: + if !mrdWrapper.config.FileSystem.IgnoreInterrupts { + select { + case <-time.After(timeout): + err = fmt.Errorf("timeout") + case <-ctx.Done(): + err = ctx.Err() + case res := <-done: + bytesRead = res.bytesRead + err = res.err + } + } else { + res := <-done bytesRead = res.bytesRead err = res.err } - duration := time.Since(start) - monitor.CaptureMultiRangeDownloaderMetrics(ctx, metricHandle, "MultiRangeDownloader::Add", start) - errDesc := "OK" if err != nil { - errDesc = err.Error() err = fmt.Errorf("MultiRangeDownloaderWrapper::Read: %w", err) - logger.Errorf("%v", err) + logger.Error(err.Error()) } - logger.Tracef("%.13v -> MultiRangeDownloader::Add (%s, [%d, %d)) (%v): %v", requestId, mrdWrapper.object.Name, startOffset, endOffset, duration, errDesc) + monitor.CaptureMultiRangeDownloaderMetrics(ctx, metricHandle, "MultiRangeDownloader::Add", start) + return } diff --git a/internal/gcsx/multi_range_downloader_wrapper_test.go b/internal/gcsx/multi_range_downloader_wrapper_test.go index 01e2924f17..f3f0849ec7 100644 --- a/internal/gcsx/multi_range_downloader_wrapper_test.go +++ b/internal/gcsx/multi_range_downloader_wrapper_test.go @@ -22,6 +22,7 @@ import ( "testing" "time" + "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/internal/clock" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" @@ -30,6 +31,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -57,7 +59,7 @@ func (t *mrdWrapperTest) SetupTest() { // Create the bucket. t.mockBucket = new(storage.TestifyMockBucket) t.mrdTimeout = time.Millisecond - t.mrdWrapper, err = NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{WaitTime: t.mrdTimeout}) + t.mrdWrapper, err = NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{WaitTime: t.mrdTimeout}, &cfg.Config{}) assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.mrdWrapper.Wrapped = fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond) t.mrdWrapper.refCount = 0 @@ -182,22 +184,36 @@ func (t *mrdWrapperTest) Test_Read_Timeout() { bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) - assert.ErrorContains(t.T(), err, "Timeout") + assert.ErrorContains(t.T(), err, "timeout") assert.Equal(t.T(), 0, bytesRead) } -func (t *mrdWrapperTest) Test_Read_ContextCancelled() { +func (t *mrdWrapperTest) TestReadContextCancelledWithInterruptsEnabled() { t.mrdWrapper.Wrapped = nil + t.mrdWrapper.config = &cfg.Config{FileSystem: cfg.FileSystemConfig{IgnoreInterrupts: false}} t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond), nil).Once() ctx, cancel := context.WithCancel(context.Background()) cancel() bytesRead, err := t.mrdWrapper.Read(ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) - assert.ErrorContains(t.T(), err, "Context Cancelled") + require.Error(t.T(), err) + assert.ErrorContains(t.T(), err, "context canceled") assert.Equal(t.T(), 0, bytesRead) } +func (t *mrdWrapperTest) TestReadContextCancelledWithInterruptsDisabled() { + t.mrdWrapper.config = &cfg.Config{FileSystem: cfg.FileSystemConfig{IgnoreInterrupts: true}} + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond), nil).Once() + ctx, cancel := context.WithCancel(context.Background()) + cancel() + + bytesRead, err := t.mrdWrapper.Read(ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) + + require.NoError(t.T(), err) + assert.Equal(t.T(), 100, bytesRead) +} + func (t *mrdWrapperTest) Test_Read_EOF() { t.mrdWrapper.Wrapped = nil t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleepAndDefaultError(t.object, t.objectData, time.Microsecond, io.EOF), nil).Once() @@ -213,7 +229,7 @@ func (t *mrdWrapperTest) Test_Read_Error() { bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) - assert.ErrorContains(t.T(), err, "Error in Add Call") + assert.ErrorContains(t.T(), err, "error in Add call") assert.Equal(t.T(), 0, bytesRead) } @@ -240,7 +256,7 @@ func (t *mrdWrapperTest) Test_NewMultiRangeDownloaderWrapper() { for _, tc := range testCases { t.Run(tc.name, func() { - _, err := NewMultiRangeDownloaderWrapper(tc.bucket, tc.obj) + _, err := NewMultiRangeDownloaderWrapper(tc.bucket, tc.obj, &cfg.Config{}) if tc.err == nil { assert.NoError(t.T(), err) } else { diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index d1548d4947..78e7692541 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -631,7 +631,7 @@ func (rr *randomReader) readFromRangeReader(ctx context.Context, p []byte, offse // if the reader peters out early. That's fine, but it means we should // have hit the limit above. if rr.reader != nil { - err = fmt.Errorf("Reader returned early by skipping %d bytes", rr.limit-rr.start) + err = fmt.Errorf("random reader returned early by skipping %d bytes", rr.limit-rr.start) return } diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 3fbe885e07..de103b55bf 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -680,7 +680,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { t.rr.wrapped.expectedOffset.Store(0) t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)) @@ -712,7 +712,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateZonalRandomReads() { t.object.Size = 20 * MiB t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(&fake.FakeReader{ReadCloser: getReadCloser(testContent)}, nil).Twice() @@ -772,7 +772,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_MRDRead() { t.rr.wrapped.seeks.Store(minSeeksForRandom + 1) t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) @@ -817,7 +817,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadFull() { t.rr.wrapped.isMRDInUse = false t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) @@ -853,7 +853,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadChunk() { t.rr.wrapped.reader = nil t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)).Times(1) @@ -891,14 +891,14 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ValidateTimeout dataSize: 100, timeout: 5 * time.Millisecond, sleepTime: 10 * time.Millisecond, - expectedErrKeyword: "Timeout", + expectedErrKeyword: "timeout", }, { name: "TimeoutValue", dataSize: 100, timeout: 5 * time.Millisecond, sleepTime: 5 * time.Millisecond, - expectedErrKeyword: "Timeout", + expectedErrKeyword: "timeout", }, } @@ -908,7 +908,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ValidateTimeout t.rr.wrapped.isMRDInUse = false t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, tc.sleepTime)).Once() diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index f17dca6c19..b60a78e9c4 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -92,7 +92,8 @@ func (bh *bucketHandle) NewReaderWithReadHandle( // This produces the exact same object and generation and does not check if // the generation is still the newest one. if req.ReadHandle != nil { - obj = obj.ReadHandle(req.ReadHandle) + // TODO: b/432639555 fix code to use read handle from previous read. + obj = obj.ReadHandle([]byte("opaque-handle")) } // NewRangeReader creates a "storage.Reader" object which is also io.ReadCloser since it contains both Read() and Close() methods present in io.ReadCloser interface. @@ -225,9 +226,7 @@ func (bh *bucketHandle) CreateObject(ctx context.Context, req *gcs.CreateObjectR wc := obj.NewWriter(ctx) wc.ChunkTransferTimeout = time.Duration(req.ChunkTransferTimeoutSecs) * time.Second wc = storageutil.SetAttrsInWriter(wc, req) - wc.ProgressFunc = func(bytesUploadedSoFar int64) { - logger.Tracef("gcs: Req %#16x: -- CreateObject(%q): %20v bytes uploaded so far", ctx.Value(gcs.ReqIdField), req.Name, bytesUploadedSoFar) - } + wc.ProgressFunc = req.CallBack // All objects in zonal buckets must be appendable. wc.Append = bh.BucketType().Zonal // FinalizeOnClose should be true for all writes for now. @@ -260,11 +259,6 @@ func (bh *bucketHandle) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cr wc.Writer = storageutil.SetAttrsInWriter(wc.Writer, req) // TODO(b/424091803): Uncomment once chunk transfer timeout issue in resumable uploads is fixed in dependencies. // wc.ChunkTransferTimeout = time.Duration(req.ChunkTransferTimeoutSecs) * time.Second - if callBack == nil { - callBack = func(bytesUploadedSoFar int64) { - logger.Tracef("gcs: Req %#16x: -- UploadBlock(%q): %20v bytes uploaded so far", ctx.Value(gcs.ReqIdField), req.Name, bytesUploadedSoFar) - } - } wc.ProgressFunc = callBack // All objects in zonal buckets must be appendable. wc.Append = bh.BucketType().Zonal @@ -279,13 +273,10 @@ func (bh *bucketHandle) CreateAppendableObjectWriter(ctx context.Context, obj := bh.getObjectHandleWithPreconditionsSet(&req.CreateObjectRequest) // To create the takeover writer, the objectHandle.Generation must be set. obj = obj.Generation(*req.CreateObjectRequest.GenerationPrecondition) - callBack := func(bytesUploadedSoFar int64) { - logger.Tracef("gcs: Req %#16x: -- UploadBlock(%q): %20v bytes uploaded so far", ctx.Value(gcs.ReqIdField), req.Name, bytesUploadedSoFar) - } opts := storage.AppendableWriterOpts{ ChunkSize: req.ChunkSize, - ProgressFunc: callBack, + ProgressFunc: req.CallBack, FinalizeOnClose: false, } diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 0feb5bef9b..6ed216c446 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -572,28 +572,6 @@ func (testSuite *BucketHandleTest) TestBucketHandle_CreateObjectChunkWriter() { } } -func (testSuite *BucketHandleTest) TestBucketHandle_CreateObjectChunkWriterWithNilCallback() { - var metaGeneration0 int64 = 0 - objectName := "test_object_1" - chunkSize := 1024 * 1024 - - w, err := testSuite.bucketHandle.CreateObjectChunkWriter(context.Background(), - &gcs.CreateObjectRequest{ - Name: objectName, - GenerationPrecondition: nil, - MetaGenerationPrecondition: &metaGeneration0, - }, - chunkSize, - nil, - ) - - require.NoError(testSuite.T(), err) - objWr, ok := (w).(*ObjectWriter) - require.True(testSuite.T(), ok) - require.NotNil(testSuite.T(), objWr) - assert.NotNil(testSuite.T(), objWr.ProgressFunc) -} - func (testSuite *BucketHandleTest) TestBucketHandle_FinalizeUploadSuccess() { createBucketHandle(testSuite, &controlpb.StorageLayout{}) diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index 26bbd4425d..e58da980b1 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -119,6 +119,7 @@ func (dr *debugReader) Close() (err error) { } func (dr *debugReader) ReadHandle() storagev2.ReadHandle { + // TODO: b/432639555 fix code to use read handle from previous read. hd := "opaque-handle" return []byte(hd) } @@ -168,16 +169,24 @@ func (b *debugBucket) CreateObject( req *gcs.CreateObjectRequest) (o *gcs.Object, err error) { id, desc, start := b.startRequest("CreateObject(%q)", req.Name) defer b.finishRequest(id, desc, start, &err) - - o, err = b.wrapped.CreateObject(context.WithValue(ctx, gcs.ReqIdField, id), req) + if req.CallBack == nil { + req.CallBack = func(bytesUploadedSoFar int64) { + logger.Tracef("gcs: Req %#16x: -- UploadBlock(%q): %20v bytes uploaded so far", id, req.Name, bytesUploadedSoFar) + } + } + o, err = b.wrapped.CreateObject(ctx, req) return } func (b *debugBucket) CreateObjectChunkWriter(ctx context.Context, req *gcs.CreateObjectRequest, chunkSize int, callBack func(bytesUploadedSoFar int64)) (wc gcs.Writer, err error) { id, desc, start := b.startRequest("CreateObjectChunkWriter(%q)", req.Name) defer b.finishRequest(id, desc, start, &err) - - wc, err = b.wrapped.CreateObjectChunkWriter(context.WithValue(ctx, gcs.ReqIdField, id), req, chunkSize, callBack) + if callBack == nil { + callBack = func(bytesUploadedSoFar int64) { + logger.Tracef("gcs: Req %#16x: -- UploadBlock(%q): %20v bytes uploaded so far", id, req.Name, bytesUploadedSoFar) + } + } + wc, err = b.wrapped.CreateObjectChunkWriter(ctx, req, chunkSize, callBack) return } @@ -185,8 +194,12 @@ func (b *debugBucket) CreateAppendableObjectWriter(ctx context.Context, req *gcs.CreateObjectChunkWriterRequest) (wc gcs.Writer, err error) { id, desc, start := b.startRequest("CreateAppendableObjectWriter(%q, %d)", req.Name, req.Offset) defer b.finishRequest(id, desc, start, &err) - - wc, err = b.wrapped.CreateAppendableObjectWriter(context.WithValue(ctx, gcs.ReqIdField, id), req) + if req.CallBack == nil { + req.CallBack = func(bytesUploadedSoFar int64) { + logger.Tracef("gcs: Req %#16x: -- UploadBlock(%q): %20v bytes uploaded so far", id, req.Name, bytesUploadedSoFar) + } + } + wc, err = b.wrapped.CreateAppendableObjectWriter(ctx, req) return } @@ -317,6 +330,7 @@ func (b *debugBucket) RenameFolder(ctx context.Context, folderName string, desti } type debugMultiRangeDownloader struct { + object string bucket *debugBucket requestID uint64 desc string @@ -325,7 +339,7 @@ type debugMultiRangeDownloader struct { } func (dmrd *debugMultiRangeDownloader) Add(output io.Writer, offset, length int64, callback func(int64, int64, error)) { - id, desc, start := dmrd.bucket.startRequest("MultiRangeDownloader.Add(%v,%v)", offset, length) + id, desc, start := dmrd.bucket.startRequest("MultiRangeDownloader.Add(%s, [%v,%v))", dmrd.object, offset, offset+length) wrapperCallback := func(offset int64, length int64, err error) { defer dmrd.bucket.finishRequest(id, desc, start, &err) if callback != nil { @@ -368,6 +382,7 @@ func (b *debugBucket) NewMultiRangeDownloader( // Return a special reader that prints debug info. mrd = &debugMultiRangeDownloader{ + object: req.Name, bucket: b, requestID: id, desc: desc, diff --git a/internal/storage/gcs/request.go b/internal/storage/gcs/request.go index 37b7b7a3aa..905902767e 100644 --- a/internal/storage/gcs/request.go +++ b/internal/storage/gcs/request.go @@ -84,6 +84,9 @@ type CreateObjectRequest struct { // meta-generation for the object name is equal to the given value. This is // only meaningful in conjunction with GenerationPrecondition. MetaGenerationPrecondition *int64 + + // CallBack function is called after upload of each chunk. + CallBack func(bytesUploadedSoFar int64) } // A request to copy an object to a new name, preserving all metadata. From c414fda04f577cf770b709bc1c5584926f8e8da7 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 31 Jul 2025 15:13:05 +0530 Subject: [PATCH 0626/1298] ci: fix only-dir testing in implicit_dir test package (#3612) * ci: allow '/' in testBucket argument value * fix: fix onlydir test config in implicit_dir test package * dump gcsfuse debug log to kokoro-logs bucket for implicit_dir package * disable irrelevant e2e tests for ZB for implicit_dir package * fix failing implicit_dir tests --- .../implicit_dir/implicit_dir_test.go | 15 ++++++++++----- .../util/client/storage_client.go | 6 +++--- .../implicit_and_explicit_dir_setup.go | 13 +++++++------ tools/integration_tests/util/setup/setup.go | 2 +- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/tools/integration_tests/implicit_dir/implicit_dir_test.go b/tools/integration_tests/implicit_dir/implicit_dir_test.go index ff759a1066..7b303f88f9 100644 --- a/tools/integration_tests/implicit_dir/implicit_dir_test.go +++ b/tools/integration_tests/implicit_dir/implicit_dir_test.go @@ -73,15 +73,20 @@ func TestMain(m *testing.M) { flagsSet := [][]string{{"--implicit-dirs"}} - if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(testEnv.ctx, testEnv.storageClient); err == nil { - flagsSet = append(flagsSet, hnsFlagSet) - } + // No need to run enable-hns and client-protocol GRPC configuration for ZB, + // as those are both by default enabled for ZB. + if !setup.IsZonalBucketRun() { + if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(testEnv.ctx, testEnv.storageClient); err == nil { + flagsSet = append(flagsSet, hnsFlagSet) + } - if !testing.Short() { - flagsSet = append(flagsSet, []string{"--client-protocol=grpc", "--implicit-dirs=true"}) + if !testing.Short() { + flagsSet = append(flagsSet, []string{"--client-protocol=grpc", "--implicit-dirs"}) + } } successCode := implicit_and_explicit_dir_setup.RunTestsForImplicitDirAndExplicitDir(flagsSet, m) + setup.SaveLogFileInCaseOfFailure(successCode) // Clean up test directory created. setup.CleanupDirectoryOnGCS(testEnv.ctx, testEnv.storageClient, path.Join(setup.TestBucket(), testDirName)) diff --git a/tools/integration_tests/util/client/storage_client.go b/tools/integration_tests/util/client/storage_client.go index 3875725ccd..f1ac5c44ae 100644 --- a/tools/integration_tests/util/client/storage_client.go +++ b/tools/integration_tests/util/client/storage_client.go @@ -168,13 +168,13 @@ func WriteToObject(ctx context.Context, client *storage.Client, object, content // Upload an object with storage.Writer. wc, err := NewWriter(ctx, o, client) if err != nil { - return fmt.Errorf("Failed to open writer for object %q: %w", o.ObjectName(), err) + return fmt.Errorf("Failed to open writer for object %q: %w", object, err) } if _, err := io.WriteString(wc, content); err != nil { - return fmt.Errorf("io.WriteSTring: %w", err) + return fmt.Errorf("io.WriteString failed for object %q: %w", object, err) } if err := wc.Close(); err != nil { - return fmt.Errorf("Writer.Close: %w", err) + return fmt.Errorf("Writer.Close failed for object %q: %w", object, err) } return nil diff --git a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go index 9730b2fca6..bdff8239c0 100644 --- a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go +++ b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go @@ -19,6 +19,7 @@ import ( "log" "os" "path" + "strings" "testing" storage "cloud.google.com/go/storage" @@ -79,11 +80,11 @@ func RemoveAndCheckIfDirIsDeleted(dirPath string, dirName string, t *testing.T) // testdataCreateObjects is equivalent of the script tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/testdata/create_objects.sh . // That script uses gcloud, but this function instead uses go client library. -// Note: testDirWithBucketName is of the form /. -func testdataCreateObjects(ctx context.Context, t *testing.T, storageClient *storage.Client, testDirWithBucketName string) { +func testdataCreateObjects(ctx context.Context, t *testing.T, storageClient *storage.Client, testDirWithoutBucketName string) { t.Helper() - - bucketName, testDirWithoutBucketName := operations.SplitBucketNameAndDirPath(t, testDirWithBucketName) + // Following is needed for error log, and because + // TestBucket can be of the form / . + bucketName, _, _ := strings.Cut(setup.TestBucket(), "/") objectName := path.Join(testDirWithoutBucketName, ImplicitDirectory, FileInImplicitDirectory) err := client.CreateObjectOnGCS(ctx, storageClient, objectName, "This is from directory fileInImplicitDir1 file implicitDirectory") @@ -108,7 +109,7 @@ func CreateImplicitDirectoryStructureUsingStorageClient(ctx context.Context, t * // testBucket/testDir/implicitDirectory/implicitSubDirectory/fileInImplicitDir2 -- File // Create implicit directory in bucket for testing. - testdataCreateObjects(ctx, t, storageClient, path.Join(setup.TestBucket(), testDir)) + testdataCreateObjects(ctx, t, storageClient, testDir) } func CreateImplicitDirectoryStructure(testDir string) { @@ -173,6 +174,6 @@ func CreateImplicitDirectoryInExplicitDirectoryStructureUsingStorageClient(ctx c // CreateExplicitDirectoryStructure writes files using GCSFuse. CreateExplicitDirectoryStructure(testDir, t) - dirPathInBucket := path.Join(setup.TestBucket(), testDir, ExplicitDirectory) + dirPathInBucket := path.Join(testDir, ExplicitDirectory) testdataCreateObjects(ctx, t, storageClient, dirPathInBucket) } diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 4bc61c1535..4b5d5099c0 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -58,7 +58,7 @@ const ( ProxyServerLogFilePrefix = "proxy-server-failed-integration-test-logs-" zoneMatcherRegex = "^[a-z]+-[a-z0-9]+-[a-z]$" regionMatcherRegex = "^[a-z]+-[a-z0-9]+$" - unsupportedCharactersInTestBucket = " /" + unsupportedCharactersInTestBucket = " " ) var ( From b8730c8bbc63cb224595aea4f4063a7f041afddd Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 31 Jul 2025 15:49:22 +0530 Subject: [PATCH 0627/1298] Revert "Fix issue with formatting (#3520)" (#3615) This reverts commit a1f8a64e0e23583ec52ea15193202868b01b4118. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ba452cf41d..e349a14730 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ generate: go generate ./... imports: generate - + goimports -w . fmt: imports go mod tidy && go fmt ./... From 445af7ccdc922a49b3b7438f89445dcc4abaa5c4 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 31 Jul 2025 16:19:30 +0530 Subject: [PATCH 0628/1298] perf(metrics): Code-generate tests for metrics. (#3614) * Code-generate tests for metrics. * Incorporate review comments. --- optimizedmetrics/otel_metrics.go | 1 - optimizedmetrics/otel_metrics_test.go | 5741 +++++++++++++++++++++++ tools/metrics-gen/main.go | 91 +- tools/metrics-gen/otel_metrics.tpl | 1 - tools/metrics-gen/otel_metrics_test.tpl | 286 ++ 5 files changed, 6108 insertions(+), 12 deletions(-) create mode 100644 optimizedmetrics/otel_metrics_test.go create mode 100644 tools/metrics-gen/otel_metrics_test.tpl diff --git a/optimizedmetrics/otel_metrics.go b/optimizedmetrics/otel_metrics.go index 099e00c4ed..c61cdd29e2 100644 --- a/optimizedmetrics/otel_metrics.go +++ b/optimizedmetrics/otel_metrics.go @@ -4289,5 +4289,4 @@ func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, obs if val := counter.Load(); val > 0 { obsrv.Observe(val, obsrvOptions...) } - } diff --git a/optimizedmetrics/otel_metrics_test.go b/optimizedmetrics/otel_metrics_test.go new file mode 100644 index 0000000000..c4bb1583f3 --- /dev/null +++ b/optimizedmetrics/otel_metrics_test.go @@ -0,0 +1,5741 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** + +package optimizedmetrics + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" +) + +// metricValueMap maps attribute sets to metric values. +type metricValueMap map[string]int64 + +// metricHistogramMap maps attribute sets to histogram data points. +type metricHistogramMap map[string]metricdata.HistogramDataPoint[int64] + +func waitForMetricsProcessing() { + time.Sleep(time.Millisecond) +} + +func setupOTel(ctx context.Context, t *testing.T) (*otelMetrics, *metric.ManualReader) { + t.Helper() + reader := metric.NewManualReader() + provider := metric.NewMeterProvider(metric.WithReader(reader)) + otel.SetMeterProvider(provider) + + m, err := NewOTelMetrics(ctx, 10, 100) + require.NoError(t, err) + return m, reader +} + +// gatherHistogramMetrics collects all histogram metrics from the reader. +// It returns a map where the key is the metric name, and the value is another map. +// The inner map's key is a string representation of the attributes, +// and the value is the metricdata.HistogramDataPoint. +func gatherHistogramMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[string]metricdata.HistogramDataPoint[int64] { + t.Helper() + var rm metricdata.ResourceMetrics + err := rd.Collect(ctx, &rm) + require.NoError(t, err) + + results := make(map[string]map[string]metricdata.HistogramDataPoint[int64]) + encoder := attribute.DefaultEncoder() // Using default encoder + + for _, sm := range rm.ScopeMetrics { + for _, m := range sm.Metrics { + // We are interested in Histogram[int64]. + hist, ok := m.Data.(metricdata.Histogram[int64]) + if !ok { + continue + } + + metricMap := make(metricHistogramMap) + for _, dp := range hist.DataPoints { + if dp.Count == 0 { + continue + } + + metricMap[dp.Attributes.Encoded(encoder)] = dp + } + + if len(metricMap) > 0 { + results[m.Name] = metricMap + } + } + } + + return results +} + +// gatherNonZeroCounterMetrics collects all non-zero counter metrics from the reader. +// It returns a map where the key is the metric name, and the value is another map. +// The inner map's key is a string representation of the attributes, +// and the value is the metric's value. +func gatherNonZeroCounterMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[string]int64 { + t.Helper() + var rm metricdata.ResourceMetrics + err := rd.Collect(ctx, &rm) + require.NoError(t, err) + + results := make(map[string]map[string]int64) + encoder := attribute.DefaultEncoder() + + for _, sm := range rm.ScopeMetrics { + for _, m := range sm.Metrics { + // We are interested in Sum[int64] which corresponds to int_counter. + sum, ok := m.Data.(metricdata.Sum[int64]) + if !ok { + continue + } + + metricMap := make(metricValueMap) + for _, dp := range sum.DataPoints { + if dp.Value == 0 { + continue + } + + metricMap[dp.Attributes.Encoded(encoder)] = dp.Value + } + + if len(metricMap) > 0 { + results[m.Name] = metricMap + } + } + } + + return results +} + +func TestFileCacheReadBytesCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "read_type_Parallel", + f: func(m *otelMetrics) { + m.FileCacheReadBytesCount(5, "Parallel") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Parallel")): 5, + }, + }, + { + name: "read_type_Random", + f: func(m *otelMetrics) { + m.FileCacheReadBytesCount(5, "Random") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Random")): 5, + }, + }, + { + name: "read_type_Sequential", + f: func(m *otelMetrics) { + m.FileCacheReadBytesCount(5, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Sequential")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.FileCacheReadBytesCount(5, "Parallel") + m.FileCacheReadBytesCount(2, "Random") + m.FileCacheReadBytesCount(3, "Parallel") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("read_type", "Parallel")): 8, + attribute.NewSet(attribute.String("read_type", "Random")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["file_cache/read_bytes_count"] + assert.True(t, ok, "file_cache/read_bytes_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestFileCacheReadCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "cache_hit_true_read_type_Parallel", + f: func(m *otelMetrics) { + m.FileCacheReadCount(5, true, "Parallel") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Parallel")): 5, + }, + }, + { + name: "cache_hit_true_read_type_Random", + f: func(m *otelMetrics) { + m.FileCacheReadCount(5, true, "Random") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Random")): 5, + }, + }, + { + name: "cache_hit_true_read_type_Sequential", + f: func(m *otelMetrics) { + m.FileCacheReadCount(5, true, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Sequential")): 5, + }, + }, + { + name: "cache_hit_false_read_type_Parallel", + f: func(m *otelMetrics) { + m.FileCacheReadCount(5, false, "Parallel") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Parallel")): 5, + }, + }, + { + name: "cache_hit_false_read_type_Random", + f: func(m *otelMetrics) { + m.FileCacheReadCount(5, false, "Random") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Random")): 5, + }, + }, + { + name: "cache_hit_false_read_type_Sequential", + f: func(m *otelMetrics) { + m.FileCacheReadCount(5, false, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Sequential")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.FileCacheReadCount(5, true, "Parallel") + m.FileCacheReadCount(2, true, "Random") + m.FileCacheReadCount(3, true, "Parallel") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Parallel")): 8, + attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Random")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["file_cache/read_count"] + assert.True(t, ok, "file_cache/read_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestFileCacheReadLatencies(t *testing.T) { + tests := []struct { + name string + latencies []time.Duration + cacheHit bool + }{ + { + name: "cache_hit_true", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + cacheHit: true, + }, + { + name: "cache_hit_false", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + cacheHit: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + var totalLatency time.Duration + + for _, latency := range tc.latencies { + m.FileCacheReadLatencies(ctx, latency, tc.cacheHit) + totalLatency += latency + } + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + metric, ok := metrics["file_cache/read_latencies"] + require.True(t, ok, "file_cache/read_latencies metric not found") + + attrs := []attribute.KeyValue{ + attribute.Bool("cache_hit", tc.cacheHit), + } + s := attribute.NewSet(attrs...) + expectedKey := s.Encoded(encoder) + dp, ok := metric[expectedKey] + require.True(t, ok, "DataPoint not found for key: %s", expectedKey) + assert.Equal(t, uint64(len(tc.latencies)), dp.Count) + assert.Equal(t, totalLatency.Microseconds(), dp.Sum) + }) + } +} + +func TestFsOpsCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "WriteFile")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "BatchForget") + m.FsOpsCount(2, "CreateFile") + m.FsOpsCount(3, "BatchForget") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("fs_op", "BatchForget")): 8, + attribute.NewSet(attribute.String("fs_op", "CreateFile")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["fs/ops_count"] + assert.True(t, ok, "fs/ops_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestFsOpsErrorCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "fs_error_category_DEVICE_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "BatchForget") + m.FsOpsErrorCount(2, "DEVICE_ERROR", "CreateFile") + m.FsOpsErrorCount(3, "DEVICE_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "BatchForget")): 8, + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateFile")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["fs/ops_error_count"] + assert.True(t, ok, "fs/ops_error_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestFsOpsLatency(t *testing.T) { + tests := []struct { + name string + latencies []time.Duration + fsOp string + }{ + { + name: "fs_op_BatchForget", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "BatchForget", + }, + { + name: "fs_op_CreateFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "CreateFile", + }, + { + name: "fs_op_CreateLink", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "CreateLink", + }, + { + name: "fs_op_CreateSymlink", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "CreateSymlink", + }, + { + name: "fs_op_Fallocate", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "Fallocate", + }, + { + name: "fs_op_FlushFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "FlushFile", + }, + { + name: "fs_op_ForgetInode", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ForgetInode", + }, + { + name: "fs_op_GetInodeAttributes", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "GetInodeAttributes", + }, + { + name: "fs_op_GetXattr", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "GetXattr", + }, + { + name: "fs_op_ListXattr", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ListXattr", + }, + { + name: "fs_op_LookUpInode", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "LookUpInode", + }, + { + name: "fs_op_MkDir", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "MkDir", + }, + { + name: "fs_op_MkNode", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "MkNode", + }, + { + name: "fs_op_OpenDir", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "OpenDir", + }, + { + name: "fs_op_OpenFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "OpenFile", + }, + { + name: "fs_op_ReadDir", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ReadDir", + }, + { + name: "fs_op_ReadFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ReadFile", + }, + { + name: "fs_op_ReadSymlink", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ReadSymlink", + }, + { + name: "fs_op_ReleaseDirHandle", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ReleaseDirHandle", + }, + { + name: "fs_op_ReleaseFileHandle", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ReleaseFileHandle", + }, + { + name: "fs_op_RemoveXattr", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "RemoveXattr", + }, + { + name: "fs_op_Rename", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "Rename", + }, + { + name: "fs_op_RmDir", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "RmDir", + }, + { + name: "fs_op_SetInodeAttributes", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "SetInodeAttributes", + }, + { + name: "fs_op_SetXattr", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "SetXattr", + }, + { + name: "fs_op_StatFS", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "StatFS", + }, + { + name: "fs_op_SyncFS", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "SyncFS", + }, + { + name: "fs_op_SyncFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "SyncFile", + }, + { + name: "fs_op_Unlink", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "Unlink", + }, + { + name: "fs_op_WriteFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "WriteFile", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + var totalLatency time.Duration + + for _, latency := range tc.latencies { + m.FsOpsLatency(ctx, latency, tc.fsOp) + totalLatency += latency + } + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + metric, ok := metrics["fs/ops_latency"] + require.True(t, ok, "fs/ops_latency metric not found") + + attrs := []attribute.KeyValue{ + attribute.String("fs_op", tc.fsOp), + } + s := attribute.NewSet(attrs...) + expectedKey := s.Encoded(encoder) + dp, ok := metric[expectedKey] + require.True(t, ok, "DataPoint not found for key: %s", expectedKey) + assert.Equal(t, uint64(len(tc.latencies)), dp.Count) + assert.Equal(t, totalLatency.Microseconds(), dp.Sum) + }) + } +} + +func TestGcsDownloadBytesCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "read_type_Parallel", + f: func(m *otelMetrics) { + m.GcsDownloadBytesCount(5, "Parallel") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Parallel")): 5, + }, + }, + { + name: "read_type_Random", + f: func(m *otelMetrics) { + m.GcsDownloadBytesCount(5, "Random") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Random")): 5, + }, + }, + { + name: "read_type_Sequential", + f: func(m *otelMetrics) { + m.GcsDownloadBytesCount(5, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Sequential")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GcsDownloadBytesCount(5, "Parallel") + m.GcsDownloadBytesCount(2, "Random") + m.GcsDownloadBytesCount(3, "Parallel") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("read_type", "Parallel")): 8, + attribute.NewSet(attribute.String("read_type", "Random")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/download_bytes_count"] + assert.True(t, ok, "gcs/download_bytes_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestGcsReadBytesCount(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + m.GcsReadBytesCount(1024) + m.GcsReadBytesCount(2048) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/read_bytes_count"] + require.True(t, ok, "gcs/read_bytes_count metric not found") + s := attribute.NewSet() + assert.Equal(t, map[string]int64{s.Encoded(encoder): 3072}, metric) +} + +func TestGcsReadCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "read_type_Parallel", + f: func(m *otelMetrics) { + m.GcsReadCount(5, "Parallel") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Parallel")): 5, + }, + }, + { + name: "read_type_Random", + f: func(m *otelMetrics) { + m.GcsReadCount(5, "Random") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Random")): 5, + }, + }, + { + name: "read_type_Sequential", + f: func(m *otelMetrics) { + m.GcsReadCount(5, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Sequential")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GcsReadCount(5, "Parallel") + m.GcsReadCount(2, "Random") + m.GcsReadCount(3, "Parallel") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("read_type", "Parallel")): 8, + attribute.NewSet(attribute.String("read_type", "Random")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/read_count"] + assert.True(t, ok, "gcs/read_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestGcsReaderCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "io_method_closed", + f: func(m *otelMetrics) { + m.GcsReaderCount(5, "closed") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("io_method", "closed")): 5, + }, + }, + { + name: "io_method_opened", + f: func(m *otelMetrics) { + m.GcsReaderCount(5, "opened") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("io_method", "opened")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GcsReaderCount(5, "closed") + m.GcsReaderCount(2, "opened") + m.GcsReaderCount(3, "closed") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("io_method", "closed")): 8, + attribute.NewSet(attribute.String("io_method", "opened")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/reader_count"] + assert.True(t, ok, "gcs/reader_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestGcsRequestCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "gcs_method_ComposeObjects", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "ComposeObjects") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "ComposeObjects")): 5, + }, + }, + { + name: "gcs_method_CreateFolder", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "CreateFolder") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "CreateFolder")): 5, + }, + }, + { + name: "gcs_method_CreateObjectChunkWriter", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "CreateObjectChunkWriter") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "CreateObjectChunkWriter")): 5, + }, + }, + { + name: "gcs_method_DeleteFolder", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "DeleteFolder") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "DeleteFolder")): 5, + }, + }, + { + name: "gcs_method_DeleteObject", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "DeleteObject") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "DeleteObject")): 5, + }, + }, + { + name: "gcs_method_FinalizeUpload", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "FinalizeUpload") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload")): 5, + }, + }, + { + name: "gcs_method_GetFolder", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "GetFolder") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "GetFolder")): 5, + }, + }, + { + name: "gcs_method_ListObjects", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "ListObjects") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "ListObjects")): 5, + }, + }, + { + name: "gcs_method_MoveObject", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "MoveObject") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "MoveObject")): 5, + }, + }, + { + name: "gcs_method_MultiRangeDownloader::Add", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "MultiRangeDownloader::Add") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "MultiRangeDownloader::Add")): 5, + }, + }, + { + name: "gcs_method_NewMultiRangeDownloader", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "NewMultiRangeDownloader") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "NewMultiRangeDownloader")): 5, + }, + }, + { + name: "gcs_method_NewReader", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "NewReader") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "NewReader")): 5, + }, + }, + { + name: "gcs_method_RenameFolder", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "RenameFolder") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "RenameFolder")): 5, + }, + }, + { + name: "gcs_method_StatObject", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "StatObject") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "StatObject")): 5, + }, + }, + { + name: "gcs_method_UpdateObject", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "UpdateObject") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "UpdateObject")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "ComposeObjects") + m.GcsRequestCount(2, "CreateFolder") + m.GcsRequestCount(3, "ComposeObjects") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("gcs_method", "ComposeObjects")): 8, + attribute.NewSet(attribute.String("gcs_method", "CreateFolder")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/request_count"] + assert.True(t, ok, "gcs/request_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestGcsRequestLatencies(t *testing.T) { + tests := []struct { + name string + latencies []time.Duration + gcsMethod string + }{ + { + name: "gcs_method_ComposeObjects", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "ComposeObjects", + }, + { + name: "gcs_method_CreateFolder", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "CreateFolder", + }, + { + name: "gcs_method_CreateObjectChunkWriter", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "CreateObjectChunkWriter", + }, + { + name: "gcs_method_DeleteFolder", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "DeleteFolder", + }, + { + name: "gcs_method_DeleteObject", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "DeleteObject", + }, + { + name: "gcs_method_FinalizeUpload", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "FinalizeUpload", + }, + { + name: "gcs_method_GetFolder", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "GetFolder", + }, + { + name: "gcs_method_ListObjects", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "ListObjects", + }, + { + name: "gcs_method_MoveObject", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "MoveObject", + }, + { + name: "gcs_method_MultiRangeDownloader::Add", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "MultiRangeDownloader::Add", + }, + { + name: "gcs_method_NewMultiRangeDownloader", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "NewMultiRangeDownloader", + }, + { + name: "gcs_method_NewReader", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "NewReader", + }, + { + name: "gcs_method_RenameFolder", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "RenameFolder", + }, + { + name: "gcs_method_StatObject", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "StatObject", + }, + { + name: "gcs_method_UpdateObject", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "UpdateObject", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + var totalLatency time.Duration + + for _, latency := range tc.latencies { + m.GcsRequestLatencies(ctx, latency, tc.gcsMethod) + totalLatency += latency + } + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + metric, ok := metrics["gcs/request_latencies"] + require.True(t, ok, "gcs/request_latencies metric not found") + + attrs := []attribute.KeyValue{ + attribute.String("gcs_method", tc.gcsMethod), + } + s := attribute.NewSet(attrs...) + expectedKey := s.Encoded(encoder) + dp, ok := metric[expectedKey] + require.True(t, ok, "DataPoint not found for key: %s", expectedKey) + assert.Equal(t, uint64(len(tc.latencies)), dp.Count) + assert.Equal(t, totalLatency.Milliseconds(), dp.Sum) + }) + } +} + +func TestGcsRetryCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "retry_error_category_OTHER_ERRORS", + f: func(m *otelMetrics) { + m.GcsRetryCount(5, "OTHER_ERRORS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS")): 5, + }, + }, + { + name: "retry_error_category_STALLED_READ_REQUEST", + f: func(m *otelMetrics) { + m.GcsRetryCount(5, "STALLED_READ_REQUEST") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GcsRetryCount(5, "OTHER_ERRORS") + m.GcsRetryCount(2, "STALLED_READ_REQUEST") + m.GcsRetryCount(3, "OTHER_ERRORS") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS")): 8, + attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/retry_count"] + assert.True(t, ok, "gcs/retry_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} diff --git a/tools/metrics-gen/main.go b/tools/metrics-gen/main.go index 6b959016cb..c8c9a5b598 100644 --- a/tools/metrics-gen/main.go +++ b/tools/metrics-gen/main.go @@ -62,16 +62,22 @@ type TemplateData struct { // Helper functions for the template. var funcMap = template.FuncMap{ - "toPascal": toPascal, - "toCamel": toCamel, - "getVarName": getVarName, - "getAtomicName": getAtomicName, - "getGoType": getGoType, - "getUnitMethod": getUnitMethod, - "joinInts": joinInts, - "isCounter": func(m Metric) bool { return m.Type == "int_counter" }, - "isHistogram": func(m Metric) bool { return m.Type == "int_histogram" }, - "buildSwitches": buildSwitches, + "toPascal": toPascal, + "toCamel": toCamel, + "getVarName": getVarName, + "getAtomicName": getAtomicName, + "getGoType": getGoType, + "getUnitMethod": getUnitMethod, + "joinInts": joinInts, + "isCounter": func(m Metric) bool { return m.Type == "int_counter" }, + "isHistogram": func(m Metric) bool { return m.Type == "int_histogram" }, + "buildSwitches": buildSwitches, + "getTestName": getTestName, + "getTestFuncArgs": getTestFuncArgs, + "getExpectedAttrs": getExpectedAttrs, + "getLatencyUnit": getLatencyUnit, + "getLatencyMethod": getLatencyMethod, + "getTestFuncArgsForHistogram": getTestFuncArgsForHistogram, } func toPascal(s string) string { @@ -150,6 +156,70 @@ func joinInts(nums []int64) string { return strings.Join(s, ", ") } +// getTestName generates a test name from an attribute combination. +func getTestName(combo AttrCombination) string { + if len(combo) == 0 { + return "no_attributes" + } + var parts = make([]string, 0, len(combo)*2) + for _, pair := range combo { + parts = append(parts, pair.Name) + parts = append(parts, pair.Value) + } + return strings.Join(parts, "_") +} + +// getTestFuncArgs generates arguments for the metric function call in tests. +func getTestFuncArgs(combo AttrCombination) string { + var parts []string + for _, pair := range combo { + if pair.Type == "string" { + parts = append(parts, `"`+pair.Value+`"`) + } else { + parts = append(parts, pair.Value) + } + } + return strings.Join(parts, ", ") +} + +// getExpectedAttrs generates attribute set for test expectations. +func getExpectedAttrs(combo AttrCombination) string { + var parts []string + for _, pair := range combo { + if pair.Type == "string" { + parts = append(parts, fmt.Sprintf(`attribute.String("%s", "%s")`, pair.Name, pair.Value)) + } else { // bool + parts = append(parts, fmt.Sprintf(`attribute.Bool("%s", %s)`, pair.Name, pair.Value)) + } + } + return strings.Join(parts, ", ") +} + +func getLatencyUnit(unit string) string { + switch unit { + case "us": + return "Microsecond" + case "ms": + return "Millisecond" + case "s": + return "Second" + default: + return "" + } +} + +func getLatencyMethod(unit string) string { + return toPascal(getLatencyUnit(unit)) + "s" +} + +func getTestFuncArgsForHistogram(prefix string, attrs []Attribute) string { + var parts []string + for _, attr := range attrs { + parts = append(parts, prefix+"."+toCamel(attr.Name)) + } + return strings.Join(parts, ", ") +} + // generateCombinations creates all possible combinations of attribute values. func generateCombinations(attributes []Attribute) []AttrCombination { if len(attributes) == 0 { @@ -378,6 +448,7 @@ func main() { createFile(&data, fmt.Sprintf("%s/metric_handle.go", *outputDir), "metric_handle.tpl") createFile(&data, fmt.Sprintf("%s/noop_metrics.go", *outputDir), "noop_metrics.tpl") createFile(&data, fmt.Sprintf("%s/otel_metrics.go", *outputDir), "otel_metrics.tpl") + createFile(&data, fmt.Sprintf("%s/otel_metrics_test.go", *outputDir), "otel_metrics_test.tpl") } diff --git a/tools/metrics-gen/otel_metrics.tpl b/tools/metrics-gen/otel_metrics.tpl index 368047d75d..f24eb39106 100644 --- a/tools/metrics-gen/otel_metrics.tpl +++ b/tools/metrics-gen/otel_metrics.tpl @@ -167,5 +167,4 @@ func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, obs if val := counter.Load(); val > 0 { obsrv.Observe(val, obsrvOptions...) } - } diff --git a/tools/metrics-gen/otel_metrics_test.tpl b/tools/metrics-gen/otel_metrics_test.tpl new file mode 100644 index 0000000000..e2ccf5d6ef --- /dev/null +++ b/tools/metrics-gen/otel_metrics_test.tpl @@ -0,0 +1,286 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** + +package optimizedmetrics + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" +) + +// metricValueMap maps attribute sets to metric values. +type metricValueMap map[string]int64 + +// metricHistogramMap maps attribute sets to histogram data points. +type metricHistogramMap map[string]metricdata.HistogramDataPoint[int64] + +func waitForMetricsProcessing() { + time.Sleep(time.Millisecond) +} + +func setupOTel(ctx context.Context, t *testing.T) (*otelMetrics, *metric.ManualReader) { + t.Helper() + reader := metric.NewManualReader() + provider := metric.NewMeterProvider(metric.WithReader(reader)) + otel.SetMeterProvider(provider) + + m, err := NewOTelMetrics(ctx, 10, 100) + require.NoError(t, err) + return m, reader +} + +// gatherHistogramMetrics collects all histogram metrics from the reader. +// It returns a map where the key is the metric name, and the value is another map. +// The inner map's key is a string representation of the attributes, +// and the value is the metricdata.HistogramDataPoint. +func gatherHistogramMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[string]metricdata.HistogramDataPoint[int64] { + t.Helper() + var rm metricdata.ResourceMetrics + err := rd.Collect(ctx, &rm) + require.NoError(t, err) + + results := make(map[string]map[string]metricdata.HistogramDataPoint[int64]) + encoder := attribute.DefaultEncoder() // Using default encoder + + for _, sm := range rm.ScopeMetrics { + for _, m := range sm.Metrics { + // We are interested in Histogram[int64]. + hist, ok := m.Data.(metricdata.Histogram[int64]) + if !ok { + continue + } + + metricMap := make(metricHistogramMap) + for _, dp := range hist.DataPoints { + if dp.Count == 0 { + continue + } + + metricMap[dp.Attributes.Encoded(encoder)] = dp + } + + if len(metricMap) > 0 { + results[m.Name] = metricMap + } + } + } + + return results +} + +// gatherNonZeroCounterMetrics collects all non-zero counter metrics from the reader. +// It returns a map where the key is the metric name, and the value is another map. +// The inner map's key is a string representation of the attributes, +// and the value is the metric's value. +func gatherNonZeroCounterMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[string]int64 { + t.Helper() + var rm metricdata.ResourceMetrics + err := rd.Collect(ctx, &rm) + require.NoError(t, err) + + results := make(map[string]map[string]int64) + encoder := attribute.DefaultEncoder() + + for _, sm := range rm.ScopeMetrics { + for _, m := range sm.Metrics { + // We are interested in Sum[int64] which corresponds to int_counter. + sum, ok := m.Data.(metricdata.Sum[int64]) + if !ok { + continue + } + + metricMap := make(metricValueMap) + for _, dp := range sum.DataPoints { + if dp.Value == 0 { + continue + } + + metricMap[dp.Attributes.Encoded(encoder)] = dp.Value + } + + if len(metricMap) > 0 { + results[m.Name] = metricMap + } + } + } + + return results +} + +{{range .Metrics}} +{{if isCounter .}} +func Test{{toPascal .Name}}(t *testing.T) { + {{- if .Attributes}} + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + {{- $metric := . -}} + {{- range $combination := (index $.AttrCombinations $metric.Name)}} + { + name: "{{getTestName $combination}}", + f: func(m *otelMetrics) { + m.{{toPascal $metric.Name}}(5, {{getTestFuncArgs $combination}}) + }, + expected: map[attribute.Set]int64{ + attribute.NewSet({{getExpectedAttrs $combination}}): 5, + }, + }, + {{- end}} + {{- $combinations := (index $.AttrCombinations $metric.Name) -}} + {{- if and .Attributes (gt (len $combinations) 1) -}} + { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + {{- $firstComb := (index $combinations 0) -}} + {{- $secondComb := (index $combinations 1) -}} + m.{{toPascal $metric.Name}}(5, {{getTestFuncArgs $firstComb}}) + m.{{toPascal $metric.Name}}(2, {{getTestFuncArgs $secondComb}}) + m.{{toPascal $metric.Name}}(3, {{getTestFuncArgs $firstComb}}) + }, + expected: map[attribute.Set]int64{ + {{- $firstComb := (index $combinations 0) -}} + {{- $secondComb := (index $combinations 1) -}} + attribute.NewSet({{getExpectedAttrs $firstComb}}): 8, + attribute.NewSet({{getExpectedAttrs $secondComb}}): 2, + }, + }, + {{- end}} + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["{{.Name}}"] + assert.True(t, ok, "{{.Name}} metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } + {{- else}} + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + m.{{toPascal .Name}}(1024) + m.{{toPascal .Name}}(2048) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["{{.Name}}"] + require.True(t, ok, "{{.Name}} metric not found") + s := attribute.NewSet() + assert.Equal(t, map[string]int64{s.Encoded(encoder): 3072}, metric) + {{- end}} +} +{{else if isHistogram .}} +func Test{{toPascal .Name}}(t *testing.T) { + {{- if .Attributes}} + tests := []struct { + name string + latencies []time.Duration + {{- range .Attributes}} + {{toCamel .Name}} {{getGoType .Type}} + {{- end}} + }{ + {{- $metric := . -}} + {{- range $combination := (index $.AttrCombinations $metric.Name)}} + { + name: "{{getTestName $combination}}", + latencies: []time.Duration{100 * time.{{getLatencyUnit $metric.Unit}}, 200 * time.{{getLatencyUnit $metric.Unit}}}, + {{- range $pair := $combination}} + {{toCamel $pair.Name}}: {{if eq $pair.Type "string"}}"{{$pair.Value}}"{{else}}{{$pair.Value}}{{end}}, + {{- end}} + }, + {{- end}} + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + var totalLatency time.Duration + + for _, latency := range tc.latencies { + m.{{toPascal .Name}}(ctx, latency, {{getTestFuncArgsForHistogram "tc" .Attributes}}) + totalLatency += latency + } + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + metric, ok := metrics["{{.Name}}"] + require.True(t, ok, "{{.Name}} metric not found") + + attrs := []attribute.KeyValue{ + {{- range .Attributes}} + attribute.{{if eq .Type "string"}}String{{else}}Bool{{end}}("{{.Name}}", tc.{{toCamel .Name}}), + {{- end}} + } + s := attribute.NewSet(attrs...) + expectedKey := s.Encoded(encoder) + dp, ok := metric[expectedKey] + require.True(t, ok, "DataPoint not found for key: %s", expectedKey) + assert.Equal(t, uint64(len(tc.latencies)), dp.Count) + assert.Equal(t, totalLatency.{{getLatencyMethod .Unit}}(), dp.Sum) + }) + } + {{- else}} + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + var totalLatency time.Duration + latencies := []time.Duration{100 * time.{{getLatencyUnit .Unit}}, 200 * time.{{getLatencyUnit .Unit}}} + + for _, latency := range latencies { + m.{{toPascal .Name}}(ctx, latency) + totalLatency += latency + } + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + metric, ok := metrics["{{.Name}}"] + require.True(t, ok, "{{.Name}} metric not found") + + s := attribute.NewSet() + expectedKey := s.Encoded(encoder) + dp, ok := metric[expectedKey] + require.True(t, ok, "DataPoint not found for key: %s", expectedKey) + assert.Equal(t, uint64(len(latencies)), dp.Count) + assert.Equal(t, totalLatency.{{getLatencyMethod .Unit}}(), dp.Sum) + {{- end}} +} +{{end}} +{{end}} From 86db5d9bda7e123aa1ef7c2f6ebc2ee875f84df9 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Fri, 1 Aug 2025 10:33:32 +0530 Subject: [PATCH 0629/1298] chores: Increase CI linux timeout to 25 minutes (#3489) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c6a5c8c53..409384ab88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -48,7 +48,7 @@ jobs: matrix: go: [ 1.24.x ] runs-on: ubuntu-latest - timeout-minutes: 20 + timeout-minutes: 25 steps: - uses: actions/checkout@v2 From ee26cb399ed0859bfcb67b2373a3beac5e18ef09 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 1 Aug 2025 10:41:52 +0530 Subject: [PATCH 0630/1298] ci(read unfinalized objects): Extend rapid_appends e2e tests - sequential and random read tests (#3543) * [temp] add debug logs in stat-cache * Revert "[temp] add debug logs in stat-cache" This reverts commit b746e298af5c3656680ccb2dc77b1c22cf2221a6. * [temp] add some debug logs in rapid_appends test * Revert "[temp] add some debug logs in rapid_appends test" This reverts commit dc519abb21b8cb6aadd130ed618bb1a6c5c9e9c9. * [temp] run only zb package rapid_appends * [temp] changes for running e2e locally * [temp] add some debug logs in rapid_appends test * Revert "[temp] add some debug logs in rapid_appends test" This reverts commit dc519abb21b8cb6aadd130ed618bb1a6c5c9e9c9. * Revert "[temp] changes for running e2e locally" This reverts commit 465a46fdccada27944a5975f2ce94ba88d9ea39a. * Revert "[temp] run only zb package rapid_appends" This reverts commit 0da4d565a4b1462e8ba8b406c695c84b0d83a39d. * fix rebase artifact * add verification for sequential/random reads * disable random-read tests for single-mount test cases * Revert "disable random-read tests for single-mount test cases" This reverts commit 59d759658960e69761a0d8bd5aa3bb195df64ca9. * address self-comments on TestAppendsAndReads * shorten readAbdVerify function code * fix build error * address more self-comments * minor readability fix * address a self-comment --- .../rapid_appends/reads_after_appends_test.go | 73 +++++++++++++++---- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/tools/integration_tests/rapid_appends/reads_after_appends_test.go b/tools/integration_tests/rapid_appends/reads_after_appends_test.go index e8b7cfb104..1102037480 100644 --- a/tools/integration_tests/rapid_appends/reads_after_appends_test.go +++ b/tools/integration_tests/rapid_appends/reads_after_appends_test.go @@ -17,17 +17,64 @@ package rapid_appends import ( "fmt" "log" + "math/rand/v2" "os" "path" "syscall" + "testing" "time" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +// declare a function type for read and verify +type readAndVerifyFunc func(t *testing.T, filePath string, expectedContent []byte) + +func readSequentiallyAndVerify(t *testing.T, filePath string, expectedContent []byte) { + readContent, err := operations.ReadFileSequentially(filePath, 1024*1024) + + // For sequential reads, we expect the content to be exactly as expected. + require.NoErrorf(t, err, "failed to read file %q sequentially: %v", filePath, err) + require.Equal(t, expectedContent, readContent) +} + +func readRandomlyAndVerify(t *testing.T, filePath string, expectedContent []byte) { + file, err := operations.OpenFileAsReadonly(filePath) + require.NoErrorf(t, err, "failed to open file %q: %v", filePath, err) + defer operations.CloseFileShouldNotThrowError(t, file) + if len(expectedContent) == 0 { + t.SkipNow() + } + fileInfo, err := file.Stat() + require.NoError(t, err) + fileSize := fileInfo.Size() + require.GreaterOrEqualf(t, fileSize, int64(len(expectedContent)), "file %q is too small to read %d bytes", filePath, len(expectedContent)) + + // Content to be read from [0, maxOffset) . + maxOffset := len(expectedContent) + // Limit number of reads if the content to read is too small. + numReads := min(maxOffset, 10) + for i := range numReads { + // Ensure offset <= maxOffset-1 . + offset := rand.IntN(maxOffset) + // Ensure (offset+readSize) <= maxOffset and readSize >= 1. + readSize := rand.IntN(maxOffset-offset) + 1 + buffer := make([]byte, readSize) + + n, err := file.ReadAt(buffer, int64(offset)) + + require.NoErrorf(t, err, "Random-read failed at iter#%d to read file %q at [%d, %d): %v", i, filePath, offset, offset+readSize, err) + require.Equalf(t, readSize, n, "failed to read %v bytes from %q at offset %v. Read bytes = %v.", readSize, filePath, offset, n) + require.Equalf(t, expectedContent[offset:offset+n], buffer[:n], "content mismatch in random read at iter#%d at offset [%d, %d): expected %q, got %q", i, offset, offset+readSize, expectedContent[offset:offset+n], buffer[:n]) + } +} + //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// @@ -40,10 +87,16 @@ func (t *CommonAppendsSuite) TestAppendsAndReads() { } testCases := []struct { - name string + name string + readAndVerify readAndVerifyFunc }{ { - name: "SequentialRead", + name: "SequentialRead", + readAndVerify: readSequentiallyAndVerify, + }, + { + name: "RandomRead", + readAndVerify: readRandomlyAndVerify, }, } @@ -91,27 +144,19 @@ func (t *CommonAppendsSuite) TestAppendsAndReads() { t.appendToFile(appendFileHandle, setup.GenerateRandomString(appendSize)) sizeAfterAppend := len(t.fileContent) - gotContent, err := operations.ReadFile(readPath) - - require.NoError(t.T(), err) - readContent := string(gotContent) // If metadata cache is enabled, gcsfuse reads up to the cached file size. // For same-mount appends/reads, file size is always current. // The initial read (i=0) bypasses cache, seeing the latest file size. if !scenario.enableMetadataCache || !t.isSyncNeededAfterAppend || (i == 0) { - assert.Equalf(t.T(), t.fileContent, readContent, "failed to match full content in non-metadata-cache/single-mount after %v appends", i+1) + tc.readAndVerify(t.T(), readPath, []byte(t.fileContent[:sizeAfterAppend])) } else { // Read only up to the cached file size (before append). - assert.Equalf(t.T(), t.fileContent[:sizeBeforeAppend], readContent, "failed to match partial content in metadata-cache dual-mount after %v appends", i+1) + tc.readAndVerify(t.T(), readPath, []byte(t.fileContent[:sizeBeforeAppend])) // Wait for metadata cache to expire to fetch the latest size for the next read. time.Sleep(time.Duration(metadataCacheTTLSecs) * time.Second) - gotContent, err = operations.ReadFile(readPath) - // Expect read up to the latest file size which is the size after the append. - require.NoError(t.T(), err) - readContent = string(gotContent) - assert.Equalf(t.T(), t.fileContent[:sizeAfterAppend], readContent, "failed to match full content in metadata-cache dual-mount after %v appends", i+1) + tc.readAndVerify(t.T(), readPath, []byte(t.fileContent[:sizeAfterAppend])) } } }) From 477e0e39e5add44cb307a1c9ca3ea82f4625911e Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Fri, 1 Aug 2025 11:08:09 +0530 Subject: [PATCH 0631/1298] chore(metrics): adding flags for metrics workers and buffer size (#3617) * adding flags for metrics worker and buffer size * test fix * split error condition --- cfg/config.go | 24 +++++++++++ cfg/params.yaml | 14 ++++++ cfg/validate.go | 6 +++ cfg/validate_test.go | 78 ++++++++++++++++++++++++++++++++++ cmd/config_validation_test.go | 4 ++ cmd/root_test.go | 28 +++++++++++- cmd/testdata/valid_config.yaml | 2 + 7 files changed, 154 insertions(+), 2 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index a5b8217e90..6cb3031d2c 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -226,6 +226,8 @@ type MetadataCacheConfig struct { } type MetricsConfig struct { + BufferSize int64 `yaml:"buffer-size"` + CloudMetricsExportIntervalSecs int64 `yaml:"cloud-metrics-export-interval-secs"` PrometheusPort int64 `yaml:"prometheus-port"` @@ -233,6 +235,8 @@ type MetricsConfig struct { StackdriverExportInterval time.Duration `yaml:"stackdriver-export-interval"` UseNewNames bool `yaml:"use-new-names"` + + Workers int64 `yaml:"workers"` } type MonitoringConfig struct { @@ -563,12 +567,24 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.IntP("metadata-cache-ttl-secs", "", 60, "The ttl value in seconds to be used for expiring items in metadata-cache. It can be set to -1 for no-ttl, 0 for no cache and > 0 for ttl-controlled metadata-cache. Any value set below -1 will throw an error.") + flagSet.IntP("metrics-buffer-size", "", 256, "The maximum number of histogram metric updates in the queue.") + + if err := flagSet.MarkHidden("metrics-buffer-size"); err != nil { + return err + } + flagSet.BoolP("metrics-use-new-names", "", false, "Use the new metric names.") if err := flagSet.MarkHidden("metrics-use-new-names"); err != nil { return err } + flagSet.IntP("metrics-workers", "", 3, "The number of workers that update histogram metrics concurrently.") + + if err := flagSet.MarkHidden("metrics-workers"); err != nil { + return err + } + flagSet.StringSliceP("o", "", []string{}, "Additional system-specific mount options. Multiple options can be passed as comma separated. For readonly, use --o ro") flagSet.StringP("only-dir", "", "", "Mount only a specific directory within the bucket. See docs/mounting for more information") @@ -1012,10 +1028,18 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("metrics.buffer-size", flagSet.Lookup("metrics-buffer-size")); err != nil { + return err + } + if err := v.BindPFlag("metrics.use-new-names", flagSet.Lookup("metrics-use-new-names")); err != nil { return err } + if err := v.BindPFlag("metrics.workers", flagSet.Lookup("metrics-workers")); err != nil { + return err + } + if err := v.BindPFlag("file-system.fuse-options", flagSet.Lookup("o")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 54bf35ba7c..1dbad61a15 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -636,6 +636,13 @@ usage: "Max size of type-cache maps which are maintained at a per-directory level." default: "4" +- config-path: "metrics.buffer-size" + flag-name: "metrics-buffer-size" + type: "int" + usage: "The maximum number of histogram metric updates in the queue." + default: "256" + hide-flag: true + - config-path: "metrics.cloud-metrics-export-interval-secs" flag-name: "cloud-metrics-export-interval-secs" type: "int" @@ -665,6 +672,13 @@ default: false hide-flag: true +- config-path: "metrics.workers" + flag-name: "metrics-workers" + type: "int" + usage: "The number of workers that update histogram metrics concurrently." + default: "3" + hide-flag: true + - config-path: "monitoring.experimental-tracing-mode" flag-name: "experimental-tracing-mode" type: "string" diff --git a/cfg/validate.go b/cfg/validate.go index 86999cc795..b14eeb065b 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -205,6 +205,12 @@ func isValidMetricsConfig(m *MetricsConfig) error { if m.PrometheusPort > maxPortNumber { return fmt.Errorf("prometheus-port must not be higher than the maximum allowed port number: %d but received: %d instead", maxPortNumber, m.PrometheusPort) } + if m.Workers < 1 { + return fmt.Errorf("number of metrics workers cannot be less than 1") + } + if m.BufferSize < 1 { + return fmt.Errorf("metrics buffer size cannot be less than 1") + } return nil } diff --git a/cfg/validate_test.go b/cfg/validate_test.go index 340dea6ba7..f44c9963b5 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -69,6 +69,10 @@ func TestValidateConfigSuccessful(t *testing.T) { MetadataCache: MetadataCacheConfig{ ExperimentalMetadataPrefetchOnMount: "disabled", }, + Metrics: MetricsConfig{ + Workers: 3, + BufferSize: 256, + }, }, }, { @@ -83,6 +87,10 @@ func TestValidateConfigSuccessful(t *testing.T) { MetadataCache: MetadataCacheConfig{ ExperimentalMetadataPrefetchOnMount: "disabled", }, + Metrics: MetricsConfig{ + Workers: 3, + BufferSize: 256, + }, }, }, { @@ -96,6 +104,10 @@ func TestValidateConfigSuccessful(t *testing.T) { GcsConnection: GcsConnectionConfig{ SequentialReadSizeMb: 200, }, + Metrics: MetricsConfig{ + Workers: 3, + BufferSize: 256, + }, }, }, { @@ -109,6 +121,10 @@ func TestValidateConfigSuccessful(t *testing.T) { GcsConnection: GcsConnectionConfig{ SequentialReadSizeMb: 200, }, + Metrics: MetricsConfig{ + Workers: 3, + BufferSize: 256, + }, }, }, { @@ -122,6 +138,10 @@ func TestValidateConfigSuccessful(t *testing.T) { GcsConnection: GcsConnectionConfig{ SequentialReadSizeMb: 200, }, + Metrics: MetricsConfig{ + Workers: 3, + BufferSize: 256, + }, }, }, { @@ -135,6 +155,10 @@ func TestValidateConfigSuccessful(t *testing.T) { MetadataCache: MetadataCacheConfig{ ExperimentalMetadataPrefetchOnMount: "sync", }, + Metrics: MetricsConfig{ + Workers: 3, + BufferSize: 256, + }, }, }, { @@ -148,6 +172,10 @@ func TestValidateConfigSuccessful(t *testing.T) { MetadataCache: MetadataCacheConfig{ ExperimentalMetadataPrefetchOnMount: "sync", }, + Metrics: MetricsConfig{ + Workers: 3, + BufferSize: 256, + }, }, }, { @@ -161,6 +189,10 @@ func TestValidateConfigSuccessful(t *testing.T) { MetadataCache: MetadataCacheConfig{ ExperimentalMetadataPrefetchOnMount: "sync", }, + Metrics: MetricsConfig{ + Workers: 3, + BufferSize: 256, + }, FileSystem: FileSystemConfig{KernelListCacheTtlSecs: 30}, }, }, @@ -184,6 +216,10 @@ func TestValidateConfigSuccessful(t *testing.T) { MetadataCache: MetadataCacheConfig{ ExperimentalMetadataPrefetchOnMount: "disabled", }, + Metrics: MetricsConfig{ + Workers: 3, + BufferSize: 256, + }, }, }, { @@ -199,6 +235,10 @@ func TestValidateConfigSuccessful(t *testing.T) { MetadataCache: MetadataCacheConfig{ ExperimentalMetadataPrefetchOnMount: "disabled", }, + Metrics: MetricsConfig{ + Workers: 3, + BufferSize: 256, + }, }, }, { @@ -212,6 +252,10 @@ func TestValidateConfigSuccessful(t *testing.T) { MetadataCache: MetadataCacheConfig{ ExperimentalMetadataPrefetchOnMount: "sync", }, + Metrics: MetricsConfig{ + Workers: 3, + BufferSize: 256, + }, FileSystem: FileSystemConfig{KernelListCacheTtlSecs: 30}, GcsRetries: GcsRetriesConfig{ChunkTransferTimeoutSecs: 15}, }, @@ -714,6 +758,8 @@ func TestValidateMetrics(t *testing.T) { name: "neg_cloud_metrics_export_interval", metricsConfig: MetricsConfig{ CloudMetricsExportIntervalSecs: -1, + Workers: 10, + BufferSize: 100, }, wantErr: false, }, @@ -721,6 +767,8 @@ func TestValidateMetrics(t *testing.T) { name: "neg_stackdriver_export_interval", metricsConfig: MetricsConfig{ StackdriverExportInterval: -1 * time.Second, + Workers: 10, + BufferSize: 100, }, wantErr: false, }, @@ -728,6 +776,8 @@ func TestValidateMetrics(t *testing.T) { name: "neg_cloud_metrics_export_interval", metricsConfig: MetricsConfig{ CloudMetricsExportIntervalSecs: 10, + Workers: 10, + BufferSize: 100, }, wantErr: false, }, @@ -742,6 +792,8 @@ func TestValidateMetrics(t *testing.T) { name: "valid_prom_port", metricsConfig: MetricsConfig{ PrometheusPort: 5550, + Workers: 10, + BufferSize: 100, }, wantErr: false, }, @@ -749,6 +801,8 @@ func TestValidateMetrics(t *testing.T) { name: "prom_disabled_0", metricsConfig: MetricsConfig{ PrometheusPort: 0, + Workers: 10, + BufferSize: 100, }, wantErr: false, }, @@ -756,6 +810,30 @@ func TestValidateMetrics(t *testing.T) { name: "prom_disabled_less_than_0", metricsConfig: MetricsConfig{ PrometheusPort: -21, + Workers: 10, + BufferSize: 100, + }, + wantErr: false, + }, + { + name: "metrics_workers_less_than_1", + metricsConfig: MetricsConfig{ + Workers: 0, + }, + wantErr: true, + }, + { + name: "metrics_buffer_size_less_than_1", + metricsConfig: MetricsConfig{ + BufferSize: 0, + }, + wantErr: true, + }, + { + name: "valid_workers_and_buffer_size", + metricsConfig: MetricsConfig{ + Workers: 10, + BufferSize: 100, }, wantErr: false, }, diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 5c7303f6d9..922ecd89e9 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -852,6 +852,8 @@ func TestValidateConfigFile_MetricsConfigSuccessful(t *testing.T) { StackdriverExportInterval: 0, CloudMetricsExportIntervalSecs: 0, PrometheusPort: 0, + Workers: 3, + BufferSize: 256, }, }, { @@ -859,6 +861,8 @@ func TestValidateConfigFile_MetricsConfigSuccessful(t *testing.T) { configFile: "testdata/valid_config.yaml", expectedConfig: &cfg.MetricsConfig{ CloudMetricsExportIntervalSecs: 10, + Workers: 10, + BufferSize: 128, }, }, } diff --git a/cmd/root_test.go b/cmd/root_test.go index 4dc5a93a76..dd6ea52c40 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1221,6 +1221,8 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { args: []string{"gcsfuse", "--cloud-metrics-export-interval-secs=10", "abc", "pqr"}, expected: &cfg.MetricsConfig{ CloudMetricsExportIntervalSecs: 10, + Workers: 3, + BufferSize: 256, }, }, { @@ -1229,6 +1231,8 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { expected: &cfg.MetricsConfig{ CloudMetricsExportIntervalSecs: 10 * 3600, StackdriverExportInterval: time.Duration(10) * time.Hour, + Workers: 3, + BufferSize: 256, }, }, { @@ -1236,6 +1240,24 @@ func TestArgsParsing_MetricsFlags(t *testing.T) { args: []string{"gcsfuse", "--metrics-use-new-names=true", "abc", "pqr"}, expected: &cfg.MetricsConfig{ UseNewNames: true, + Workers: 3, + BufferSize: 256, + }, + }, + { + name: "metrics_workers_non_default", + args: []string{"gcsfuse", "--metrics-workers=10", "abc", "pqr"}, + expected: &cfg.MetricsConfig{ + Workers: 10, + BufferSize: 256, + }, + }, + { + name: "metrics_buffer_size_non_default", + args: []string{"gcsfuse", "--metrics-buffer-size=1024", "abc", "pqr"}, + expected: &cfg.MetricsConfig{ + Workers: 3, + BufferSize: 1024, }, }, } @@ -1267,12 +1289,12 @@ func TestArgsParsing_MetricsViewConfig(t *testing.T) { { name: "default", cfgFile: "empty.yml", - expected: &cfg.MetricsConfig{}, + expected: &cfg.MetricsConfig{Workers: 3, BufferSize: 256}, }, { name: "cloud-metrics-export-interval-secs-positive", cfgFile: "metrics_export_interval_positive.yml", - expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 100}, + expected: &cfg.MetricsConfig{CloudMetricsExportIntervalSecs: 100, Workers: 3, BufferSize: 256}, }, { name: "stackdriver-export-interval-positive", @@ -1280,6 +1302,8 @@ func TestArgsParsing_MetricsViewConfig(t *testing.T) { expected: &cfg.MetricsConfig{ CloudMetricsExportIntervalSecs: 12 * 3600, StackdriverExportInterval: 12 * time.Hour, + Workers: 3, + BufferSize: 256, }, }, } diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index a265082f9c..cefee9aa04 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -76,3 +76,5 @@ metadata-cache: metrics: cloud-metrics-export-interval-secs: 10 + workers: 10 + buffer-size: 128 From 86f76382f301c2d1928fc3cfa4dd6fc5db566183 Mon Sep 17 00:00:00 2001 From: Pranjal Chaturvedi Date: Fri, 1 Aug 2025 06:27:57 +0000 Subject: [PATCH 0632/1298] Add FileName in FileClobberedLogs --- internal/fs/gcsfuse_errors/gcsfuse_errors.go | 5 +++-- internal/fs/gcsfuse_errors/gcsfuse_errors_test.go | 12 +++++++++--- internal/fs/inode/file.go | 6 ++++++ internal/gcsx/client_readers/range_reader.go | 1 + internal/gcsx/random_reader.go | 1 + 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/internal/fs/gcsfuse_errors/gcsfuse_errors.go b/internal/fs/gcsfuse_errors/gcsfuse_errors.go index b5714d954f..262eb697c1 100644 --- a/internal/fs/gcsfuse_errors/gcsfuse_errors.go +++ b/internal/fs/gcsfuse_errors/gcsfuse_errors.go @@ -21,11 +21,12 @@ import ( // FileClobberedError represents a file clobbering scenario where a file was // modified or deleted while it was being accessed. type FileClobberedError struct { - Err error + Err error + FileName string } func (fce *FileClobberedError) Error() string { - return fmt.Sprintf("The file was modified or deleted by another process, possibly due to concurrent modification: %v", fce.Err) + return fmt.Sprintf("The file %q was modified or deleted by another process, possibly due to concurrent modification: %v", fce.FileName, fce.Err) } func (fce *FileClobberedError) Unwrap() error { diff --git a/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go b/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go index 15f12555ab..ef0c08cbfb 100644 --- a/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go +++ b/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go @@ -25,24 +25,30 @@ import ( func TestFileClobberedError(t *testing.T) { testCases := []struct { name string + fileName string err error wantErrMsg string }{ { name: "with_underlying_error", + fileName: "foo.txt", err: fmt.Errorf("some error"), - wantErrMsg: "The file was modified or deleted by another process, possibly due to concurrent modification: some error", + wantErrMsg: "The file \"foo.txt\" was modified or deleted by another process, possibly due to concurrent modification: some error", }, { name: "without_underlying_error", + fileName: "bar.txt", err: nil, - wantErrMsg: "The file was modified or deleted by another process, possibly due to concurrent modification: ", + wantErrMsg: "The file \"bar.txt\" was modified or deleted by another process, possibly due to concurrent modification: ", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - clobberedErr := &FileClobberedError{Err: tc.err} + clobberedErr := &FileClobberedError{ + Err: tc.err, + FileName: tc.fileName, + } gotErrMsg := clobberedErr.Error() diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index bd75eb2fca..55fbd79d71 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -263,6 +263,7 @@ func (f *FileInode) openReader(ctx context.Context) (io.ReadCloser, error) { if errors.As(err, ¬FoundError) { err = &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("NewReader: %w", err), + FileName: f.src.Name, } } if err != nil { @@ -626,6 +627,7 @@ func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, o if errors.As(err, &preconditionErr) { return false, &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("f.bwh.Write(): %w", err), + FileName: f.src.Name, } } // Fall back to temp file for Out-Of-Order Writes. @@ -654,6 +656,7 @@ func (f *FileInode) flushUsingBufferedWriteHandler() error { if errors.As(err, &preconditionErr) { return &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("f.bwh.Flush(): %w", err), + FileName: f.src.Name, } } if err != nil { @@ -677,6 +680,7 @@ func (f *FileInode) SyncPendingBufferedWrites() (gcsSynced bool, err error) { if errors.As(err, &preconditionErr) { err = &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("f.bwh.Sync(): %w", err), + FileName: f.src.Name, } return } @@ -797,6 +801,7 @@ func (f *FileInode) fetchLatestGcsObject(ctx context.Context) (*gcs.Object, erro if isClobbered { return nil, &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("file was clobbered"), + FileName: f.src.Name, } } return latestGcsObj, nil @@ -859,6 +864,7 @@ func (f *FileInode) syncUsingContent(ctx context.Context) error { if errors.As(err, &preconditionErr) { return &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("SyncObject: %w", err), + FileName: f.src.Name, } } diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index df0c1e6e3e..9e91c84018 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -265,6 +265,7 @@ func (rr *RangeReader) startRead(start int64, end int64) error { if errors.As(err, ¬FoundError) { err = &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("NewReader: %w", err), + FileName: rr.object.Name, } cancel() return err diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 78e7692541..3f18d13cbc 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -497,6 +497,7 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { if errors.As(err, ¬FoundError) { err = &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("NewReader: %w", err), + FileName: rr.object.Name, } return } From 71423251305f531537cf4d48784a7eaef1bc703a Mon Sep 17 00:00:00 2001 From: Pranjal Chaturvedi Date: Fri, 1 Aug 2025 07:39:42 +0000 Subject: [PATCH 0633/1298] Update testfile to handle changes --- internal/fs/wrappers/error_mapping_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/fs/wrappers/error_mapping_test.go b/internal/fs/wrappers/error_mapping_test.go index 206c0382cc..ff031dcacc 100644 --- a/internal/fs/wrappers/error_mapping_test.go +++ b/internal/fs/wrappers/error_mapping_test.go @@ -95,6 +95,7 @@ func (testSuite *ErrorMapping) TestUnAuthenticatedHttpGoogleApiError() { func (testSuite *ErrorMapping) TestFileClobberedErrorWithPreconditionErrCfg() { clobberedErr := &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("some error"), + FileName: "foo.txt", } gotErrno := errno(clobberedErr, true) @@ -105,6 +106,7 @@ func (testSuite *ErrorMapping) TestFileClobberedErrorWithPreconditionErrCfg() { func (testSuite *ErrorMapping) TestFileClobberedErrorWithoutPreconditionErrCfg() { clobberedErr := &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("some error"), + FileName: "foo.txt", } gotErrno := errno(clobberedErr, false) From 0be74ff476bc84ddc6e29d8293c21ccbc3f55405 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Fri, 1 Aug 2025 16:17:34 +0530 Subject: [PATCH 0634/1298] dev guide fix (#3623) --- docs/dev_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev_guide.md b/docs/dev_guide.md index 9548fae122..e0a1ac40df 100644 --- a/docs/dev_guide.md +++ b/docs/dev_guide.md @@ -175,7 +175,7 @@ write end-to-end tests for GCSFuse. GODEBUG=asyncpreemptoff=1 CGO_ENABLED=0 go test ./tools/integration_tests/$TEST_PACKAGE_NAME/... -p 1 -short --integrationTest -v --testbucket=$TEST_BUCKET_NAME --timeout=60m -run $TEST_NAME ``` 4. **Run all tests as pre-submit:** Existing GCSFuse end-to-end tests can be run - as a pre-submit check by adding the `execute-integration-tests` label to your + as a pre-submit check by adding the `execute-integration-tests` and `kokoro:run` label to your pull request. Ask one of your assigned code reviewers to apply this label, which will trigger the tests. Your reviewer will share any test failure details on the pull request. From 37e24eb26d856121702aac39e211bfa1aa05ceaa Mon Sep 17 00:00:00 2001 From: Sam Vendittelli Date: Fri, 1 Aug 2025 11:56:56 +0100 Subject: [PATCH 0635/1298] chore(deps): bump go version to 1.24.5 (#3603) * chore(deps): bump go version to 1.24.5 * chore(deps): bump go version in other files --- Dockerfile | 2 +- go.mod | 2 +- perfmetrics/scripts/install_go.sh | 2 +- .../scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh | 4 ++-- perfmetrics/scripts/ml_tests/pytorch/run_model.sh | 2 +- .../ml_tests/tf/resnet/setup_scripts/setup_container.sh | 4 ++-- perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh | 2 +- perfmetrics/scripts/read_cache/setup.sh | 2 +- tools/cd_scripts/e2e_test.sh | 2 +- tools/containerize_gcsfuse_docker/Dockerfile | 2 +- tools/integration_tests/improved_run_e2e_tests.sh | 2 +- tools/integration_tests/run_e2e_tests.sh | 2 +- tools/package_gcsfuse_docker/Dockerfile | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Dockerfile b/Dockerfile index f210a2a523..e7b4582a6a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,7 +17,7 @@ # Mount the gcsfuse to /mnt/gcs: # > docker run --privileged --device /fuse -v /mnt/gcs:/gcs:rw,rshared gcsfuse -FROM golang:1.24.0-alpine AS builder +FROM golang:1.24.5-alpine AS builder RUN apk add git diff --git a/go.mod b/go.mod index 08205beb05..f455826fce 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/googlecloudplatform/gcsfuse/v3 -go 1.24.0 +go 1.24.5 require ( cloud.google.com/go/auth v0.16.3 diff --git a/perfmetrics/scripts/install_go.sh b/perfmetrics/scripts/install_go.sh index bb40d40ef7..ab30ac336a 100755 --- a/perfmetrics/scripts/install_go.sh +++ b/perfmetrics/scripts/install_go.sh @@ -22,7 +22,7 @@ set -euo pipefail if [[ $# -ne 1 ]]; then echo "This script requires exactly one argument." echo "Usage: $0 " - echo "Example: $0 1.24.0" + echo "Example: $0 1.24.5" exit 1 fi diff --git a/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh b/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh index b39135c228..b3bd40ff3f 100755 --- a/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh +++ b/perfmetrics/scripts/ml_tests/checkpoint/Jax/run_checkpoints.sh @@ -23,9 +23,9 @@ sudo apt-get update echo "Installing git" sudo apt-get install git # Install Golang. -#wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-amd64.tar.gz -q +#wget -O go_tar.tar.gz https://go.dev/dl/go1.24.5.linux-amd64.tar.gz -q architecture=$(dpkg --print-architecture) -wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-${architecture}.tar.gz -q +wget -O go_tar.tar.gz https://go.dev/dl/go1.24.5.linux-${architecture}.tar.gz -q sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go_tar.tar.gz export PATH=$PATH:/usr/local/go/bin # Install latest gcloud version for compatability with HNS bucket. diff --git a/perfmetrics/scripts/ml_tests/pytorch/run_model.sh b/perfmetrics/scripts/ml_tests/pytorch/run_model.sh index 312eb4a8e0..20f2a473f7 100755 --- a/perfmetrics/scripts/ml_tests/pytorch/run_model.sh +++ b/perfmetrics/scripts/ml_tests/pytorch/run_model.sh @@ -20,7 +20,7 @@ NUM_EPOCHS=80 TEST_BUCKET="gcsfuse-ml-data" # Install golang -wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-amd64.tar.gz -q +wget -O go_tar.tar.gz https://go.dev/dl/go1.24.5.linux-amd64.tar.gz -q rm -rf /usr/local/go && tar -C /usr/local -xzf go_tar.tar.gz export PATH=$PATH:/usr/local/go/bin diff --git a/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh b/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh index 7c25249587..a3e5dfcf75 100755 --- a/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh +++ b/perfmetrics/scripts/ml_tests/tf/resnet/setup_scripts/setup_container.sh @@ -13,13 +13,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Installs go1.24.0 on the container, builds gcsfuse using log_rotation file +# Installs go1.24.5 on the container, builds gcsfuse using log_rotation file # and installs tf-models-official v2.13.2, makes update to include clear_kernel_cache # and epochs functionality, and runs the model # Install go lang BUCKET_TYPE=$1 -wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-amd64.tar.gz -q +wget -O go_tar.tar.gz https://go.dev/dl/go1.24.5.linux-amd64.tar.gz -q sudo rm -rf /usr/local/go && tar -xzf go_tar.tar.gz && sudo mv go /usr/local export PATH=$PATH:/usr/local/go/bin diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index b9faacee7e..43d34339ca 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -21,7 +21,7 @@ readonly EXECUTE_INTEGRATION_TEST_LABEL_ON_ZB="execute-integration-tests-on-zb" readonly EXECUTE_PACKAGE_BUILD_TEST_LABEL="execute-package-build-tests" readonly EXECUTE_CHECKPOINT_TEST_LABEL="execute-checkpoint-test" readonly BUCKET_LOCATION=us-west4 -readonly GO_VERSION="1.24.0" +readonly GO_VERSION="1.24.5" readonly REQUIRED_BASH_VERSION_FOR_E2E_SCRIPT="5.1" curl https://api.github.com/repos/GoogleCloudPlatform/gcsfuse/pulls/$KOKORO_GITHUB_PULL_REQUEST_NUMBER >> pr.json diff --git a/perfmetrics/scripts/read_cache/setup.sh b/perfmetrics/scripts/read_cache/setup.sh index 8c59b95430..2683b7f787 100755 --- a/perfmetrics/scripts/read_cache/setup.sh +++ b/perfmetrics/scripts/read_cache/setup.sh @@ -64,7 +64,7 @@ sed -i 's/define \+FIO_IO_U_PLAT_GROUP_NR \+\([0-9]\+\)/define FIO_IO_U_PLAT_GRO cd - # Install and validate go. -version=1.24.0 +version=1.24.5 wget -O go_tar.tar.gz https://go.dev/dl/go${version}.linux-amd64.tar.gz -q sudo rm -rf /usr/local/go tar -xzf go_tar.tar.gz && sudo mv go /usr/local diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index a1e4c45561..e04b1a3e67 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -156,7 +156,7 @@ else fi # install go -wget -O go_tar.tar.gz https://go.dev/dl/go1.24.0.linux-${architecture}.tar.gz +wget -O go_tar.tar.gz https://go.dev/dl/go1.24.5.linux-${architecture}.tar.gz sudo tar -C /usr/local -xzf go_tar.tar.gz export PATH=${PATH}:/usr/local/go/bin #Write gcsfuse and go version to log file diff --git a/tools/containerize_gcsfuse_docker/Dockerfile b/tools/containerize_gcsfuse_docker/Dockerfile index 3f20048176..41561ddea2 100644 --- a/tools/containerize_gcsfuse_docker/Dockerfile +++ b/tools/containerize_gcsfuse_docker/Dockerfile @@ -34,7 +34,7 @@ ARG OS_VERSION ARG OS_NAME # Image with gcsfuse installed and its package (.deb) -FROM golang:1.24.0 as gcsfuse-package +FROM golang:1.24.5 as gcsfuse-package RUN apt-get update -qq && apt-get install -y ruby ruby-dev rubygems build-essential rpm fuse && gem install --no-document bundler diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh index 03e3328488..f11a64e56c 100755 --- a/tools/integration_tests/improved_run_e2e_tests.sh +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -52,7 +52,7 @@ fi log_info "Bash version: ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}" # Constants -readonly GO_VERSION="1.24.0" +readonly GO_VERSION="1.24.5" readonly DEFAULT_PROJECT_ID="gcs-fuse-test-ml" readonly TPCZERO_PROJECT_ID="tpczero-system:gcsfuse-test-project" readonly TPC_BUCKET_LOCATION="u-us-prp1" diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index 585e0149c2..e3005de2f5 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -249,7 +249,7 @@ function upgrade_gcloud_version() { function install_packages() { # Install required go version. - ./perfmetrics/scripts/install_go.sh "1.24.0" + ./perfmetrics/scripts/install_go.sh "1.24.5" export PATH="/usr/local/go/bin:$PATH" sudo apt-get update diff --git a/tools/package_gcsfuse_docker/Dockerfile b/tools/package_gcsfuse_docker/Dockerfile index d5d877e176..6470be60a4 100644 --- a/tools/package_gcsfuse_docker/Dockerfile +++ b/tools/package_gcsfuse_docker/Dockerfile @@ -17,7 +17,7 @@ # Copy the gcsfuse packages to the host: # > docker run -it -v /tmp:/output gcsfuse-release cp -r /packages /output -FROM golang:1.24.0 as builder +FROM golang:1.24.5 as builder RUN apt-get update -qq && apt-get install -y ruby ruby-dev rubygems build-essential rpm && gem install --no-document bundler -v "2.4.12" From c2a7d57cbfca5215be13bdfe1f028761455ea4ca Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Fri, 1 Aug 2025 11:05:52 +0000 Subject: [PATCH 0636/1298] feat(parallel random reads): Changes to enable parallel random read handling in random reader (#3577) Changes to enable parallel read handling in the randomReader. The core idea is to use a mutex to serialize access for RangeReader while allowing MultiRangeReader operations to proceed in parallel. The idea is to determine read type without taking any locks and then allow parallel processing for MRD. For range reader, we would take a lock to synchronize access. Since reads wait on a lock for range reader, the reader type computation is done again before actually reading via range reader to ensure that the read should not be served by MRD. --- .../gcsx/multi_range_downloader_wrapper.go | 12 +- internal/gcsx/random_reader.go | 236 ++++++--- internal/gcsx/random_reader_stretchr_test.go | 471 ++++++++++++++---- internal/gcsx/random_reader_test.go | 24 +- 4 files changed, 572 insertions(+), 171 deletions(-) diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go index 72b4334c53..5d64585c21 100644 --- a/internal/gcsx/multi_range_downloader_wrapper.go +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -69,7 +69,7 @@ type MultiRangeDownloaderWrapper struct { // Refcount is used to determine when to close the MultiRangeDownloader. refCount int // Mutex is used to synchronize access over refCount. - mu sync.Mutex + mu sync.RWMutex // Holds the cancel function, which can be called to cancel the cleanup function. cancelCleanup context.CancelFunc // Used for waiting for timeout (helps us in mocking the functionality). @@ -94,8 +94,8 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) GetMinObject() *gcs.MinObject { // GetRefCount returns current refcount. func (mrdWrapper *MultiRangeDownloaderWrapper) GetRefCount() int { - mrdWrapper.mu.Lock() - defer mrdWrapper.mu.Unlock() + mrdWrapper.mu.RLock() + defer mrdWrapper.mu.RUnlock() return mrdWrapper.refCount } @@ -165,11 +165,15 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) ensureMultiRangeDownloader() (err if mrdWrapper.object == nil || mrdWrapper.bucket == nil { return fmt.Errorf("ensureMultiRangeDownloader error: Missing minObject or bucket") } + + mrdWrapper.mu.RLock() // Create the MRD if it does not exist. // In case the existing MRD is unusable due to closed stream, recreate the MRD. if mrdWrapper.Wrapped == nil || mrdWrapper.Wrapped.Error() != nil { + mrdWrapper.mu.RUnlock() mrdWrapper.mu.Lock() defer mrdWrapper.mu.Unlock() + // Checking if the mrdWrapper state is same after taking the lock. if mrdWrapper.Wrapped == nil || mrdWrapper.Wrapped.Error() != nil { var mrd gcs.MultiRangeDownloader @@ -183,6 +187,8 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) ensureMultiRangeDownloader() (err mrdWrapper.Wrapped = mrd } } + } else { + mrdWrapper.mu.RUnlock() } return } diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 78e7692541..9ab3aa1dcb 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -19,6 +19,7 @@ import ( "fmt" "io" "math" + "sync" "sync/atomic" "time" @@ -53,6 +54,8 @@ const minSeeksForRandom = 2 // TODO(b/385826024): Revert timeout to an appropriate value const TimeoutForMultiRangeRead = time.Hour +var FallbackToNewRangeReader = errors.New("fallback to new range reader is required") + // RandomReader is an object that knows how to read ranges within a particular // generation of a particular GCS object. Optimised for (large) sequential reads. // @@ -168,7 +171,7 @@ type randomReader struct { mrdWrapper *MultiRangeDownloaderWrapper // boolean variable to determine if MRD is being used or not. - isMRDInUse bool + isMRDInUse atomic.Bool metricHandle metrics.MetricHandle @@ -177,6 +180,15 @@ type randomReader struct { // Specifies the next expected offset for the reads. Used to distinguish between // sequential and random reads. expectedOffset atomic.Int64 + + // To synchronize reads served from range reader. + mu sync.Mutex +} + +type readInfo struct { + readType int64 + expectedOffset int64 + seekRecorded bool } func (rr *randomReader) CheckInvariants() { @@ -311,6 +323,12 @@ func (rr *randomReader) ReadAt( if offset >= int64(rr.object.Size) { err = io.EOF return + } else if offset < 0 { + err = fmt.Errorf( + "illegal offset %d for %d byte object", + offset, + rr.object.Size) + return } // Note: If we are reading the file for the first time and read type is sequential @@ -329,60 +347,42 @@ func (rr *randomReader) ReadAt( return } - // Check first if we can read using existing reader. if not, determine which - // api to use and call gcs accordingly. + // Not taking any lock for getting reader type to ensure random read requests do not wait. + readInfo := rr.getReadInfo(offset, false) + reqReaderType := readerType(readInfo.readType, rr.bucket.BucketType()) - // When the offset is AFTER the reader position, try to seek forward, within reason. - // This happens when the kernel page cache serves some data. It's very common for - // concurrent reads, often by only a few 128kB fuse read requests. The aim is to - // re-use GCS connection and avoid throwing away already read data. - // For parallel sequential reads to a single file, not throwing away the connections - // is a 15-20x improvement in throughput: 150-200 MiB/s instead of 10 MiB/s. - if rr.reader != nil && rr.start < offset && offset-rr.start < maxReadSize { - bytesToSkip := offset - rr.start - discardedBytes, copyError := io.CopyN(io.Discard, rr.reader, bytesToSkip) - // io.EOF is expected if the reader is shorter than the requested offset to read. - if copyError != nil && !errors.Is(copyError, io.EOF) { - logger.Warnf("Error while skipping reader bytes: %v", copyError) - } - rr.start += discardedBytes - } + if reqReaderType == RangeReader { + rr.mu.Lock() + expectedOffset := rr.expectedOffset.Load() - // If we have an existing reader, but it's positioned at the wrong place, - // clean it up and throw it away. - // We will also clean up the existing reader if it can't serve the entire request. - dataToRead := math.Min(float64(offset+int64(len(p))), float64(rr.object.Size)) - if rr.reader != nil && (rr.start != offset || int64(dataToRead) > rr.limit) { - rr.closeReader() - rr.reader = nil - rr.cancel = nil - } - - if rr.reader != nil { - objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, -1, rr.readType.Load()) - return - } - - // If the data can't be served from the existing reader, then we need to update the seeks. - // If current offset is not same as expected offset, its a random read. - if expectedOffset := rr.expectedOffset.Load(); expectedOffset != 0 && expectedOffset != offset { - rr.seeks.Add(1) - } + // Calculating reader type again for zonal buckets in case another read has been served + // since last computation. This is to ensure that we don't use range reader incorrectly + // when MRD should've been used. + if rr.bucket.BucketType().Zonal && readInfo.expectedOffset != expectedOffset { + readInfo = rr.getReadInfo(offset, readInfo.seekRecorded) + reqReaderType = readerType(readInfo.readType, rr.bucket.BucketType()) + } - // If we don't have a reader, determine whether to read from NewReader or MRR. - end, err := rr.getReadInfo(offset, int64(len(p))) - if err != nil { - err = fmt.Errorf("ReadAt: getReaderInfo: %w", err) - return + if reqReaderType == MultiRangeReader { + rr.mu.Unlock() + } else { + defer rr.mu.Unlock() + + // Check first if we can read using existing reader. if not, create a new range reader + objectData.Size, err = rr.readFromExistingRangeReader(ctx, p, offset) + if errors.Is(err, FallbackToNewRangeReader) { + // reader does not exist and need to be created, get the end offset. + end := rr.getEndOffset(offset) + objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, end, readInfo.readType) + } + return + } } - readerType := readerType(rr.readType.Load(), offset, end, rr.bucket.BucketType()) - if readerType == RangeReader { - objectData.Size, err = rr.readFromRangeReader(ctx, p, offset, end, rr.readType.Load()) - return + if reqReaderType == MultiRangeReader { + objectData.Size, err = rr.readFromMultiRangeReader(ctx, p, offset, offset+int64(len(p)), TimeoutForMultiRangeRead) } - objectData.Size, err = rr.readFromMultiRangeReader(ctx, p, offset, end, TimeoutForMultiRangeRead) return } @@ -393,18 +393,22 @@ func (rr *randomReader) Object() (o *gcs.MinObject) { func (rr *randomReader) Destroy() { defer func() { - if rr.isMRDInUse { + if rr.isMRDInUse.Load() { err := rr.mrdWrapper.DecrementRefCount() if err != nil { logger.Errorf("randomReader::Destroy:%v", err) } - rr.isMRDInUse = false + rr.isMRDInUse.Store(false) } }() // Close out the reader, if we have one. if rr.reader != nil { - rr.closeReader() + rr.mu.Lock() + defer rr.mu.Unlock() + if rr.reader != nil { + rr.closeReader() + } rr.reader = nil rr.cancel = nil } @@ -516,26 +520,63 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { return } -// getReaderInfo determines the readType and provides the range to query GCS. -// Range here is [start, end]. End is computed using the readType, start offset -// and size of the data the callers needs. -func (rr *randomReader) getReadInfo( - start int64, - size int64) (end int64, err error) { - // Make sure start and size are legal. - if start < 0 || uint64(start) > rr.object.Size || size < 0 { - err = fmt.Errorf( - "range [%d, %d) is illegal for %d-byte object", - start, - start+size, - rr.object.Size) - return +// isSeekNeeded determines if the current read at `offset` should be considered a +// seek, given the previous read pattern & the expected offset. +func isSeekNeeded(readType, offset, expectedOffset int64) bool { + if expectedOffset == 0 { + return false } - if err != nil { - return + if readType == metrics.ReadTypeRandom { + return offset != expectedOffset + } + + if readType == metrics.ReadTypeSequential { + return offset < expectedOffset || offset > expectedOffset+maxReadSize + } + + return false +} + +// getReadInfo determines the read strategy (sequential or random) for a read +// request at a given offset and returns read metadata. It also updates the +// reader's internal state based on the read pattern. +func (rr *randomReader) getReadInfo(offset int64, seekRecorded bool) readInfo { + readType := rr.readType.Load() + expOffset := rr.expectedOffset.Load() + numSeeks := rr.seeks.Load() + + if !seekRecorded && isSeekNeeded(readType, offset, expOffset) { + numSeeks = rr.seeks.Add(1) + seekRecorded = true + } + + if numSeeks >= minSeeksForRandom { + readType = metrics.ReadTypeRandom + } + + averageReadBytes := rr.totalReadBytes.Load() + if numSeeks > 0 { + averageReadBytes /= numSeeks + } + + if averageReadBytes >= maxReadSize { + readType = metrics.ReadTypeSequential } + rr.readType.Store(readType) + return readInfo{ + readType: readType, + expectedOffset: expOffset, + seekRecorded: seekRecorded, + } +} + +// getEndOffset returns the end offset for the range to query GCS. +// Range here is [start, end]. End is computed for sequential reads using +// start offset and size of the data the callers needs. +func (rr *randomReader) getEndOffset( + start int64) (end int64) { // GCS requests are expensive. Prefer to issue read requests defined by // sequentialReadSizeMb flag. Sequential reads will simply sip from the fire house // with each call to ReadAt. In practice, GCS will fill the TCP buffers @@ -549,7 +590,6 @@ func (rr *randomReader) getReadInfo( // (average read size in bytes rounded up to the next MiB). end = int64(rr.object.Size) if seeks := rr.seeks.Load(); seeks >= minSeeksForRandom { - rr.readType.Store(metrics.ReadTypeRandom) averageReadBytes := rr.totalReadBytes.Load() / seeks if averageReadBytes < maxReadSize { randomReadSize := int64(((averageReadBytes / MiB) + 1) * MiB) @@ -577,16 +617,66 @@ func (rr *randomReader) getReadInfo( } // readerType specifies the go-sdk interface to use for reads. -func readerType(readType int64, start int64, end int64, bucketType gcs.BucketType) ReaderType { - bytesToBeRead := end - start - if readType == metrics.ReadTypeRandom && bytesToBeRead < maxReadSize && bucketType.Zonal { +func readerType(readType int64, bucketType gcs.BucketType) ReaderType { + if readType == metrics.ReadTypeRandom && bucketType.Zonal { return MultiRangeReader } return RangeReader } +// skipBytes attempts to advance the reader position to the given offset without +// discarding the existing reader. +// LOCKS_REQUIRED (rr.mu) +func (rr *randomReader) skipBytes(offset int64) { + // When the offset is AFTER the reader position, try to seek forward, within reason. + // This happens when the kernel page cache serves some data. It's very common for + // concurrent reads, often by only a few 128kB fuse read requests. The aim is to + // re-use GCS connection and avoid throwing away already read data. + // For parallel sequential reads to a single file, not throwing away the connections + // is a 15-20x improvement in throughput: 150-200 MiB/s instead of 10 MiB/s. + if rr.reader != nil && rr.start < offset && offset-rr.start < maxReadSize { + bytesToSkip := offset - rr.start + discardedBytes, copyError := io.CopyN(io.Discard, rr.reader, bytesToSkip) + // io.EOF is expected if the reader is shorter than the requested offset to read. + if copyError != nil && !errors.Is(copyError, io.EOF) { + logger.Warnf("Error while skipping reader bytes: %v", copyError) + } + rr.start += discardedBytes + } +} + +// invalidateReaderIfMisalignedOrTooSmall ensures that the existing reader is valid +// for the requested offset and length. If the reader is misaligned (not at the requested +// offset) or cannot serve the full request within its limit, it is closed and discarded. +// LOCKS_REQUIRED (rr.mu) +func (rr *randomReader) invalidateReaderIfMisalignedOrTooSmall(startOffset, endOffset int64) { + // If we have an existing reader, but it's positioned at the wrong place, + // clean it up and throw it away. + // We will also clean up the existing reader if it can't serve the entire request. + dataToRead := math.Min(float64(endOffset), float64(rr.object.Size)) + if rr.reader != nil && (rr.start != startOffset || int64(dataToRead) > rr.limit) { + rr.closeReader() + rr.reader = nil + rr.cancel = nil + } +} + +// readFromExistingRangeReader attempts to read data from an existing reader if one is available. +// If a reader exists and the read is successful, the data is returned. +// Otherwise, it returns an error indicating that a new reader is needed. +// LOCKS_REQUIRED (rr.mu) +func (rr *randomReader) readFromExistingRangeReader(ctx context.Context, p []byte, offset int64) (n int, err error) { + rr.skipBytes(offset) + rr.invalidateReaderIfMisalignedOrTooSmall(offset, offset+int64(len(p))) + if rr.reader != nil { + return rr.readFromRangeReader(ctx, p, offset, offset+int64(len(p)), rr.readType.Load()) + } + return 0, FallbackToNewRangeReader +} + // readFromRangeReader reads using the NewReader interface of go-sdk. Its uses // the existing reader if available, otherwise makes a call to GCS. +// LOCKS_REQUIRED (rr.mu) func (rr *randomReader) readFromRangeReader(ctx context.Context, p []byte, offset int64, end int64, readType int64) (n int, err error) { // If we don't have a reader, start a read operation. if rr.reader == nil { @@ -655,8 +745,7 @@ func (rr *randomReader) readFromMultiRangeReader(ctx context.Context, p []byte, return 0, fmt.Errorf("readFromMultiRangeReader: Invalid MultiRangeDownloaderWrapper") } - if !rr.isMRDInUse { - rr.isMRDInUse = true + if rr.isMRDInUse.CompareAndSwap(false, true) { rr.mrdWrapper.IncrementRefCount() } @@ -667,6 +756,7 @@ func (rr *randomReader) readFromMultiRangeReader(ctx context.Context, p []byte, } // closeReader fetches the readHandle before closing the reader instance. +// LOCKS_REQUIRED (rr.mu) func (rr *randomReader) closeReader() { rr.readHandle = rr.reader.ReadHandle() err := rr.reader.Close() diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index de103b55bf..0f7e9e57b8 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -17,11 +17,11 @@ package gcsx import ( "context" "errors" - "fmt" "io" "os" "path" "strings" + "sync" "testing" "time" @@ -87,97 +87,229 @@ func (t *RandomReaderStretchrTest) TearDownTest() { t.rr.Destroy() } -func (t *RandomReaderStretchrTest) Test_ReadInfo() { - t.object.Size = 10 * MiB +func (t *RandomReaderStretchrTest) Test_GetReadInfo() { testCases := []struct { - name string - start int64 - size int64 + name string + offset int64 + seekRecorded bool + initialReadType int64 + initialExpOffset int64 + initialNumSeeks uint64 + initialTotalReadBytes uint64 + expectedReadType int64 + expectedNumSeeks uint64 }{ { - name: "startLessThanZero", - start: -1, - size: 10, + name: "First Read", + offset: 0, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 0, + initialNumSeeks: 0, + initialTotalReadBytes: 0, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 0, }, { - name: "sizeLessThanZero", - start: -0, - size: -1, + name: "Sequential Read", + offset: 10, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 10, + initialNumSeeks: 0, + initialTotalReadBytes: 100, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 0, }, { - name: "startGreaterThanObjectSize", - start: int64(t.object.Size + 1), - size: int64(t.object.Size), + name: "Sequential read with small forward jump and high average read bytes is still sequential", + offset: 100, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 10, + initialNumSeeks: 0, + initialTotalReadBytes: 10000000, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 0, + }, + { + name: "Sequential read with large forward jump is a seek", + offset: 50 + maxReadSize + 1, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: 0, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 1, + }, + { + name: "Sequential read with backward jump is a seek", + offset: 49, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: 0, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 1, + }, + { + name: "Contiguous random read is not a seek", + offset: 50, + seekRecorded: false, + initialReadType: metrics.ReadTypeRandom, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeRandom, + expectedNumSeeks: minSeeksForRandom, + }, + { + name: "Non-contiguous random read is a seek", + offset: 100, + seekRecorded: false, + initialReadType: metrics.ReadTypeRandom, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeRandom, + expectedNumSeeks: minSeeksForRandom + 1, + }, + { + name: "Switches to random read after enough seeks", + offset: 50 + maxReadSize + 1, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom - 1, + initialTotalReadBytes: 1000, + expectedReadType: metrics.ReadTypeRandom, + expectedNumSeeks: minSeeksForRandom, + }, + { + name: "Switches back to sequential with high average read bytes", + offset: 100, + seekRecorded: false, + initialReadType: metrics.ReadTypeRandom, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: maxReadSize * (minSeeksForRandom + 1), + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: minSeeksForRandom + 1, + }, + { + name: "Seek recorded: sequential large forward jump", + offset: 50 + maxReadSize + 1, + seekRecorded: true, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: 0, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 0, // Not incremented + }, + { + name: "Seek recorded: sequential backward jump", + offset: 49, + seekRecorded: true, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: 1, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 1, // Not incremented + }, + { + name: "Seek recorded: non-contiguous random read", + offset: 100, + seekRecorded: true, + initialReadType: metrics.ReadTypeRandom, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeRandom, + expectedNumSeeks: minSeeksForRandom, // Not incremented + }, + { + name: "Seek recorded: does not switch to random", + offset: 50 + maxReadSize + 1, + seekRecorded: true, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom - 1, + initialTotalReadBytes: 1000, + expectedReadType: metrics.ReadTypeSequential, // Does not switch + expectedNumSeeks: minSeeksForRandom - 1, // Not incremented }, } for _, tc := range testCases { t.Run(tc.name, func() { - _, err := t.rr.wrapped.getReadInfo(tc.start, tc.size) - - assert.Error(t.T(), err) - errorString := fmt.Sprintf( - "range [%d, %d) is illegal for %d-byte object", tc.start, tc.start+tc.size, t.object.Size) - assert.Equal(t.T(), errorString, err.Error()) + rr := &randomReader{} + rr.readType.Store(tc.initialReadType) + rr.expectedOffset.Store(tc.initialExpOffset) + rr.seeks.Store(tc.initialNumSeeks) + rr.totalReadBytes.Store(tc.initialTotalReadBytes) + + readInfo := rr.getReadInfo(tc.offset, tc.seekRecorded) + assert.Equal(t.T(), tc.expectedReadType, readInfo.readType, "Read type mismatch") + assert.Equal(t.T(), tc.expectedNumSeeks, rr.seeks.Load(), "Number of seeks mismatch") }) } } -func (t *RandomReaderStretchrTest) Test_ReadInfo_Sequential() { - var testCases = []struct { - testName string - expectedEnd int64 - start int64 - objectSize uint64 - }{ - {"10MBObject", 10 * MiB, 0, 10 * MiB}, - {"ReadSizeGreaterThanObjectSize", 10 * MiB, int64(t.object.Size - 1), 10 * MiB}, - {"ObjectSizeGreaterThanReadSize", int64(sequentialReadSizeInBytes), 0, 50 * MiB}, - } +func (t *RandomReaderStretchrTest) Test_ReadAt_ParallelMRDReads() { + // Setup + t.rr.wrapped.reader = nil + t.rr.wrapped.seeks.Store(minSeeksForRandom) + t.rr.wrapped.readType.Store(metrics.ReadTypeRandom) + t.object.Size = 20 * MiB + testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - for _, tc := range testCases { - t.Run(tc.testName, func() { - t.object.Size = tc.objectSize - end, err := t.rr.wrapped.getReadInfo(tc.start, 10) + // Mock bucket and MRD + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}) + fakeMRDWrapper, err := NewMultiRangeDownloaderWrapper(t.mockBucket, t.object, &cfg.Config{}) + require.NoError(t.T(), err) + t.rr.wrapped.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloader(t.object, testContent), nil) - assert.NoError(t.T(), err) - assert.Equal(t.T(), metrics.ReadTypeSequential, t.rr.wrapped.readType.Load()) - assert.Equal(t.T(), tc.expectedEnd, end) - }) + // Parallel reads + tasks := []struct { + offset int64 + size int + }{ + {0, 1 * MiB}, + {2 * MiB, 2 * MiB}, + {5 * MiB, 1 * MiB}, + {10 * MiB, 5 * MiB}, } -} -func (t *RandomReaderStretchrTest) Test_ReadInfo_Random() { - t.rr.wrapped.seeks.Store(2) - var testCases = []struct { - testName string - expectedEnd int64 - start int64 - objectSize uint64 - totalReadBytes uint64 - }{ - // TotalReadByte is 10MB, so average is 10/2 = 5MB >1MB and <8MB - {"RangeBetween1And8MB", 6 * MiB, 0, 50 * MiB, 10 * MiB}, - // TotalReadByte is 1MB, so average is 1/2 = 0.5MB which is <1MB - {"ReadSizeLessThan1MB", minReadSize, 0, 50 * MiB, 1 * MiB}, - // TotalReadByte is 1MB, so average is 10/2 = 5MB which is <8MB - {"ReadSizeLessThan8MB", 6 * MiB, 0, 50 * MiB, 10 * MiB}, - // TotalReadByte is 1MB, so average is 20/2 = 10MB which is >8MB - {"ReadSizeGreaterThan8MB", sequentialReadSizeInBytes, 0, 50 * MiB, 20 * MiB}, - {"ReadSizeGreaterThanObjectSize", 5 * MiB, 5*MiB - 1, 5 * MiB, 2 * MiB}, + var wg sync.WaitGroup + var totalBytesReadFromTasks uint64 + + for _, task := range tasks { + wg.Add(1) + totalBytesReadFromTasks += uint64(task.size) + go func(offset int64, size int) { + defer wg.Done() + buf := make([]byte, size) + // Each goroutine gets its own context. + ctx := context.Background() + objData, err := t.rr.wrapped.ReadAt(ctx, buf, offset) + + require.NoError(t.T(), err) + require.Equal(t.T(), size, objData.Size) + require.Equal(t.T(), testContent[offset:offset+int64(size)], buf) + }(task.offset, task.size) } - for _, tc := range testCases { - t.Run(tc.testName, func() { - t.object.Size = tc.objectSize - t.rr.wrapped.totalReadBytes.Store(tc.totalReadBytes) - end, err := t.rr.wrapped.getReadInfo(tc.start, 10) + wg.Wait() - assert.NoError(t.T(), err) - assert.Equal(t.T(), metrics.ReadTypeRandom, t.rr.wrapped.readType.Load()) - assert.Equal(t.T(), tc.expectedEnd, end) - }) - } + // Validation + assert.Equal(t.T(), totalBytesReadFromTasks, t.rr.wrapped.totalReadBytes.Load()) + assert.Equal(t.T(), 1, t.rr.wrapped.mrdWrapper.GetRefCount()) + assert.True(t.T(), t.rr.wrapped.isMRDInUse.Load()) } func (t *RandomReaderStretchrTest) Test_ReaderType() { @@ -197,14 +329,6 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { bucketType: gcs.BucketType{Zonal: true}, readerType: MultiRangeReader, }, - { - name: "ZonalBucketRandomReadLargerThan8MB", - readType: metrics.ReadTypeRandom, - start: 0, - end: 9 * MiB, - bucketType: gcs.BucketType{Zonal: true}, - readerType: RangeReader, - }, { name: "ZonalBucketSequentialRead", readType: metrics.ReadTypeSequential, @@ -233,12 +357,183 @@ func (t *RandomReaderStretchrTest) Test_ReaderType() { for _, tc := range testCases { t.Run(tc.name, func() { - readerType := readerType(tc.readType, tc.start, tc.end, tc.bucketType) + readerType := readerType(tc.readType, tc.bucketType) assert.Equal(t.T(), readerType, tc.readerType) }) } } +func (t *RandomReaderStretchrTest) Test_GetEndOffset() { + testCases := []struct { + name string + start int64 + objectSize int64 + initialReadType int64 + initialNumSeeks uint64 + initialTotalReadBytes uint64 + sequentialReadSizeMb int32 + expectedEnd int64 + }{ + { + name: "Sequential Read, Fits in sequentialReadSizeMb", + start: 0, + objectSize: 10 * MiB, + initialReadType: metrics.ReadTypeSequential, + initialNumSeeks: 0, + initialTotalReadBytes: 0, + sequentialReadSizeMb: 22, + expectedEnd: 10 * MiB, + }, + { + name: "Sequential Read, Object Larger than sequentialReadSizeMb", + start: 0, + objectSize: 50 * MiB, + initialReadType: metrics.ReadTypeSequential, + initialNumSeeks: 0, + initialTotalReadBytes: 0, + sequentialReadSizeMb: 22, + expectedEnd: 22 * MiB, + }, + { + name: "Sequential Read, Respects object size", + start: 5 * MiB, + objectSize: 7 * MiB, + initialReadType: metrics.ReadTypeSequential, + initialNumSeeks: 0, + initialTotalReadBytes: 0, + sequentialReadSizeMb: 22, + expectedEnd: 7 * MiB, + }, + { + name: "Random Read, Min read size", + start: 0, + objectSize: 5 * MiB, + initialReadType: metrics.ReadTypeRandom, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 1000, + sequentialReadSizeMb: 22, + expectedEnd: minReadSize, + }, + { + name: "Random Read, Averages less than minReadSize", + start: 0, + objectSize: 50 * MiB, + initialReadType: metrics.ReadTypeRandom, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 100 * 1024, // 100KiB + sequentialReadSizeMb: 22, + expectedEnd: minReadSize, // Should be atleast minReadSize + }, + { + name: "Random Read, Start Offset Non-Zero", + start: 5 * MiB, + objectSize: 50 * MiB, + initialReadType: metrics.ReadTypeRandom, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 2 * MiB, // avg read bytes = 1MiB + sequentialReadSizeMb: 22, + expectedEnd: 5*MiB + 2*MiB, // avg read bytes + 1MiB + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + rr := &randomReader{ + object: &gcs.MinObject{Size: uint64(tc.objectSize)}, + sequentialReadSizeMb: tc.sequentialReadSizeMb, + } + rr.readType.Store(tc.initialReadType) + rr.seeks.Store(tc.initialNumSeeks) + rr.totalReadBytes.Store(tc.initialTotalReadBytes) + + end := rr.getEndOffset(tc.start) + + assert.Equal(t.T(), tc.expectedEnd, end, "End offset mismatch") + }) + } +} + +func (t *RandomReaderStretchrTest) Test_IsSeekNeeded() { + testCases := []struct { + name string + readType int64 + offset int64 + expectedOffset int64 + want bool + }{ + { + name: "First read, expectedOffset is 0", + readType: metrics.ReadTypeSequential, + offset: 100, + expectedOffset: 0, + want: false, + }, + { + name: "Random read, same offset", + readType: metrics.ReadTypeRandom, + offset: 100, + expectedOffset: 100, + want: false, + }, + { + name: "Random read, different offset", + readType: metrics.ReadTypeRandom, + offset: 200, + expectedOffset: 100, + want: true, + }, + { + name: "Sequential read, same offset", + readType: metrics.ReadTypeSequential, + offset: 100, + expectedOffset: 100, + want: false, + }, + { + name: "Sequential read, small forward jump within maxReadSize", + readType: metrics.ReadTypeSequential, + offset: 100 + maxReadSize/2, + expectedOffset: 100, + want: false, + }, + { + name: "Sequential read, forward jump to boundary of maxReadSize", + readType: metrics.ReadTypeSequential, + offset: 100 + maxReadSize, + expectedOffset: 100, + want: false, + }, + { + name: "Sequential read, large forward jump beyond maxReadSize", + readType: metrics.ReadTypeSequential, + offset: 100 + maxReadSize + 1, + expectedOffset: 100, + want: true, + }, + { + name: "Sequential read, backward jump", + readType: metrics.ReadTypeSequential, + offset: 99, + expectedOffset: 100, + want: true, + }, + { + name: "Unknown read type", + readType: -1, // An invalid read type + offset: 200, + expectedOffset: 100, + want: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + got := isSeekNeeded(tc.readType, tc.offset, tc.expectedOffset) + assert.Equal(t.T(), tc.want, got) + }) + } +} + func (t *RandomReaderStretchrTest) Test_ReadFromRangeReader_WhenExistingReaderIsNil() { testCases := []struct { name string @@ -514,7 +809,7 @@ func (t *RandomReaderStretchrTest) Test_ExistingReader_WrongOffset() { On("NewReaderWithReadHandle", mock.Anything, readObjectRequest). Return(nil, errors.New(string(tc.readHandle))). Times(1) - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(2) buf := make([]byte, 1) @@ -546,7 +841,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequ ReadHandle: expectedHandleInRequest, } t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil) - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(2) requestSize := 6 buf := make([]byte, requestSize) @@ -581,7 +876,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequ ReadHandle: expectedHandleInRequest, } t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil) - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(2) requestSize := 6 buf := make([]byte, requestSize) @@ -674,7 +969,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { t.Run(tc.name, func() { assert.Equal(t.T(), len(tc.readRanges), len(tc.expectedReadTypes), "Test Parameter Error: readRanges and expectedReadTypes should have same length") t.rr.wrapped.reader = nil - t.rr.wrapped.isMRDInUse = false + t.rr.wrapped.isMRDInUse.Store(false) t.rr.wrapped.seeks.Store(0) t.rr.wrapped.readType.Store(metrics.ReadTypeSequential) t.rr.wrapped.expectedOffset.Store(0) @@ -684,7 +979,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { assert.Nil(t.T(), err, "Error in creating MRDWrapper") t.rr.wrapped.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)) - t.mockBucket.On("BucketType", mock.Anything).Return(tc.bucketType).Times(len(tc.readRanges)) + t.mockBucket.On("BucketType", mock.Anything).Return(tc.bucketType).Times(len(tc.readRanges) * 2) for i, readRange := range tc.readRanges { t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(&fake.FakeReader{ReadCloser: getReadCloser(testContent)}, nil).Once() @@ -704,7 +999,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateReadType() { // This test validates the bug fix where seeks are not updated correctly in case of zonal bucket random reads (b/410904634). func (t *RandomReaderStretchrTest) Test_ReadAt_ValidateZonalRandomReads() { t.rr.wrapped.reader = nil - t.rr.wrapped.isMRDInUse = false + t.rr.wrapped.isMRDInUse.Store(false) t.rr.wrapped.seeks.Store(0) t.rr.wrapped.readType.Store(metrics.ReadTypeSequential) t.rr.wrapped.expectedOffset.Store(0) @@ -767,7 +1062,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_MRDRead() { for _, tc := range testCases { t.Run(tc.name, func() { t.rr.wrapped.reader = nil - t.rr.wrapped.isMRDInUse = false + t.rr.wrapped.isMRDInUse.Store(false) t.rr.wrapped.expectedOffset.Store(10) t.rr.wrapped.seeks.Store(minSeeksForRandom + 1) t.object.Size = uint64(tc.dataSize) @@ -814,7 +1109,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ReadFull() { for _, tc := range testCases { t.Run(tc.name, func() { t.rr.wrapped.reader = nil - t.rr.wrapped.isMRDInUse = false + t.rr.wrapped.isMRDInUse.Store(false) t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) @@ -905,7 +1200,7 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ValidateTimeout for _, tc := range testCases { t.Run(tc.name, func() { t.rr.wrapped.reader = nil - t.rr.wrapped.isMRDInUse = false + t.rr.wrapped.isMRDInUse.Store(false) t.object.Size = uint64(tc.dataSize) testContent := testutil.GenerateRandomBytes(int(t.object.Size)) fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) @@ -983,7 +1278,7 @@ func (t *RandomReaderStretchrTest) Test_ReadAt_WithAndWithoutReadConfig() { } t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, expectedReadObjectRequest).Return(rc, nil).Once() // BucketType is called by ReadAt -> getReadInfo -> readerType to determine reader strategy. - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: false}).Once() + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: false}).Twice() buf := make([]byte, readLength) objectData, err := t.rr.ReadAt(buf, readOffset) diff --git a/internal/gcsx/random_reader_test.go b/internal/gcsx/random_reader_test.go index f6b7fdd937..1a0a7f04be 100644 --- a/internal/gcsx/random_reader_test.go +++ b/internal/gcsx/random_reader_test.go @@ -236,7 +236,7 @@ func (t *RandomReaderTest) NoExistingReader() { // The bucket should be called to set up a new reader. ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). WillOnce(Return(nil, errors.New(""))) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) buf := make([]byte, 1) _, err := t.rr.ReadAt(buf, 0) @@ -258,6 +258,8 @@ func (t *RandomReaderTest) ExistingReader_ReadAtOffsetAfterTheReaderPosition() { t.rr.wrapped.start = currentStartOffset t.rr.wrapped.limit = readerLimit + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) + buf := make([]byte, readSize) _, err := t.rr.ReadAt(buf, readAtOffset) @@ -270,7 +272,7 @@ func (t *RandomReaderTest) ExistingReader_ReadAtOffsetAfterTheReaderPosition() { func (t *RandomReaderTest) NewReaderReturnsError() { ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). WillOnce(Return(nil, errors.New("taco"))) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) buf := make([]byte, 1) _, err := t.rr.ReadAt(buf, 0) @@ -286,7 +288,7 @@ func (t *RandomReaderTest) ReaderFails() { ExpectCall(t.bucket, "NewReaderWithReadHandle")(Any(), Any()). WillOnce(Return(rc, nil)) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) // Call buf := make([]byte, 3) @@ -303,6 +305,8 @@ func (t *RandomReaderTest) ReaderNotExhausted() { } rc := &fake.FakeReader{ReadCloser: cc} + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) + t.rr.wrapped.reader = rc t.rr.wrapped.cancel = func() {} t.rr.wrapped.start = 1 @@ -329,6 +333,8 @@ func (t *RandomReaderTest) ReaderExhausted_ReadFinished() { Reader: strings.NewReader("abc"), } + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) + t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: rc} t.rr.wrapped.cancel = func() {} t.rr.wrapped.start = 1 @@ -354,6 +360,8 @@ func (t *RandomReaderTest) PropagatesCancellation() { finishRead := make(chan struct{}) rc := io.NopCloser(&blockingReader{finishRead}) + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) + t.rr.wrapped.reader = &fake.FakeReader{ReadCloser: rc} t.rr.wrapped.start = 1 t.rr.wrapped.limit = 4 @@ -397,6 +405,8 @@ func (t *RandomReaderTest) DoesntPropagateCancellationAfterReturning() { t.rr.wrapped.start = 1 t.rr.wrapped.limit = 4 + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) + // Snoop on when cancel is called. cancelCalled := make(chan struct{}) t.rr.wrapped.cancel = func() { close(cancelCalled) } @@ -443,7 +453,7 @@ func (t *RandomReaderTest) UpgradesReadsToObjectSize() { Any(), AllOf(rangeStartIs(1), rangeLimitIs(objectSize))). WillOnce(Return(rc, nil)) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) // Call through. buf := make([]byte, readSize) @@ -485,7 +495,7 @@ func (t *RandomReaderTest) UpgradeReadsToAverageSize() { rangeStartIs(start), rangeLimitIs(start+expectedBytesToRead), )).WillOnce(Return(rc, nil)) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) // Call through. buf := make([]byte, readSize) @@ -519,7 +529,7 @@ func (t *RandomReaderTest) UpgradesSequentialReads_ExistingReader() { Any(), AllOf(rangeStartIs(1), rangeLimitIs(1+sequentialReadSizeInBytes))). WillOnce(Return(rc, nil)) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) // Call through. buf := make([]byte, readSize) @@ -554,7 +564,7 @@ func (t *RandomReaderTest) UpgradesSequentialReads_NoExistingReader() { Any(), AllOf(rangeStartIs(1), rangeLimitIs(1+readSize))). WillOnce(Return(rc, nil)) - ExpectCall(t.bucket, "BucketType")().WillOnce(Return(t.bucketType)) + ExpectCall(t.bucket, "BucketType")().Times(2).WillOnce(Return(t.bucketType)) // Call through. buf := make([]byte, readSize) From 3741dfa24c45bb15e50cc352be5f54355943784f Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Fri, 1 Aug 2025 17:22:18 +0530 Subject: [PATCH 0637/1298] feat(block-pool): adding tryGet similar to Get() but doesn't block execution when block-pool is full (#3625) * feat(block-pool): adding tryGet method to return early in case block-limit reached in block-pool * review comments --- internal/block/block_pool.go | 27 ++++++++++ internal/block/block_pool_test.go | 82 +++++++++++++++++++++++++++---- 2 files changed, 99 insertions(+), 10 deletions(-) diff --git a/internal/block/block_pool.go b/internal/block/block_pool.go index a6bef63304..ec5f5b9dd9 100644 --- a/internal/block/block_pool.go +++ b/internal/block/block_pool.go @@ -103,6 +103,33 @@ func (bp *GenBlockPool[T]) Get() (T, error) { } } +// TryGet returns a block if available, or an error if no blocks can be allocated. +// It returns an existing block if it's ready for reuse or creates a new one if required. +// Not thread-safe, calling from multiple goroutines may lead to memory leaks because +// of race conditions. +func (bp *GenBlockPool[T]) TryGet() (T, error) { + select { + case b := <-bp.freeBlocksCh: + // Reset the block for reuse. + b.Reuse() + return b, nil + + default: + if bp.canAllocateBlock() { + b, err := bp.createBlockFunc(bp.blockSize) + if err != nil { + var zero T + return zero, err + } + + bp.totalBlocks++ + return b, nil + } + var zero T + return zero, CantAllocateAnyBlockError + } +} + // canAllocateBlock checks if a new block can be allocated. func (bp *GenBlockPool[T]) canAllocateBlock() bool { // If max blocks limit is reached, then no more blocks can be allocated. diff --git a/internal/block/block_pool_test.go b/internal/block/block_pool_test.go index 9fd1df39a2..f5c6f6a4c6 100644 --- a/internal/block/block_pool_test.go +++ b/internal/block/block_pool_test.go @@ -16,7 +16,6 @@ package block import ( "fmt" - "io" "testing" "time" @@ -75,21 +74,53 @@ func (t *BlockPoolTest) TestInitBlockPoolForNegativeMaxBlocks() { } // Represents when block is available on the freeBlocksCh. -func (t *BlockPoolTest) TestGetWhenBlockIsAvailableForReuse() { +func (t *BlockPoolTest) TestTryGetWhenBlockIsAvailableForReuse() { bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) // Creating a block with some data and send it to blockCh. b, err := createBlock(2) require.Nil(t.T(), err) - content := []byte("hi") - n, err := b.Write(content) - require.Equal(t.T(), 2, n) + bp.freeBlocksCh <- b + // Setting totalBlocks same as maxBlocks to ensure no new blocks are created. + bp.totalBlocks = 10 + + block, err := bp.TryGet() + + require.Nil(t.T(), err) + require.NotNil(t.T(), block) + // This ensures the block is reset. + assert.Equal(t.T(), int64(0), block.Size()) +} + +func (t *BlockPoolTest) TestTryGetWhenTotalBlocksIsLessThanThanMaxBlocks() { + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) + require.Nil(t.T(), err) + + block, err := bp.TryGet() + + require.Nil(t.T(), err) + require.NotNil(t.T(), block) + assert.Equal(t.T(), int64(0), block.Size()) +} + +func (t *BlockPoolTest) TestTryGetToCreateLargeBlock() { + // Creating block of size 1TB + bp, err := NewGenBlockPool(1024*1024*1024*1024, 10, semaphore.NewWeighted(10), createBlock) + require.Nil(t.T(), err) + + _, err = bp.TryGet() + + require.NotNil(t.T(), err) + assert.Equal(t.T(), "mmap error: cannot allocate memory", err.Error()) +} + +// Represents when block is available on the freeBlocksCh. +func (t *BlockPoolTest) TestGetWhenBlockIsAvailableForReuse() { + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) - // Validating the content of the block - require.Equal(t.T(), int64(0), b.(*memoryBlock).readSeek) - output, err := io.ReadAll(b) + // Creating a block with some data and send it to blockCh. + b, err := createBlock(2) require.Nil(t.T(), err) - require.Equal(t.T(), content, output) bp.freeBlocksCh <- b // Setting totalBlocks same as maxBlocks to ensure no new blocks are created. bp.totalBlocks = 10 @@ -244,6 +275,37 @@ func (t *BlockPoolTest) TestBlockPoolCreationFailsWhenGlobalMaxBlocksIsZero() { assert.ErrorContains(t.T(), err, CantAllocateAnyBlockError.Error()) } +func (t *BlockPoolTest) TestTryGetWhenLimitedByGlobalBlocks() { + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(2), createBlock) + require.Nil(t.T(), err) + // 2 blocks can be created. + b1, err1 := bp.TryGet() + require.Nil(t.T(), err1) + require.NotNil(t.T(), b1) + b2, err2 := bp.TryGet() + require.Nil(t.T(), err2) + require.NotNil(t.T(), b2) + + b3, err3 := bp.TryGet() + + require.Nil(t.T(), b3) + require.NotNil(t.T(), err3) + require.ErrorIs(t.T(), err3, CantAllocateAnyBlockError) + require.Equal(t.T(), int64(2), bp.totalBlocks) +} + +func (t *BlockPoolTest) TestTryGetWhenTotalBlocksEqualToMaxBlocks() { + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) + require.Nil(t.T(), err) + bp.totalBlocks = 10 + + b, err := bp.TryGet() + + require.NotNil(t.T(), err) + assert.Equal(t.T(), CantAllocateAnyBlockError, err) + require.Nil(t.T(), b) +} + func (t *BlockPoolTest) TestGetWhenLimitedByGlobalBlocks() { bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(2), createBlock) require.Nil(t.T(), err) @@ -258,7 +320,7 @@ func (t *BlockPoolTest) TestGetWhenLimitedByGlobalBlocks() { } func (t *BlockPoolTest) TestGetWhenTotalBlocksEqualToMaxBlocks() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(2), createBlock) + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) bp.totalBlocks = 10 From 1b3a328ccdacc28116a0303cff79c208cc00cdaf Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Sat, 2 Aug 2025 11:55:48 +0530 Subject: [PATCH 0638/1298] feat(bufferedread): Integrate Buffered Reader with the filesystem (#3618) * Integrate Buffered Reader with filesystem * Update internal/fs/fs.go * resolved comments * Move NewStaticWorkerPoolForCurrentCPU to workerpool --- internal/bufferedread/buffered_reader.go | 49 +++++--- internal/bufferedread/buffered_reader_test.go | 15 +-- internal/fs/fs.go | 26 +++- internal/fs/handle/file.go | 27 +++-- internal/fs/handle/file_test.go | 24 ++-- internal/gcsx/read_manager/read_manager.go | 31 +++++ .../gcsx/read_manager/read_manager_test.go | 113 ++++++++++++++++-- internal/workerpool/static_worker_pool.go | 22 ++++ .../workerpool/static_worker_pool_test.go | 20 ++++ 9 files changed, 264 insertions(+), 63 deletions(-) diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index f7b6b0c1f8..244a21a10b 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -24,6 +24,7 @@ import ( "github.com/google/uuid" "github.com/googlecloudplatform/gcsfuse/v3/common" "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" @@ -34,18 +35,19 @@ import ( // ErrPrefetchBlockNotAvailable is returned when a block cannot be // acquired from the pool for prefetching. This can be used by callers to -// implement a fallback mechanism, e.g. falling back to a direct GCS read. +// implement a fallback mechanism, e.g. falling back to another reader. var ErrPrefetchBlockNotAvailable = errors.New("block for prefetching not available") type BufferedReadConfig struct { MaxPrefetchBlockCnt int64 // Maximum number of blocks that can be prefetched. PrefetchBlockSizeBytes int64 // Size of each block to be prefetched. InitialPrefetchBlockCnt int64 // Number of blocks to prefetch initially. - PrefetchMultiplier int64 // Multiplier for number of blocks to prefetch. - RandomReadsThreshold int64 // Number of random reads after which the reader falls back to GCS reader. } -const MiB = 1 << 20 +const ( + defaultRandomReadsThreshold = 3 + defaultPrefetchMultiplier = 2 +) // blockQueueEntry holds a data block with a function // to cancel its in-flight download. @@ -65,7 +67,7 @@ type BufferedReader struct { nextBlockIndexToPrefetch int64 // randomSeekCount is the number of random seeks performed. This is used to - // detect if the read pattern is random and fall back to a simpler GCS reader. + // detect if the read pattern is random and fall back to another reader. randomSeekCount int64 // numPrefetchBlocks is the number of blocks to prefetch in the next @@ -82,6 +84,10 @@ type BufferedReader struct { ctx context.Context cancelFunc context.CancelFunc + + prefetchMultiplier int64 // Multiplier for number of blocks to prefetch. + + randomReadsThreshold int64 // Number of random reads after which the reader falls back to another reader. } // NewBufferedReader returns a new bufferedReader instance. @@ -102,6 +108,8 @@ func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *Buffere blockPool: blockpool, workerPool: workerPool, metricHandle: metricHandle, + prefetchMultiplier: defaultPrefetchMultiplier, + randomReadsThreshold: defaultRandomReadsThreshold, } reader.ctx, reader.cancelFunc = context.WithCancel(context.Background()) @@ -111,13 +119,13 @@ func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *Buffere // handleRandomRead detects and handles random read patterns. A read is considered // random if the requested offset is outside the currently prefetched window. // If the number of detected random reads exceeds a configured threshold, it -// returns a gcsx.FallbackToAnotherReader error to signal that a simpler GCS -// reader should be used. +// returns a gcsx.FallbackToAnotherReader error to signal that another reader +// should be used. func (p *BufferedReader) handleRandomRead(offset int64) error { - // Exit early if we have already decided to fall back to a GCS reader. This - // avoids re-evaluating the read pattern on every call when the random read - // threshold has been met. - if p.randomSeekCount > p.config.RandomReadsThreshold { + // Exit early if we have already decided to fall back to another reader. + // This avoids re-evaluating the read pattern on every call when the random + // read threshold has been met. + if p.randomSeekCount > p.randomReadsThreshold { return gcsx.FallbackToAnotherReader } @@ -139,9 +147,9 @@ func (p *BufferedReader) handleRandomRead(offset int64) error { p.blockPool.Release(entry.block) } - if p.randomSeekCount > p.config.RandomReadsThreshold { - logger.Tracef("Too many random reads for object %q (count: %d, threshold: %d); falling back to GCS reader.", - p.object.Name, p.randomSeekCount, p.config.RandomReadsThreshold) + if p.randomSeekCount > p.randomReadsThreshold { + logger.Tracef("Too many random reads for object %q (count: %d, threshold: %d); falling back to another reader.", + p.object.Name, p.randomSeekCount, p.randomReadsThreshold) return gcsx.FallbackToAnotherReader } @@ -253,6 +261,9 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) if p.blockQueue.IsEmpty() { if err = p.freshStart(off); err != nil { + if errors.Is(err, ErrPrefetchBlockNotAvailable) { + return resp, gcsx.FallbackToAnotherReader + } err = fmt.Errorf("ReadAt: freshStart failed: %w", err) break } @@ -340,7 +351,7 @@ func (p *BufferedReader) prefetch() error { } // Set the size for the next multiplicative prefetch. - p.numPrefetchBlocks *= p.config.PrefetchMultiplier + p.numPrefetchBlocks *= p.prefetchMultiplier // Do not prefetch more than MaxPrefetchBlockCnt blocks. if p.numPrefetchBlocks > p.config.MaxPrefetchBlockCnt { @@ -442,7 +453,7 @@ func (p *BufferedReader) CheckInvariants() { } // The prefetch block size must be at least 1 MiB. - if p.config.PrefetchBlockSizeBytes < MiB { + if p.config.PrefetchBlockSizeBytes < util.MiB { panic(fmt.Sprintf("BufferedReader: PrefetchBlockSizeBytes must be at least 1 MiB, but is %d", p.config.PrefetchBlockSizeBytes)) } @@ -451,8 +462,8 @@ func (p *BufferedReader) CheckInvariants() { panic(fmt.Sprintf("BufferedReader: blockQueue length %d exceeds limit %d", p.blockQueue.Len(), p.config.MaxPrefetchBlockCnt)) } - // The random seek count should never exceed RandomReadsThreshold. - if p.randomSeekCount > p.config.RandomReadsThreshold { - panic(fmt.Sprintf("BufferedReader: randomSeekCount %d exceeds threshold %d", p.randomSeekCount, p.config.RandomReadsThreshold)) + // The random seek count should never exceed randomReadsThreshold. + if p.randomSeekCount > p.randomReadsThreshold { + panic(fmt.Sprintf("BufferedReader: randomSeekCount %d exceeds threshold %d", p.randomSeekCount, p.randomReadsThreshold)) } } diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index d86585703a..2708fc7138 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -22,6 +22,7 @@ import ( "testing" "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" @@ -41,8 +42,6 @@ const ( testGlobalMaxBlocks int64 = 20 testPrefetchBlockSizeBytes int64 = 1024 testInitialPrefetchBlockCnt int64 = 2 - testPrefetchMultiplier int64 = 2 - testRandomReadsThreshold int64 = 3 ) type BufferedReaderTest struct { @@ -113,8 +112,6 @@ func (t *BufferedReaderTest) SetupTest() { MaxPrefetchBlockCnt: testMaxPrefetchBlockCnt, PrefetchBlockSizeBytes: testPrefetchBlockSizeBytes, InitialPrefetchBlockCnt: testInitialPrefetchBlockCnt, - PrefetchMultiplier: testPrefetchMultiplier, - RandomReadsThreshold: testRandomReadsThreshold, } var err error t.workerPool, err = workerpool.NewStaticWorkerPool(5, 10) @@ -200,7 +197,7 @@ func (t *BufferedReaderTest) TestCheckInvariantsRandomSeekCountExceedsThreshold( reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err, "NewBufferedReader should not return error") - reader.randomSeekCount = t.config.RandomReadsThreshold + 1 + reader.randomSeekCount = reader.randomReadsThreshold + 1 assert.Panics(t.T(), func() { reader.CheckInvariants() }) } @@ -234,13 +231,13 @@ func (t *BufferedReaderTest) TestCheckInvariantsPrefetchBlockSizeTooSmall() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err, "NewBufferedReader should not return error") - reader.config.PrefetchBlockSizeBytes = MiB - 1 + reader.config.PrefetchBlockSizeBytes = util.MiB - 1 assert.Panics(t.T(), func() { reader.CheckInvariants() }, "Should panic for block size less than 1 MiB") } func (t *BufferedReaderTest) TestCheckInvariantsNoPanic() { - t.config.PrefetchBlockSizeBytes = MiB + t.config.PrefetchBlockSizeBytes = util.MiB reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err, "NewBufferedReader should not return error") @@ -542,7 +539,6 @@ func (t *BufferedReaderTest) TestPrefetch() { func (t *BufferedReaderTest) TestPrefetchWithMultiplicativeIncrease() { t.config.InitialPrefetchBlockCnt = 1 - t.config.PrefetchMultiplier = 2 reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err) // First prefetch schedules 1 block. @@ -990,7 +986,6 @@ func (t *BufferedReaderTest) TestReadAtSpanningMultipleBlocks() { func (t *BufferedReaderTest) TestReadAtSequentialReadAcrossBlocks() { t.config.InitialPrefetchBlockCnt = 1 - t.config.PrefetchMultiplier = 2 reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err) // Mock reads for all blocks that will be downloaded. @@ -1032,9 +1027,9 @@ func (t *BufferedReaderTest) TestReadAtSequentialReadAcrossBlocks() { } func (t *BufferedReaderTest) TestReadAtFallsBackAfterRandomReads() { - t.config.RandomReadsThreshold = 2 t.config.InitialPrefetchBlockCnt = 1 reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + reader.randomReadsThreshold = 2 require.NoError(t.T(), err) buf := make([]byte, 10) // Mock GCS calls for the first random read, which will download block 2 and prefetch block 3. diff --git a/internal/fs/fs.go b/internal/fs/fs.go index f6f75d376f..18346da371 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -30,6 +30,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/metadata" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" + "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" "github.com/googlecloudplatform/gcsfuse/v3/metrics" "golang.org/x/sync/semaphore" @@ -205,11 +206,20 @@ func NewFileSystem(ctx context.Context, serverCfg *ServerConfig) (fuseutil.FileS metricHandle: serverCfg.MetricHandle, enableAtomicRenameObject: serverCfg.NewConfig.EnableAtomicRenameObject, globalMaxWriteBlocksSem: semaphore.NewWeighted(serverCfg.NewConfig.Write.GlobalMaxBlocks), + globalMaxReadBlocksSem: semaphore.NewWeighted(serverCfg.NewConfig.Read.GlobalMaxBlocks), } if serverCfg.Notifier != nil { fs.notifier = serverCfg.Notifier } + if serverCfg.NewConfig.Read.EnableBufferedRead { + var err error + fs.bufferedReadWorkerPool, err = workerpool.NewStaticWorkerPoolForCurrentCPU() + if err != nil { + return nil, fmt.Errorf("failed to create worker pool for buffered read: %w", err) + } + } + // Set up root bucket var root inode.DirInode if serverCfg.BucketName == "" || serverCfg.BucketName == "_" { @@ -508,6 +518,15 @@ type fileSystem struct { // It is used to invalidate the kernel's dentry cache, // providing feedback to the kernel about dynamic content changes. notifier *fuse.Notifier + + // bufferedReadWorkerPool is used for asynchronous prefetching of data for buffered reads. + // It executes download tasks associated with prefetch blocks. + bufferedReadWorkerPool workerpool.WorkerPool + + // globalMaxReadBlocksSem is a semaphore that limits the total number of blocks + // that can be allocated for buffered read across all file-handles in the file system. + // This helps control the overall memory usage for buffered reads. + globalMaxReadBlocksSem *semaphore.Weighted } //////////////////////////////////////////////////////////////////////// @@ -1624,6 +1643,9 @@ func (fs *fileSystem) Destroy() { if fs.fileCacheHandler != nil { _ = fs.fileCacheHandler.Destroy() } + if fs.bufferedReadWorkerPool != nil { + fs.bufferedReadWorkerPool.Stop() + } } func (fs *fileSystem) StatFS( @@ -2011,7 +2033,7 @@ func (fs *fileSystem) CreateFile( // CreateFile() invoked to create new files, can be safely considered as filehandle // opened in append mode. - fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, util.Append, fs.newConfig) + fs.handles[handleID] = handle.NewFileHandle(child.(*inode.FileInode), fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, util.Append, fs.newConfig, fs.bufferedReadWorkerPool, fs.globalMaxReadBlocksSem) op.Handle = handleID fs.mu.Unlock() @@ -2774,7 +2796,7 @@ func (fs *fileSystem) OpenFile( // Figure out the mode in which the file is being opened. openMode := util.FileOpenMode(op) - fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, openMode, fs.newConfig) + fs.handles[handleID] = handle.NewFileHandle(in, fs.fileCacheHandler, fs.cacheFileForRangeRead, fs.metricHandle, openMode, fs.newConfig, fs.bufferedReadWorkerPool, fs.globalMaxReadBlocksSem) op.Handle = handleID // When we observe object generations that we didn't create, we assign them diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 5a5f3c5749..c73de2c41d 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -26,9 +26,11 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx/read_manager" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/syncutil" "golang.org/x/net/context" + "golang.org/x/sync/semaphore" ) type FileHandle struct { @@ -65,17 +67,26 @@ type FileHandle struct { // Mount configuration. config *cfg.Config + + // bufferedReadWorkerPool is used to execute download tasks for buffered reads. + bufferedReadWorkerPool workerpool.WorkerPool + + // globalMaxReadBlocksSem is a semaphore that limits the total number of blocks + // that can be allocated for buffered read across all files in the file system. + globalMaxReadBlocksSem *semaphore.Weighted } // LOCKS_REQUIRED(fh.inode.mu) -func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle metrics.MetricHandle, openMode util.OpenMode, c *cfg.Config) (fh *FileHandle) { +func NewFileHandle(inode *inode.FileInode, fileCacheHandler *file.CacheHandler, cacheFileForRangeRead bool, metricHandle metrics.MetricHandle, openMode util.OpenMode, c *cfg.Config, bufferedReadWorkerPool workerpool.WorkerPool, globalMaxReadBlocksSem *semaphore.Weighted) (fh *FileHandle) { fh = &FileHandle{ - inode: inode, - fileCacheHandler: fileCacheHandler, - cacheFileForRangeRead: cacheFileForRangeRead, - metricHandle: metricHandle, - openMode: openMode, - config: c, + inode: inode, + fileCacheHandler: fileCacheHandler, + cacheFileForRangeRead: cacheFileForRangeRead, + metricHandle: metricHandle, + openMode: openMode, + config: c, + bufferedReadWorkerPool: bufferedReadWorkerPool, + globalMaxReadBlocksSem: globalMaxReadBlocksSem, } fh.inode.RegisterFileHandle(fh.openMode == util.Read) @@ -323,6 +334,8 @@ func (fh *FileHandle) tryEnsureReadManager(ctx context.Context, sequentialReadSi MetricHandle: fh.metricHandle, MrdWrapper: &fh.inode.MRDWrapper, Config: fh.config, + WorkerPool: fh.bufferedReadWorkerPool, + GlobalMaxBlocksSem: fh.globalMaxReadBlocksSem, }) return nil diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 165ea92a5f..6a20639b42 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -145,7 +145,7 @@ func (t *fileTest) TestFileHandleWrite() { parent := createDirInode(&t.bucket, &t.clock) config := &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: false}} in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj", nil, false) - fh := NewFileHandle(in, nil, false, nil, util.Write, &cfg.Config{}) + fh := NewFileHandle(in, nil, false, nil, util.Write, &cfg.Config{}, nil, nil) data := []byte("hello") _, err := fh.Write(t.ctx, data, 0) @@ -168,7 +168,7 @@ func (t *fileTest) Test_Read_Success() { expectedData := []byte("hello from reader") parent := createDirInode(&t.bucket, &t.clock) in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, "test_obj_reader", expectedData, false) - fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) buf := make([]byte, len(expectedData)) fh.inode.Lock() @@ -184,7 +184,7 @@ func (t *fileTest) Test_ReadWithReadManager_Success() { expectedData := []byte("hello from readManager") parent := createDirInode(&t.bucket, &t.clock) in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, "test_obj_readManager", expectedData, false) - fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) buf := make([]byte, len(expectedData)) fh.inode.Lock() @@ -216,7 +216,7 @@ func (t *fileTest) Test_ReadWithReadManager_ErrorScenarios() { t.SetupTest() parent := createDirInode(&t.bucket, &t.clock) testInode := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, []byte("data"), false) - fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) + fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) fh.inode.Lock() mockRM := new(read_manager.MockReadManager) mockRM.On("ReadAt", t.ctx, dst, int64(0)).Return(gcsx.ReaderResponse{}, tc.returnErr) @@ -254,7 +254,7 @@ func (t *fileTest) Test_Read_ErrorScenarios() { t.SetupTest() parent := createDirInode(&t.bucket, &t.clock) testInode := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, []byte("data"), false) - fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) + fh := NewFileHandle(testInode, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) fh.inode.Lock() mockReader := new(gcsx.MockRandomReader) mockReader.On("ReadAt", t.ctx, dst, int64(0)).Return(gcsx.ObjectData{}, tc.returnErr) @@ -279,7 +279,7 @@ func (t *fileTest) Test_ReadWithReadManager_FallbackToInode() { object := gcs.MinObject{Name: "test_obj", Generation: 0} parent := createDirInode(&t.bucket, &t.clock) in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, objectData, true) - fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) fh.inode.Lock() mockRM := new(read_manager.MockReadManager) mockRM.On("Destroy").Return() @@ -301,7 +301,7 @@ func (t *fileTest) Test_Read_FallbackToInode() { object := gcs.MinObject{Name: "test_obj", Generation: 0} parent := createDirInode(&t.bucket, &t.clock) in := createFileInode(t.T(), &t.bucket, &t.clock, nil, parent, object.Name, objectData, true) - fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) fh.inode.Lock() mockR := new(gcsx.MockRandomReader) mockR.On("Destroy").Return() @@ -337,7 +337,7 @@ func (t *fileTest) TestOpenMode() { parent := createDirInode(&t.bucket, &t.clock) config := &cfg.Config{Write: cfg.WriteConfig{EnableStreamingWrites: false}} in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj", nil, false) - fh := NewFileHandle(in, nil, false, nil, tc.openMode, &cfg.Config{}) + fh := NewFileHandle(in, nil, false, nil, tc.openMode, &cfg.Config{}, nil, nil) openMode := fh.OpenMode() @@ -356,7 +356,7 @@ func (t *fileTest) TestFileHandle_Destroy_WithReaderAndReadManager() { mockReader.On("Destroy").Once() mockReadManager.On("Destroy").Once() // Construct file handle with mocks - fh := NewFileHandle(fileInode, nil, false, nil, util.Read, config) + fh := NewFileHandle(fileInode, nil, false, nil, util.Read, config, nil, nil) fh.reader = mockReader fh.readManager = mockReadManager @@ -372,7 +372,7 @@ func (t *fileTest) TestFileHandle_Destroy_WithNilReaderAndReadManager() { config := &cfg.Config{} fileInode := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "destroy_test_nil_obj", nil, false) // Construct file handle with nils - fh := NewFileHandle(fileInode, nil, false, nil, util.Read, config) + fh := NewFileHandle(fileInode, nil, false, nil, util.Read, config, nil, nil) fh.reader = nil fh.readManager = nil @@ -392,7 +392,7 @@ func (t *fileTest) TestFileHandle_CheckInvariants_WithNonNilReaderAndManager() { // Expectations mockReader.On("CheckInvariants").Once() mockRM.On("CheckInvariants").Once() - fh := NewFileHandle(fileInode, nil, false, nil, util.Read, config) + fh := NewFileHandle(fileInode, nil, false, nil, util.Read, config, nil, nil) fh.reader = mockReader fh.readManager = mockRM @@ -409,7 +409,7 @@ func (t *fileTest) TestFileHandle_CheckInvariants_WithNilReaderAndManager() { config := &cfg.Config{} in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_check_invariants_nil", nil, false) - fh := NewFileHandle(in, nil, false, nil, util.Read, config) + fh := NewFileHandle(in, nil, false, nil, util.Read, config, nil, nil) // Should not panic even if both are nil assert.NotPanics(t.T(), func() { diff --git a/internal/gcsx/read_manager/read_manager.go b/internal/gcsx/read_manager/read_manager.go index 69c03e2b00..f455008e2e 100644 --- a/internal/gcsx/read_manager/read_manager.go +++ b/internal/gcsx/read_manager/read_manager.go @@ -20,11 +20,17 @@ import ( "io" "github.com/googlecloudplatform/gcsfuse/v3/cfg" + + "github.com/googlecloudplatform/gcsfuse/v3/internal/bufferedread" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" + "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" clientReaders "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx/client_readers" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" + "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" "github.com/googlecloudplatform/gcsfuse/v3/metrics" + "golang.org/x/sync/semaphore" ) type ReadManager struct { @@ -44,6 +50,8 @@ type ReadManagerConfig struct { MetricHandle metrics.MetricHandle MrdWrapper *gcsx.MultiRangeDownloaderWrapper Config *cfg.Config + GlobalMaxBlocksSem *semaphore.Weighted + WorkerPool workerpool.WorkerPool } // NewReadManager creates a new ReadManager for the given GCS object, @@ -65,6 +73,29 @@ func NewReadManager(object *gcs.MinObject, bucket gcs.Bucket, config *ReadManage readers = append(readers, fileCacheReader) // File cache reader is prioritized. } + // If buffered read is enabled, initialize the buffered reader and add it to the readers. + if config.Config.Read.EnableBufferedRead { + readConfig := config.Config.Read + bufferedReadConfig := &bufferedread.BufferedReadConfig{ + MaxPrefetchBlockCnt: readConfig.MaxBlocksPerHandle, + PrefetchBlockSizeBytes: readConfig.BlockSizeMb * util.MiB, + InitialPrefetchBlockCnt: readConfig.StartBlocksPerHandle, + } + bufferedReader, err := bufferedread.NewBufferedReader( + object, + bucket, + bufferedReadConfig, + config.GlobalMaxBlocksSem, + config.WorkerPool, + config.MetricHandle, + ) + if err != nil { + logger.Warnf("Failed to create bufferedReader: %v. Buffered reading will be disabled for this file handle.", err) + } else { + readers = append(readers, bufferedReader) + } + } + // Initialize the GCS reader, which is always present. gcsReader := clientReaders.NewGCSReader( object, diff --git a/internal/gcsx/read_manager/read_manager_test.go b/internal/gcsx/read_manager/read_manager_test.go index 389c3aa14a..57a7694682 100644 --- a/internal/gcsx/read_manager/read_manager_test.go +++ b/internal/gcsx/read_manager/read_manager_test.go @@ -26,6 +26,7 @@ import ( "testing/iotest" "github.com/googlecloudplatform/gcsfuse/v3/cfg" + "github.com/googlecloudplatform/gcsfuse/v3/internal/bufferedread" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/file/downloader" "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/lru" @@ -37,10 +38,12 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" testUtil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/suite" + "golang.org/x/sync/semaphore" ) const ( @@ -53,13 +56,28 @@ const ( // Helpers //////////////////////////////////////////////////////////////////////// -func (t *readManagerTest) readManagerConfig(fileCacheEnable bool) *ReadManagerConfig { +func (t *readManagerTest) readManagerConfig(fileCacheEnable bool, bufferedReadEnable bool) *ReadManagerConfig { config := &ReadManagerConfig{ SequentialReadSizeMB: sequentialReadSizeInMb, CacheFileForRangeRead: false, MetricHandle: metrics.NewNoopMetrics(), MrdWrapper: nil, + Config: &cfg.Config{ + Read: cfg.ReadConfig{ + EnableBufferedRead: bufferedReadEnable, + MaxBlocksPerHandle: 10, + BlockSizeMb: 1, + StartBlocksPerHandle: 2, + }, + }, + GlobalMaxBlocksSem: semaphore.NewWeighted(20), + } + if bufferedReadEnable { + t.workerPool, _ = workerpool.NewStaticWorkerPool(5, 20) + t.workerPool.Start() + config.WorkerPool = t.workerPool } + if fileCacheEnable { cacheDir := path.Join(os.Getenv("HOME"), "test_cache_dir") lruCache := lru.NewCache(cacheMaxSize) @@ -100,6 +118,7 @@ type readManagerTest struct { readManager *ReadManager ctx context.Context bucketType gcs.BucketType + workerPool workerpool.WorkerPool } func TestNonZonalBucketReadManagerTestSuite(t *testing.T) { @@ -118,18 +137,22 @@ func (t *readManagerTest) SetupTest() { } t.mockBucket = new(storage.TestifyMockBucket) t.ctx = context.Background() - t.readManager = NewReadManager(t.object, t.mockBucket, t.readManagerConfig(true)) + t.readManager = NewReadManager(t.object, t.mockBucket, t.readManagerConfig(true, false)) } func (t *readManagerTest) TearDownTest() { t.readManager.Destroy() + if t.workerPool != nil { + t.workerPool.Stop() + t.workerPool = nil + } } // ////////////////////////////////////////////////////////////////////// // Tests // ////////////////////////////////////////////////////////////////////// -func (t *readManagerTest) Test_NewReadManager_WithFileCacheHandler() { - config := t.readManagerConfig(true) +func (t *readManagerTest) Test_NewReadManager_WithFileCacheHandlerOnly() { + config := t.readManagerConfig(true, false) rm := NewReadManager(t.object, t.mockBucket, config) @@ -141,8 +164,8 @@ func (t *readManagerTest) Test_NewReadManager_WithFileCacheHandler() { assert.True(t.T(), ok2, "Second reader should be GCSReader") } -func (t *readManagerTest) Test_NewReadManager_WithoutFileCacheHandler() { - config := t.readManagerConfig(false) +func (t *readManagerTest) Test_NewReadManager_WithoutFileCacheAndBufferedRead() { + config := t.readManagerConfig(false, false) rm := NewReadManager(t.object, t.mockBucket, config) @@ -152,6 +175,48 @@ func (t *readManagerTest) Test_NewReadManager_WithoutFileCacheHandler() { assert.True(t.T(), ok, "Only reader should be GCSReader") } +func (t *readManagerTest) Test_NewReadManager_WithBufferedRead() { + config := t.readManagerConfig(false, true) + + rm := NewReadManager(t.object, t.mockBucket, config) + + assert.Equal(t.T(), t.object, rm.Object()) + assert.Len(t.T(), rm.readers, 2) // BufferedReader and GCSReader + _, ok1 := rm.readers[0].(*bufferedread.BufferedReader) + _, ok2 := rm.readers[1].(*clientReaders.GCSReader) + assert.True(t.T(), ok1, "First reader should be BufferedReader") + assert.True(t.T(), ok2, "Second reader should be GCSReader") +} + +func (t *readManagerTest) Test_NewReadManager_WithFileCacheAndBufferedRead() { + config := t.readManagerConfig(true, true) + defer os.RemoveAll(path.Join(os.Getenv("HOME"), "test_cache_dir")) + + rm := NewReadManager(t.object, t.mockBucket, config) + + assert.Equal(t.T(), t.object, rm.Object()) + assert.Len(t.T(), rm.readers, 3) // FileCacheReader, BufferedReader, GCSReader + _, ok1 := rm.readers[0].(*gcsx.FileCacheReader) + _, ok2 := rm.readers[1].(*bufferedread.BufferedReader) + _, ok3 := rm.readers[2].(*clientReaders.GCSReader) + assert.True(t.T(), ok1, "First reader should be FileCacheReader") + assert.True(t.T(), ok2, "Second reader should be BufferedReader") + assert.True(t.T(), ok3, "Third reader should be GCSReader") +} + +func (t *readManagerTest) Test_NewReadManager_BufferedReaderCreationFails() { + config := t.readManagerConfig(false, true) + // Exhaust the semaphore + config.GlobalMaxBlocksSem = semaphore.NewWeighted(0) + + rm := NewReadManager(t.object, t.mockBucket, config) + + assert.Equal(t.T(), t.object, rm.Object()) + assert.Len(t.T(), rm.readers, 1) // Only GCSReader + _, ok := rm.readers[0].(*clientReaders.GCSReader) + assert.True(t.T(), ok, "Only reader should be GCSReader") +} + func (t *readManagerTest) Test_ReadAt_EmptyRead() { // Nothing should happen. readerResponse, err := t.readAt(0, 0) @@ -198,7 +263,7 @@ func (t *readManagerTest) Test_ReadAt_NoExistingReader() { } func (t *readManagerTest) Test_ReadAt_ReaderFailsWithTimeout() { - t.readManager = NewReadManager(t.object, t.mockBucket, t.readManagerConfig(false)) + t.readManager = NewReadManager(t.object, t.mockBucket, t.readManagerConfig(false, false)) r := iotest.OneByteReader(iotest.TimeoutReader(strings.NewReader("xxx"))) rc := &fake.FakeReader{ReadCloser: io.NopCloser(r)} t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(rc, nil).Once() @@ -258,21 +323,43 @@ func (t *readManagerTest) Test_ReadAt_R1FailsR2Succeeds() { expectedResp := gcsx.ReaderResponse{Size: 10} mockReader1 := new(gcsx.MockReader) mockReader2 := new(gcsx.MockReader) - t.readManager = &ReadManager{ + rm := &ReadManager{ object: t.object, readers: []gcsx.Reader{mockReader1, mockReader2}, } mockReader1.On("ReadAt", t.ctx, buf, offset).Return(gcsx.ReaderResponse{}, gcsx.FallbackToAnotherReader).Once() - mockReader1.On("CheckInvariants").Maybe() - mockReader1.On("Destroy").Maybe() + mockReader1.On("Destroy").Once() mockReader2.On("ReadAt", t.ctx, buf, offset).Return(expectedResp, nil).Once() - mockReader2.On("CheckInvariants").Maybe() - mockReader2.On("Destroy").Maybe() + mockReader2.On("Destroy").Once() - resp, err := t.readAt(offset, 10) + resp, err := rm.ReadAt(t.ctx, buf, offset) + rm.Destroy() assert.NoError(t.T(), err, "expected no error when second reader succeeds") assert.Equal(t.T(), expectedResp, resp, "expected response from second reader") mockReader1.AssertExpectations(t.T()) mockReader2.AssertExpectations(t.T()) } + +func (t *readManagerTest) Test_ReadAt_BufferedReaderFallsBack() { + offset := int64(0) + buf := make([]byte, 10) + mockBufferedReader := new(gcsx.MockReader) + mockGCSReader := new(gcsx.MockReader) + rm := &ReadManager{ + object: t.object, + readers: []gcsx.Reader{mockBufferedReader, mockGCSReader}, + } + mockBufferedReader.On("ReadAt", t.ctx, buf, offset).Return(gcsx.ReaderResponse{}, gcsx.FallbackToAnotherReader).Once() + mockBufferedReader.On("Destroy").Once() + mockGCSReader.On("ReadAt", t.ctx, buf, offset).Return(gcsx.ReaderResponse{Size: 10}, nil).Once() + mockGCSReader.On("Destroy").Once() + + resp, err := rm.ReadAt(t.ctx, buf, offset) + rm.Destroy() + + assert.NoError(t.T(), err) + assert.Equal(t.T(), gcsx.ReaderResponse{Size: 10}, resp) + mockBufferedReader.AssertExpectations(t.T()) + mockGCSReader.AssertExpectations(t.T()) +} diff --git a/internal/workerpool/static_worker_pool.go b/internal/workerpool/static_worker_pool.go index c11d85ef53..deb7af8c25 100644 --- a/internal/workerpool/static_worker_pool.go +++ b/internal/workerpool/static_worker_pool.go @@ -16,6 +16,7 @@ package workerpool import ( "fmt" + "runtime" "sync" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" @@ -59,6 +60,27 @@ func NewStaticWorkerPool(priorityWorker uint32, normalWorker uint32) (*staticWor }, nil } +// NewStaticWorkerPoolForCurrentCPU creates and starts a new worker pool. The +// number of workers is determined based on the number of available CPUs. +func NewStaticWorkerPoolForCurrentCPU() (WorkerPool, error) { + // It's a general heuristic to use 2-3 times the number of CPUs for I/O-bound tasks. + // We use 3x here as a balance between parallelism and resource consumption. + const workersPerCPU = 3 + totalWorkers := workersPerCPU * runtime.NumCPU() + + // 10% of total workers for priority, rounded up. + priorityWorkers := (totalWorkers + 9) / 10 + normalWorkers := totalWorkers - priorityWorkers + + wp, err := NewStaticWorkerPool(uint32(priorityWorkers), uint32(normalWorkers)) + if err != nil { + return nil, err + } + + wp.Start() + return wp, nil +} + // Start all the workers and wait till they start receiving requests func (swp *staticWorkerPool) Start() { for i := uint32(0); i < swp.priorityWorker; i++ { diff --git a/internal/workerpool/static_worker_pool_test.go b/internal/workerpool/static_worker_pool_test.go index 89634eadca..6ac1ae3a42 100644 --- a/internal/workerpool/static_worker_pool_test.go +++ b/internal/workerpool/static_worker_pool_test.go @@ -15,6 +15,7 @@ package workerpool import ( + "runtime" "testing" "time" @@ -154,3 +155,22 @@ func TestStaticWorkerPool_Stop(t *testing.T) { assert.Panics(t, func() { pool.normalCh <- &dummyTask{} }, "normalCh channel is not closed.") assert.Panics(t, func() { pool.priorityCh <- &dummyTask{} }, "priorityCh channel is not closed.") } + +func TestNewStaticWorkerPoolForCurrentCPU(t *testing.T) { + pool, err := NewStaticWorkerPoolForCurrentCPU() + require.NoError(t, err) + require.NotNil(t, pool) + defer pool.Stop() + staticPool, ok := pool.(*staticWorkerPool) + require.True(t, ok, "The returned pool should be of type *staticWorkerPool") + totalWorkers := 3 * runtime.NumCPU() + expectedPriorityWorkers := (3*runtime.NumCPU() + 9) / 10 + expectedNormalWorkers := totalWorkers - expectedPriorityWorkers + dt := &dummyTask{} + + pool.Schedule(true, dt) + + assert.Equal(t, uint32(expectedPriorityWorkers), staticPool.priorityWorker) + assert.Equal(t, uint32(expectedNormalWorkers), staticPool.normalWorker) + assert.Eventually(t, func() bool { return dt.executed }, 100*time.Millisecond, time.Millisecond, "Task was not executed in time.") +} From 97b7272a7a8a1f0bf99794397585fb3f7e46f2c9 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 4 Aug 2025 09:17:23 +0530 Subject: [PATCH 0639/1298] removing backslash from gcs operation logging (#3627) --- internal/storage/debug_bucket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index e58da980b1..8590aab44a 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -137,7 +137,7 @@ func (b *debugBucket) BucketType() gcs.BucketType { } func setupReader(ctx context.Context, b *debugBucket, req *gcs.ReadObjectRequest, method string) (gcs.StorageReader, error) { - id, desc, start := b.startRequest("%q(%q, %v)", method, req.Name, req.Range) + id, desc, start := b.startRequest("%s(%q, %v)", method, req.Name, req.Range) // Call through. rc, err := b.wrapped.NewReaderWithReadHandle(ctx, req) From 105b9b191fa8cebc42b0022d3b3c87f2b040e903 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:53:01 +0530 Subject: [PATCH 0640/1298] feat(MoveObject): Enable move object for flat bucket (#3536) * enable moveObject api for flat bucket * added composite tests * rename function names * atomic rename in dir rename * adding unit tests * lint fix * small fix * fix unit tests * adding unit tests --- internal/fs/flat_bucket_test.go | 65 ++++ internal/fs/fs.go | 32 +- internal/fs/fs_test.go | 4 +- internal/fs/hns_bucket_test.go | 270 +--------------- internal/fs/rename_dir_test.go | 293 ++++++++++++++++++ ...ket_common_test.go => rename_file_test.go} | 8 +- internal/fs/zonal_bucket_test.go | 2 +- 7 files changed, 395 insertions(+), 279 deletions(-) create mode 100644 internal/fs/flat_bucket_test.go create mode 100644 internal/fs/rename_dir_test.go rename internal/fs/{hns_bucket_common_test.go => rename_file_test.go} (93%) diff --git a/internal/fs/flat_bucket_test.go b/internal/fs/flat_bucket_test.go new file mode 100644 index 0000000000..e92dcbf11d --- /dev/null +++ b/internal/fs/flat_bucket_test.go @@ -0,0 +1,65 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type FlatBucketTests struct { + suite.Suite + fsTest + RenameDirTests + RenameFileTests +} + +func TestFlatBucketTests(t *testing.T) { suite.Run(t, new(FlatBucketTests)) } + +func (t *FlatBucketTests) SetT(testingT *testing.T) { + t.Suite.SetT(testingT) + t.RenameDirTests.SetT(testingT) + t.RenameFileTests.SetT(testingT) +} + +func (t *FlatBucketTests) SetupSuite() { + t.serverCfg.RenameDirLimit = 20 + t.serverCfg.ImplicitDirectories = true + t.fsTest.SetUpTestSuite() +} + +func (t *FlatBucketTests) TearDownSuite() { + t.fsTest.TearDownTestSuite() +} + +func (t *FlatBucketTests) SetupTest() { + err := t.createObjects( + map[string]string{ + "foo/test2/": "", + "foo/test/": "", + "foo/file1.txt": file1Content, + "foo/file2.txt": file2Content, + "foo/test/file3.txt": "xyz", + "foo/implicit_dir/file3.txt": "xxw", + "bar/file1.txt": "-1234556789", + }) + require.NoError(t.T(), err) +} + +func (t *FlatBucketTests) TearDownTest() { + t.fsTest.TearDown() +} diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 18346da371..f9979c7cc1 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2295,10 +2295,10 @@ func (fs *fileSystem) renameFile(ctx context.Context, op *fuseops.RenameOp, oldO if err != nil { return fmt.Errorf("flushPendingWrites: %w", err) } - if (oldObject.Bucket().BucketType().Hierarchical && fs.enableAtomicRenameObject) || oldObject.Bucket().BucketType().Zonal { - return fs.renameHierarchicalFile(ctx, oldParent, op.OldName, updatedMinObject, newParent, op.NewName) + if fs.enableAtomicRenameObject || oldObject.Bucket().BucketType().Zonal { + return fs.atomicRename(ctx, oldParent, op.OldName, updatedMinObject, newParent, op.NewName) } - return fs.renameNonHierarchicalFile(ctx, oldParent, op.OldName, updatedMinObject, newParent, op.NewName) + return fs.nonAtomicRename(ctx, oldParent, op.OldName, updatedMinObject, newParent, op.NewName) } // LOCKS_EXCLUDED(fileInode) @@ -2319,7 +2319,7 @@ func (fs *fileSystem) flushPendingWrites(ctx context.Context, fileInode *inode.F // LOCKS_EXCLUDED(oldParent) // LOCKS_EXCLUDED(newParent) -func (fs *fileSystem) renameHierarchicalFile(ctx context.Context, oldParent inode.DirInode, oldName string, oldObject *gcs.MinObject, newParent inode.DirInode, newName string) error { +func (fs *fileSystem) atomicRename(ctx context.Context, oldParent inode.DirInode, oldName string, oldObject *gcs.MinObject, newParent inode.DirInode, newName string) error { oldParent.Lock() defer oldParent.Unlock() @@ -2335,7 +2335,7 @@ func (fs *fileSystem) renameHierarchicalFile(ctx context.Context, oldParent inod } if err := fs.invalidateChildFileCacheIfExist(oldParent, oldName); err != nil { - return fmt.Errorf("renameHierarchicalFile: while invalidating cache for delete file: %w", err) + return fmt.Errorf("atomicRename: while invalidating cache for renamed file: %w", err) } // Insert new file in type cache. @@ -2346,7 +2346,7 @@ func (fs *fileSystem) renameHierarchicalFile(ctx context.Context, oldParent inod // LOCKS_EXCLUDED(oldParent) // LOCKS_EXCLUDED(newParent) -func (fs *fileSystem) renameNonHierarchicalFile( +func (fs *fileSystem) nonAtomicRename( ctx context.Context, oldParent inode.DirInode, oldName string, @@ -2373,7 +2373,7 @@ func (fs *fileSystem) renameNonHierarchicalFile( &oldObject.MetaGeneration) if err := fs.invalidateChildFileCacheIfExist(oldParent, oldObject.Name); err != nil { - return fmt.Errorf("renameNonHierarchicalFile: while invalidating cache for delete file: %w", err) + return fmt.Errorf("nonAtomicRename: while invalidating cache for delete file: %w", err) } oldParent.Unlock() @@ -2556,11 +2556,19 @@ func (fs *fileSystem) renameNonHierarchicalDir( } o := descendant.MinObject - if _, err := newDir.CloneToChildFile(ctx, nameDiff, o); err != nil { - return fmt.Errorf("copy file %q: %w", o.Name, err) - } - if err := oldDir.DeleteChildFile(ctx, nameDiff, o.Generation, &o.MetaGeneration); err != nil { - return fmt.Errorf("delete file %q: %w", o.Name, err) + // If the descendant is a directory (ExplicitDirType) or has an unknown type, handle it by cloning and deleting. + if descendant.Type() == metadata.ExplicitDirType || descendant.Type() == metadata.UnknownType { + if _, err = newDir.CloneToChildFile(ctx, nameDiff, o); err != nil { + return fmt.Errorf("copy file %q: %w", o.Name, err) + } + if err = oldDir.DeleteChildFile(ctx, nameDiff, o.Generation, &o.MetaGeneration); err != nil { + return fmt.Errorf("delete file %q: %w", o.Name, err) + } + } else { + // For regular files, perform an in-place rename to the new directory. + if _, err = oldDir.RenameFile(ctx, o, path.Join(newDir.Name().GcsObjectName(), nameDiff)); err != nil { + return fmt.Errorf("renameFile: while renaming file: %w", err) + } } if err = fs.invalidateChildFileCacheIfExist(oldDir, o.Name); err != nil { diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index f6a7c6ae74..41d02cdc1b 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -151,7 +151,9 @@ func (t *fsTest) SetUpTestSuite() { chunkTransferTimeoutSecs: 10, tmpObjectPrefix: ".gcsfuse_tmp/", } - t.serverCfg.RenameDirLimit = RenameDirLimit + if t.serverCfg.RenameDirLimit == 0 { + t.serverCfg.RenameDirLimit = RenameDirLimit + } t.serverCfg.SequentialReadSizeMb = SequentialReadSizeMb if t.serverCfg.NewConfig == nil { diff --git a/internal/fs/hns_bucket_test.go b/internal/fs/hns_bucket_test.go index 50aa20494b..2c4e759891 100644 --- a/internal/fs/hns_bucket_test.go +++ b/internal/fs/hns_bucket_test.go @@ -15,9 +15,7 @@ package fs_test import ( - "fmt" "os" - "os/exec" "path" "strings" "testing" @@ -37,8 +35,10 @@ import ( ) type HNSBucketTests struct { + suite.Suite fsTest - HNSBucketCommonTest + RenameFileTests + RenameDirTests } type dirEntry struct { @@ -59,6 +59,12 @@ var expectedFooDirEntries = []dirEntry{ func TestHNSBucketTests(t *testing.T) { suite.Run(t, new(HNSBucketTests)) } +func (t *HNSBucketTests) SetT(testingT *testing.T) { + t.Suite.SetT(testingT) + t.RenameDirTests.SetT(testingT) + t.RenameFileTests.SetT(testingT) +} + func (t *HNSBucketTests) SetupSuite() { t.serverCfg.ImplicitDirectories = false t.serverCfg.NewConfig = &cfg.Config{ @@ -132,264 +138,6 @@ func (t *HNSBucketTests) TestDeleteImplicitDir() { assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) } -func (t *HNSBucketTests) TestRenameFolderWithSrcDirectoryDoesNotExist() { - oldDirPath := path.Join(mntDir, "foo_not_exist") - newDirPath := path.Join(mntDir, "foo_rename") - - err := os.Rename(oldDirPath, newDirPath) - - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - _, err = os.Stat(newDirPath) - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) -} - -func (t *HNSBucketTests) TestRenameFolderWithDstDirectoryNotEmpty() { - oldDirPath := path.Join(mntDir, "foo") - _, err := os.Stat(oldDirPath) - assert.NoError(t.T(), err) - // In the setup phase, we created file1.txt within the bar directory. - newDirPath := path.Join(mntDir, "bar") - _, err = os.Stat(newDirPath) - assert.NoError(t.T(), err) - - err = os.Rename(oldDirPath, newDirPath) - - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "file exists")) -} - -func (t *HNSBucketTests) TestRenameFolderWithEmptySourceDirectory() { - oldDirPath := path.Join(mntDir, "foo", "test2") - _, err := os.Stat(oldDirPath) - assert.NoError(t.T(), err) - newDirPath := path.Join(mntDir, "foo_rename") - _, err = os.Stat(newDirPath) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - - err = os.Rename(oldDirPath, newDirPath) - - assert.NoError(t.T(), err) - _, err = os.Stat(oldDirPath) - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - _, err = os.Stat(newDirPath) - assert.NoError(t.T(), err) - dirEntries, err := os.ReadDir(newDirPath) - assert.NoError(t.T(), err) - assert.Equal(t.T(), 0, len(dirEntries)) -} - -func (t *HNSBucketTests) TestRenameFolderWithSourceDirectoryHaveLocalFiles() { - oldDirPath := path.Join(mntDir, "foo", "test") - _, err := os.Stat(oldDirPath) - assert.NoError(t.T(), err) - file, err := os.OpenFile(path.Join(oldDirPath, "file4.txt"), os.O_RDWR|os.O_CREATE, filePerms) - assert.NoError(t.T(), err) - defer file.Close() - newDirPath := path.Join(mntDir, "bar", "foo_rename") - - err = os.Rename(oldDirPath, newDirPath) - - assert.Error(t.T(), err) - // In the logs, we encountered the following error: - // "Rename: operation not supported, can't rename directory 'test' with open files: operation not supported." - // This was translated to an "operation not supported" error at the kernel level. - assert.True(t.T(), strings.Contains(err.Error(), "operation not supported")) -} - -func (t *HNSBucketTests) TestRenameFolderWithSameParent() { - oldDirPath := path.Join(mntDir, "foo") - _, err := os.Stat(oldDirPath) - require.NoError(t.T(), err) - newDirPath := path.Join(mntDir, "foo_rename") - _, err = os.Stat(newDirPath) - require.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - - err = os.Rename(oldDirPath, newDirPath) - - assert.NoError(t.T(), err) - _, err = os.Stat(oldDirPath) - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - _, err = os.Stat(newDirPath) - assert.NoError(t.T(), err) - dirEntries, err := os.ReadDir(newDirPath) - assert.NoError(t.T(), err) - assert.Equal(t.T(), 5, len(dirEntries)) - actualDirEntries := []dirEntry{} - for _, d := range dirEntries { - actualDirEntries = append(actualDirEntries, dirEntry{ - name: d.Name(), - isDir: d.IsDir(), - }) - } - assert.ElementsMatch(t.T(), actualDirEntries, expectedFooDirEntries) -} - -func (t *HNSBucketTests) TestRenameFolderWithExistingEmptyDestDirectory() { - oldDirPath := path.Join(mntDir, "foo", "test") - _, err := os.Stat(oldDirPath) - require.NoError(t.T(), err) - newDirPath := path.Join(mntDir, "foo", "test2") - _, err = os.Stat(newDirPath) - require.NoError(t.T(), err) - - // Go's Rename function does not support renaming a directory into an existing empty directory. - // To achieve this, we call a Python rename function as a workaround. - cmd := exec.Command("python3", "-c", fmt.Sprintf("import os; os.rename('%s', '%s')", oldDirPath, newDirPath)) - _, err = cmd.CombinedOutput() - - assert.NoError(t.T(), err) - _, err = os.Stat(oldDirPath) - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - _, err = os.Stat(newDirPath) - assert.NoError(t.T(), err) - dirEntries, err := os.ReadDir(newDirPath) - assert.NoError(t.T(), err) - assert.Equal(t.T(), 1, len(dirEntries)) - assert.Equal(t.T(), "file3.txt", dirEntries[0].Name()) - assert.False(t.T(), dirEntries[0].IsDir()) -} - -func (t *HNSBucketTests) TestRenameFolderWithDifferentParents() { - oldDirPath := path.Join(mntDir, "foo") - _, err := os.Stat(oldDirPath) - assert.NoError(t.T(), err) - newDirPath := path.Join(mntDir, "bar", "foo_rename") - - err = os.Rename(oldDirPath, newDirPath) - - assert.NoError(t.T(), err) - _, err = os.Stat(oldDirPath) - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - _, err = os.Stat(newDirPath) - assert.NoError(t.T(), err) - dirEntries, err := os.ReadDir(newDirPath) - assert.NoError(t.T(), err) - assert.Equal(t.T(), 5, len(dirEntries)) - actualDirEntries := []dirEntry{} - for _, d := range dirEntries { - actualDirEntries = append(actualDirEntries, dirEntry{ - name: d.Name(), - isDir: d.IsDir(), - }) - } - assert.ElementsMatch(t.T(), actualDirEntries, expectedFooDirEntries) -} - -func (t *HNSBucketTests) TestRenameFolderWithOpenGCSFile() { - oldDirPath := path.Join(mntDir, "bar") - _, err := os.Stat(oldDirPath) - assert.NoError(t.T(), err) - newDirPath := path.Join(mntDir, "bar_rename") - filePath := path.Join(oldDirPath, "file1.txt") - f, err := os.Open(filePath) - require.NoError(t.T(), err) - - err = os.Rename(oldDirPath, newDirPath) - - require.NoError(t.T(), err) - _, err = f.WriteString("test") - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "bad file descriptor")) - assert.NoError(t.T(), f.Close()) - _, err = os.Stat(oldDirPath) - assert.Error(t.T(), err) - assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - _, err = os.Stat(newDirPath) - assert.NoError(t.T(), err) - dirEntries, err := os.ReadDir(newDirPath) - assert.NoError(t.T(), err) - assert.Equal(t.T(), 1, len(dirEntries)) - assert.Equal(t.T(), "file1.txt", dirEntries[0].Name()) - assert.False(t.T(), dirEntries[0].IsDir()) -} - -// Create directory foo. -// Stat the directory foo. -// Rename directory foo --> foo_rename -// Stat the old directory. -// Stat the new directory. -// Read new directory and validate. -// Create old directory again with same name - foo -// Stat the directory - foo -// Read directory again and validate it is empty. -func (t *HNSBucketTests) TestCreateDirectoryWithSameNameAfterRename() { - oldDirPath := path.Join(mntDir, "foo") - _, err := os.Stat(oldDirPath) - require.NoError(t.T(), err) - newDirPath := path.Join(mntDir, "foo_rename") - // Rename directory foo --> foo_rename - err = os.Rename(oldDirPath, newDirPath) - require.NoError(t.T(), err) - // Stat old directory. - _, err = os.Stat(oldDirPath) - require.Error(t.T(), err) - require.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) - // Stat new directory. - _, err = os.Stat(newDirPath) - require.NoError(t.T(), err) - // Read new directory and validate. - dirEntries, err := os.ReadDir(newDirPath) - require.NoError(t.T(), err) - require.Equal(t.T(), 5, len(dirEntries)) - actualDirEntries := []dirEntry{} - for _, d := range dirEntries { - actualDirEntries = append(actualDirEntries, dirEntry{ - name: d.Name(), - isDir: d.IsDir(), - }) - } - require.ElementsMatch(t.T(), actualDirEntries, expectedFooDirEntries) - - // Create old directory again. - err = os.Mkdir(oldDirPath, dirPerms) - - assert.NoError(t.T(), err) - _, err = os.Stat(oldDirPath) - assert.NoError(t.T(), err) - dirEntries, err = os.ReadDir(oldDirPath) - assert.NoError(t.T(), err) - assert.Equal(t.T(), 0, len(dirEntries)) -} - -// Create directory - foo/test2 -// Create local file in directory - foo/test2/test.txt -// Stat the local file - foo/test2/test.txt -// Delete directory - rm -r foo/test2 -// Create directory again - foo/test2 -// Create local file with the same name in directory - foo/test2/test.txt -// Stat the local file - foo/test2/test.txt -func (t *HNSBucketTests) TestCreateLocalFileInSamePathAfterDeletingParentDirectory() { - dirPath := path.Join(mntDir, "foo", "test2") - filePath := path.Join(dirPath, "test.txt") - // Create local file in side it. - f1, err := os.Create(filePath) - defer require.NoError(t.T(), f1.Close()) - require.NoError(t.T(), err) - _, err = os.Stat(filePath) - require.NoError(t.T(), err) - // Delete directory rm -r foo/test2 - err = os.RemoveAll(dirPath) - assert.NoError(t.T(), err) - // Create directory again foo/test2 - err = os.Mkdir(dirPath, dirPerms) - assert.NoError(t.T(), err) - - // Create local file again. - f2, err := os.Create(filePath) - defer require.NoError(t.T(), f2.Close()) - - assert.NoError(t.T(), err) - _, err = os.Stat(filePath) - assert.NoError(t.T(), err) -} - // ////////////////////////////////////////////////////////////////////// // HNS bucket with caching support tests // ////////////////////////////////////////////////////////////////////// diff --git a/internal/fs/rename_dir_test.go b/internal/fs/rename_dir_test.go new file mode 100644 index 0000000000..ddcfef51c5 --- /dev/null +++ b/internal/fs/rename_dir_test.go @@ -0,0 +1,293 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package fs_test + +import ( + "fmt" + "os" + "os/exec" + "path" + "strings" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +type RenameDirTests struct { + suite.Suite +} + +//////////////////////////////////////////////////////////////////////// +// Tests +//////////////////////////////////////////////////////////////////////// + +func (t *RenameDirTests) TestRenameFolderWithSrcDirectoryDoesNotExist() { + oldDirPath := path.Join(mntDir, "foo_not_exist") + newDirPath := path.Join(mntDir, "foo_rename") + + err := os.Rename(oldDirPath, newDirPath) + + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + _, err = os.Stat(newDirPath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) +} + +func (t *RenameDirTests) TestRenameFolderWithDstDirectoryNotEmpty() { + oldDirPath := path.Join(mntDir, "foo") + _, err := os.Stat(oldDirPath) + assert.NoError(t.T(), err) + // In the setup phase, we created file1.txt within the bar directory. + newDirPath := path.Join(mntDir, "bar") + _, err = os.Stat(newDirPath) + assert.NoError(t.T(), err) + + err = os.Rename(oldDirPath, newDirPath) + + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "file exists")) +} + +func (t *RenameDirTests) TestRenameFolderWithEmptySourceDirectory() { + oldDirPath := path.Join(mntDir, "foo", "test2") + _, err := os.Stat(oldDirPath) + assert.NoError(t.T(), err) + newDirPath := path.Join(mntDir, "foo_rename") + _, err = os.Stat(newDirPath) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + + err = os.Rename(oldDirPath, newDirPath) + + assert.NoError(t.T(), err) + _, err = os.Stat(oldDirPath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + _, err = os.Stat(newDirPath) + assert.NoError(t.T(), err) + dirEntries, err := os.ReadDir(newDirPath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), 0, len(dirEntries)) +} + +func (t *RenameDirTests) TestRenameFolderWithSourceDirectoryHaveLocalFiles() { + oldDirPath := path.Join(mntDir, "foo", "test") + _, err := os.Stat(oldDirPath) + assert.NoError(t.T(), err) + file, err := os.OpenFile(path.Join(oldDirPath, "file4.txt"), os.O_RDWR|os.O_CREATE, filePerms) + assert.NoError(t.T(), err) + defer file.Close() + newDirPath := path.Join(mntDir, "bar", "foo_rename") + + err = os.Rename(oldDirPath, newDirPath) + + assert.Error(t.T(), err) + // In the logs, we encountered the following error: + // "Rename: operation not supported, can't rename directory 'test' with open files: operation not supported." + // This was translated to an "operation not supported" error at the kernel level. + assert.True(t.T(), strings.Contains(err.Error(), "operation not supported")) +} + +func (t *RenameDirTests) TestRenameFolderWithSameParent() { + oldDirPath := path.Join(mntDir, "foo") + _, err := os.Stat(oldDirPath) + require.NoError(t.T(), err) + newDirPath := path.Join(mntDir, "foo_rename") + _, err = os.Stat(newDirPath) + require.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + + err = os.Rename(oldDirPath, newDirPath) + + assert.NoError(t.T(), err) + _, err = os.Stat(oldDirPath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + _, err = os.Stat(newDirPath) + assert.NoError(t.T(), err) + dirEntries, err := os.ReadDir(newDirPath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), 5, len(dirEntries)) + actualDirEntries := []dirEntry{} + for _, d := range dirEntries { + actualDirEntries = append(actualDirEntries, dirEntry{ + name: d.Name(), + isDir: d.IsDir(), + }) + } + assert.ElementsMatch(t.T(), actualDirEntries, expectedFooDirEntries) +} + +func (t *RenameDirTests) TestRenameFolderWithExistingEmptyDestDirectory() { + oldDirPath := path.Join(mntDir, "foo", "test") + _, err := os.Stat(oldDirPath) + require.NoError(t.T(), err) + newDirPath := path.Join(mntDir, "foo", "test2") + _, err = os.Stat(newDirPath) + require.NoError(t.T(), err) + + // Go's Rename function does not support renaming a directory into an existing empty directory. + // To achieve this, we call a Python rename function as a workaround. + cmd := exec.Command("python3", "-c", fmt.Sprintf("import os; os.rename('%s', '%s')", oldDirPath, newDirPath)) + _, err = cmd.CombinedOutput() + + assert.NoError(t.T(), err) + _, err = os.Stat(oldDirPath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + _, err = os.Stat(newDirPath) + assert.NoError(t.T(), err) + dirEntries, err := os.ReadDir(newDirPath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), 1, len(dirEntries)) + assert.Equal(t.T(), "file3.txt", dirEntries[0].Name()) + assert.False(t.T(), dirEntries[0].IsDir()) +} + +func (t *RenameDirTests) TestRenameFolderWithDifferentParents() { + oldDirPath := path.Join(mntDir, "foo") + _, err := os.Stat(oldDirPath) + assert.NoError(t.T(), err) + newDirPath := path.Join(mntDir, "bar", "foo_rename") + + err = os.Rename(oldDirPath, newDirPath) + + assert.NoError(t.T(), err) + _, err = os.Stat(oldDirPath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + _, err = os.Stat(newDirPath) + assert.NoError(t.T(), err) + dirEntries, err := os.ReadDir(newDirPath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), 5, len(dirEntries)) + actualDirEntries := []dirEntry{} + for _, d := range dirEntries { + actualDirEntries = append(actualDirEntries, dirEntry{ + name: d.Name(), + isDir: d.IsDir(), + }) + } + assert.ElementsMatch(t.T(), actualDirEntries, expectedFooDirEntries) +} + +func (t *RenameDirTests) TestRenameFolderWithOpenGCSFile() { + oldDirPath := path.Join(mntDir, "bar") + _, err := os.Stat(oldDirPath) + assert.NoError(t.T(), err) + newDirPath := path.Join(mntDir, "bar_rename") + filePath := path.Join(oldDirPath, "file1.txt") + f, err := os.Open(filePath) + require.NoError(t.T(), err) + + err = os.Rename(oldDirPath, newDirPath) + + require.NoError(t.T(), err) + _, err = f.WriteString("test") + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "bad file descriptor")) + assert.NoError(t.T(), f.Close()) + _, err = os.Stat(oldDirPath) + assert.Error(t.T(), err) + assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + _, err = os.Stat(newDirPath) + assert.NoError(t.T(), err) + dirEntries, err := os.ReadDir(newDirPath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), 1, len(dirEntries)) + assert.Equal(t.T(), "file1.txt", dirEntries[0].Name()) + assert.False(t.T(), dirEntries[0].IsDir()) +} + +// Create directory foo. +// Stat the directory foo. +// Rename directory foo --> foo_rename +// Stat the old directory. +// Stat the new directory. +// Read new directory and validate. +// Create old directory again with same name - foo +// Stat the directory - foo +// Read directory again and validate it is empty. +func (t *RenameDirTests) TestCreateDirectoryWithSameNameAfterRename() { + oldDirPath := path.Join(mntDir, "foo") + _, err := os.Stat(oldDirPath) + require.NoError(t.T(), err) + newDirPath := path.Join(mntDir, "foo_rename") + // Rename directory foo --> foo_rename + err = os.Rename(oldDirPath, newDirPath) + require.NoError(t.T(), err) + // Stat old directory. + _, err = os.Stat(oldDirPath) + require.Error(t.T(), err) + require.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) + // Stat new directory. + _, err = os.Stat(newDirPath) + require.NoError(t.T(), err) + // Read new directory and validate. + dirEntries, err := os.ReadDir(newDirPath) + require.NoError(t.T(), err) + require.Equal(t.T(), 5, len(dirEntries)) + actualDirEntries := []dirEntry{} + for _, d := range dirEntries { + actualDirEntries = append(actualDirEntries, dirEntry{ + name: d.Name(), + isDir: d.IsDir(), + }) + } + require.ElementsMatch(t.T(), actualDirEntries, expectedFooDirEntries) + + // Create old directory again. + err = os.Mkdir(oldDirPath, dirPerms) + + assert.NoError(t.T(), err) + _, err = os.Stat(oldDirPath) + assert.NoError(t.T(), err) + dirEntries, err = os.ReadDir(oldDirPath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), 0, len(dirEntries)) +} + +// Create directory - foo/test2 +// Create local file in directory - foo/test2/test.txt +// Stat the local file - foo/test2/test.txt +// Delete directory - rm -r foo/test2 +// Create directory again - foo/test2 +// Create local file with the same name in directory - foo/test2/test.txt +// Stat the local file - foo/test2/test.txt +func (t *RenameDirTests) TestCreateLocalFileInSamePathAfterDeletingParentDirectory() { + dirPath := path.Join(mntDir, "foo", "test2") + filePath := path.Join(dirPath, "test.txt") + // Create local file in side it. + f1, err := os.Create(filePath) + defer require.NoError(t.T(), f1.Close()) + require.NoError(t.T(), err) + _, err = os.Stat(filePath) + require.NoError(t.T(), err) + // Delete directory rm -r foo/test2 + err = os.RemoveAll(dirPath) + assert.NoError(t.T(), err) + // Create directory again foo/test2 + err = os.Mkdir(dirPath, dirPerms) + assert.NoError(t.T(), err) + + // Create local file again. + f2, err := os.Create(filePath) + defer require.NoError(t.T(), f2.Close()) + + assert.NoError(t.T(), err) + _, err = os.Stat(filePath) + assert.NoError(t.T(), err) +} diff --git a/internal/fs/hns_bucket_common_test.go b/internal/fs/rename_file_test.go similarity index 93% rename from internal/fs/hns_bucket_common_test.go rename to internal/fs/rename_file_test.go index 7cff8bbd4c..ba6131d250 100644 --- a/internal/fs/hns_bucket_common_test.go +++ b/internal/fs/rename_file_test.go @@ -24,7 +24,7 @@ import ( "github.com/stretchr/testify/suite" ) -type HNSBucketCommonTest struct { +type RenameFileTests struct { suite.Suite } @@ -32,7 +32,7 @@ type HNSBucketCommonTest struct { // Tests //////////////////////////////////////////////////////////////////////// -func (t *HNSBucketCommonTest) TestRenameFileWithSrcFileDoesNotExist() { +func (t *RenameFileTests) TestRenameFileWithSrcFileDoesNotExist() { oldFilePath := path.Join(mntDir, "file") newFilePath := path.Join(mntDir, "file_rename") @@ -45,7 +45,7 @@ func (t *HNSBucketCommonTest) TestRenameFileWithSrcFileDoesNotExist() { assert.True(t.T(), strings.Contains(err.Error(), "no such file or directory")) } -func (t *HNSBucketCommonTest) TestRenameFileWithDstDestFileExist() { +func (t *RenameFileTests) TestRenameFileWithDstDestFileExist() { oldFilePath := path.Join(mntDir, "foo", "file1.txt") _, err := os.Stat(oldFilePath) assert.NoError(t.T(), err) @@ -64,7 +64,7 @@ func (t *HNSBucketCommonTest) TestRenameFileWithDstDestFileExist() { assert.Equal(t.T(), file1Content, string(content)) } -func (t *HNSBucketCommonTest) TestRenameFile() { +func (t *RenameFileTests) TestRenameFile() { testCases := []struct { name string oldFilePath string diff --git a/internal/fs/zonal_bucket_test.go b/internal/fs/zonal_bucket_test.go index 21ee775582..a278caffb0 100644 --- a/internal/fs/zonal_bucket_test.go +++ b/internal/fs/zonal_bucket_test.go @@ -24,7 +24,7 @@ import ( ) type ZonalBucketTests struct { - HNSBucketCommonTest + RenameFileTests fsTest } From 20114989fffbe3d07fb3c42b76cf8692587db281 Mon Sep 17 00:00:00 2001 From: Pranjal Chaturvedi Date: Mon, 4 Aug 2025 14:44:13 +0530 Subject: [PATCH 0641/1298] style(fixit): Updated sync error logs to include ObjectName (#3616) * Updated sync error logs to include ObjectName * Changed error logging location to a Public method * Made changes in CreateObject to log the FileName * Remove changes in fs.go * git reset hard and making changes in bucket handler as req * Remove unecessary whitespace --- internal/storage/bucket_handle.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index b60a78e9c4..356fc457ab 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -234,14 +234,14 @@ func (bh *bucketHandle) CreateObject(ctx context.Context, req *gcs.CreateObjectR // Copy the contents to the writer. if _, err = io.Copy(wc, req.Contents); err != nil { - err = fmt.Errorf("error in io.Copy: %w", err) + err = fmt.Errorf("failed io.Copy for %q: %w", req.Name, err) return } // We can't use defer to close the writer, because we need to close the // writer successfully before calling Attrs() method of writer. if err = wc.Close(); err != nil { - err = fmt.Errorf("error in closing writer : %w", err) + err = fmt.Errorf("failed closing writer for %q: %w", req.Name, err) return } From 9e31c8fbaa661ed49e9668ee0c72f980aef9ffe1 Mon Sep 17 00:00:00 2001 From: Pranjal Chaturvedi Date: Mon, 4 Aug 2025 09:40:25 +0000 Subject: [PATCH 0642/1298] Change FileName to ObjectName --- internal/fs/gcsfuse_errors/gcsfuse_errors.go | 6 +++--- internal/fs/gcsfuse_errors/gcsfuse_errors_test.go | 8 ++++---- internal/fs/inode/file.go | 14 +++++++------- internal/fs/wrappers/error_mapping_test.go | 8 ++++---- internal/gcsx/client_readers/range_reader.go | 4 ++-- internal/gcsx/random_reader.go | 2 +- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/internal/fs/gcsfuse_errors/gcsfuse_errors.go b/internal/fs/gcsfuse_errors/gcsfuse_errors.go index 262eb697c1..f2f5564a53 100644 --- a/internal/fs/gcsfuse_errors/gcsfuse_errors.go +++ b/internal/fs/gcsfuse_errors/gcsfuse_errors.go @@ -21,12 +21,12 @@ import ( // FileClobberedError represents a file clobbering scenario where a file was // modified or deleted while it was being accessed. type FileClobberedError struct { - Err error - FileName string + Err error + ObjectName string } func (fce *FileClobberedError) Error() string { - return fmt.Sprintf("The file %q was modified or deleted by another process, possibly due to concurrent modification: %v", fce.FileName, fce.Err) + return fmt.Sprintf("The file %q was modified or deleted by another process, possibly due to concurrent modification: %v", fce.ObjectName, fce.Err) } func (fce *FileClobberedError) Unwrap() error { diff --git a/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go b/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go index ef0c08cbfb..812c67add0 100644 --- a/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go +++ b/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go @@ -25,19 +25,19 @@ import ( func TestFileClobberedError(t *testing.T) { testCases := []struct { name string - fileName string + objectName string err error wantErrMsg string }{ { name: "with_underlying_error", - fileName: "foo.txt", + objectName: "a/b/c/foo.txt", err: fmt.Errorf("some error"), - wantErrMsg: "The file \"foo.txt\" was modified or deleted by another process, possibly due to concurrent modification: some error", + wantErrMsg: "The file \"a/b/c/foo.txt\" was modified or deleted by another process, possibly due to concurrent modification: some error", }, { name: "without_underlying_error", - fileName: "bar.txt", + objectName: "bar.txt", err: nil, wantErrMsg: "The file \"bar.txt\" was modified or deleted by another process, possibly due to concurrent modification: ", }, diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index 55fbd79d71..b20849268f 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -262,8 +262,8 @@ func (f *FileInode) openReader(ctx context.Context) (io.ReadCloser, error) { var notFoundError *gcs.NotFoundError if errors.As(err, ¬FoundError) { err = &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("NewReader: %w", err), - FileName: f.src.Name, + Err: fmt.Errorf("NewReader: %w", err), + ObjectName: f.src.Name, } } if err != nil { @@ -626,7 +626,7 @@ func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, o var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { return false, &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("f.bwh.Write(): %w", err), + Err: fmt.Errorf("f.bwh.Write(): %w", err), FileName: f.src.Name, } } @@ -655,7 +655,7 @@ func (f *FileInode) flushUsingBufferedWriteHandler() error { var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { return &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("f.bwh.Flush(): %w", err), + Err: fmt.Errorf("f.bwh.Flush(): %w", err), FileName: f.src.Name, } } @@ -679,7 +679,7 @@ func (f *FileInode) SyncPendingBufferedWrites() (gcsSynced bool, err error) { var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { err = &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("f.bwh.Sync(): %w", err), + Err: fmt.Errorf("f.bwh.Sync(): %w", err), FileName: f.src.Name, } return @@ -800,7 +800,7 @@ func (f *FileInode) fetchLatestGcsObject(ctx context.Context) (*gcs.Object, erro } if isClobbered { return nil, &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("file was clobbered"), + Err: fmt.Errorf("file was clobbered"), FileName: f.src.Name, } } @@ -863,7 +863,7 @@ func (f *FileInode) syncUsingContent(ctx context.Context) error { var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { return &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("SyncObject: %w", err), + Err: fmt.Errorf("SyncObject: %w", err), FileName: f.src.Name, } } diff --git a/internal/fs/wrappers/error_mapping_test.go b/internal/fs/wrappers/error_mapping_test.go index ff031dcacc..56b2e8def2 100644 --- a/internal/fs/wrappers/error_mapping_test.go +++ b/internal/fs/wrappers/error_mapping_test.go @@ -94,8 +94,8 @@ func (testSuite *ErrorMapping) TestUnAuthenticatedHttpGoogleApiError() { func (testSuite *ErrorMapping) TestFileClobberedErrorWithPreconditionErrCfg() { clobberedErr := &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("some error"), - FileName: "foo.txt", + Err: fmt.Errorf("some error"), + ObjectName: "foo.txt", } gotErrno := errno(clobberedErr, true) @@ -105,8 +105,8 @@ func (testSuite *ErrorMapping) TestFileClobberedErrorWithPreconditionErrCfg() { func (testSuite *ErrorMapping) TestFileClobberedErrorWithoutPreconditionErrCfg() { clobberedErr := &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("some error"), - FileName: "foo.txt", + Err: fmt.Errorf("some error"), + ObjectName: "foo.txt", } gotErrno := errno(clobberedErr, false) diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index 9e91c84018..ab20c0fa8d 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -264,8 +264,8 @@ func (rr *RangeReader) startRead(start int64, end int64) error { var notFoundError *gcs.NotFoundError if errors.As(err, ¬FoundError) { err = &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("NewReader: %w", err), - FileName: rr.object.Name, + Err: fmt.Errorf("NewReader: %w", err), + ObjectName: rr.object.Name, } cancel() return err diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index 3f18d13cbc..c0e526fd38 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -497,7 +497,7 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { if errors.As(err, ¬FoundError) { err = &gcsfuse_errors.FileClobberedError{ Err: fmt.Errorf("NewReader: %w", err), - FileName: rr.object.Name, + ObjectName: rr.object.Name, } return } From ed8c8d70c5c4e17bb4d6d7b870ad9c85fd75c4eb Mon Sep 17 00:00:00 2001 From: Pranjal Chaturvedi Date: Mon, 4 Aug 2025 09:55:14 +0000 Subject: [PATCH 0643/1298] Formatting and Change Filename attribute name to ObjectName --- .../fs/gcsfuse_errors/gcsfuse_errors_test.go | 4 ++-- internal/fs/inode/file.go | 20 +++++++++---------- internal/gcsx/random_reader.go | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go b/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go index 812c67add0..8050d1e206 100644 --- a/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go +++ b/internal/fs/gcsfuse_errors/gcsfuse_errors_test.go @@ -46,8 +46,8 @@ func TestFileClobberedError(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { clobberedErr := &FileClobberedError{ - Err: tc.err, - FileName: tc.fileName, + Err: tc.err, + ObjectName: tc.objectName, } gotErrMsg := clobberedErr.Error() diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index b20849268f..a6587cad0c 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -626,8 +626,8 @@ func (f *FileInode) writeUsingBufferedWrites(ctx context.Context, data []byte, o var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { return false, &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("f.bwh.Write(): %w", err), - FileName: f.src.Name, + Err: fmt.Errorf("f.bwh.Write(): %w", err), + ObjectName: f.src.Name, } } // Fall back to temp file for Out-Of-Order Writes. @@ -655,8 +655,8 @@ func (f *FileInode) flushUsingBufferedWriteHandler() error { var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { return &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("f.bwh.Flush(): %w", err), - FileName: f.src.Name, + Err: fmt.Errorf("f.bwh.Flush(): %w", err), + ObjectName: f.src.Name, } } if err != nil { @@ -679,8 +679,8 @@ func (f *FileInode) SyncPendingBufferedWrites() (gcsSynced bool, err error) { var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { err = &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("f.bwh.Sync(): %w", err), - FileName: f.src.Name, + Err: fmt.Errorf("f.bwh.Sync(): %w", err), + ObjectName: f.src.Name, } return } @@ -800,8 +800,8 @@ func (f *FileInode) fetchLatestGcsObject(ctx context.Context) (*gcs.Object, erro } if isClobbered { return nil, &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("file was clobbered"), - FileName: f.src.Name, + Err: fmt.Errorf("file was clobbered"), + ObjectName: f.src.Name, } } return latestGcsObj, nil @@ -863,8 +863,8 @@ func (f *FileInode) syncUsingContent(ctx context.Context) error { var preconditionErr *gcs.PreconditionError if errors.As(err, &preconditionErr) { return &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("SyncObject: %w", err), - FileName: f.src.Name, + Err: fmt.Errorf("SyncObject: %w", err), + ObjectName: f.src.Name, } } diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index c0e526fd38..5849dcf9c8 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -496,7 +496,7 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { var notFoundError *gcs.NotFoundError if errors.As(err, ¬FoundError) { err = &gcsfuse_errors.FileClobberedError{ - Err: fmt.Errorf("NewReader: %w", err), + Err: fmt.Errorf("NewReader: %w", err), ObjectName: rr.object.Name, } return From 4c65ae3a49b32d2794f0ad3d3a4b0cde83ce2efa Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:42:32 +0530 Subject: [PATCH 0644/1298] fix(rapid appends): Create zero byte reader only for GCS objects (#3632) * dont create zero byte readers for dir in zb * unit tests for the isGCSObject method --- internal/storage/bucket_handle.go | 14 ++++++++++++-- internal/storage/bucket_handle_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 356fc457ab..0a19d37827 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -23,6 +23,7 @@ import ( "context" "fmt" "io" + "strings" "time" "cloud.google.com/go/storage" @@ -143,7 +144,7 @@ func (bh *bucketHandle) StatObject(ctx context.Context, err = fmt.Errorf("error in fetching object attributes: %w", err) return } - if attrs.Finalized.IsZero() { + if attrs.Finalized.IsZero() && isGCSObject(attrs) { if err = bh.fetchLatestSizeOfUnfinalizedObject(ctx, attrs); err != nil { err = fmt.Errorf("failed to fetch the latest size of unfinalized object %q: %w", attrs.Name, err) return @@ -423,7 +424,7 @@ func (bh *bucketHandle) ListObjects(ctx context.Context, req *gcs.ListObjectsReq err = fmt.Errorf("error in iterating through objects: %w", err) return } - if attrs.Finalized.IsZero() { + if attrs.Finalized.IsZero() && isGCSObject(attrs) { if err = bh.fetchLatestSizeOfUnfinalizedObject(ctx, attrs); err != nil { err = fmt.Errorf("failed to fetch the latest size of unfinalized object %q: %w", attrs.Name, err) return @@ -707,3 +708,12 @@ func (bh *bucketHandle) GCSName(obj *gcs.MinObject) string { func isStorageConditionsNotEmpty(conditions storage.Conditions) bool { return conditions != (storage.Conditions{}) } + +// isGCSObject determines whether the GCS resource represented by attrs is a GCS object +// and not a folder/directory resource. +func isGCSObject(attrs *storage.ObjectAttrs) bool { + if !strings.HasSuffix(attrs.Name, "/") && attrs.Prefix == "" && attrs.Name != "" { + return true + } + return false +} diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index 6ed216c446..cd97a79271 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -1624,3 +1624,29 @@ func (testSuite *BucketHandleTest) TestCreateFolderWithGivenName() { assert.NoError(testSuite.T(), err) assert.Equal(testSuite.T(), gcs.GCSFolder(TestBucketName, &mockFolder), folder) } + +func (testSuite *BucketHandleTest) TestIsGCSObject() { + testCases := []struct { + name string + attrs *storage.ObjectAttrs + expected bool + }{ + { + name: "gcsObject", + attrs: &storage.ObjectAttrs{Name: "a/b/c.txt", Prefix: ""}, + expected: true, + }, + { + name: "gcsFolder", + attrs: &storage.ObjectAttrs{Name: "", Prefix: "a/"}, + expected: false, + }, + } + + for _, tc := range testCases { + testSuite.Run(tc.name, func() { + actual := isGCSObject(tc.attrs) + assert.Equal(testSuite.T(), tc.expected, actual) + }) + } +} From 2a9b1ce609ac6e302c3a015492826503bea4295d Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:37:25 +0530 Subject: [PATCH 0645/1298] chore(rapid appends): Enable rapid appends flag by default (#3633) * enabling rapid appends flag by default * config validations update * update test scenario: correct size reported * cleanup --- cfg/config.go | 20 +- cfg/params.yaml | 13 +- cmd/config_validation_test.go | 4 +- cmd/mount.go | 2 +- cmd/root_test.go | 229 +++++++++--------- cmd/testdata/valid_config.yaml | 1 + internal/fs/fs.go | 2 +- internal/fs/inode/file.go | 2 +- internal/fs/inode/file_test.go | 10 +- internal/gcsx/bucket_manager.go | 10 +- .../rapid_appends/appends_test.go | 14 +- .../rapid_appends/reads_after_appends_test.go | 8 +- .../rapid_appends/suites_test.go | 2 +- .../unfinalized_object_operations_test.go | 9 +- 14 files changed, 160 insertions(+), 166 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 6cb3031d2c..2f1b1413de 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -294,9 +294,9 @@ type WriteConfig struct { CreateEmptyFile bool `yaml:"create-empty-file"` - EnableStreamingWrites bool `yaml:"enable-streaming-writes"` + EnableRapidAppends bool `yaml:"enable-rapid-appends"` - ExperimentalEnableRapidAppends bool `yaml:"experimental-enable-rapid-appends"` + EnableStreamingWrites bool `yaml:"enable-streaming-writes"` GlobalMaxBlocks int64 `yaml:"global-max-blocks"` @@ -419,6 +419,8 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-nonexistent-type-cache", "", false, "Once set, if an inode is not found in GCS, a type cache entry with type NonexistentType will be created. This also means new file/dir created might not be seen. For example, if this flag is set, and metadata-cache-ttl-secs is set, then if we create the same file/node in the meantime using the same mount, since we are not refreshing the cache, it will still return nil.") + flagSet.BoolP("enable-rapid-appends", "", true, "Enables support for appends to unfinalized object using streaming writes") + flagSet.BoolP("enable-read-stall-retry", "", true, "To turn on/off retries for stalled read requests. This is based on a timeout that changes depending on how long similar requests took in the past.") if err := flagSet.MarkHidden("enable-read-stall-retry"); err != nil { @@ -741,12 +743,6 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.BoolP("write-experimental-enable-rapid-appends", "", false, "Enables support for appends to unfinalized object using streaming writes") - - if err := flagSet.MarkHidden("write-experimental-enable-rapid-appends"); err != nil { - return err - } - flagSet.IntP("write-global-max-blocks", "", 4, "Specifies the maximum number of blocks available for streaming writes across all files. The value should be >= 0 or -1 (for infinite blocks). A value of 0 disables streaming writes.") flagSet.IntP("write-max-blocks-per-file", "", 1, "Specifies the maximum number of blocks to be used by a single file for streaming writes. The value should be >= 1 or -1 (for infinite blocks).") @@ -856,6 +852,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("write.enable-rapid-appends", flagSet.Lookup("enable-rapid-appends")); err != nil { + return err + } + if err := v.BindPFlag("gcs-retries.read-stall.enable", flagSet.Lookup("enable-read-stall-retry")); err != nil { return err } @@ -1176,10 +1176,6 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } - if err := v.BindPFlag("write.experimental-enable-rapid-appends", flagSet.Lookup("write-experimental-enable-rapid-appends")); err != nil { - return err - } - if err := v.BindPFlag("write.global-max-blocks", flagSet.Lookup("write-global-max-blocks")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 1dbad61a15..2905593a02 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -822,6 +822,12 @@ usage: "For a new file, it creates an empty file in Cloud Storage bucket as a hold." default: false +- config-path: "write.enable-rapid-appends" + flag-name: "enable-rapid-appends" + type: "bool" + usage: "Enables support for appends to unfinalized object using streaming writes" + default: true + - config-path: "write.enable-streaming-writes" flag-name: "enable-streaming-writes" type: "bool" @@ -829,13 +835,6 @@ default: true hide-flag: false -- config-path: "write.experimental-enable-rapid-appends" - flag-name: "write-experimental-enable-rapid-appends" - type: "bool" - usage: "Enables support for appends to unfinalized object using streaming writes" - default: false - hide-flag: true - - config-path: "write.global-max-blocks" flag-name: "write-global-max-blocks" type: "int" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 922ecd89e9..137fae300f 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -194,7 +194,9 @@ func TestValidateConfigFile_WriteConfig(t *testing.T) { BlockSizeMb: 32 * util.MiB, EnableStreamingWrites: true, GlobalMaxBlocks: 4, - MaxBlocksPerFile: 1}, + MaxBlocksPerFile: 1, + EnableRapidAppends: true, + }, }, }, { diff --git a/cmd/mount.go b/cmd/mount.go index 5c81a58d0c..65c8d10298 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -99,7 +99,7 @@ be interacting with the file system.`) AppendThreshold: 1 << 21, // 2 MiB, a total guess. ChunkTransferTimeoutSecs: newConfig.GcsRetries.ChunkTransferTimeoutSecs, TmpObjectPrefix: ".gcsfuse_tmp/", - ExperimentalEnableRapidAppends: newConfig.Write.ExperimentalEnableRapidAppends, + EnableRapidAppends: newConfig.Write.EnableRapidAppends, } bm := gcsx.NewBucketManager(bucketCfg, storageHandle) diff --git a/cmd/root_test.go b/cmd/root_test.go index dd6ea52c40..07dbd4f45c 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -251,122 +251,122 @@ func TestArgsParsing_ImplicitDirsFlag(t *testing.T) { } func TestArgsParsing_WriteConfigFlags(t *testing.T) { tests := []struct { - name string - args []string - expectedCreateEmptyFile bool - expectedEnableStreamingWrites bool - expectedExperimentalEnableRapidAppends bool - expectedWriteBlockSizeMB int64 - expectedWriteGlobalMaxBlocks int64 - expectedWriteMaxBlocksPerFile int64 + name string + args []string + expectedCreateEmptyFile bool + expectedEnableStreamingWrites bool + expectedEnableRapidAppends bool + expectedWriteBlockSizeMB int64 + expectedWriteGlobalMaxBlocks int64 + expectedWriteMaxBlocksPerFile int64 }{ { - name: "Test create-empty-file flag true works when streaming writes are explicitly disabled.", - args: []string{"gcsfuse", "--create-empty-file=true", "--enable-streaming-writes=false", "abc", "pqr"}, - expectedCreateEmptyFile: true, - expectedEnableStreamingWrites: false, - expectedExperimentalEnableRapidAppends: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: 4, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test create-empty-file flag false.", - args: []string{"gcsfuse", "--create-empty-file=false", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedExperimentalEnableRapidAppends: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: 4, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test default flags.", - args: []string{"gcsfuse", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedExperimentalEnableRapidAppends: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: 4, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test enable-streaming-writes flag true.", - args: []string{"gcsfuse", "--enable-streaming-writes", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedExperimentalEnableRapidAppends: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: 4, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test enable-streaming-writes flag false.", - args: []string{"gcsfuse", "--enable-streaming-writes=false", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: false, - expectedExperimentalEnableRapidAppends: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: 4, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test experimental-enable-rapid-appends flag true.", - args: []string{"gcsfuse", "--write-experimental-enable-rapid-appends", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedExperimentalEnableRapidAppends: true, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: 4, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test positive write-block-size-mb flag.", - args: []string{"gcsfuse", "--enable-streaming-writes", "--write-block-size-mb=10", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedExperimentalEnableRapidAppends: false, - expectedWriteBlockSizeMB: 10 * util.MiB, - expectedWriteGlobalMaxBlocks: 4, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test positive write-global-max-blocks flag.", - args: []string{"gcsfuse", "--enable-streaming-writes", "--write-global-max-blocks=10", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedExperimentalEnableRapidAppends: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: 10, - expectedWriteMaxBlocksPerFile: 1, - }, - { - name: "Test positive write-max-blocks-per-file flag.", - args: []string{"gcsfuse", "--enable-streaming-writes", "--write-max-blocks-per-file=10", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedExperimentalEnableRapidAppends: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: 4, - expectedWriteMaxBlocksPerFile: 10, - }, - { - name: "Test high performance config values.", - args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "abc", "pqr"}, - expectedEnableStreamingWrites: true, - expectedExperimentalEnableRapidAppends: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: 1600, - }, - { - name: "Test high performance config values with --write-global-max-blocks flag overriden.", - args: []string{"gcsfuse", "--write-global-max-blocks=2000", "--disable-autoconfig=false", "abc", "pqr"}, - expectedCreateEmptyFile: false, - expectedEnableStreamingWrites: true, - expectedExperimentalEnableRapidAppends: false, - expectedWriteBlockSizeMB: 32 * util.MiB, - expectedWriteGlobalMaxBlocks: 2000, - expectedWriteMaxBlocksPerFile: 1, + name: "Test create-empty-file flag true works when streaming writes are explicitly disabled.", + args: []string{"gcsfuse", "--create-empty-file=true", "--enable-streaming-writes=false", "abc", "pqr"}, + expectedCreateEmptyFile: true, + expectedEnableStreamingWrites: false, + expectedEnableRapidAppends: true, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 4, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test create-empty-file flag false.", + args: []string{"gcsfuse", "--create-empty-file=false", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedEnableRapidAppends: true, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 4, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test default flags.", + args: []string{"gcsfuse", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedEnableRapidAppends: true, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 4, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test enable-streaming-writes flag true.", + args: []string{"gcsfuse", "--enable-streaming-writes", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedEnableRapidAppends: true, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 4, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test enable-streaming-writes flag false.", + args: []string{"gcsfuse", "--enable-streaming-writes=false", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: false, + expectedEnableRapidAppends: true, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 4, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test enable-rapid-appends flag true.", + args: []string{"gcsfuse", "--enable-rapid-appends=false", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedEnableRapidAppends: false, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 4, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test positive write-block-size-mb flag.", + args: []string{"gcsfuse", "--enable-streaming-writes", "--write-block-size-mb=10", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedEnableRapidAppends: true, + expectedWriteBlockSizeMB: 10 * util.MiB, + expectedWriteGlobalMaxBlocks: 4, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test positive write-global-max-blocks flag.", + args: []string{"gcsfuse", "--enable-streaming-writes", "--write-global-max-blocks=10", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedEnableRapidAppends: true, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 10, + expectedWriteMaxBlocksPerFile: 1, + }, + { + name: "Test positive write-max-blocks-per-file flag.", + args: []string{"gcsfuse", "--enable-streaming-writes", "--write-max-blocks-per-file=10", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedEnableRapidAppends: true, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 4, + expectedWriteMaxBlocksPerFile: 10, + }, + { + name: "Test high performance config values.", + args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "abc", "pqr"}, + expectedEnableStreamingWrites: true, + expectedEnableRapidAppends: true, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 1600, + }, + { + name: "Test high performance config values with --write-global-max-blocks flag overriden.", + args: []string{"gcsfuse", "--write-global-max-blocks=2000", "--disable-autoconfig=false", "abc", "pqr"}, + expectedCreateEmptyFile: false, + expectedEnableStreamingWrites: true, + expectedEnableRapidAppends: true, + expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteGlobalMaxBlocks: 2000, + expectedWriteMaxBlocksPerFile: 1, }, } @@ -387,6 +387,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { assert.Equal(t, tc.expectedEnableStreamingWrites, wc.EnableStreamingWrites) assert.Equal(t, tc.expectedWriteBlockSizeMB, wc.BlockSizeMb) assert.Equal(t, tc.expectedWriteGlobalMaxBlocks, wc.GlobalMaxBlocks) + assert.Equal(t, tc.expectedEnableRapidAppends, wc.EnableRapidAppends) } }) } diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index cefee9aa04..3f34ea5936 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -12,6 +12,7 @@ write: global-max-blocks: 20 block-size-mb: 10 max-blocks-per-file: 2 + enable-rapid-appends: false file-cache: cache-file-for-range-read: true download-chunk-size-mb: 300 diff --git a/internal/fs/fs.go b/internal/fs/fs.go index f9979c7cc1..2711a0e1d8 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2931,7 +2931,7 @@ func (fs *fileSystem) WriteFile( } return err } - if fs.newConfig.Write.ExperimentalEnableRapidAppends { + if fs.newConfig.Write.EnableRapidAppends { // Serve the request via the file handle. gcsSynced, err = fh.Write(ctx, op.Data, op.Offset) } else { diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index bd75eb2fca..1b208f988e 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -1064,7 +1064,7 @@ func (f *FileInode) areBufferedWritesSupported(openMode util.OpenMode, obj *gcs. if f.local || obj.Size == 0 { return true } - if !f.config.Write.ExperimentalEnableRapidAppends { + if !f.config.Write.EnableRapidAppends { return false } if openMode == util.Append && f.bucket.BucketType().Zonal && obj.Finalized.IsZero() { diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index 777f82999a..beeb019830 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -246,7 +246,7 @@ func (t *FileTest) TestAreBufferedWritesSupported() { object.Finalized = tc.finalized t.backingObj = storageutil.ConvertObjToMinObject(object) t.createInode() - t.in.config.Write.ExperimentalEnableRapidAppends = true + t.in.config.Write.EnableRapidAppends = true isSupported := t.in.areBufferedWritesSupported(tc.openMode, object) @@ -1898,9 +1898,9 @@ func getWriteConfig() *cfg.WriteConfig { func getWriteConfigWithEnabledRapidAppends() *cfg.WriteConfig { return &cfg.WriteConfig{ - MaxBlocksPerFile: 10, - BlockSizeMb: 10, - EnableStreamingWrites: true, - ExperimentalEnableRapidAppends: true, + MaxBlocksPerFile: 10, + BlockSizeMb: 10, + EnableStreamingWrites: true, + EnableRapidAppends: true, } } diff --git a/internal/gcsx/bucket_manager.go b/internal/gcsx/bucket_manager.go index 5f3dd44631..c8c4bf0a9b 100644 --- a/internal/gcsx/bucket_manager.go +++ b/internal/gcsx/bucket_manager.go @@ -64,10 +64,10 @@ type BucketConfig struct { // Note that if the process fails or is interrupted the temporary object will // not be cleaned up, so the user must ensure that TmpObjectPrefix is // periodically garbage collected. - AppendThreshold int64 - ChunkTransferTimeoutSecs int64 - TmpObjectPrefix string - ExperimentalEnableRapidAppends bool + AppendThreshold int64 + ChunkTransferTimeoutSecs int64 + TmpObjectPrefix string + EnableRapidAppends bool } // BucketManager manages the lifecycle of buckets. @@ -170,7 +170,7 @@ func (bm *bucketManager) SetUpBucket( if name == canned.FakeBucketName { b = canned.MakeFakeBucket(ctx) } else { - b, err = bm.storageHandle.BucketHandle(ctx, name, bm.config.BillingProject, bm.config.ExperimentalEnableRapidAppends) + b, err = bm.storageHandle.BucketHandle(ctx, name, bm.config.BillingProject, bm.config.EnableRapidAppends) if err != nil { err = fmt.Errorf("BucketHandle: %w", err) return diff --git a/tools/integration_tests/rapid_appends/appends_test.go b/tools/integration_tests/rapid_appends/appends_test.go index 90b43cb99d..f01755b065 100644 --- a/tools/integration_tests/rapid_appends/appends_test.go +++ b/tools/integration_tests/rapid_appends/appends_test.go @@ -42,7 +42,7 @@ func (t *DualMountAppendsSuite) TestAppendSessionInvalidatedByAnotherClientUponT const initialContent = "dummy content" const appendContent = "appended content" for _, flags := range [][]string{ - {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + {"--enable-rapid-appends=true", "--write-block-size-mb=1"}, } { var err error func() { @@ -99,7 +99,7 @@ func (t *SingleMountAppendsSuite) TestContentAppendedInNonAppendModeNotVisibleTi t.T().Skip() for _, flags := range [][]string{ - {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + {"--enable-rapid-appends=true", "--write-block-size-mb=1"}, } { func() { t.mountPrimaryMount(flags) @@ -143,7 +143,7 @@ func (t *SingleMountAppendsSuite) TestContentAppendedInNonAppendModeNotVisibleTi func (t *SingleMountAppendsSuite) TestAppendsToFinalizedObjectNotVisibleUntilClose() { const initialContent = "dummy content" for _, flags := range [][]string{ - {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + {"--enable-rapid-appends=true", "--write-block-size-mb=1"}, } { func() { t.mountPrimaryMount(flags) @@ -184,7 +184,7 @@ func (t *SingleMountAppendsSuite) TestAppendsToFinalizedObjectNotVisibleUntilClo func (t *SingleMountAppendsSuite) TestAppendsVisibleInRealTimeWithConcurrentRPlusHandle() { const initialContent = "dummy content" for _, flags := range [][]string{ - {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + {"--enable-rapid-appends=true", "--write-block-size-mb=1"}, } { func() { t.mountPrimaryMount(flags) @@ -238,7 +238,7 @@ func (t *SingleMountAppendsSuite) TestRandomWritesVisibleAfterCloseWithConcurren const initialContent = "dummy content" for _, flags := range [][]string{ - {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + {"--enable-rapid-appends=true", "--write-block-size-mb=1"}, } { func() { t.mountPrimaryMount(flags) @@ -291,7 +291,7 @@ func (t *SingleMountAppendsSuite) TestFallbackHappensWhenNonAppendHandleDoesFirs t.T().Skip() for _, flags := range [][]string{ - {"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"}, + {"--enable-rapid-appends=true", "--write-block-size-mb=1"}, } { func() { t.mountPrimaryMount(flags) @@ -336,7 +336,7 @@ func (t *SingleMountAppendsSuite) TestFallbackHappensWhenNonAppendHandleDoesFirs func (t *SingleMountAppendsSuite) TestKernelShouldSeeUpdatedSizeOnAppends() { const initialContent = "dummy content" - flags := []string{"--write-experimental-enable-rapid-appends=true", "--write-block-size-mb=1"} + flags := []string{"--enable-rapid-appends=true", "--write-block-size-mb=1"} log.Printf("Running test with flags: %v", flags) testCases := []struct { diff --git a/tools/integration_tests/rapid_appends/reads_after_appends_test.go b/tools/integration_tests/rapid_appends/reads_after_appends_test.go index 1102037480..1b1ef1b7d7 100644 --- a/tools/integration_tests/rapid_appends/reads_after_appends_test.go +++ b/tools/integration_tests/rapid_appends/reads_after_appends_test.go @@ -108,19 +108,19 @@ func (t *CommonAppendsSuite) TestAppendsAndReads() { // all cache disabled enableMetadataCache: false, enableFileCache: false, - flags: []string{"--write-experimental-enable-rapid-appends=true", "--write-global-max-blocks=-1", "--metadata-cache-ttl-secs=0"}, + flags: []string{"--enable-rapid-appends=true", "--write-global-max-blocks=-1", "--metadata-cache-ttl-secs=0"}, }, { enableMetadataCache: true, enableFileCache: false, - flags: []string{"--write-experimental-enable-rapid-appends=true", "--write-global-max-blocks=-1", metadataCacheEnableFlag}, + flags: []string{"--enable-rapid-appends=true", "--write-global-max-blocks=-1", metadataCacheEnableFlag}, }, { enableMetadataCache: true, enableFileCache: true, - flags: []string{"--write-experimental-enable-rapid-appends=true", "--write-global-max-blocks=-1", metadataCacheEnableFlag, "--file-cache-max-size-mb=-1", fileCacheDirFlag()}, + flags: []string{"--enable-rapid-appends=true", "--write-global-max-blocks=-1", metadataCacheEnableFlag, "--file-cache-max-size-mb=-1", fileCacheDirFlag()}, }, { enableMetadataCache: false, enableFileCache: true, - flags: []string{"--write-experimental-enable-rapid-appends=true", "--write-global-max-blocks=-1", "--metadata-cache-ttl-secs=0", "--file-cache-max-size-mb=-1", fileCacheDirFlag()}, + flags: []string{"--enable-rapid-appends=true", "--write-global-max-blocks=-1", "--metadata-cache-ttl-secs=0", "--file-cache-max-size-mb=-1", fileCacheDirFlag()}, }} { func() { t.mountPrimaryMount(scenario.flags) diff --git a/tools/integration_tests/rapid_appends/suites_test.go b/tools/integration_tests/rapid_appends/suites_test.go index 5414c9e470..c2e3ec8aca 100644 --- a/tools/integration_tests/rapid_appends/suites_test.go +++ b/tools/integration_tests/rapid_appends/suites_test.go @@ -39,7 +39,7 @@ type mountPoint struct { var ( // TODO(b/432179045): `--write-global-max-blocks=-1` is needed right now because of a bug in global semaphore release. - secondaryMountFlags = []string{"--write-experimental-enable-rapid-appends=true", "--write-global-max-blocks=-1"} + secondaryMountFlags = []string{"--enable-rapid-appends=true", "--write-global-max-blocks=-1"} ) // ////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go index 798158e1c5..2be2ce607b 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go @@ -54,20 +54,15 @@ func (t *unfinalizedObjectOperations) TeardownTest() {} // Test scenarios //////////////////////////////////////////////////////////////////////// -func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCreatedOutsideOfMountReports0Size() { +func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCreatedOutsideOfMountReportsNonZeroSize() { size := operations.MiB writer := client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), setup.GenerateRandomString(size)) + defer writer.Close() statRes, err := operations.StatFile(path.Join(t.testDirPath, t.fileName)) require.NoError(t.T(), err) assert.Equal(t.T(), t.fileName, (*statRes).Name()) - assert.EqualValues(t.T(), 0, (*statRes).Size()) - // After object is finalized, correct size should be reported. - err = writer.Close() - require.NoError(t.T(), err) - statRes, err = operations.StatFile(path.Join(t.testDirPath, t.fileName)) - require.NoError(t.T(), err) assert.EqualValues(t.T(), size, (*statRes).Size()) } From 70e1adf37f8b3796256140414deb62a16931a9f3 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 5 Aug 2025 10:17:11 +0530 Subject: [PATCH 0646/1298] style(buffered-reader): Improving log and error message (#3626) * improving log and error message * review comments * minor review --- internal/block/prefetch_block.go | 8 ++-- internal/bufferedread/buffered_reader.go | 48 +++++++++---------- internal/bufferedread/buffered_reader_test.go | 16 +++++++ internal/bufferedread/download_task.go | 6 +-- 4 files changed, 47 insertions(+), 31 deletions(-) diff --git a/internal/block/prefetch_block.go b/internal/block/prefetch_block.go index 4ac1711f75..17468e8626 100644 --- a/internal/block/prefetch_block.go +++ b/internal/block/prefetch_block.go @@ -92,7 +92,7 @@ func createPrefetchBlock(blockSize int64) (PrefetchBlock, error) { prot, flags := syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_ANON|syscall.MAP_PRIVATE addr, err := syscall.Mmap(-1, 0, int(blockSize), prot, flags) if err != nil { - return nil, fmt.Errorf("mmap error: %v", err) + return nil, fmt.Errorf("createPrefetchBlock: Mmap: %w", err) } mb := memoryBlock{ @@ -115,7 +115,7 @@ func createPrefetchBlock(blockSize int64) (PrefetchBlock, error) { // It returns the number of bytes read and an error if any. func (pmb *prefetchMemoryBlock) ReadAt(p []byte, off int64) (n int, err error) { if off < 0 || off >= pmb.Size() { - return 0, fmt.Errorf("offset %d is out of bounds for block size %d", off, pmb.Size()) + return 0, fmt.Errorf("prefetchMemoryBlock.ReadAt: offset %d is out of bounds for block size %d", off, pmb.Size()) } n = copy(p, pmb.buffer[pmb.offset.start+off:pmb.offset.end]) @@ -135,12 +135,12 @@ func (pmb *prefetchMemoryBlock) AbsStartOff() int64 { func (pmb *prefetchMemoryBlock) SetAbsStartOff(startOff int64) error { if startOff < 0 { - return fmt.Errorf("startOff cannot be negative, got %d", startOff) + return fmt.Errorf("SetAbsStartOff: negative startOff %d is not allowed", startOff) } // If absStartOff is already set, then return an error. if pmb.absStartOff >= 0 { - return fmt.Errorf("AbsStartOff is already set, it should be set only once.") + return fmt.Errorf("SetAbsStartOff: absStartOff is already set, re-setting is not allowed.") } pmb.absStartOff = startOff diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index 244a21a10b..999ffde5bd 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -30,6 +30,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" "github.com/googlecloudplatform/gcsfuse/v3/metrics" + "github.com/jacobsa/fuse/fuseops" "golang.org/x/sync/semaphore" ) @@ -47,6 +48,7 @@ type BufferedReadConfig struct { const ( defaultRandomReadsThreshold = 3 defaultPrefetchMultiplier = 2 + ReadOp = "readOp" ) // blockQueueEntry holds a data block with a function @@ -94,7 +96,7 @@ type BufferedReader struct { func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *BufferedReadConfig, globalMaxBlocksSem *semaphore.Weighted, workerPool workerpool.WorkerPool, metricHandle metrics.MetricHandle) (*BufferedReader, error) { blockpool, err := block.NewPrefetchBlockPool(config.PrefetchBlockSizeBytes, config.MaxPrefetchBlockCnt, globalMaxBlocksSem) if err != nil { - return nil, fmt.Errorf("failed to create worker pool: %w", err) + return nil, fmt.Errorf("NewBufferedReader: failed to create block-pool: %w", err) } reader := &BufferedReader{ @@ -148,8 +150,7 @@ func (p *BufferedReader) handleRandomRead(offset int64) error { } if p.randomSeekCount > p.randomReadsThreshold { - logger.Tracef("Too many random reads for object %q (count: %d, threshold: %d); falling back to another reader.", - p.object.Name, p.randomSeekCount, p.randomReadsThreshold) + logger.Warnf("handleRandomRead: random seek count %d exceeded threshold %d, falling back to another reader", p.randomSeekCount, p.randomReadsThreshold) return gcsx.FallbackToAnotherReader } @@ -225,11 +226,14 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) start := time.Now() initOff := off blockIdx := initOff / p.config.PrefetchBlockSizeBytes - var bytesRead int var err error + handleID := int64(-1) // As 0 is a valid handle ID, we use -1 to indicate no handle. + if readOp, ok := ctx.Value(ReadOp).(*fuseops.ReadFileOp); ok { + handleID = int64(readOp.Handle) + } - logger.Tracef("%.13v <- ReadAt(offset=%d, len=%d, blockIdx=%d)", reqID, off, len(inputBuf), blockIdx) + logger.Tracef("%.13v <- ReadAt(%s:/%s, %d, %d, %d, %d)", reqID, p.bucket.Name(), p.object.Name, handleID, off, len(inputBuf), blockIdx) if off >= int64(p.object.Size) { err = io.EOF @@ -242,10 +246,8 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) defer func() { dur := time.Since(start) - if err != nil && err != io.EOF { - logger.Errorf("%.13v -> ReadAt failed (offset=%d, len=%d, blockIdx=%d): err: %v (%v)", reqID, initOff, len(inputBuf), blockIdx, err, dur) - } else { - logger.Tracef("%.13v -> ReadAt OK (offset=%d, len=%d, read=%d) (%v)", reqID, initOff, len(inputBuf), bytesRead, dur) + if err == nil || errors.Is(err, io.EOF) { + logger.Tracef("%.13v -> ReadAt(): Ok(%v)", reqID, dur) } }() @@ -264,7 +266,7 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) if errors.Is(err, ErrPrefetchBlockNotAvailable) { return resp, gcsx.FallbackToAnotherReader } - err = fmt.Errorf("ReadAt: freshStart failed: %w", err) + err = fmt.Errorf("BufferedReader.ReadAt: freshStart failed: %w", err) break } prefetchTriggered = true @@ -275,7 +277,7 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) status, waitErr := blk.AwaitReady(ctx) if waitErr != nil { - err = fmt.Errorf("ReadAt: AwaitReady failed: %w", waitErr) + err = fmt.Errorf("BufferedReader.ReadAt: AwaitReady failed: %w", waitErr) break } @@ -286,9 +288,9 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) switch status.State { case block.BlockStateDownloadFailed: - err = fmt.Errorf("ReadAt: download failed: %w", status.Err) + err = fmt.Errorf("BufferedReader.ReadAt: download failed: %w", status.Err) default: - err = fmt.Errorf("ReadAt: unexpected block state: %d", status.State) + err = fmt.Errorf("BufferedReader.ReadAt: unexpected block state: %d", status.State) } break } @@ -299,7 +301,7 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) off += int64(n) if readErr != nil && !errors.Is(readErr, io.EOF) { - err = fmt.Errorf("ReadAt: block.ReadAt failed: %w", readErr) + err = fmt.Errorf("BufferedReader.ReadAt: block.ReadAt: %w", readErr) break } @@ -315,7 +317,7 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) if !prefetchTriggered { prefetchTriggered = true if pfErr := p.prefetch(); pfErr != nil { - logger.Warnf("Prefetch failed: %v", pfErr) + logger.Warnf("BufferedReader.ReadAt: while prefetching: %v", pfErr) } } } @@ -338,8 +340,6 @@ func (p *BufferedReader) prefetch() error { return nil } - logger.Tracef("Prefetching %d blocks", blockCountToPrefetch) - totalBlockCount := (int64(p.object.Size) + p.config.PrefetchBlockSizeBytes - 1) / p.config.PrefetchBlockSizeBytes for i := int64(0); i < blockCountToPrefetch; i++ { if p.nextBlockIndexToPrefetch >= totalBlockCount { @@ -371,12 +371,12 @@ func (p *BufferedReader) freshStart(currentOffset int64) error { // Schedule the first block as urgent. if err := p.scheduleNextBlock(true); err != nil { - return fmt.Errorf("freshStart: initial scheduling failed: %w", err) + return fmt.Errorf("freshStart: while scheduling first block: %w", err) } // Prefetch the initial blocks. if err := p.prefetch(); err != nil { - return fmt.Errorf("freshStart: prefetch failed: %w", err) + return fmt.Errorf("freshStart: while prefetching: %w", err) } return nil } @@ -387,7 +387,7 @@ func (p *BufferedReader) scheduleNextBlock(urgent bool) error { b, err := p.blockPool.Get() if err != nil || b == nil { if err != nil { - logger.Warnf("failed to get block from pool: %v", err) + logger.Warnf("scheduleNextBlock: failed to get block from pool: %v", err) } return ErrPrefetchBlockNotAvailable } @@ -404,13 +404,13 @@ func (p *BufferedReader) scheduleNextBlock(urgent bool) error { func (p *BufferedReader) scheduleBlockWithIndex(b block.PrefetchBlock, blockIndex int64, urgent bool) error { startOffset := blockIndex * p.config.PrefetchBlockSizeBytes if err := b.SetAbsStartOff(startOffset); err != nil { - return fmt.Errorf("failed to set start offset on block: %w", err) + return fmt.Errorf("scheduleBlockWithIndex: failed to set start offset: %w", err) } ctx, cancel := context.WithCancel(p.ctx) task := NewDownloadTask(ctx, p.object, p.bucket, b, p.readHandle) - logger.Tracef("Scheduling block (%s, offset %d).", p.object.Name, startOffset) + logger.Tracef("Scheduling block: (%s, %d, %t).", p.object.Name, blockIndex, urgent) p.blockQueue.Push(&blockQueueEntry{ block: b, cancel: cancel, @@ -432,14 +432,14 @@ func (p *BufferedReader) Destroy() { // We expect a context.Canceled error here, but we wait to ensure the // block's worker goroutine has finished before releasing the block. if _, err := bqe.block.AwaitReady(p.ctx); err != nil && err != context.Canceled { - logger.Warnf("bufferedread: error waiting for block on destroy: %v", err) + logger.Warnf("Destroy: while waiting for block on destroy: %v", err) } p.blockPool.Release(bqe.block) } err := p.blockPool.ClearFreeBlockChannel(true) if err != nil { - logger.Warnf("bufferedread: error clearing free block channel: %v", err) + logger.Warnf("Destroy: while clearing free block channel: %v", err) } p.blockPool = nil } diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 2708fc7138..864672d410 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -679,6 +679,7 @@ func (t *BufferedReaderTest) TestReadAtOffsetBeyondEOF() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err) buf := make([]byte, 10) + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. resp, err := reader.ReadAt(t.ctx, buf, int64(t.object.Size+1)) @@ -690,6 +691,7 @@ func (t *BufferedReaderTest) TestReadAtEmptyBuffer() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err) buf := make([]byte, 0) + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. resp, err := reader.ReadAt(t.ctx, buf, 0) @@ -704,6 +706,7 @@ func (t *BufferedReaderTest) TestReadAtBackwardSeekIsRandomRead() { // This is a random read since offset != 0 and queue is empty. startOffset := int64(3072) // block 3 t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(startOffset) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), startOffset), nil).Once() + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(startOffset+testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), startOffset+testPrefetchBlockSizeBytes), nil).Once() @@ -757,6 +760,7 @@ func (t *BufferedReaderTest) TestReadAtForwardSeekDiscardsPreviousBlocks() { t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 3*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 3*testPrefetchBlockSizeBytes), nil).Once() t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 4*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 4*testPrefetchBlockSizeBytes), nil).Once() readOffset := int64(2048) + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. // Read the entire block at offset 2048 to trigger the prefetch logic. _, err = reader.ReadAt(t.ctx, make([]byte, 1024), readOffset) @@ -780,6 +784,7 @@ func (t *BufferedReaderTest) TestReadAtInitialDownloadFails() { require.NoError(t.T(), err) downloadError := errors.New("gcs error") t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.AnythingOfType("*gcs.ReadObjectRequest")).Return(nil, downloadError) + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. buf := make([]byte, 10) _, err = reader.ReadAt(t.ctx, buf, 0) @@ -812,6 +817,7 @@ func (t *BufferedReaderTest) TestReadAtAwaitReadyCancelled() { reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) ctx, cancel := context.WithCancel(context.Background()) cancel() + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. // Read with a cancelled context. _, err = reader.ReadAt(ctx, make([]byte, 10), 0) @@ -829,6 +835,7 @@ func (t *BufferedReaderTest) TestReadAtBlockStateDownloadFailed() { downloadError := errors.New("simulated download error") b.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadFailed, Err: downloadError}) reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. // Read from a reader where the next block has failed to download. _, err = reader.ReadAt(t.ctx, make([]byte, 10), 0) @@ -850,6 +857,7 @@ func (t *BufferedReaderTest) TestReadAtBlockDownloadCancelled() { require.NoError(t.T(), err) b.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadFailed, Err: context.Canceled}) reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. // Read from a reader where the next block download was cancelled. _, err = reader.ReadAt(t.ctx, make([]byte, 10), 0) @@ -871,6 +879,7 @@ func (t *BufferedReaderTest) TestReadAtBlockStateUnexpected() { require.NoError(t.T(), err) b.NotifyReady(block.BlockStatus{State: block.BlockStateInProgress}) reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. // Read from a reader where the next block is in an unexpected state. _, err = reader.ReadAt(t.ctx, make([]byte, 10), 0) @@ -896,6 +905,7 @@ func (t *BufferedReaderTest) TestReadAtFromDownloadedBlock() { b.NotifyReady(block.BlockStatus{State: block.BlockStateDownloaded}) reader.blockQueue.Push(&blockQueueEntry{block: b, cancel: func() {}}) buf := make([]byte, 5) + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. // Read from a block that is already downloaded and in the queue. resp, err := reader.ReadAt(t.ctx, buf, 0) @@ -913,6 +923,7 @@ func (t *BufferedReaderTest) TestReadAtExactlyToEndOfFile() { t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 0), nil).Once() t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), 50, testPrefetchBlockSizeBytes), nil).Once() buf := make([]byte, t.object.Size) + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. // Read the entire file. resp, err := reader.ReadAt(t.ctx, buf, 0) @@ -932,6 +943,7 @@ func (t *BufferedReaderTest) TestReadAtSucceedsWhenPrefetchFails() { prefetchError := errors.New("prefetch failed") t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 2*uint64(testPrefetchBlockSizeBytes) })).Return(nil, prefetchError).Once() buf := make([]byte, testPrefetchBlockSizeBytes) + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. // Read the first block. This should succeed, even though a background prefetch will fail. resp, err := reader.ReadAt(t.ctx, buf, 0) @@ -973,6 +985,7 @@ func (t *BufferedReaderTest) TestReadAtSpanningMultipleBlocks() { t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(2*testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 2*testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. resp, err := reader.ReadAt(t.ctx, buf, readOffset) @@ -1005,6 +1018,7 @@ func (t *BufferedReaderTest) TestReadAtSequentialReadAcrossBlocks() { })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 3*testPrefetchBlockSizeBytes), nil).Once() buf1 := make([]byte, testPrefetchBlockSizeBytes) buf2 := make([]byte, testPrefetchBlockSizeBytes) + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. // Perform two sequential reads. _, err = reader.ReadAt(t.ctx, buf1, 0) @@ -1035,6 +1049,7 @@ func (t *BufferedReaderTest) TestReadAtFallsBackAfterRandomReads() { // Mock GCS calls for the first random read, which will download block 2 and prefetch block 3. t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 2*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 2*testPrefetchBlockSizeBytes), nil).Once() t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 3*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 3*testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. // First random read should succeed. _, err = reader.ReadAt(t.ctx, buf, 2*testPrefetchBlockSizeBytes) require.NoError(t.T(), err, "Random read #1 should succeed") @@ -1064,6 +1079,7 @@ func (t *BufferedReaderTest) TestReadAtExceedsObjectSize() { t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == uint64(testPrefetchBlockSizeBytes) && r.Range.Limit == objectSize })).Return(createFakeReaderWithOffset(t.T(), 512, testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("Name").Return("test-bucket").Maybe() // Bucket name used for logging. resp, err := reader.ReadAt(t.ctx, buf, readOffset) diff --git a/internal/bufferedread/download_task.go b/internal/bufferedread/download_task.go index 45114d29a4..9d12c9d5b2 100644 --- a/internal/bufferedread/download_task.go +++ b/internal/bufferedread/download_task.go @@ -66,7 +66,7 @@ func (p *DownloadTask) Execute() { var err error defer func() { if err == nil { - logger.Tracef("Download: -> block (%s, %v) completed in: %v.", p.object.Name, blockId, time.Since(stime)) + logger.Tracef("Download: -> block (%s, %v) Ok(%v).", p.object.Name, blockId, time.Since(stime)) p.block.NotifyReady(block.BlockStatus{State: block.BlockStateDownloaded}) } else if errors.Is(err, context.Canceled) && p.ctx.Err() == context.Canceled { logger.Tracef("Download: -> block (%s, %v) cancelled: %v.", p.object.Name, blockId, err) @@ -95,13 +95,13 @@ func (p *DownloadTask) Execute() { ReadHandle: p.readHandle, }) if err != nil { - err = fmt.Errorf("while reader-creations: %w", err) + err = fmt.Errorf("DownloadTask.Execute: while reader-creations: %w", err) return } _, err = io.CopyN(p.block, newReader, int64(end-start)) if err != nil { - err = fmt.Errorf("while copying data: %w", err) + err = fmt.Errorf("DownloadTask.Execute: while data-copy: %w", err) return } } From 0c18cd143524ab58dfaee6cfb7f9fab0be05029c Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 5 Aug 2025 15:39:01 +0530 Subject: [PATCH 0647/1298] fix: Add flag check in accessing moveObject API for flat bucket (#3640) * add flag check * review comment --- internal/fs/fs.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 2711a0e1d8..1c797d668e 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2556,8 +2556,10 @@ func (fs *fileSystem) renameNonHierarchicalDir( } o := descendant.MinObject - // If the descendant is a directory (ExplicitDirType) or has an unknown type, handle it by cloning and deleting. - if descendant.Type() == metadata.ExplicitDirType || descendant.Type() == metadata.UnknownType { + // Use copy-delete if atomic rename is disabled, or if the object is a directory or of unknown type. + // Otherwise, for files with atomic rename enabled, use move. + isDirOrUnknown := descendant.Type() == metadata.ExplicitDirType || descendant.Type() == metadata.UnknownType + if !fs.enableAtomicRenameObject || isDirOrUnknown { if _, err = newDir.CloneToChildFile(ctx, nameDiff, o); err != nil { return fmt.Errorf("copy file %q: %w", o.Name, err) } From 94cadedc441cff3dea1ce4df72b4b01328fbbf5f Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 5 Aug 2025 16:47:04 +0530 Subject: [PATCH 0648/1298] fix: streaming writes global max blocks semaphore (#3584) * include semaphore fix * fix in unlink flow * add unit tests * review comment * review comment * review comment --- internal/block/block_pool.go | 10 +++++++--- internal/block/block_pool_test.go | 14 ++++++++++++++ internal/bufferedwrites/buffered_write_handler.go | 4 +++- .../bufferedwrites/buffered_write_handler_test.go | 12 ++++++++++-- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/internal/block/block_pool.go b/internal/block/block_pool.go index ec5f5b9dd9..f2e10fe6cb 100644 --- a/internal/block/block_pool.go +++ b/internal/block/block_pool.go @@ -172,12 +172,16 @@ func (bp *GenBlockPool[T]) ClearFreeBlockChannel(releaseLastBlock bool) error { return fmt.Errorf("munmap error: %v", err) } bp.totalBlocks-- - // Release semaphore for last block iff releaseLastBlock is true. - if bp.totalBlocks != 0 || releaseLastBlock { + // Release semaphore for all but the last block. + if bp.totalBlocks != 0 { bp.globalMaxBlocksSem.Release(1) } default: - // Return if there are no more blocks on the channel. + // We are here, it means there are no more blocks in the free blocks channel. + // Release semaphore for last block iff releaseLastBlock is true. + if releaseLastBlock { + bp.globalMaxBlocksSem.Release(1) + } return nil } } diff --git a/internal/block/block_pool_test.go b/internal/block/block_pool_test.go index f5c6f6a4c6..7a1f599615 100644 --- a/internal/block/block_pool_test.go +++ b/internal/block/block_pool_test.go @@ -208,6 +208,20 @@ func (t *BlockPoolTest) TestClearFreeBlockChannel() { } } +func (t *BlockPoolTest) TestClearFreeBlockChannelWhenTotalBlocksIsZero() { + bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(1), createBlock) + require.Nil(t.T(), err) + require.Equal(t.T(), int64(0), bp.totalBlocks) + + err = bp.ClearFreeBlockChannel(true) + + require.Nil(t.T(), err) + require.Equal(t.T(), int64(0), bp.totalBlocks) + // Check if semaphore is released correctly. + require.True(t.T(), bp.globalMaxBlocksSem.TryAcquire(1)) + require.False(t.T(), bp.globalMaxBlocksSem.TryAcquire(1)) +} + func (t *BlockPoolTest) TestBlockPoolCreationAcquiresGlobalSem() { globalBlocksSem := semaphore.NewWeighted(1) diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index 9c82ad3462..cd09295cbe 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -307,7 +307,9 @@ func (wh *bufferedWriteHandlerImpl) writeDataForTruncatedSize() error { func (wh *bufferedWriteHandlerImpl) Unlink() { wh.uploadHandler.CancelUpload() - err := wh.blockPool.ClearFreeBlockChannel(true) + // Since bwh is not cleared after unlink, we will not release last block yet. + // Last block will be released when file handle for this file is closed. + err := wh.blockPool.ClearFreeBlockChannel(false) if err != nil { // Only logging an error in case of resource leak. logger.Errorf("blockPool.ClearFreeBlockChannel() failed: %v", err) diff --git a/internal/bufferedwrites/buffered_write_handler_test.go b/internal/bufferedwrites/buffered_write_handler_test.go index 8e49c0b22b..1b474e443e 100644 --- a/internal/bufferedwrites/buffered_write_handler_test.go +++ b/internal/bufferedwrites/buffered_write_handler_test.go @@ -37,7 +37,8 @@ const chunkTransferTimeoutSecs int64 = 10 var errUploadFailure = errors.New("error while uploading object to GCS") type BufferedWriteTest struct { - bwh BufferedWriteHandler + bwh BufferedWriteHandler + globalSemaphore *semaphore.Weighted suite.Suite } @@ -52,13 +53,14 @@ func (testSuite *BufferedWriteTest) SetupTest() { func (testSuite *BufferedWriteTest) setupTestWithBucketType(bucketType gcs.BucketType) { bucket := fake.NewFakeBucket(timeutil.RealClock(), "FakeBucketName", bucketType) + testSuite.globalSemaphore = semaphore.NewWeighted(10) bwh, err := NewBWHandler(&CreateBWHandlerRequest{ Object: nil, ObjectName: "testObject", Bucket: bucket, BlockSize: blockSize, MaxBlocksPerFile: 10, - GlobalMaxBlocksSem: semaphore.NewWeighted(10), + GlobalMaxBlocksSem: testSuite.globalSemaphore, ChunkTransferTimeoutSecs: chunkTransferTimeoutSecs, }) require.Nil(testSuite.T(), err) @@ -463,6 +465,9 @@ func (testSuite *BufferedWriteTest) TestUnlinkBeforeWrite() { assert.Nil(testSuite.T(), bwhImpl.uploadHandler.cancelFunc) assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) assert.Equal(testSuite.T(), 0, bwhImpl.uploadHandler.blockPool.TotalFreeBlocks()) + // Check if semaphore is released correctly. Last block should not be released. + assert.True(testSuite.T(), testSuite.globalSemaphore.TryAcquire(9)) + assert.False(testSuite.T(), testSuite.globalSemaphore.TryAcquire(1)) } func (testSuite *BufferedWriteTest) TestUnlinkAfterWrite() { @@ -482,6 +487,9 @@ func (testSuite *BufferedWriteTest) TestUnlinkAfterWrite() { assert.True(testSuite.T(), cancelCalled) assert.Equal(testSuite.T(), 0, len(bwhImpl.uploadHandler.uploadCh)) assert.Equal(testSuite.T(), 0, bwhImpl.uploadHandler.blockPool.TotalFreeBlocks()) + // Check if semaphore is released correctly. Last block should not be released. + assert.True(testSuite.T(), testSuite.globalSemaphore.TryAcquire(9)) + assert.False(testSuite.T(), testSuite.globalSemaphore.TryAcquire(1)) } func (testSuite *BufferedWriteTest) TestReFlushAfterUploadFails() { From b0a219eca7851c17f3c726bbe52dc7375648ebd6 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Tue, 5 Aug 2025 20:44:46 +0530 Subject: [PATCH 0649/1298] cd script changes to run read cache tests on a seperate VM in louhi (#3324) * e2e script changes for running read cache tests on a seperate VM in louhi * Fix exit code assignment * adding desc comments --- tools/cd_scripts/e2e_test.sh | 362 +++++++++++++++++++---------------- 1 file changed, 193 insertions(+), 169 deletions(-) diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index e04b1a3e67..7a99fedd30 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -27,37 +27,43 @@ sudo /usr/local/google-cloud-sdk/install.sh export PATH=/usr/local/google-cloud-sdk/bin:$PATH gcloud version && rm gcloud.tar.gz -# Extract the metadata parameters passed, for which we need the zone of the GCE VM +# Extract the metadata parameters passed, for which we need the zone of the GCE VM # on which the tests are supposed to run. ZONE=$(curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/zone) echo "Got ZONE=\"${ZONE}\" from metadata server." # The format for the above extracted zone is projects/{project-id}/zones/{zone}, thus, from this # need extracted zone name. -ZONE_NAME=$(basename $ZONE) +ZONE_NAME=$(basename "$ZONE") # This parameter is passed as the GCE VM metadata at the time of creation.(Logic is handled in louhi stage script) RUN_ON_ZB_ONLY=$(gcloud compute instances describe "$HOSTNAME" --zone="$ZONE_NAME" --format='get(metadata.run-on-zb-only)') +RUN_READ_CACHE_TESTS_ONLY=$(gcloud compute instances describe "$HOSTNAME" --zone="$ZONE_NAME" --format='get(metadata.run-read-cache-only)') echo "RUN_ON_ZB_ONLY flag set to : \"${RUN_ON_ZB_ONLY}\"" +echo "RUN_READ_CACHE_TESTS_ONLY flag set to : \"${RUN_READ_CACHE_TESTS_ONLY}\"" # Logging the tests being run on the active GCE VM if [[ "$RUN_ON_ZB_ONLY" == "true" ]]; then - echo "Running integration tests for Zonal bucket only..." + echo "Running integration tests for Zonal bucket only..." else - echo "Running integration tests for non-zonal buckets only..." + echo "Running integration tests for non-zonal buckets only..." +fi + +# Logging the tests being run on the active GCE VM +if [[ "$RUN_READ_CACHE_TESTS_ONLY" == "true" ]]; then + echo "Running read cache test only..." fi #details.txt file contains the release version and commit hash of the current release. -gcloud storage cp gs://gcsfuse-release-packages/version-detail/details.txt . +gcloud storage cp gs://gcsfuse-release-packages/version-detail/details.txt . # Writing VM instance name to details.txt (Format: release-test-) -curl http://metadata.google.internal/computeMetadata/v1/instance/name -H "Metadata-Flavor: Google" >> details.txt +curl http://metadata.google.internal/computeMetadata/v1/instance/name -H "Metadata-Flavor: Google" >>details.txt # Based on the os type(from vm instance name) in detail.txt, run the following commands to add starterscriptuser -if grep -q ubuntu details.txt || grep -q debian details.txt; -then -# For ubuntu and debian os - sudo adduser --ingroup google-sudoers --disabled-password --home=/home/starterscriptuser --gecos "" starterscriptuser +if grep -q ubuntu details.txt || grep -q debian details.txt; then + # For ubuntu and debian os + sudo adduser --ingroup google-sudoers --disabled-password --home=/home/starterscriptuser --gecos "" starterscriptuser else -# For rhel and centos - sudo adduser -g google-sudoers --home-dir=/home/starterscriptuser starterscriptuser + # For rhel and centos + sudo adduser -g google-sudoers --home-dir=/home/starterscriptuser starterscriptuser fi # Run the following as starterscriptuser @@ -71,11 +77,12 @@ set -x export PATH=/usr/local/google-cloud-sdk/bin:$PATH # Export the RUN_ON_ZB_ONLY variable so that it is available in the environment of the 'starterscriptuser' user. -# Since we are running the subsequent script as 'starterscriptuser' using sudo, the environment of 'starterscriptuser' +# Since we are running the subsequent script as 'starterscriptuser' using sudo, the environment of 'starterscriptuser' # would not automatically have access to the environment variables set by the original user (i.e. $RUN_ON_ZB_ONLY). -# By exporting this variable, we ensure that the value of RUN_ON_ZB_ONLY is passed into the 'starterscriptuser' script +# By exporting this variable, we ensure that the value of RUN_ON_ZB_ONLY is passed into the 'starterscriptuser' script # and can be used for conditional logic or decisions within that script. export RUN_ON_ZB_ONLY='$RUN_ON_ZB_ONLY' +export RUN_READ_CACHE_TESTS_ONLY='$RUN_READ_CACHE_TESTS_ONLY' #Copy details.txt to starterscriptuser home directory and create logs.txt cd ~/ @@ -88,7 +95,7 @@ LOG_FILE='~/logs.txt' if [[ "$RUN_ON_ZB_ONLY" == "true" ]]; then LOG_FILE='~/logs-zonal.txt' fi - + echo "User: $USER" &>> ${LOG_FILE} echo "Current Working Directory: $(pwd)" &>> ${LOG_FILE} @@ -190,7 +197,6 @@ TEST_DIR_PARALLEL=( "local_file" "log_rotation" "mounting" - "read_cache" "gzip" "write_large_files" "rename_dir_limit" @@ -228,7 +234,6 @@ TEST_DIR_PARALLEL_ZONAL=( mounting mount_timeout negative_stat_cache - read_cache read_large_files rename_dir_limit stale_handle @@ -242,7 +247,7 @@ TEST_DIR_PARALLEL_ZONAL=( #streaming_writes ) -#For Zonal Buckets : These tests never become parallel as they are changing bucket permissions. +# For Zonal Buckets : These tests never become parallel as they are changing bucket permissions. TEST_DIR_NON_PARALLEL_ZONAL=( "managed_folders" "readonly" @@ -254,135 +259,134 @@ TEST_LOGS_FILE=$(mktemp) INTEGRATION_TEST_TIMEOUT=240m +# This method runs test packages in sequence.Necessary when the tests involves +# permissions modification etc. +# Arguments: +# $1: BUCKET_NAME (The name of the GCS bucket to use for tests.) +# $2: IS_ZONAL_BUCKET_FLAG (Boolean flag: 'true' if the bucket is zonal, 'false' otherwise.) +# $3: NAME_OF_TEST_DIR_ARRAY (The shell variable name of the array containing test directory.) function run_non_parallel_tests() { - local exit_code=0 - local -n test_array=$1 - local BUCKET_NAME=$2 - local zonal=$3 + if [ "$#" -ne 3 ]; then + echo "Incorrect number of arguments passed, Expecting + " + exit 1 + fi + local exit_code=0 # Initialize to 0 for success + local BUCKET_NAME=$1 + local zonal=$2 + if [[ -z $3 ]]; then + return 1 # The name of the test array cannot be empty. + fi + local -n test_array=$3 # Create a nameref to this array. + for test_dir_np in "${test_array[@]}" do test_path_non_parallel="./tools/integration_tests/$test_dir_np" # To make it clear whether tests are running on a flat or HNS or zonal bucket, We kept the log file naming # convention to include the bucket name as a suffix (e.g., package_name_bucket_name). local log_file="/tmp/${test_dir_np}_${BUCKET_NAME}.log" - echo $log_file >> $TEST_LOGS_FILE - # Executing integration tests - GODEBUG=asyncpreemptoff=1 go test $test_path_non_parallel -p 1 --zonal=${zonal} --integrationTest -v --testbucket=$BUCKET_NAME --testInstalledPackage=true -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 + echo "$log_file" >> "$TEST_LOGS_FILE" # Use double quotes for log_file + GODEBUG=asyncpreemptoff=1 go test "$test_path_non_parallel" -p 1 --zonal="${zonal}" --integrationTest -v --testbucket="$BUCKET_NAME" --testInstalledPackage=true -timeout "$INTEGRATION_TEST_TIMEOUT" > "$log_file" 2>&1 exit_code_non_parallel=$? - if [ $exit_code_non_parallel != 0 ]; then + if [ $exit_code_non_parallel -ne 0 ]; then exit_code=$exit_code_non_parallel fi done return $exit_code } +#This method runs test packages in parallel. +# Arguments: +# $1: BUCKET_NAME (The name of the GCS bucket to use for tests.) +# $2: IS_ZONAL_BUCKET_FLAG (Boolean flag: 'true' if the bucket is zonal, 'false' otherwise.) +# $3: NAME_OF_TEST_DIR_ARRAY (The shell variable name of the array containing test directory.) function run_parallel_tests() { + if [ "$#" -ne 3 ]; then + echo "Incorrect number of arguments passed, Expecting + " + exit 1 + fi local exit_code=0 - local -n test_array=$1 - local BUCKET_NAME=$2 - local zonal=$3 + local BUCKET_NAME=$1 + local zonal=$2 + if [[ -z $3 ]]; then + return 1 # The name of the test array cannot be empty. + fi + local -n test_array=$3 # Create a nameref to this array. local pids=() for test_dir_p in "${test_array[@]}" do test_path_parallel="./tools/integration_tests/$test_dir_p" - # To make it clear whether tests are running on a flat or HNS bucket, We kept the log file naming + # To make it clear whether tests are running on a flat or HNS or zonal bucket, We kept the log file naming # convention to include the bucket name as a suffix (e.g., package_name_bucket_name). local log_file="/tmp/${test_dir_p}_${BUCKET_NAME}.log" - echo $log_file >> $TEST_LOGS_FILE - # Executing integration tests - GODEBUG=asyncpreemptoff=1 go test $test_path_parallel -p 1 --zonal=${zonal} --integrationTest -v --testbucket=$BUCKET_NAME --testInstalledPackage=true -timeout $INTEGRATION_TEST_TIMEOUT > "$log_file" 2>&1 & - pid=$! # Store the PID of the background process - pids+=("$pid") # Optionally add the PID to an array for later + echo "$log_file" >> "$TEST_LOGS_FILE" + GODEBUG=asyncpreemptoff=1 go test "$test_path_parallel" -p 1 --zonal="${zonal}" --integrationTest -v --testbucket="$BUCKET_NAME" --testInstalledPackage=true -timeout "$INTEGRATION_TEST_TIMEOUT" > "$log_file" 2>&1 & + pid=$! + pids+=("$pid") done - # Wait for processes and collect exit codes for pid in "${pids[@]}"; do - wait $pid + wait "$pid" exit_code_parallel=$? - if [ $exit_code_parallel != 0 ]; then + if [ $exit_code_parallel -ne 0 ]; then exit_code=$exit_code_parallel fi done return $exit_code } -function run_e2e_tests_for_flat_bucket() { - flat_bucket_name_non_parallel=$(sed -n 3p ~/details.txt) - echo "Flat Bucket name to run tests sequentially: "$flat_bucket_name_non_parallel +#Common method to invoke e2e tests on different types of buckets: flat, HNS or Zonal +# Arguments: +# $1: BUCKET-TYPE (flat/hns/zonal) +# $2: TEST_DIR_PARALLEL (list of test packages that can be run in parallel) +# $3: TEST_DIR_NON_PARALLEL (list of test packages that should be run in sequence) +# $4: IS_ZONAL_BUCKET_FLAG (Boolean flag: 'true' if the bucket is zonal, 'false' otherwise.) +function run_e2e_tests() { + if [ "$#" -ne 4 ]; then + echo "Incorrect number of arguments passed, Expecting + + " + exit 1 + fi + local testcase=$1 + local -n test_dir_parallel=$2 + local -n test_dir_non_parallel=$3 + local is_zonal=$4 + local overall_exit_code=0 + + prefix=$(sed -n 3p ~/details.txt) + if [[ "$testcase" != "flat" ]]; then + prefix=$(sed -n 3p ~/details.txt)-$testcase + fi - flat_bucket_name_parallel=$(sed -n 3p ~/details.txt)-parallel - echo "Flat Bucket name to run tests parallelly: "$flat_bucket_name_parallel + local bkt_non_parallel=$prefix + echo "Bucket name to run non-parallel tests sequentially: $bkt_non_parallel" + + local bkt_parallel=$prefix-parallel + echo "Bucket name to run parallel tests: $bkt_parallel" echo "Running parallel tests..." - run_parallel_tests TEST_DIR_PARALLEL "$flat_bucket_name_parallel" false & + run_parallel_tests "$bkt_parallel" "$is_zonal" test_dir_parallel & # Pass the name of the array parallel_tests_pid=$! - echo "Running non parallel tests ..." - run_non_parallel_tests TEST_DIR_NON_PARALLEL "$flat_bucket_name_non_parallel" false & - non_parallel_tests_pid=$! - - # Wait for all tests to complete. - wait $parallel_tests_pid - parallel_tests_exit_code=$? - wait $non_parallel_tests_pid - non_parallel_tests_exit_code=$? - - if [ $non_parallel_tests_exit_code != 0 ] || [ $parallel_tests_exit_code != 0 ]; then - return 1 - fi -} - -function run_e2e_tests_for_hns_bucket(){ - hns_bucket_name_non_parallel=$(sed -n 3p ~/details.txt)-hns - echo "HNS Bucket name to run tests sequentially: "$hns_bucket_name_non_parallel - - hns_bucket_name_parallel=$(sed -n 3p ~/details.txt)-hns-parallel - echo "HNS Bucket name to run tests parallelly: "$hns_bucket_name_parallel - - echo "Running tests for HNS bucket" - run_parallel_tests TEST_DIR_PARALLEL "$hns_bucket_name_parallel" false & - parallel_tests_hns_group_pid=$! - run_non_parallel_tests TEST_DIR_NON_PARALLEL "$hns_bucket_name_non_parallel" false & - non_parallel_tests_hns_group_pid=$! - - # Wait for all tests to complete. - wait $parallel_tests_hns_group_pid - parallel_tests_hns_group_exit_code=$? - wait $non_parallel_tests_hns_group_pid - non_parallel_tests_hns_group_exit_code=$? - - if [ $parallel_tests_hns_group_exit_code != 0 ] || [ $non_parallel_tests_hns_group_exit_code != 0 ]; then - return 1 - fi -} - -function run_e2e_tests_for_zonal_bucket(){ - zonal_bucket_name_non_parallel=$(sed -n 3p ~/details.txt)-zonal - echo "Zonal Bucket name to run tests sequentially: "$zonal_bucket_name_non_parallel + echo "Running non parallel tests ..." + run_non_parallel_tests "$bkt_non_parallel" "$is_zonal" test_dir_non_parallel & # Pass the name of the array + non_parallel_tests_pid=$! - zonal_bucket_name_parallel=$(sed -n 3p ~/details.txt)-zonal-parallel - echo "Zonal Bucket name to run tests parallely: "$zonal_bucket_name_parallel + wait "$parallel_tests_pid" + local parallel_tests_exit_code=$? + wait "$non_parallel_tests_pid" + local non_parallel_tests_exit_code=$? - echo "Running tests for Zonal bucket" - run_parallel_tests TEST_DIR_PARALLEL_ZONAL "$zonal_bucket_name_parallel" true & - parallel_tests_zonal_group_pid=$! - run_non_parallel_tests TEST_DIR_NON_PARALLEL_ZONAL "$zonal_bucket_name_non_parallel" true & - non_parallel_tests_zonal_group_pid=$! - - # Wait for all tests to complete. - wait $parallel_tests_zonal_group_pid - parallel_tests_zonal_group_exit_code=$? - wait $non_parallel_tests_zonal_group_pid - non_parallel_tests_zonal_group_exit_code=$? - - if [ $parallel_tests_zonal_group_exit_code != 0 ] || [ $non_parallel_tests_zonal_group_exit_code != 0 ]; then - return 1 + if [ "$non_parallel_tests_exit_code" -ne 0 ]; then + overall_exit_code=$non_parallel_tests_exit_code fi -} - -function run_e2e_tests_for_emulator() { - ./tools/integration_tests/emulator_tests/emulator_tests.sh true > ~/logs-emulator.txt + if [ "$parallel_tests_exit_code" -ne 0 ]; then + overall_exit_code=$parallel_tests_exit_code + fi + return $overall_exit_code } function gather_test_logs() { @@ -407,76 +411,96 @@ function gather_test_logs() { done } -if [[ "$RUN_ON_ZB_ONLY" == "true" ]]; then - echo "Started integration tests for Zonal bucket ..." - run_e2e_tests_for_zonal_bucket & - e2e_tests_zonal_bucket_pid=$! - - wait $e2e_tests_zonal_bucket_pid - e2e_tests_zonal_bucket_status=$? - - gather_test_logs - - if [ $e2e_tests_zonal_bucket_status != 0 ]; - then - echo "Test failures detected in Zonal bucket." &>> ~/logs-zonal.txt - else - touch success-zonal.txt - gcloud storage cp success-zonal.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ +# Function to log test results and upload them to GCS based on exit status. +# Arguments: $1 = name of the associative array containing testcase exit statuses. +function log_based_on_exit_status() { + if [ "$#" -ne 1 ]; then + echo "Incorrect number of arguments passed, Expecting " + exit 1 fi + gather_test_logs + local -n exit_status_array=$1 + + for testcase in "${!exit_status_array[@]}" + do + local logfile="" + local successfile="" + if [[ "$testcase" == "flat" ]]; then + logfile="$HOME/logs.txt" + successfile="$HOME/success.txt" + else + logfile="$HOME/logs-$testcase.txt" + successfile="$HOME/success-$testcase.txt" + fi + if [ "${exit_status_array["$testcase"]}" != 0 ]; + then + echo "Test failures detected in $testcase bucket." &>> $logfile + else + touch $successfile + gcloud storage cp $successfile gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + fi + gcloud storage cp $logfile gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + done - gcloud storage cp ~/logs-zonal.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ -else - echo "Started integration tests for HNS bucket..." - run_e2e_tests_for_hns_bucket & - e2e_tests_hns_bucket_pid=$! - - echo "Started integration tests for FLAT bucket..." - run_e2e_tests_for_flat_bucket & - e2e_tests_flat_bucket_pid=$! - - echo "Started emulator tests..." - run_e2e_tests_for_emulator & - e2e_tests_emulator_pid=$! - - wait $e2e_tests_emulator_pid - e2e_tests_emulator_status=$? +} - wait $e2e_tests_flat_bucket_pid - e2e_tests_flat_bucket_status=$? +# Function to run emulator-based E2E tests and log results. +function run_e2e_tests_for_emulator_and_log() { + ./tools/integration_tests/emulator_tests/emulator_tests.sh true > ~/logs-emulator.txt + emulator_test_status=$? + if [ $e2e_tests_emulator_status != 0 ]; + then + echo "Test failures detected in emulator based tests." &>> ~/logs-emulator.txt + else + touch success-emulator.txt + gcloud storage cp success-emulator.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + fi + gcloud storage cp ~/logs-emulator.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ +} - wait $e2e_tests_hns_bucket_pid - e2e_tests_hns_bucket_status=$? +# Declare an associative array to store the exit status of different test runs. +declare -A exit_status +if [[ "$RUN_READ_CACHE_TESTS_ONLY" == "true" ]]; then + read_cache_test_dir_parallel=() # Empty for read cache tests only + read_cache_test_dir_non_parallel=("read_cache") - gather_test_logs + # Run E2E tests for flat, HNS, and zonal buckets with only read cache tests. + # Running sequentially due to known limitations of simultaneous execution. + run_e2e_tests "flat" read_cache_test_dir_parallel read_cache_test_dir_non_parallel false + exit_status["flat"]=$? - if [ $e2e_tests_flat_bucket_status != 0 ] - then - echo "Test failures detected in FLAT bucket." &>> ~/logs.txt - else - touch success.txt - gcloud storage cp success.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ - fi - gcloud storage cp ~/logs.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ - - if [ $e2e_tests_hns_bucket_status != 0 ]; - then - echo "Test failures detected in HNS bucket." &>> ~/logs-hns.txt - else - touch success-hns.txt - gcloud storage cp success-hns.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ - fi - gcloud storage cp ~/logs-hns.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ + run_e2e_tests "hns" read_cache_test_dir_parallel read_cache_test_dir_non_parallel false + exit_status["hns"]=$? - if [ $e2e_tests_emulator_status != 0 ]; - then - echo "Test failures detected in emulator based tests." &>> ~/logs-emulator.txt - else - touch success-emulator.txt - gcloud storage cp success-emulator.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ - fi + run_e2e_tests "zonal" read_cache_test_dir_parallel read_cache_test_dir_non_parallel true + exit_status["zonal"]=$? +else + # If not running *only* read cache tests, proceed with full test suites. + if [[ "$RUN_ON_ZB_ONLY" == "true" ]]; then + # If only zonal bucket tests are to be run. + run_e2e_tests "zonal" TEST_DIR_PARALLEL_ZONAL TEST_DIR_NON_PARALLEL_ZONAL true + exit_status["zonal"]=$? + else + # Run flat and HNS tests concurrently in the background. + run_e2e_tests "flat" TEST_DIR_PARALLEL TEST_DIR_NON_PARALLEL false & + flat_test_pid=$! + + run_e2e_tests "hns" TEST_DIR_PARALLEL TEST_DIR_NON_PARALLEL false & + hns_test_pid=$! + + # Wait for PIDs and populate exit_status associative array + wait $flat_test_pid + exit_status["flat"]=$? + + wait $hns_test_pid + exit_status["hns"]=$? + + # Run emulator tests and log their results. + run_e2e_tests_for_emulator_and_log + fi - gcloud storage cp ~/logs-emulator.txt gs://gcsfuse-release-packages/v$(sed -n 1p ~/details.txt)/$(sed -n 3p ~/details.txt)/ fi +#Log results based on the collected exit statuses. +log_based_on_exit_status exit_status -' +' From 2786b8686443c9637d387a09bf3bf6c8c4e93584 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 5 Aug 2025 21:54:13 +0530 Subject: [PATCH 0650/1298] refactor(block): return early io.EOF while reading from block (#3581) * early eof return from block reader * Trigger Build --- internal/block/block.go | 5 +++++ internal/block/block_test.go | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/internal/block/block.go b/internal/block/block.go index ed99b4d6a7..c72e908c03 100644 --- a/internal/block/block.go +++ b/internal/block/block.go @@ -88,6 +88,11 @@ func (m *memoryBlock) Read(bytes []byte) (int, error) { n := copy(bytes, m.buffer[m.readSeek:m.offset.end]) m.readSeek += int64(n) + // If readSeek is beyond the end of the block, return EOF early. + if m.readSeek >= m.offset.end { + return n, io.EOF + } + return n, nil } diff --git a/internal/block/block_test.go b/internal/block/block_test.go index c977eb4188..1c97aa1fd0 100644 --- a/internal/block/block_test.go +++ b/internal/block/block_test.go @@ -198,7 +198,7 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockReadWithReadBufferMoreThanBlock n, err = mb.Read(readBuffer) - require.NoError(testSuite.T(), err) + require.Error(testSuite.T(), io.EOF, err) require.Equal(testSuite.T(), 11, n) // Read should return all bytes written. } @@ -258,7 +258,9 @@ func (testSuite *MemoryBlockTest) TestMemoryBlockSeek() { require.Equal(t, tt.expectedOffset, offset) readBuffer := make([]byte, 5) n, err = mb.Read(readBuffer) - require.Nil(t, err) + require.Condition(t, func() bool { + return err == nil || errors.Is(err, io.EOF) + }, "Read err can be nil or io.EOF") require.Equal(t, 5, n) assert.Equal(t, tt.expectedOutput, string(readBuffer)) }) From 7274664f327a07b812f363436f993d7ba2b7b957 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Wed, 6 Aug 2025 10:27:39 +0530 Subject: [PATCH 0651/1298] Use tryGet to avoid deadlock (#3643) --- internal/bufferedread/buffered_reader.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index 999ffde5bd..b874d24c23 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -383,8 +383,7 @@ func (p *BufferedReader) freshStart(currentOffset int64) error { // scheduleNextBlock schedules the next block for prefetch. func (p *BufferedReader) scheduleNextBlock(urgent bool) error { - // TODO(b/426060431): Replace Get() with TryGet(). Assuming, the current blockPool.Get() gets blocked if block is not available. - b, err := p.blockPool.Get() + b, err := p.blockPool.TryGet() if err != nil || b == nil { if err != nil { logger.Warnf("scheduleNextBlock: failed to get block from pool: %v", err) From 2d87f6bd34902456c0deb9369b5539ba374e6648 Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Wed, 6 Aug 2025 13:16:08 +0000 Subject: [PATCH 0652/1298] feat(parallel random reads): Changes to enable parallel random read handling in gcs reader (#3619) Changes to enable parallel random reads in the GCS reader. This is achieved by refactoring the read logic in gcs_reader.go, introducing a mutex to manage concurrent access for range reader while allowing reads to go in parallel for MRD. The changes also include updates to the test suite to cover the new logic and parallel read scenarios. Since, we're removed the high level fh lock for read operation (ReadFile in fs.go), we've added extra synchronization logic in file handle operations. --- internal/fs/fs.go | 2 - internal/fs/handle/file.go | 310 +++++++------ internal/fs/handle/file_test.go | 2 - internal/gcsx/client_readers/gcs_reader.go | 157 +++++-- .../gcsx/client_readers/gcs_reader_test.go | 430 ++++++++++++++---- .../gcsx/client_readers/multi_range_reader.go | 10 +- .../client_readers/multi_range_reader_test.go | 8 +- internal/gcsx/client_readers/range_reader.go | 36 +- .../gcsx/client_readers/range_reader_test.go | 14 +- .../gcsx/read_manager/read_manager_test.go | 6 +- 10 files changed, 651 insertions(+), 324 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 1c797d668e..dbed9e7d54 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -2835,9 +2835,7 @@ func (fs *fileSystem) ReadFile( fh := fs.handles[op.Handle].(*handle.FileHandle) fs.mu.Unlock() - fh.Lock() fh.Inode().Lock() - defer fh.Unlock() if fh.Inode().IsUsingBWH() { // Flush Pending streaming writes and issue read within same inode lock. // TODO(b/417136852): Remove bucket type check and call only flushFile diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index c73de2c41d..3904b075e1 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -126,104 +126,166 @@ func (fh *FileHandle) Unlock() { fh.mu.Unlock() } +// lockHandleAndRelockInode is a helper function which locks fh.mu maintaing the locking +// order, i.e. it first unlocks inode lock, then locks fh.mu (RLock or RWlock) and then +// relocks inode lock. +// LOCKS_REQUIRED(fh.inode.mu) +func (fh *FileHandle) lockHandleAndRelockInode(rLock bool) { + if rLock { + fh.inode.Unlock() + fh.mu.RLock() + fh.inode.Lock() + } else { + fh.inode.Unlock() + fh.mu.Lock() + fh.inode.Lock() + } +} + +// unlockHandleAndInode is a helper function which unlocks fh.inode.mu & fh.mu in order. +// LOCKS_REQUIRED(fh.mu) +// LOCKS_REQUIRED(fh.inode.mu) +func (fh *FileHandle) unlockHandleAndInode(rLock bool) { + if rLock { + fh.inode.Unlock() + fh.mu.RUnlock() + } else { + fh.inode.Unlock() + fh.mu.Unlock() + } +} + // ReadWithReadManager reads data at the given offset using the read manager if available, // falling back to inode.Read otherwise. It may be more efficient than directly calling inode.Read. // -// LOCKS_REQUIRED(fh.mu) // LOCKS_REQUIRED(fh.inode.mu) // UNLOCK_FUNCTION(fh.inode.mu) func (fh *FileHandle) ReadWithReadManager(ctx context.Context, dst []byte, offset int64, sequentialReadSizeMb int32) ([]byte, int, error) { - // fh.inode.mu is already locked to ensure that we have a readManager for its current - // state, or clear fh.readManager if it's not possible to create one (probably - // because the inode is dirty). - err := fh.tryEnsureReadManager(ctx, sequentialReadSizeMb) - if err != nil { - fh.inode.Unlock() - return nil, 0, fmt.Errorf("tryEnsureReadManager: %w", err) + // If content cache enabled, CacheEnsureContent forces the file handler to fall through to the inode + // and fh.inode.SourceGenerationIsAuthoritative() will return false + if err := fh.inode.CacheEnsureContent(ctx); err != nil { + return nil, 0, fmt.Errorf("failed to ensure inode content: %w", err) } - // If we have an appropriate readManager, unlock the inode and use that. This - // allows reads to proceed concurrently with other operations; in particular, - // multiple reads can run concurrently. It's safe because the user can't tell - // if a concurrent write started during or after a read. - if fh.readManager != nil { + if !fh.inode.SourceGenerationIsAuthoritative() { + // Read from inode if source generation is not authoratative + defer fh.inode.Unlock() + n, err := fh.inode.Read(ctx, dst, offset) + return dst, n, err + } + + fh.lockHandleAndRelockInode(true) + defer fh.mu.RUnlock() + + // If the inode is dirty, there's nothing we can do. Throw away our readManager if + // we have one & create a new readManager + if fh.isValidReadManager() { fh.inode.Unlock() + } else { + minObj := fh.inode.Source() + bucket := fh.inode.Bucket() + mrdWrapper := &fh.inode.MRDWrapper - var readerResponse gcsx.ReaderResponse - readerResponse, err = fh.readManager.ReadAt(ctx, dst, offset) - switch { - case errors.Is(err, io.EOF): - if err != io.EOF { - logger.Warnf("Unexpected EOF error encountered while reading, err: %v type: %T ", err, err) - } - return nil, 0, io.EOF - - case err != nil: - return nil, 0, fmt.Errorf("fh.readManager.ReadAt: %w", err) - } + // Acquire a RWLock on file handle as we will update readManager + fh.unlockHandleAndInode(true) + fh.mu.Lock() - return readerResponse.DataBuf, readerResponse.Size, nil + fh.destroyReadManager() + // Create a new read manager for the current inode state. + fh.readManager = read_manager.NewReadManager(minObj, bucket, &read_manager.ReadManagerConfig{ + SequentialReadSizeMB: sequentialReadSizeMb, + FileCacheHandler: fh.fileCacheHandler, + CacheFileForRangeRead: fh.cacheFileForRangeRead, + MetricHandle: fh.metricHandle, + MrdWrapper: mrdWrapper, + Config: fh.config, + }) + + // Release RWLock and take RLock on file handle again. Inode lock is not needed now. + fh.mu.Unlock() + fh.mu.RLock() } - // If read manager is not available, fall back to reading via inode - defer fh.inode.Unlock() + // Use the readManager to read data. + var readerResponse gcsx.ReaderResponse + var err error + readerResponse, err = fh.readManager.ReadAt(ctx, dst, offset) + switch { + case errors.Is(err, io.EOF): + if err != io.EOF { + logger.Warnf("Unexpected EOF error encountered while reading, err: %v type: %T ", err, err) + } + return nil, 0, io.EOF - n, err := fh.inode.Read(ctx, dst, offset) + case err != nil: + return nil, 0, fmt.Errorf("fh.readManager.ReadAt: %w", err) + } - // Return the original dst buffer and number of bytes read - return dst, n, err + return readerResponse.DataBuf, readerResponse.Size, nil } // Equivalent to locking fh.Inode() and calling fh.Inode().Read, but may be // more efficient. // -// LOCKS_REQUIRED(fh.mu) // LOCKS_REQUIRED(fh.inode.mu) // UNLOCK_FUNCTION(fh.inode.mu) func (fh *FileHandle) Read(ctx context.Context, dst []byte, offset int64, sequentialReadSizeMb int32) (output []byte, n int, err error) { - // fh.inode.mu is already locked to ensure that we have a reader for its current - // state, or clear fh.reader if it's not possible to create one (probably - // because the inode is dirty). - err = fh.tryEnsureReader(ctx, sequentialReadSizeMb) + // If content cache enabled, CacheEnsureContent forces the file handler to fall through to the inode + // and fh.inode.SourceGenerationIsAuthoritative() will return false + err = fh.inode.CacheEnsureContent(ctx) if err != nil { - fh.inode.Unlock() - err = fmt.Errorf("tryEnsureReader: %w", err) - return + return nil, 0, fmt.Errorf("failed to ensure inode content: %w", err) } - // If we have an appropriate reader, unlock the inode and use that. This - // allows reads to proceed concurrently with other operations; in particular, - // multiple reads can run concurrently. It's safe because the user can't tell - // if a concurrent write started during or after a read. - if fh.reader != nil { + // If the inode is dirty, there's nothing we can do. Throw away our reader if + // we have one. + if !fh.inode.SourceGenerationIsAuthoritative() { + defer fh.inode.Unlock() + n, err = fh.inode.Read(ctx, dst, offset) + return dst, n, err + } + + fh.lockHandleAndRelockInode(true) + defer fh.mu.RUnlock() + + if fh.isValidReader() { fh.inode.Unlock() + } else { + minObj := fh.inode.Source() + bucket := fh.inode.Bucket() + mrdWrapper := &fh.inode.MRDWrapper + + // Acquire a RWLock on file handle as we will update reader + fh.unlockHandleAndInode(true) + fh.mu.Lock() + + fh.destroyReader() + // Attempt to create an appropriate reader. + fh.reader = gcsx.NewRandomReader(minObj, bucket, sequentialReadSizeMb, fh.fileCacheHandler, fh.cacheFileForRangeRead, fh.metricHandle, mrdWrapper, fh.config) + + // Release RWLock and take RLock on file handle again + fh.mu.Unlock() + fh.mu.RLock() + } - var objectData gcsx.ObjectData - objectData, err = fh.reader.ReadAt(ctx, dst, offset) - switch { - case errors.Is(err, io.EOF): - if err != io.EOF { - logger.Warnf("Unexpected EOF error encountered while reading, err: %v type: %T ", err, err) - err = io.EOF - } - return - - case err != nil: - err = fmt.Errorf("fh.reader.ReadAt: %w", err) - return + // Use the reader to read data. + var objectData gcsx.ObjectData + objectData, err = fh.reader.ReadAt(ctx, dst, offset) + switch { + case errors.Is(err, io.EOF): + if err != io.EOF { + logger.Warnf("Unexpected EOF error encountered while reading, err: %v type: %T ", err, err) + err = io.EOF } + return - output = objectData.DataBuf - n = objectData.Size + case err != nil: + err = fmt.Errorf("fh.reader.ReadAt: %w", err) return } - // Otherwise we must fall through to the inode. - defer fh.inode.Unlock() - n, err = fh.inode.Read(ctx, dst, offset) - // Setting dst as output since output is used by the caller to read the data. - output = dst - + output = objectData.DataBuf + n = objectData.Size return } @@ -253,102 +315,56 @@ func (fh *FileHandle) checkInvariants() { } } -// If possible, ensure that fh.reader is set to an appropriate random reader -// for the current state of the inode otherwise set it to nil. -// -// LOCKS_REQUIRED(fh) -// LOCKS_REQUIRED(fh.inode) -func (fh *FileHandle) tryEnsureReader(ctx context.Context, sequentialReadSizeMb int32) (err error) { - // If content cache enabled, CacheEnsureContent forces the file handler to fall through to the inode - // and fh.inode.SourceGenerationIsAuthoritative() will return false - err = fh.inode.CacheEnsureContent(ctx) - if err != nil { - return - } - // If the inode is dirty, there's nothing we can do. Throw away our reader if - // we have one. - if !fh.inode.SourceGenerationIsAuthoritative() { - if fh.reader != nil { - fh.reader.Destroy() - fh.reader = nil - } - +// destroyReadManager is a helper function to safely destroy the readManager & set it to nil. +// LOCKS_REQUIRED(fh.mu) +// LOCKS_REQUIRED(fh.inode.mu) +func (fh *FileHandle) destroyReadManager() { + if fh.readManager == nil { return } - - // If we already have a reader, and it's at the appropriate generation, we - // can use it otherwise we must throw it away. - if fh.reader != nil { - if fh.reader.Object().Generation == fh.inode.SourceGeneration().Object { - // Update reader object size to source object size. - fh.reader.Object().Size = fh.inode.SourceGeneration().Size - return - } - fh.reader.Destroy() - fh.reader = nil - } - - // Attempt to create an appropriate reader. - rr := gcsx.NewRandomReader(fh.inode.Source(), fh.inode.Bucket(), sequentialReadSizeMb, fh.fileCacheHandler, fh.cacheFileForRangeRead, fh.metricHandle, &fh.inode.MRDWrapper, fh.config) - - fh.reader = rr - return + fh.readManager.Destroy() + fh.readManager = nil } -// If possible, ensure that fh.readManager is set to an appropriate read manager -// for the current state of the inode otherwise set it to nil. -// -// LOCKS_REQUIRED(fh) -// LOCKS_REQUIRED(fh.inode) -func (fh *FileHandle) tryEnsureReadManager(ctx context.Context, sequentialReadSizeMb int32) error { - // If content cache enabled, CacheEnsureContent forces the file handler to fall through to the inode - // and fh.inode.SourceGenerationIsAuthoritative() will return false - if err := fh.inode.CacheEnsureContent(ctx); err != nil { - return fmt.Errorf("failed to ensure inode content: %w", err) - } - - // If the inode is dirty, there's nothing we can do. Throw away our readManager if - // we have one. - if !fh.inode.SourceGenerationIsAuthoritative() { - fh.destroyReadManager() - return nil - } - +// isValidReadManager is a helper function which validates & returns whether the +// current readManager is valid or not. +// LOCKS_REQUIRED(fh.mu.RLock) +// LOCKS_REQUIRED(fh.inode.mu) +func (fh *FileHandle) isValidReadManager() bool { // If we already have a readManager, and it's at the appropriate generation, we // can use it otherwise we must throw it away. if fh.readManager != nil && fh.readManager.Object().Generation == fh.inode.SourceGeneration().Object { // Update reader object size to source object size. fh.readManager.Object().Size = fh.inode.SourceGeneration().Size - return nil + return true } - - // If we reached here, either no readManager exists, or the existing one is outdated. - // Destroy any old read manager before creating a new one. - fh.destroyReadManager() - - // Create a new read manager for the current inode state. - fh.readManager = read_manager.NewReadManager(fh.inode.Source(), fh.inode.Bucket(), &read_manager.ReadManagerConfig{ - SequentialReadSizeMB: sequentialReadSizeMb, - FileCacheHandler: fh.fileCacheHandler, - CacheFileForRangeRead: fh.cacheFileForRangeRead, - MetricHandle: fh.metricHandle, - MrdWrapper: &fh.inode.MRDWrapper, - Config: fh.config, - WorkerPool: fh.bufferedReadWorkerPool, - GlobalMaxBlocksSem: fh.globalMaxReadBlocksSem, - }) - - return nil + return false } -// destroyReadManager is a helper function to safely destroy and nil the readManager. -// This assumes the necessary locks (fh.mu, fh.inode.mu) are already held by the caller. -func (fh *FileHandle) destroyReadManager() { - if fh.readManager == nil { +// destroyReader is a helper function to safely destroy the reader and set it to nil. +// LOCKS_REQUIRED(fh.mu) +// LOCKS_REQUIRED(fh.inode.mu) +func (fh *FileHandle) destroyReader() { + if fh.reader == nil { return } - fh.readManager.Destroy() - fh.readManager = nil + fh.reader.Destroy() + fh.reader = nil +} + +// isValidReader is a helper function which validates & returns whether the +// current reader is valid or not. +// LOCKS_REQUIRED(fh.mu.RLock) +// LOCKS_REQUIRED(fh.inode.mu) +func (fh *FileHandle) isValidReader() bool { + // If we already have a reader, and it's at the appropriate generation, we + // can use it otherwise we must throw it away. + if fh.reader != nil && fh.reader.Object().Generation == fh.inode.SourceGeneration().Object { + // Update reader object size to source object size. + fh.reader.Object().Size = fh.inode.SourceGeneration().Size + return true + } + return false } func (fh *FileHandle) OpenMode() util.OpenMode { diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 6a20639b42..073ab8c733 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -282,7 +282,6 @@ func (t *fileTest) Test_ReadWithReadManager_FallbackToInode() { fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) fh.inode.Lock() mockRM := new(read_manager.MockReadManager) - mockRM.On("Destroy").Return() fh.readManager = mockRM output, n, err := fh.ReadWithReadManager(t.ctx, dst, 0, 200) @@ -304,7 +303,6 @@ func (t *fileTest) Test_Read_FallbackToInode() { fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) fh.inode.Lock() mockR := new(gcsx.MockRandomReader) - mockR.On("Destroy").Return() fh.reader = mockR output, n, err := fh.Read(t.ctx, dst, 0, 200) diff --git a/internal/gcsx/client_readers/gcs_reader.go b/internal/gcsx/client_readers/gcs_reader.go index 7ad75388c8..4282bdfb66 100644 --- a/internal/gcsx/client_readers/gcs_reader.go +++ b/internal/gcsx/client_readers/gcs_reader.go @@ -16,9 +16,9 @@ package gcsx import ( "context" - "errors" "fmt" "io" + "sync" "sync/atomic" "github.com/googlecloudplatform/gcsfuse/v3/cfg" @@ -52,6 +52,17 @@ const ( MultiRangeReaderType ) +// readInfo Stores information for this read request. +type readInfo struct { + // readType stores the read type evaluated for this request. + readType int64 + // expectedOffset stores the expected offset for this request. Will be + // used to determine if re-evaluation of readType is required or not with range reader. + expectedOffset int64 + // seekRecorded tells whether a seek has been performed for this read request. + seekRecorded bool +} + type GCSReader struct { gcsx.Reader object *gcs.MinObject @@ -74,6 +85,9 @@ type GCSReader struct { // totalReadBytes is the total number of bytes read by the reader. totalReadBytes atomic.Uint64 + + // mu synchronizes reads through range reader. + mu sync.Mutex } type GCSReaderConfig struct { @@ -93,84 +107,131 @@ func NewGCSReader(obj *gcs.MinObject, bucket gcs.Bucket, config *GCSReaderConfig } } -func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (gcsx.ReaderResponse, error) { - var readerResponse gcsx.ReaderResponse +func (gr *GCSReader) ReadAt(ctx context.Context, p []byte, offset int64) (readerResponse gcsx.ReaderResponse, err error) { if offset >= int64(gr.object.Size) { return readerResponse, io.EOF + } else if offset < 0 { + err := fmt.Errorf( + "illegal offset %d for %d byte object", + offset, + gr.object.Size) + return readerResponse, err } - gr.rangeReader.invalidateReaderIfMisalignedOrTooSmall(offset, p) - readReq := &gcsx.GCSReaderRequest{ Buffer: p, Offset: offset, - EndOffset: -1, + EndOffset: offset + int64(len(p)), } defer func() { gr.updateExpectedOffset(offset + int64(readerResponse.Size)) gr.totalReadBytes.Add(uint64(readerResponse.Size)) }() - var err error - readerResponse, err = gr.rangeReader.readFromExistingReader(ctx, readReq) - if err == nil { - return readerResponse, nil - } - if !errors.Is(err, gcsx.FallbackToAnotherReader) { - return readerResponse, err - } - - // If the data can't be served from the existing reader, then we need to update the seeks. - // If current offset is not same as expected offset, it's a random read. - if expectedOffset := gr.expectedOffset.Load(); expectedOffset != 0 && expectedOffset != offset { - gr.seeks.Add(1) - } - - // If we don't have a reader, determine whether to read from RangeReader or MultiRangeReader. - end, err := gr.getReadInfo(offset, int64(len(p))) - if err != nil { - err = fmt.Errorf("ReadAt: getReaderInfo: %w", err) - return readerResponse, err + // Not taking any lock for getting reader type to ensure random read requests do not wait. + readInfo := gr.getReadInfo(offset, false) + reqReaderType := gr.readerType(readInfo.readType, gr.bucket.BucketType()) + + if reqReaderType == RangeReaderType { + gr.mu.Lock() + + // In case of multiple threads reading parallely, it is possible that many of them might be waiting + // at this lock and hence the earlier calculated value of readerType might not be valid once they + // acquire the lock. Hence, needs to be calculated again. + // Recalculating only for ZB and only when another read had been performed between now and + // the time when readerType was calculated for this request. + if gr.bucket.BucketType().Zonal && readInfo.expectedOffset != gr.expectedOffset.Load() { + readInfo = gr.getReadInfo(offset, readInfo.seekRecorded) + reqReaderType = gr.readerType(readInfo.readType, gr.bucket.BucketType()) + } + // If the readerType is range reader after re calculation, then use range reader. + // Otherwise fall back to MultiRange Downloder + if reqReaderType == RangeReaderType { + defer gr.mu.Unlock() + // Calculate the end offset based on previous read requests. + // It will be used if a new range reader needs to be created. + readReq.EndOffset = gr.getEndOffset(readReq.Offset) + readerResponse, err = gr.rangeReader.ReadAt(ctx, readReq) + return readerResponse, err + } + gr.mu.Unlock() } - readReq.EndOffset = end - readerType := gr.readerType(offset, end, gr.bucket.BucketType()) - if readerType == RangeReaderType { - readerResponse, err = gr.rangeReader.ReadAt(ctx, readReq) - return readerResponse, err + if reqReaderType == MultiRangeReaderType { + readerResponse, err = gr.mrr.ReadAt(ctx, readReq) } - readerResponse, err = gr.mrr.ReadAt(ctx, readReq) - return readerResponse, err } // readerType specifies the go-sdk interface to use for reads. -func (gr *GCSReader) readerType(start int64, end int64, bucketType gcs.BucketType) ReaderType { - bytesToBeRead := end - start - if gr.readType.Load() == metrics.ReadTypeRandom && bytesToBeRead < maxReadSize && bucketType.Zonal { +func (gr *GCSReader) readerType(readType int64, bucketType gcs.BucketType) ReaderType { + if readType == metrics.ReadTypeRandom && bucketType.Zonal { return MultiRangeReaderType } return RangeReaderType } -// getReadInfo determines the readType and provides the range to query GCS. -// Range here is [start, end]. end is computed using the readType, start offset -// and size of the data the caller needs. -func (gr *GCSReader) getReadInfo(start int64, size int64) (int64, error) { - // Make sure start and size are legal. - if start < 0 || uint64(start) > gr.object.Size || size < 0 { - return 0, fmt.Errorf("range [%d, %d) is illegal for %d-byte object", start, start+size, gr.object.Size) +// isSeekNeeded determines if the current read at `offset` should be considered a +// seek, given the previous read pattern & the expected offset. +func isSeekNeeded(readType, offset, expectedOffset int64) bool { + if expectedOffset == 0 { + return false } - // Determine the end position based on the read pattern. - end := gr.determineEnd(start) + if readType == metrics.ReadTypeRandom { + return offset != expectedOffset + } - // Limit the end position to sequentialReadSizeMb. + if readType == metrics.ReadTypeSequential { + return offset < expectedOffset || offset > expectedOffset+maxReadSize + } + + return false +} + +func (gr *GCSReader) getEndOffset( + start int64) (end int64) { + + end = gr.determineEnd(start) end = gr.limitEnd(start, end) + return end +} + +// getReadInfo determines the read strategy (sequential or random) for a read +// request at a given offset and returns read metadata. It also updates the +// reader's internal state based on the read pattern. +// seekRecorded parameter describes whether a seek has already been recorded for this request. +func (gr *GCSReader) getReadInfo(offset int64, seekRecorded bool) readInfo { + readType := gr.readType.Load() + expOffset := gr.expectedOffset.Load() + numSeeks := gr.seeks.Load() + + if !seekRecorded && isSeekNeeded(readType, offset, expOffset) { + numSeeks = gr.seeks.Add(1) + seekRecorded = true + } + + if numSeeks >= minSeeksForRandom { + readType = metrics.ReadTypeRandom + } + + averageReadBytes := gr.totalReadBytes.Load() + if numSeeks > 0 { + averageReadBytes /= numSeeks + } + + if averageReadBytes >= maxReadSize { + readType = metrics.ReadTypeSequential + } - return end, nil + gr.readType.Store(readType) + return readInfo{ + readType: readType, + expectedOffset: expOffset, + seekRecorded: seekRecorded, + } } // determineEnd calculates the end position for a read operation based on the current read pattern. @@ -210,6 +271,8 @@ func (gr *GCSReader) updateExpectedOffset(offset int64) { } func (gr *GCSReader) Destroy() { + gr.mu.Lock() + defer gr.mu.Unlock() gr.rangeReader.destroy() gr.mrr.destroy() } diff --git a/internal/gcsx/client_readers/gcs_reader_test.go b/internal/gcsx/client_readers/gcs_reader_test.go index 77c12beacc..a5ef2c4eef 100644 --- a/internal/gcsx/client_readers/gcs_reader_test.go +++ b/internal/gcsx/client_readers/gcs_reader_test.go @@ -18,6 +18,7 @@ import ( "context" "io" "strings" + "sync" "testing" "time" @@ -160,7 +161,7 @@ func (t *gcsReaderTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedDataSi ReadHandle: expectedHandleInRequest, } t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil) - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(2) requestSize := 6 readerResponse, err := t.readAt(2, int64(requestSize)) @@ -195,7 +196,7 @@ func (t *gcsReaderTest) Test_ReadAt_ExistingReaderLimitIsLessThanRequestedObject ReadHandle: expectedHandleInRequest, } t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(rc, nil) - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(2) requestSize := 6 readerResponse, err := t.readAt(0, int64(requestSize)) @@ -218,6 +219,7 @@ func (t *gcsReaderTest) Test_ReadAt_ExistingReaderIsFine() { t.gcsReader.totalReadBytes.Store(2) t.gcsReader.rangeReader.limit = 5 requestSize := 3 + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(2) readerResponse, err := t.readAt(2, int64(requestSize)) @@ -259,7 +261,7 @@ func (t *gcsReaderTest) Test_ExistingReader_WrongOffset() { content := "abcde" rc := &fake.FakeReader{ReadCloser: getReadCloser([]byte(content))} t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(rc, nil).Times(1) - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(2) requestSize := 6 readerResponse, err := t.readAt(0, int64(requestSize)) @@ -321,7 +323,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { t.Run(tc.name, func() { t.SetupTest() require.Equal(t.T(), len(tc.readRanges), len(tc.expectedReadTypes), "Test Parameter Error: readRanges and expectedReadTypes should have same length") - t.gcsReader.mrr.isMRDInUse = false + t.gcsReader.mrr.isMRDInUse.Store(false) t.gcsReader.seeks.Store(0) t.gcsReader.rangeReader.readType = metrics.ReadTypeSequential t.gcsReader.expectedOffset.Store(0) @@ -331,7 +333,7 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateReadType() { require.NoError(t.T(), err, "Error in creating MRDWrapper") t.gcsReader.mrr.mrdWrapper = &fakeMRDWrapper t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, time.Microsecond)) - t.mockBucket.On("BucketType", mock.Anything).Return(tc.bucketType).Times(len(tc.readRanges)) + t.mockBucket.On("BucketType", mock.Anything).Return(tc.bucketType).Times(2 * len(tc.readRanges)) for i, readRange := range tc.readRanges { t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(&fake.FakeReader{ReadCloser: getReadCloser(testContent)}, nil).Once() @@ -371,6 +373,7 @@ func (t *gcsReaderTest) Test_ReadAt_PropagatesCancellation() { // Channel to track read completion readReturned := make(chan struct{}) var err error + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{}).Times(2) go func() { _, err = t.gcsReader.ReadAt(ctx, make([]byte, 2), 0) @@ -407,129 +410,341 @@ func (t *gcsReaderTest) Test_ReadAt_PropagatesCancellation() { } } -func (t *gcsReaderTest) Test_ReadInfo_WithInvalidInput() { - t.object.Size = 10 * MiB +func (t *gcsReaderTest) Test_IsSeekNeeded() { testCases := []struct { - name string - start int64 - size int64 + name string + readType int64 + offset int64 + expectedOffset int64 + want bool }{ { - name: "startLessThanZero", - start: -1, - size: 10, + name: "First read, expectedOffset is 0", + readType: metrics.ReadTypeSequential, + offset: 100, + expectedOffset: 0, + want: false, + }, + { + name: "Random read, same offset", + readType: metrics.ReadTypeRandom, + offset: 100, + expectedOffset: 100, + want: false, + }, + { + name: "Random read, different offset", + readType: metrics.ReadTypeRandom, + offset: 200, + expectedOffset: 100, + want: true, + }, + { + name: "Sequential read, same offset", + readType: metrics.ReadTypeSequential, + offset: 100, + expectedOffset: 100, + want: false, }, { - name: "sizeLessThanZero", - start: 0, - size: -1, + name: "Sequential read, small forward jump within maxReadSize", + readType: metrics.ReadTypeSequential, + offset: 100 + maxReadSize/2, + expectedOffset: 100, + want: false, }, { - name: "startGreaterThanObjectSize", - start: int64(t.object.Size + 1), - size: int64(t.object.Size), + name: "Sequential read, forward jump to boundary of maxReadSize", + readType: metrics.ReadTypeSequential, + offset: 100 + maxReadSize, + expectedOffset: 100, + want: false, + }, + { + name: "Sequential read, large forward jump beyond maxReadSize", + readType: metrics.ReadTypeSequential, + offset: 100 + maxReadSize + 1, + expectedOffset: 100, + want: true, + }, + { + name: "Sequential read, backward jump", + readType: metrics.ReadTypeSequential, + offset: 99, + expectedOffset: 100, + want: true, + }, + { + name: "Unknown read type", + readType: -1, // An invalid read type + offset: 200, + expectedOffset: 100, + want: false, }, } for _, tc := range testCases { t.Run(tc.name, func() { - _, err := t.gcsReader.getReadInfo(tc.start, tc.size) - - assert.Error(t.T(), err) + got := isSeekNeeded(tc.readType, tc.offset, tc.expectedOffset) + assert.Equal(t.T(), tc.want, got) }) } } -func (t *gcsReaderTest) Test_ReadInfo_Sequential() { +func (t *gcsReaderTest) Test_GetEndOffset() { testCases := []struct { - name string - start int64 - objectSize uint64 - expectedEnd int64 + name string + start int64 + objectSize int64 + initialReadType int64 + initialNumSeeks uint64 + initialTotalReadBytes uint64 + sequentialReadSizeMb int32 + expectedEnd int64 }{ { - name: "ExactSizeRead", // start 0, object = 10MB - start: 0, - objectSize: 10 * MiB, - expectedEnd: 10 * MiB, + name: "Sequential Read, Fits in sequentialReadSizeMb", + start: 0, + objectSize: 10 * MiB, + initialReadType: metrics.ReadTypeSequential, + initialNumSeeks: 0, + initialTotalReadBytes: 0, + sequentialReadSizeMb: 22, + expectedEnd: 10 * MiB, }, { - name: "ReadSizeGreaterThanObjectSize", // start near end, should clamp to objectSize - start: int64(10*MiB - 1), - objectSize: 10 * MiB, - expectedEnd: 10 * MiB, + name: "Sequential Read, Object Larger than sequentialReadSizeMb", + start: 0, + objectSize: 50 * MiB, + initialReadType: metrics.ReadTypeSequential, + initialNumSeeks: 0, + initialTotalReadBytes: 0, + sequentialReadSizeMb: 22, + expectedEnd: 22 * MiB, }, { - name: "ObjectSizeGreaterThanReadSize", // default read size applies - start: 0, - objectSize: 50 * MiB, - expectedEnd: 22 * MB, // equals to sequentialReadSizeInMb + name: "Sequential Read, Respects object size", + start: 5 * MiB, + objectSize: 7 * MiB, + initialReadType: metrics.ReadTypeSequential, + initialNumSeeks: 0, + initialTotalReadBytes: 0, + sequentialReadSizeMb: 22, + expectedEnd: 7 * MiB, + }, + { + name: "Random Read, Min read size", + start: 0, + objectSize: 5 * MiB, + initialReadType: metrics.ReadTypeRandom, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 1000, + sequentialReadSizeMb: 22, + expectedEnd: minReadSize, + }, + { + name: "Random Read, Averages less than minReadSize", + start: 0, + objectSize: 50 * MiB, + initialReadType: metrics.ReadTypeRandom, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 100 * 1024, // 100KiB + sequentialReadSizeMb: 22, + expectedEnd: minReadSize, // Should be atleast minReadSize + }, + { + name: "Random Read, Start Offset Non-Zero", + start: 5 * MiB, + objectSize: 50 * MiB, + initialReadType: metrics.ReadTypeRandom, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 2 * MiB, // avg read bytes = 1MiB + sequentialReadSizeMb: 22, + expectedEnd: 5*MiB + 2*MiB, // avg read bytes + 1MiB }, } for _, tc := range testCases { t.Run(tc.name, func() { - t.SetupTest() - t.object.Size = tc.objectSize + t.object.Size = uint64(tc.objectSize) + t.gcsReader.readType.Store(tc.initialReadType) + t.gcsReader.seeks.Store(tc.initialNumSeeks) + t.gcsReader.totalReadBytes.Store(tc.initialTotalReadBytes) - end, err := t.gcsReader.getReadInfo(tc.start, int64(tc.objectSize)) + end := t.gcsReader.getEndOffset(tc.start) - assert.NoError(t.T(), err) - assert.Equal(t.T(), metrics.ReadTypeSequential, t.gcsReader.readType.Load()) - assert.Equal(t.T(), tc.expectedEnd, end) + assert.Equal(t.T(), tc.expectedEnd, end, "End offset mismatch") }) } } -func (t *gcsReaderTest) Test_ReadInfo_Random() { - t.gcsReader.seeks.Store(2) +func (t *gcsReaderTest) Test_GetReadInfo() { testCases := []struct { - name string - start int64 - objectSize uint64 - totalReadBytes uint64 - expectedEnd int64 + name string + offset int64 + seekRecorded bool + initialReadType int64 + initialExpOffset int64 + initialNumSeeks uint64 + initialTotalReadBytes uint64 + expectedReadType int64 + expectedNumSeeks uint64 }{ { - name: "RangeBetween1And8MB", - start: 0, - objectSize: 50 * MiB, - totalReadBytes: 10 * MiB, - expectedEnd: 6 * MiB, + name: "First Read", + offset: 0, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 0, + initialNumSeeks: 0, + initialTotalReadBytes: 0, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 0, + }, + { + name: "Sequential Read", + offset: 10, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 10, + initialNumSeeks: 0, + initialTotalReadBytes: 100, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 0, + }, + { + name: "Sequential read with small forward jump and high average read bytes is still sequential", + offset: 100, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 10, + initialNumSeeks: 0, + initialTotalReadBytes: 10000000, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 0, + }, + { + name: "Sequential read with large forward jump is a seek", + offset: 50 + maxReadSize + 1, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: 0, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 1, + }, + { + name: "Sequential read with backward jump is a seek", + offset: 49, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: 0, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 1, + }, + { + name: "Contiguous random read is not a seek", + offset: 50, + seekRecorded: false, + initialReadType: metrics.ReadTypeRandom, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeRandom, + expectedNumSeeks: minSeeksForRandom, + }, + { + name: "Non-contiguous random read is a seek", + offset: 100, + seekRecorded: false, + initialReadType: metrics.ReadTypeRandom, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeRandom, + expectedNumSeeks: minSeeksForRandom + 1, }, { - name: "ReadSizeLessThan1MB", - start: 0, - objectSize: 50 * MiB, - totalReadBytes: 1 * MiB, // avg = 0.5MB - expectedEnd: MB, // equals to minReadSize + name: "Switches to random read after enough seeks", + offset: 50 + maxReadSize + 1, + seekRecorded: false, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom - 1, + initialTotalReadBytes: 1000, + expectedReadType: metrics.ReadTypeRandom, + expectedNumSeeks: minSeeksForRandom, }, { - name: "ReadSizeGreaterThan8MB", - start: 0, - objectSize: 50 * MiB, - totalReadBytes: 20 * MiB, - expectedEnd: 22 * MB, // equals to sequentialReadSizeInMb + name: "Switches back to sequential with high average read bytes", + offset: 100, + seekRecorded: false, + initialReadType: metrics.ReadTypeRandom, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: maxReadSize * (minSeeksForRandom + 1), + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: minSeeksForRandom + 1, }, { - name: "ReadSizeGreaterThan8MB", - start: 5*MiB - 1, - objectSize: 5 * MiB, - totalReadBytes: 2 * MiB, - expectedEnd: 5 * MiB, + name: "Seek recorded: sequential large forward jump", + offset: 50 + maxReadSize + 1, + seekRecorded: true, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: 0, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 0, // Not incremented + }, + { + name: "Seek recorded: sequential backward jump", + offset: 49, + seekRecorded: true, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: 1, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeSequential, + expectedNumSeeks: 1, // Not incremented + }, + { + name: "Seek recorded: non-contiguous random read", + offset: 100, + seekRecorded: true, + initialReadType: metrics.ReadTypeRandom, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom, + initialTotalReadBytes: 50 * 1024, + expectedReadType: metrics.ReadTypeRandom, + expectedNumSeeks: minSeeksForRandom, // Not incremented + }, + { + name: "Seek recorded: does not switch to random", + offset: 50 + maxReadSize + 1, + seekRecorded: true, + initialReadType: metrics.ReadTypeSequential, + initialExpOffset: 50, + initialNumSeeks: minSeeksForRandom - 1, + initialTotalReadBytes: 1000, + expectedReadType: metrics.ReadTypeSequential, // Does not switch + expectedNumSeeks: minSeeksForRandom - 1, // Not incremented }, } for _, tc := range testCases { t.Run(tc.name, func() { - t.object.Size = tc.objectSize - t.gcsReader.totalReadBytes.Store(tc.totalReadBytes) - - end, err := t.gcsReader.getReadInfo(tc.start, int64(tc.objectSize)) - - assert.NoError(t.T(), err) - assert.Equal(t.T(), metrics.ReadTypeRandom, t.gcsReader.readType.Load()) - assert.Equal(t.T(), tc.expectedEnd, end) + t.gcsReader.readType.Store(tc.initialReadType) + t.gcsReader.expectedOffset.Store(tc.initialExpOffset) + t.gcsReader.seeks.Store(tc.initialNumSeeks) + t.gcsReader.totalReadBytes.Store(tc.initialTotalReadBytes) + + readInfo := t.gcsReader.getReadInfo(tc.offset, tc.seekRecorded) + assert.Equal(t.T(), tc.expectedReadType, readInfo.readType, "Read type mismatch") + assert.Equal(t.T(), tc.expectedNumSeeks, t.gcsReader.seeks.Load(), "Number of seeks mismatch") }) } } @@ -588,8 +803,7 @@ func (t *gcsReaderTest) Test_ReadAt_WithAndWithoutReadConfig() { ReadHandle: nil, // No existing read handle } t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, expectedReadObjectRequest).Return(rc, nil).Once() - // BucketType is called by ReadAt -> getReadInfo -> readerType to determine reader strategy. - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: false}).Once() + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: false}).Twice() objectData, err := t.readAt(readOffset, int64(readLength)) @@ -610,7 +824,7 @@ func (t *gcsReaderTest) Test_ReadAt_WithAndWithoutReadConfig() { // This test validates the bug fix where seeks are not updated correctly in case of zonal bucket random reads (b/410904634). func (t *gcsReaderTest) Test_ReadAt_ValidateZonalRandomReads() { t.gcsReader.rangeReader.reader = nil - t.gcsReader.mrr.isMRDInUse = false + t.gcsReader.mrr.isMRDInUse.Store(false) t.gcsReader.seeks.Store(0) t.gcsReader.rangeReader.readType = metrics.ReadTypeSequential t.gcsReader.expectedOffset.Store(0) @@ -648,3 +862,53 @@ func (t *gcsReaderTest) Test_ReadAt_ValidateZonalRandomReads() { assert.Equal(t.T(), int64(readRange[1]), t.gcsReader.expectedOffset.Load()) } } + +func (t *gcsReaderTest) Test_ReadAt_ParallelRandomReads() { + // Setup + t.gcsReader.seeks.Store(minSeeksForRandom) + t.gcsReader.readType.Store(metrics.ReadTypeRandom) + t.object.Size = 20 * MiB + testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) + + // Mock bucket and MRD + t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}) + fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapper(t.mockBucket, t.object, &cfg.Config{}) + require.NoError(t.T(), err) + t.gcsReader.mrr.mrdWrapper = &fakeMRDWrapper + t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloader(t.object, testContent), nil) + + // Parallel reads + tasks := []struct { + offset int64 + size int + }{ + {0, 1 * MiB}, + {2 * MiB, 2 * MiB}, + {5 * MiB, 1 * MiB}, + {10 * MiB, 5 * MiB}, + } + + var wg sync.WaitGroup + var totalBytesReadFromTasks uint64 + + for _, task := range tasks { + wg.Add(1) + totalBytesReadFromTasks += uint64(task.size) + go func(offset int64, size int) { + defer wg.Done() + buf := make([]byte, size) + // Each goroutine gets its own context. + ctx := context.Background() + objData, err := t.gcsReader.ReadAt(ctx, buf, offset) + + require.NoError(t.T(), err) + require.Equal(t.T(), size, objData.Size) + require.Equal(t.T(), testContent[offset:offset+int64(size)], buf) + }(task.offset, task.size) + } + + wg.Wait() + + // Validation + assert.Equal(t.T(), totalBytesReadFromTasks, t.gcsReader.totalReadBytes.Load()) +} diff --git a/internal/gcsx/client_readers/multi_range_reader.go b/internal/gcsx/client_readers/multi_range_reader.go index c96822b307..e78a0f8f0d 100644 --- a/internal/gcsx/client_readers/multi_range_reader.go +++ b/internal/gcsx/client_readers/multi_range_reader.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "io" + "sync/atomic" "time" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" @@ -40,7 +41,7 @@ type MultiRangeReader struct { mrdWrapper *gcsx.MultiRangeDownloaderWrapper // boolean variable to determine if MRD is being used or not. - isMRDInUse bool + isMRDInUse atomic.Bool metricHandle metrics.MetricHandle } @@ -73,8 +74,7 @@ func (mrd *MultiRangeReader) readFromMultiRangeReader(ctx context.Context, p []b return 0, fmt.Errorf("readFromMultiRangeReader: Invalid MultiRangeDownloaderWrapper") } - if !mrd.isMRDInUse { - mrd.isMRDInUse = true + if mrd.isMRDInUse.CompareAndSwap(false, true) { mrd.mrdWrapper.IncrementRefCount() } @@ -99,11 +99,11 @@ func (mrd *MultiRangeReader) ReadAt(ctx context.Context, req *gcsx.GCSReaderRequ } func (mrd *MultiRangeReader) destroy() { - if mrd.isMRDInUse { + if mrd.isMRDInUse.Load() { err := mrd.mrdWrapper.DecrementRefCount() if err != nil { logger.Errorf("randomReader::Destroy:%v", err) } - mrd.isMRDInUse = false + mrd.isMRDInUse.Store(false) } } diff --git a/internal/gcsx/client_readers/multi_range_reader_test.go b/internal/gcsx/client_readers/multi_range_reader_test.go index ecb523cf66..c7c60a7df6 100644 --- a/internal/gcsx/client_readers/multi_range_reader_test.go +++ b/internal/gcsx/client_readers/multi_range_reader_test.go @@ -95,7 +95,7 @@ func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_ReadFull() { for _, tc := range testCases { t.Run(tc.name, func() { - t.multiRangeReader.isMRDInUse = false + t.multiRangeReader.isMRDInUse.Store(false) t.object.Size = uint64(tc.dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) @@ -115,7 +115,7 @@ func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_ReadFull() { } func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_TimeoutExceeded() { - t.multiRangeReader.isMRDInUse = false + t.multiRangeReader.isMRDInUse.Store(false) dataSize := 100 t.object.Size = uint64(dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) @@ -135,7 +135,7 @@ func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_TimeoutExceeded() { } func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_TimeoutNotExceeded() { - t.multiRangeReader.isMRDInUse = false + t.multiRangeReader.isMRDInUse.Store(false) dataSize := 100 t.object.Size = uint64(dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) @@ -221,7 +221,7 @@ func (t *multiRangeReaderTest) Test_ReadAt_MRDRead() { for _, tc := range testCases { t.Run(tc.name, func() { - t.multiRangeReader.isMRDInUse = false + t.multiRangeReader.isMRDInUse.Store(false) t.object.Size = uint64(tc.dataSize) testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index ab20c0fa8d..44d65fba95 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -118,13 +118,10 @@ func (rr *RangeReader) ReadAt(ctx context.Context, req *gcsx.GCSReaderRequest) ( } var err error - if req.Offset >= int64(rr.object.Size) { - err = io.EOF - return readerResponse, err + readerResponse.Size, err = rr.readFromExistingReader(ctx, req) + if errors.Is(err, gcsx.FallbackToAnotherReader) { + readerResponse.Size, err = rr.readFromRangeReader(ctx, req.Buffer, req.Offset, req.EndOffset, rr.readType) } - - readerResponse.Size, err = rr.readFromRangeReader(ctx, req.Buffer, req.Offset, req.EndOffset, rr.readType) - return readerResponse, err } @@ -316,16 +313,14 @@ func (rr *RangeReader) skipBytes(offset int64) { // a new reader unnecessarily. // // Parameters: -// - offset: the starting byte position of the requested read. -// - p: the buffer representing the size of the requested read. -func (rr *RangeReader) invalidateReaderIfMisalignedOrTooSmall(offset int64, p []byte) { - rr.skipBytes(offset) - +// - startOffset: the starting byte position of the requested read. +// - endOffset: the ending byte position of the requested read. +func (rr *RangeReader) invalidateReaderIfMisalignedOrTooSmall(startOffset, endOffset int64) { // If we have an existing reader, but it's positioned at the wrong place, // clean it up and throw it away. // We will also clean up the existing reader if it can't serve the entire request. - dataToRead := math.Min(float64(offset+int64(len(p))), float64(rr.object.Size)) - if rr.reader != nil && (rr.start != offset || int64(dataToRead) > rr.limit) { + dataToRead := math.Min(float64(endOffset), float64(rr.object.Size)) + if rr.reader != nil && (rr.start != startOffset || int64(dataToRead) > rr.limit) { rr.closeReader() rr.reader = nil rr.cancel = nil @@ -335,14 +330,15 @@ func (rr *RangeReader) invalidateReaderIfMisalignedOrTooSmall(offset int64, p [] // readFromExistingReader attempts to read data from an existing reader if one is available. // If a reader exists and the read is successful, the data is returned. // Otherwise, it returns an error indicating that a fallback to another reader is needed. -// Make sure to call invalidateReaderIfMisalignedOrTooSmall before using this method. -func (rr *RangeReader) readFromExistingReader(ctx context.Context, req *gcsx.GCSReaderRequest) (gcsx.ReaderResponse, error) { +func (rr *RangeReader) readFromExistingReader(ctx context.Context, req *gcsx.GCSReaderRequest) (int, error) { + rr.skipBytes(req.Offset) + // Since we are reading from an existing reader, we only need to read what was requested. + endOffset := min(req.Offset + int64(len(req.Buffer))) + + rr.invalidateReaderIfMisalignedOrTooSmall(req.Offset, endOffset) if rr.reader != nil { - return rr.ReadAt(ctx, req) + return rr.readFromRangeReader(ctx, req.Buffer, req.Offset, endOffset, rr.readType) } - return gcsx.ReaderResponse{ - DataBuf: req.Buffer, - Size: 0, - }, gcsx.FallbackToAnotherReader + return 0, gcsx.FallbackToAnotherReader } diff --git a/internal/gcsx/client_readers/range_reader_test.go b/internal/gcsx/client_readers/range_reader_test.go index c23927dc82..7a1e298309 100644 --- a/internal/gcsx/client_readers/range_reader_test.go +++ b/internal/gcsx/client_readers/range_reader_test.go @@ -369,7 +369,7 @@ func (t *rangeReaderTest) Test_invalidateReaderIfMisalignedOrTooSmall() { t.Run(tt.name, func() { tt.readerSetup() - t.rangeReader.invalidateReaderIfMisalignedOrTooSmall(tt.offset, make([]byte, tt.bufferSize)) + t.rangeReader.invalidateReaderIfMisalignedOrTooSmall(tt.offset, tt.offset+int64(tt.bufferSize)) if tt.expectReaderNil { assert.Nil(t.T(), t.rangeReader.reader, "rangeReader.reader should be nil") @@ -477,8 +477,8 @@ func (t *rangeReaderTest) Test_ReadAt_DoesntPropagateCancellationAfterReturning( // Set up a reader that will return three bytes. content := "xyz" t.rangeReader.reader = &fake.FakeReader{ReadCloser: getReadCloser([]byte(content))} - t.rangeReader.start = 1 - t.rangeReader.limit = 4 + t.rangeReader.start = 0 + t.rangeReader.limit = 3 // Snoop on when cancel is called. cancelCalled := make(chan struct{}) t.rangeReader.cancel = func() { close(cancelCalled) } @@ -615,11 +615,3 @@ func (t *rangeReaderTest) Test_ReadAt_ReaderNotExhausted() { assert.Equal(t.T(), rc, t.rangeReader.reader) assert.Equal(t.T(), offset+bufSize, t.rangeReader.start) } - -func (t *rangeReaderTest) Test_ReadAt_InvalidOffset() { - t.object.Size = 50 - - _, err := t.readAt(65, int64(t.object.Size)) - - assert.True(t.T(), errors.Is(err, io.EOF), "expected %v error got %v", io.EOF, err) -} diff --git a/internal/gcsx/read_manager/read_manager_test.go b/internal/gcsx/read_manager/read_manager_test.go index 57a7694682..e621e0a217 100644 --- a/internal/gcsx/read_manager/read_manager_test.go +++ b/internal/gcsx/read_manager/read_manager_test.go @@ -253,7 +253,7 @@ func (t *readManagerTest) Test_ReadAt_InvalidOffset() { func (t *readManagerTest) Test_ReadAt_NoExistingReader() { // The bucket should be called to set up a new reader. t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(nil, errors.New("network error")) - t.mockBucket.On("BucketType", mock.Anything).Return(t.bucketType).Times(2) + t.mockBucket.On("BucketType", mock.Anything).Return(t.bucketType) t.mockBucket.On("Name").Return("test-bucket") _, err := t.readAt(0, 1) @@ -267,7 +267,7 @@ func (t *readManagerTest) Test_ReadAt_ReaderFailsWithTimeout() { r := iotest.OneByteReader(iotest.TimeoutReader(strings.NewReader("xxx"))) rc := &fake.FakeReader{ReadCloser: io.NopCloser(r)} t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(rc, nil).Once() - t.mockBucket.On("BucketType", mock.Anything).Return(t.bucketType).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(t.bucketType).Times(2) _, err := t.readAt(0, 3) @@ -278,7 +278,7 @@ func (t *readManagerTest) Test_ReadAt_ReaderFailsWithTimeout() { func (t *readManagerTest) Test_ReadAt_FileClobbered() { t.mockBucket.On("NewReaderWithReadHandle", mock.Anything, mock.Anything).Return(nil, &gcs.NotFoundError{}) - t.mockBucket.On("BucketType", mock.Anything).Return(t.bucketType).Times(1) + t.mockBucket.On("BucketType", mock.Anything).Return(t.bucketType).Times(2) t.mockBucket.On("Name").Return("test-bucket") _, err := t.readAt(1, 3) From 1623724e2d94cad2d1f009e32adef05c4fd04e72 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 6 Aug 2025 20:51:23 +0530 Subject: [PATCH 0653/1298] install latest gcloud version (#3647) --- tools/cd_scripts/e2e_test.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 7a99fedd30..cc58a038aa 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -174,6 +174,7 @@ go version |& tee -a ${LOG_FILE} export PATH=${PATH}:/usr/local/go/bin git clone https://github.com/googlecloudplatform/gcsfuse |& tee -a ${LOG_FILE} cd gcsfuse +bash ./perfmetrics/scripts/install_latest_gcloud.sh # Installation of crcmod is working through pip only on rhel and centos. # For debian and ubuntu, we are installing through sudo apt. From 18f780fd0bff6f6ee95eb476b091f4570e403b82 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 7 Aug 2025 10:08:14 +0530 Subject: [PATCH 0654/1298] Handle Symlink scenario in rename (#3648) --- internal/fs/fs.go | 44 ++++++++++++++++++------------- internal/fs/implicit_dirs_test.go | 29 ++++++++++++++++++++ internal/fs/inode/symlink.go | 27 +++++++++++++++++-- internal/fs/inode/symlink_test.go | 16 ++++++++++- internal/fs/rename_dir_test.go | 24 +++++++++++++++++ internal/fs/rename_file_test.go | 28 ++++++++++++++++++++ 6 files changed, 147 insertions(+), 21 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index dbed9e7d54..023f21d201 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -825,6 +825,7 @@ func (fs *fileSystem) mintInode(ic inode.Core) (in inode.Inode) { in = inode.NewSymlinkInode( id, ic.FullName, + ic.Bucket, ic.MinObject, fuseops.InodeAttributes{ Uid: fs.uid, @@ -2240,15 +2241,15 @@ func (fs *fileSystem) Rename( newParent := fs.dirInodeOrDie(op.NewParent) fs.mu.Unlock() - if oldInode, ok := oldParent.(inode.BucketOwnedInode); !ok { + if oldParentInode, ok := oldParent.(inode.BucketOwnedInode); !ok { // The old parent is not owned by any bucket, which means it's the base // directory that holds all the buckets' root directories. So, this op // is to rename a bucket, which is not supported. return fmt.Errorf("rename a bucket: %w", syscall.ENOTSUP) } else { // The target path must exist in the same bucket. - oldBucket := oldInode.Bucket().Name() - if newInode, ok := newParent.(inode.BucketOwnedInode); !ok || oldBucket != newInode.Bucket().Name() { + oldBucket := oldParentInode.Bucket().Name() + if newParentInode, ok := newParent.(inode.BucketOwnedInode); !ok || oldBucket != newParentInode.Bucket().Name() { return fmt.Errorf("move out of bucket %q: %w", oldBucket, syscall.ENOTSUP) } } @@ -2276,26 +2277,33 @@ func (fs *fileSystem) Rename( } return fs.renameNonHierarchicalDir(ctx, oldParent, op.OldName, newParent, op.NewName) } - childFileInode, ok := child.(*inode.FileInode) - if !ok { - return fmt.Errorf("child inode (id %v) is neither file nor directory inode", child.ID()) - } - // TODO(b/402335988): Fix rename flow for local files when streaming writes is disabled. - // If object to be renamed is a local file inode and streaming writes are disabled, rename operation is not supported. - if childFileInode.IsLocal() && !fs.newConfig.Write.EnableStreamingWrites { - return fmt.Errorf("cannot rename open file %q: %w", op.OldName, syscall.ENOTSUP) - } - return fs.renameFile(ctx, op, childFileInode, oldParent, newParent) + + return fs.renameFile(ctx, op, childBktOwned, oldParent, newParent) } // LOCKS_EXCLUDED(oldParent) // LOCKS_EXCLUDED(newParent) -func (fs *fileSystem) renameFile(ctx context.Context, op *fuseops.RenameOp, oldObject *inode.FileInode, oldParent, newParent inode.DirInode) error { - updatedMinObject, err := fs.flushPendingWrites(ctx, oldObject) - if err != nil { - return fmt.Errorf("flushPendingWrites: %w", err) +func (fs *fileSystem) renameFile(ctx context.Context, op *fuseops.RenameOp, child inode.BucketOwnedInode, oldParent, newParent inode.DirInode) error { + var updatedMinObject *gcs.MinObject + var err error + + switch c := child.(type) { + case *inode.FileInode: + // TODO(b/402335988): Fix rename flow for local files when streaming writes is disabled. + // If object to be renamed is a local file inode and streaming writes are disabled, rename operation is not supported. + if c.IsLocal() && !fs.newConfig.Write.EnableStreamingWrites { + return fmt.Errorf("cannot rename open file %q: %w", op.OldName, syscall.ENOTSUP) + } + updatedMinObject, err = fs.flushPendingWrites(ctx, c) + if err != nil { + return fmt.Errorf("flushPendingWrites: %w", err) + } + case *inode.SymlinkInode: + updatedMinObject = c.Source() + default: + return fmt.Errorf("child inode (id %v) is not a file or symlink inode", child.ID()) } - if fs.enableAtomicRenameObject || oldObject.Bucket().BucketType().Zonal { + if fs.enableAtomicRenameObject || child.Bucket().BucketType().Zonal { return fs.atomicRename(ctx, oldParent, op.OldName, updatedMinObject, newParent, op.NewName) } return fs.nonAtomicRename(ctx, oldParent, op.OldName, updatedMinObject, newParent, op.NewName) diff --git a/internal/fs/implicit_dirs_test.go b/internal/fs/implicit_dirs_test.go index 1bfb7cec14..9272a6002b 100644 --- a/internal/fs/implicit_dirs_test.go +++ b/internal/fs/implicit_dirs_test.go @@ -571,3 +571,32 @@ func (t *ImplicitDirsTest) AtimeCtimeAndMtime() { ExpectThat(ctime, timeutil.TimeNear(mountTime, delta)) ExpectThat(mtime, timeutil.TimeNear(mountTime, delta)) } + +func (t *ImplicitDirsTest) RenameSymlinkToImplicitDir() { + // Create an implicit directory "foo" by creating a file within it. + err := t.createWithContents("foo/bar", "taco") + AssertEq(nil, err) + // Create a symlink. + oldPath := path.Join(mntDir, "symlink") + target := "foo/bar" + err = os.Symlink(target, oldPath) + AssertEq(nil, err) + newPath := path.Join(mntDir, "symlink_new") + + // Attempt to rename the symlink to a new path. + err = os.Rename(oldPath, newPath) + + AssertEq(nil, err) + // The old path should no longer exist. + _, err = os.Lstat(oldPath) + AssertNe(nil, err) + AssertTrue(os.IsNotExist(err), "err: %v", err) + // The new path should now be a symlink. + fi, err := os.Lstat(newPath) + AssertEq(nil, err) + AssertEq(os.ModeSymlink, fi.Mode()&os.ModeSymlink) + // The new symlink should point to the correct target. + targetRead, err := os.Readlink(newPath) + AssertEq(nil, err) + AssertEq(target, targetRead) +} diff --git a/internal/fs/inode/symlink.go b/internal/fs/inode/symlink.go index d8adbbac8f..ffd4d3c630 100644 --- a/internal/fs/inode/symlink.go +++ b/internal/fs/inode/symlink.go @@ -17,6 +17,7 @@ package inode import ( "sync" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/jacobsa/fuse/fuseops" "golang.org/x/net/context" @@ -44,6 +45,7 @@ type SymlinkInode struct { id fuseops.InodeID name Name + bucket *gcsx.SyncerBucket sourceGeneration Generation attrs fuseops.InodeAttributes target string @@ -66,12 +68,14 @@ var _ Inode = &SymlinkInode{} func NewSymlinkInode( id fuseops.InodeID, name Name, + bucket *gcsx.SyncerBucket, m *gcs.MinObject, attrs fuseops.InodeAttributes) (s *SymlinkInode) { // Create the inode. s = &SymlinkInode{ - id: id, - name: name, + id: id, + name: name, + bucket: bucket, sourceGeneration: Generation{ Object: m.Generation, Metadata: m.MetaGeneration, @@ -153,3 +157,22 @@ func (s *SymlinkInode) Target() (target string) { func (s *SymlinkInode) Unlink() { } + +// Bucket returns the bucket that owns this inode. +func (s *SymlinkInode) Bucket() *gcsx.SyncerBucket { + return s.bucket +} + +// Source returns the MinObject from which this inode was created. +func (s *SymlinkInode) Source() *gcs.MinObject { + return &gcs.MinObject{ + Name: s.name.GcsObjectName(), + Generation: s.sourceGeneration.Object, + MetaGeneration: s.sourceGeneration.Metadata, + Size: s.sourceGeneration.Size, + Metadata: map[string]string{ + SymlinkMetadataKey: s.target, + }, + Updated: s.attrs.Mtime, + } +} diff --git a/internal/fs/inode/symlink_test.go b/internal/fs/inode/symlink_test.go index b80671d464..7ae87917e4 100644 --- a/internal/fs/inode/symlink_test.go +++ b/internal/fs/inode/symlink_test.go @@ -19,11 +19,14 @@ import ( "os" "testing" + "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/jacobsa/fuse/fuseops" "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/inode" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" . "github.com/jacobsa/ogletest" + "github.com/jacobsa/timeutil" ) func TestSymlink(t *testing.T) { RunTests(t) } @@ -33,6 +36,7 @@ func TestSymlink(t *testing.T) { RunTests(t) } //////////////////////////////////////////////////////////////////////// type SymlinkTest struct { + bucket *gcsx.SyncerBucket } var _ SetUpInterface = &CoreTest{} @@ -40,6 +44,16 @@ var _ TearDownInterface = &CoreTest{} func init() { RegisterTestSuite(&SymlinkTest{}) } +func (t *SymlinkTest) SetUp(ti *TestInfo) { + bucket := gcsx.NewSyncerBucket( + 1, + 10, // ChunkTransferTimeoutSecs + ".gcsfuse_tmp/", + fake.NewFakeBucket(timeutil.RealClock(), "some-bucket", gcs.BucketType{}), + ) + t.bucket = &bucket +} + //////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////// @@ -82,7 +96,7 @@ func (t *SymlinkTest) TestAttributes() { Mode: 0777 | os.ModeSymlink, } name := inode.NewFileName(inode.NewRootName("some-bucket"), m.Name) - s := inode.NewSymlinkInode(fuseops.InodeID(42), name, m, attrs) + s := inode.NewSymlinkInode(fuseops.InodeID(42), name, t.bucket, m, attrs) tests := []struct { name string clobberedCheck bool diff --git a/internal/fs/rename_dir_test.go b/internal/fs/rename_dir_test.go index ddcfef51c5..f9d7533036 100644 --- a/internal/fs/rename_dir_test.go +++ b/internal/fs/rename_dir_test.go @@ -28,6 +28,7 @@ import ( type RenameDirTests struct { suite.Suite + fsTest } //////////////////////////////////////////////////////////////////////// @@ -83,6 +84,29 @@ func (t *RenameDirTests) TestRenameFolderWithEmptySourceDirectory() { assert.Equal(t.T(), 0, len(dirEntries)) } +func (t *RenameDirTests) TestRenameSymlinkToExplicitDir() { + targetDirName := "target_dir" + err := os.Mkdir(path.Join(mntDir, targetDirName), dirPerms) + require.NoError(t.T(), err) + oldPath := path.Join(mntDir, "symlink_old") + err = os.Symlink(targetDirName, oldPath) + require.NoError(t.T(), err) + newPath := path.Join(mntDir, "symlink_new") + + err = os.Rename(oldPath, newPath) + + assert.NoError(t.T(), err) + _, err = os.Lstat(oldPath) + assert.Error(t.T(), err) + assert.True(t.T(), os.IsNotExist(err)) + fi, err := os.Lstat(newPath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), os.ModeSymlink, fi.Mode()&os.ModeSymlink) + targetRead, err := os.Readlink(newPath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), targetDirName, targetRead) +} + func (t *RenameDirTests) TestRenameFolderWithSourceDirectoryHaveLocalFiles() { oldDirPath := path.Join(mntDir, "foo", "test") _, err := os.Stat(oldDirPath) diff --git a/internal/fs/rename_file_test.go b/internal/fs/rename_file_test.go index ba6131d250..6b3f0054f6 100644 --- a/internal/fs/rename_file_test.go +++ b/internal/fs/rename_file_test.go @@ -109,3 +109,31 @@ func (t *RenameFileTests) TestRenameFile() { }) } } + +func (t *RenameFileTests) TestRenameSymlinkToFile() { + // Create a target file for the symlink to point to. + targetPath := path.Join(mntDir, "target") + err := os.WriteFile(targetPath, []byte("taco"), filePerms) + require.NoError(t.T(), err) + // Create the symbolic link that we will rename. + oldPath := path.Join(mntDir, "symlink_old") + err = os.Symlink(targetPath, oldPath) + require.NoError(t.T(), err) + newPath := path.Join(mntDir, "symlink_new") + + err = os.Rename(oldPath, newPath) + + assert.NoError(t.T(), err) + // The old path should no longer exist. + _, err = os.Lstat(oldPath) + assert.Error(t.T(), err) + assert.True(t.T(), os.IsNotExist(err), "err: %v", err) + // The new path should now be a symlink, having replaced the original file. + fi, err := os.Lstat(newPath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), os.ModeSymlink, fi.Mode()&os.ModeSymlink) + // The new symlink should point to the correct target. + targetRead, err := os.Readlink(newPath) + assert.NoError(t.T(), err) + assert.Equal(t.T(), targetPath, targetRead) +} From 2cff1407f2ef6cc70591fc46954e887fd3af9c25 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Thu, 7 Aug 2025 10:09:30 +0530 Subject: [PATCH 0655/1298] ci(micro bench): Spin GCE vm and run micro benchmark instead of on kokoro machine (#3631) * run tests with separate VM * testing on VM * update script * update script * update script * small fix * update script * update script * run tests in clean env * update path * update path * use arg * use arg * testing * testing * testing * test on kokoro machine * test on kokoro machine * test on kokoro machine * test on kokoro machine * test on kokoro machine * fix small things * review comments * small fix * review comment --- .../scripts/build_and_install_gcsfuse.sh | 35 ++++-- .../gcp_ubuntu/micro_benchmarks/build.sh | 118 ++++++++---------- .../micro_benchmarks/continuous.cfg | 7 -- .../micro_benchmarks/run_microbenchmark.sh | 115 +++++++++++++++++ 4 files changed, 191 insertions(+), 84 deletions(-) create mode 100755 perfmetrics/scripts/micro_benchmarks/run_microbenchmark.sh diff --git a/perfmetrics/scripts/build_and_install_gcsfuse.sh b/perfmetrics/scripts/build_and_install_gcsfuse.sh index 7e7035c861..7fa9ad87dd 100755 --- a/perfmetrics/scripts/build_and_install_gcsfuse.sh +++ b/perfmetrics/scripts/build_and_install_gcsfuse.sh @@ -16,22 +16,35 @@ # This script will build gcsfuse package on given commitId or branch and install it on the machine. # This will stop execution when any command will have non-zero status. set -e -# e.g. architecture=arm64 or amd64 + +# --- Determine architecture (e.g., amd64, arm64) --- architecture=$(dpkg --print-architecture) -echo "Installing docker..." -sudo mkdir -p /etc/apt/keyrings -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg -echo \ - "deb [arch=${architecture} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ - $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null -sudo apt-get update -sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y +# --- Install Docker only if not already installed --- +if ! command -v docker &> /dev/null; then + echo "Installing docker..." + sudo mkdir -p /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + echo \ + "deb [arch=${architecture} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y +else + echo "Docker is already installed. Skipping Docker installation." +fi + +# --- Build and install gcsfuse --- echo "Building and installing gcsfuse..." -# $1 refers to branch or commit-id on which we want to build package. branch=$1 -# Build the gcsfuse package using the same commands used during release. +if [ -z "$branch" ]; then + echo "Usage: $0 " + exit 1 +fi + GCSFUSE_VERSION=0.0.0 + +# Build the gcsfuse package using Docker sudo docker buildx build --load ./tools/package_gcsfuse_docker/ -t gcsfuse:$branch --build-arg ARCHITECTURE=${architecture} --build-arg GCSFUSE_VERSION=$GCSFUSE_VERSION --build-arg BRANCH_NAME=$branch --platform=linux/${architecture} sudo docker run -v $HOME/release:/release gcsfuse:$branch cp -r /packages /release/ sudo dpkg -i $HOME/release/packages/gcsfuse_${GCSFUSE_VERSION}_${architecture}.deb diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh index d76ce8dfab..e198c7b809 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh @@ -13,69 +13,55 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -e -sudo apt-get update - -echo "Installing git" -sudo apt-get install git - -cd "${KOKORO_ARTIFACTS_DIR}/github/gcsfuse" -echo "Building and installing gcsfuse" -# Get the latest commitId of yesterday in the log file. Build gcsfuse and run -commitId=$(git log --before='yesterday 23:59:59' --max-count=1 --pretty=%H) -./perfmetrics/scripts/build_and_install_gcsfuse.sh $commitId - -echo "Upgrading Python3 version" -./perfmetrics/scripts/upgrade_python3.sh - -# Path to locally installed upgraded Python -PYTHON_BIN="$HOME/.local/python-3.11.9/bin/python3.11" - -cd "./perfmetrics/scripts/micro_benchmarks" - -echo "Installing dependencies using upgraded Python..." -"$PYTHON_BIN" -m venv venv -source venv/bin/activate -pip install -r requirements.txt - -# Temporarily allow script to continue after command failure -set +e - -echo "Running Python scripts for hns bucket..." - -FILE_SIZE_READ_GB=15 -READ_LOG_FILE="${KOKORO_ARTIFACTS_DIR}/gcsfuse-logs-single-threaded-read-${FILE_SIZE_READ_GB}gb-test.txt" -GCSFUSE_READ_FLAGS="--log-file $READ_LOG_FILE" -python3 read_single_thread.py --bucket single-threaded-tests --gcsfuse-config "$GCSFUSE_READ_FLAGS" --total-files 10 --file-size-gb "$FILE_SIZE_READ_GB" -exit_read_code=$? - -FILE_SIZE_WRITE_GB=15 -WRITE_LOG_FILE="${KOKORO_ARTIFACTS_DIR}/gcsfuse-logs-single-threaded-write-${FILE_SIZE_WRITE_GB}gb-test.txt" -GCSFUSE_WRITE_FLAGS="--log-file $WRITE_LOG_FILE" -python3 write_single_thread.py --bucket single-threaded-tests --gcsfuse-config "$GCSFUSE_WRITE_FLAGS" --total-files 1 --file-size-gb "$FILE_SIZE_WRITE_GB" -exit_write_code=$? - -deactivate - -# Re-enable strict mode -set -e - -# Final result -exit_code=0 -if [[ $exit_read_code -ne 0 ]]; then - echo "Read benchmark failed with exit code $exit_read_code" - exit_code=$exit_read_code -fi - -if [[ $exit_write_code -ne 0 ]]; then - echo "Write benchmark failed with exit code $exit_write_code" - exit_code=$exit_write_code -fi - -if [[ $exit_code != 0 ]]; then - echo "Benchmarks failed." - exit $exit_code -fi - -echo "Benchmarks completed successfully." -exit 0 +set -euo pipefail + +VM_NAME="periodic-micro-benchmark-tests" +ZONE="us-west1-b" +REPO_DIR="~/github/gcsfuse" +MOUNTED_DIR="$REPO_DIR/perfmetrics/scripts/micro_benchmarks/gcs" +TEST_SCRIPT_PATH="github/gcsfuse/perfmetrics/scripts/micro_benchmarks/run_microbenchmark.sh" +GCSFUSE_REPO="https://github.com/GoogleCloudPlatform/gcsfuse.git" + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" +} + +run_script_on_vm() { + log "Running benchmark script on VM with clean setup..." + + sudo gcloud compute ssh "$VM_NAME" --zone "$ZONE" --internal-ip --command " + set -euxo pipefail + + MOUNTED_DIR=\"$MOUNTED_DIR\" + GCSFUSE_REPO=\"$GCSFUSE_REPO\" + TEST_SCRIPT_PATH=\"$TEST_SCRIPT_PATH\" + + sudo apt-get update -y + sudo apt-get install -y git + + # Unmount if gcsfuse mount exists + if mountpoint -q \"\$MOUNTED_DIR\"; then + echo \"\$MOUNTED_DIR is mounted. Attempting to unmount...\" + sudo fusermount -u \"\$MOUNTED_DIR\" || sudo umount \"\$MOUNTED_DIR\" + fi + + # Clean up any existing repo + rm -rf ~/github + + # Clone fresh repo + mkdir -p ~/github + git clone \"\$GCSFUSE_REPO\" ~/github/gcsfuse + cd ~/github/gcsfuse + commitId=\$(git log --before='yesterday 23:59:59' --max-count=1 --pretty=%H) + git checkout \$commitId + + # Run benchmark + echo \"Triggering benchmark script...\" + bash ~/\$TEST_SCRIPT_PATH + " + + log "Benchmark script executed successfully on VM." +} + +# ---- Main Execution ---- +run_script_on_vm diff --git a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/continuous.cfg b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/continuous.cfg index 2ea8471574..75bbaa39bc 100644 --- a/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/continuous.cfg +++ b/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/continuous.cfg @@ -12,11 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. -action { - define_artifacts { - regex: "gcsfuse-logs-single-threaded*" - strip_prefix: "github/gcsfuse/perfmetrics/scripts" - } -} - build_file: "gcsfuse/perfmetrics/scripts/continuous_test/gcp_ubuntu/micro_benchmarks/build.sh" diff --git a/perfmetrics/scripts/micro_benchmarks/run_microbenchmark.sh b/perfmetrics/scripts/micro_benchmarks/run_microbenchmark.sh new file mode 100755 index 0000000000..cf159dc151 --- /dev/null +++ b/perfmetrics/scripts/micro_benchmarks/run_microbenchmark.sh @@ -0,0 +1,115 @@ +#!/bin/bash +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -euo pipefail # Exit on error, unset variables are errors, pipe fails propagate + +log() { + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" +} + +# --- Constants --- +VENV_DIR="venv" +ARTIFACT_BUCKET_PATH="gcsfuse-kokoro-logs/prod/gcsfuse/gcp_ubuntu/periodic/micro_benchmark" +DATE=$(date +%Y-%m-%d) + +# --- Functions --- +cleanup_mounts() { + log "Cleaning up any stale gcsfuse mounts..." + for mnt in $(mount | grep gcsfuse | awk '{print $3}'); do + log "Unmounting $mnt" + sudo fusermount -u "$mnt" || true + done +} + + +prepare_venv() { + log "Setting up Python virtual environment..." + if [[ ! -d "$VENV_DIR" ]]; then + python3 -m venv "$VENV_DIR" + fi + source "$VENV_DIR/bin/activate" + pip install -U pip setuptools + pip install -r "requirements.txt" +} + +run_benchmark() { + local rw=$1 # "read" or "write" operation type + local script_path=$2 # Path to the benchmark script (e.g., read_single_thread.py) + local file_size_gb=$3 # Size of each file to read/write in GB + local total_files=$4 # Total number of files to process + + echo "Running $rw benchmark with file size $file_size_gb GB and total files $total_files..." + local log_file="/tmp/gcsfuse-logs-single-threaded-${rw}-${file_size_gb}gb-test.txt" + + # Clean old log file if it exists + rm -f "$log_file" + + # Pass log file flag as a string. + local gcsfuse_flags="--log-file $log_file" + + log "Running $rw benchmark..." + if ! python3 "$script_path" --bucket single-threaded-tests \ + --gcsfuse-config "$gcsfuse_flags" \ + --total-files "$total_files" \ + --file-size-gb "$file_size_gb"; then + log "$rw benchmark failed. Copying log to gs://$ARTIFACT_BUCKET_PATH/$DATE" + gcloud storage cp "$log_file" "gs://$ARTIFACT_BUCKET_PATH/$DATE/" + gcloud storage cat "gs://$ARTIFACT_BUCKET_PATH/$DATE/$(basename "$log_file")" + return 1 + fi + + return 0 +} + +# --- Main Script --- +log "Installing dependencies..." +sudo apt-get update -y +sudo apt-get install -y git gnupg python3-venv + +cd "$HOME/github/gcsfuse" +commitId=$(git rev-parse --short HEAD) +./perfmetrics/scripts/build_and_install_gcsfuse.sh $commitId + +cd "perfmetrics/scripts/micro_benchmarks" +# Cleanup previous mounts if any +cleanup_mounts +prepare_venv + +READ_GB=15 +TOTAL_READ_FILES=10 +WRITE_GB=15 +TOTAL_WRITE_FILES=1 +exit_code=0 + +if ! run_benchmark "read" "read_single_thread.py" "$READ_GB" "$TOTAL_READ_FILES"; then + echo "Read benchmark failed." + exit_code=1 +fi + +if ! run_benchmark "write" "write_single_thread.py" "$WRITE_GB" "$TOTAL_WRITE_FILES"; then + echo "Write benchmark failed." + exit_code=1 +fi + +deactivate || true +cleanup_mounts + +if [[ $exit_code -ne 0 ]]; then + log "One or more benchmarks failed." + exit $exit_code +fi + +log "Benchmarks completed successfully." +exit 0 From 5830d5f1c17252cfa536974fcf71e23db8fc71c1 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 7 Aug 2025 13:54:36 +0530 Subject: [PATCH 0656/1298] test: Add integration tests for rename of symlinks (#3649) * Add integration tests * Add e2e test for rename to symlinks * resolved comments --- internal/fs/rename_dir_test.go | 6 +- internal/fs/rename_file_test.go | 8 +-- .../explicit_dir/rename_sym_link_test.go | 53 ++++++++++++++++++ .../implicit_dir/rename_sym_link_test.go | 56 +++++++++++++++++++ .../integration_tests/local_file/sym_link.go | 27 +++++++-- .../operations/rename_file_test.go | 30 ++++++++++ 6 files changed, 167 insertions(+), 13 deletions(-) create mode 100644 tools/integration_tests/explicit_dir/rename_sym_link_test.go create mode 100644 tools/integration_tests/implicit_dir/rename_sym_link_test.go diff --git a/internal/fs/rename_dir_test.go b/internal/fs/rename_dir_test.go index f9d7533036..524e8452f7 100644 --- a/internal/fs/rename_dir_test.go +++ b/internal/fs/rename_dir_test.go @@ -95,15 +95,15 @@ func (t *RenameDirTests) TestRenameSymlinkToExplicitDir() { err = os.Rename(oldPath, newPath) - assert.NoError(t.T(), err) + require.NoError(t.T(), err) _, err = os.Lstat(oldPath) assert.Error(t.T(), err) assert.True(t.T(), os.IsNotExist(err)) fi, err := os.Lstat(newPath) - assert.NoError(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), os.ModeSymlink, fi.Mode()&os.ModeSymlink) targetRead, err := os.Readlink(newPath) - assert.NoError(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), targetDirName, targetRead) } diff --git a/internal/fs/rename_file_test.go b/internal/fs/rename_file_test.go index 6b3f0054f6..5fd83a53f2 100644 --- a/internal/fs/rename_file_test.go +++ b/internal/fs/rename_file_test.go @@ -123,17 +123,17 @@ func (t *RenameFileTests) TestRenameSymlinkToFile() { err = os.Rename(oldPath, newPath) - assert.NoError(t.T(), err) + require.NoError(t.T(), err) // The old path should no longer exist. _, err = os.Lstat(oldPath) - assert.Error(t.T(), err) + require.Error(t.T(), err) assert.True(t.T(), os.IsNotExist(err), "err: %v", err) // The new path should now be a symlink, having replaced the original file. fi, err := os.Lstat(newPath) - assert.NoError(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), os.ModeSymlink, fi.Mode()&os.ModeSymlink) // The new symlink should point to the correct target. targetRead, err := os.Readlink(newPath) - assert.NoError(t.T(), err) + require.NoError(t.T(), err) assert.Equal(t.T(), targetPath, targetRead) } diff --git a/tools/integration_tests/explicit_dir/rename_sym_link_test.go b/tools/integration_tests/explicit_dir/rename_sym_link_test.go new file mode 100644 index 0000000000..d2e1254cd5 --- /dev/null +++ b/tools/integration_tests/explicit_dir/rename_sym_link_test.go @@ -0,0 +1,53 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package explicit_dir_test + +import ( + "os" + "path" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRenameSymlinkToExplicitDir(t *testing.T) { + testDir := setup.SetupTestDirectory(DirForExplicitDirTests) + targetDirName := "target_dir" + targetDirPath := path.Join(testDir, targetDirName) + err := os.Mkdir(targetDirPath, setup.DirPermission_0755) + require.NoError(t, err) + oldSymlinkPath := path.Join(testDir, "symlink_old") + err = os.Symlink(targetDirPath, oldSymlinkPath) + require.NoError(t, err) + newSymlinkPath := path.Join(testDir, "symlink_new") + + err = os.Rename(oldSymlinkPath, newSymlinkPath) + + require.NoError(t, err) + _, err = os.Lstat(oldSymlinkPath) + require.Error(t, err) + assert.True(t, os.IsNotExist(err)) + fi, err := os.Lstat(newSymlinkPath) + require.NoError(t, err) + assert.Equal(t, os.ModeSymlink, fi.Mode()&os.ModeType) + targetRead, err := os.Readlink(newSymlinkPath) + require.NoError(t, err) + assert.Equal(t, targetDirPath, targetRead) + targetFi, err := os.Stat(newSymlinkPath) + require.NoError(t, err) + assert.True(t, targetFi.IsDir()) +} diff --git a/tools/integration_tests/implicit_dir/rename_sym_link_test.go b/tools/integration_tests/implicit_dir/rename_sym_link_test.go new file mode 100644 index 0000000000..3ee372707c --- /dev/null +++ b/tools/integration_tests/implicit_dir/rename_sym_link_test.go @@ -0,0 +1,56 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package implicit_dir_test + +import ( + "os" + "path" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRenameSymlinkToImplicitDir(t *testing.T) { + testDir := setup.SetupTestDirectory(DirForImplicitDirTests) + implicitDirName := "implicit_dir" + // Create an object that defines an implicit directory. This creates `implicit_dir/`. + objectNameInGCS := path.Join(DirForImplicitDirTests, implicitDirName, "placeholder") + err := client.CreateObjectOnGCS(testEnv.ctx, testEnv.storageClient, objectNameInGCS, "") + require.NoError(t, err) + implicitDirPath := path.Join(testDir, implicitDirName) + oldSymlinkPath := path.Join(testDir, "symlink_old") + err = os.Symlink(implicitDirPath, oldSymlinkPath) + require.NoError(t, err) + newSymlinkPath := path.Join(testDir, "symlink_new") + + err = os.Rename(oldSymlinkPath, newSymlinkPath) + + require.NoError(t, err) + _, err = os.Lstat(oldSymlinkPath) + require.Error(t, err) + assert.True(t, os.IsNotExist(err)) + fi, err := os.Lstat(newSymlinkPath) + require.NoError(t, err) + assert.Equal(t, os.ModeSymlink, fi.Mode()&os.ModeType) + targetRead, err := os.Readlink(newSymlinkPath) + require.NoError(t, err) + assert.Equal(t, implicitDirPath, targetRead) + targetFi, err := os.Stat(newSymlinkPath) + require.NoError(t, err) + assert.True(t, targetFi.IsDir()) +} diff --git a/tools/integration_tests/local_file/sym_link.go b/tools/integration_tests/local_file/sym_link.go index 1b4e21c6f8..d469698b7e 100644 --- a/tools/integration_tests/local_file/sym_link.go +++ b/tools/integration_tests/local_file/sym_link.go @@ -18,12 +18,13 @@ package local_file import ( "os" "path" - "strings" "testing" . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func createAndVerifySymLink(t *testing.T) (filePath, symlink string, fh *os.File) { @@ -44,8 +45,7 @@ func createAndVerifySymLink(t *testing.T) (filePath, symlink string, fh *os.File func (t *CommonLocalFileTestSuite) TestCreateSymlinkForLocalFile() { _, _, fh := createAndVerifySymLink(t.T()) - CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, - FileName1, FileContents, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, FileContents, t.T()) } func (t *CommonLocalFileTestSuite) TestReadSymlinkForDeletedLocalFile() { @@ -57,7 +57,22 @@ func (t *CommonLocalFileTestSuite) TestReadSymlinkForDeletedLocalFile() { // Reading symlink should fail. _, err := os.Stat(symlink) - if err == nil || !strings.Contains(err.Error(), "no such file or directory") { - t.T().Fatalf("Reading symlink for deleted local file did not fail.") - } + + require.Error(t.T(), err) + assert.True(t.T(), os.IsNotExist(err), "Reading symlink for deleted local file should have failed with 'no such file or directory'. Got: %v", err) +} + +func (t *CommonLocalFileTestSuite) TestRenameSymlinkForLocalFile() { + filePath, symlinkPath, fh := createAndVerifySymLink(t.T()) + newSymlinkPath := path.Join(testDirPath, "newSymlink") + + err := os.Rename(symlinkPath, newSymlinkPath) + + require.NoError(t.T(), err, "os.Rename failed for symlink") + _, err = os.Lstat(symlinkPath) + require.Error(t.T(), err) + assert.True(t.T(), os.IsNotExist(err), "Old symlink should not exist after rename. err: %v", err) + operations.VerifyReadLink(filePath, newSymlinkPath, t.T()) + operations.VerifyReadFile(newSymlinkPath, FileContents, t.T()) + CloseFileAndValidateContentFromGCS(ctx, storageClient, fh, testDirName, FileName1, FileContents, t.T()) } diff --git a/tools/integration_tests/operations/rename_file_test.go b/tools/integration_tests/operations/rename_file_test.go index cc3bb470ef..51c6fc8b0f 100644 --- a/tools/integration_tests/operations/rename_file_test.go +++ b/tools/integration_tests/operations/rename_file_test.go @@ -16,6 +16,7 @@ package operations_test import ( + "os" "path" "strings" "testing" @@ -23,6 +24,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestRenameFile(t *testing.T) { @@ -60,3 +62,31 @@ func TestRenameFileWithSrcFileDoesNoExist(t *testing.T) { assert.Error(t, err) assert.True(t, strings.Contains(err.Error(), "no such file or directory")) } + +func TestRenameSymlinkToFile(t *testing.T) { + testDir := setup.SetupTestDirectory(DirForOperationTests) + targetName := "target.txt" + targetPath := path.Join(testDir, targetName) + err := os.WriteFile(targetPath, []byte("taco"), setup.FilePermission_0600) + require.NoError(t, err) + oldSymlinkPath := path.Join(testDir, "symlink_old") + err = os.Symlink(targetPath, oldSymlinkPath) + require.NoError(t, err) + newSymlinkPath := path.Join(testDir, "symlink_new") + + err = os.Rename(oldSymlinkPath, newSymlinkPath) + + require.NoError(t, err) + _, err = os.Lstat(oldSymlinkPath) + require.Error(t, err) + assert.True(t, os.IsNotExist(err)) + fi, err := os.Lstat(newSymlinkPath) + require.NoError(t, err) + assert.Equal(t, os.ModeSymlink, fi.Mode()&os.ModeType) + targetRead, err := os.Readlink(newSymlinkPath) + require.NoError(t, err) + assert.Equal(t, targetPath, targetRead) + content, err := operations.ReadFile(newSymlinkPath) + require.NoError(t, err) + assert.Equal(t, "taco", string(content)) +} From 572541c13d28e703dfb6e74258432d63f9c44df2 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 7 Aug 2025 14:38:36 +0530 Subject: [PATCH 0657/1298] add logs if bucket creation fails (#3652) --- tools/integration_tests/improved_run_e2e_tests.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh index f11a64e56c..a37944d086 100755 --- a/tools/integration_tests/improved_run_e2e_tests.sh +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -329,12 +329,13 @@ setup_package_buckets () { local bucket_type="$3" local exit_code=0 for package in "${package_array[@]}"; do - local bucket_name - bucket_name=$(create_bucket "$package" "$bucket_type") + local output + output=$(create_bucket "$package" "$bucket_type") if [ $? -eq 0 ]; then - package_bucket_array+=("${package} ${bucket_name} ${bucket_type}") + package_bucket_array+=("${package} ${output} ${bucket_type}") else exit_code=1 + log_error_locked "$output" fi done return $exit_code From 59ad5f6d5a3ace3d53e434c03e1c902c033ec99c Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Thu, 7 Aug 2025 17:17:26 +0530 Subject: [PATCH 0658/1298] feat(block-pool): adding reserved block support in block-pool (#3645) * reserved block support in block-pool * adding more unit test * Review comments --- internal/block/block_pool.go | 66 ++-- internal/block/block_pool_test.go | 330 ++++++++++++++---- internal/bufferedread/buffered_reader.go | 3 +- internal/bufferedread/download_task_test.go | 2 +- .../bufferedwrites/buffered_write_handler.go | 2 +- .../bufferedwrites/upload_handler_test.go | 2 +- 6 files changed, 306 insertions(+), 99 deletions(-) diff --git a/internal/block/block_pool.go b/internal/block/block_pool.go index f2e10fe6cb..4e1de2feed 100644 --- a/internal/block/block_pool.go +++ b/internal/block/block_pool.go @@ -21,7 +21,7 @@ import ( "golang.org/x/sync/semaphore" ) -var CantAllocateAnyBlockError error = errors.New("cant allocate any streaming write block as global max blocks limit is reached") +var CantAllocateAnyBlockError error = errors.New("cant allocate any block as global max blocks limit is reached") type GenBlock interface { // Reuse resets the block for reuse. @@ -31,7 +31,16 @@ type GenBlock interface { Deallocate() error } -// GenBlockPool handles the creation of blocks as per the user configuration. +// GenBlockPool is a generic block pool for managing blocks that implement the GenBlock interface. +// It offers methods to get blocks, return blocks to the free pool, and clear the free pool. +// This implementation is NOT thread-safe - concurrent access from multiple goroutines requires external synchronization. +// +// Block allocation is controlled by maxBlocks (per-pool limit) and a global semaphore (cross-pool limit). +// When the global limit is reached, Get() will block until blocks become available, while TryGet() +// returns an error immediately to avoid blocking. +// +// The pool supports reserving blocks at creation time - these reserved blocks hold semaphore permits +// and can only be released when clearing the pool with releaseReservedBlocks=true. type GenBlockPool[T GenBlock] struct { // Channel holding free blocks. freeBlocksCh chan T @@ -45,6 +54,9 @@ type GenBlockPool[T GenBlock] struct { // Total number of blocks created so far. totalBlocks int64 + // Number of blocks reserved at the time of block pool creation. + reservedBlocks int64 + // Semaphore used to limit the total number of blocks created across // different files. globalMaxBlocksSem *semaphore.Weighted @@ -54,26 +66,31 @@ type GenBlockPool[T GenBlock] struct { } // NewGenBlockPool creates the blockPool based on the user configuration. -func NewGenBlockPool[T GenBlock](blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphore.Weighted, createBlockFunc func(blockSize int64) (T, error)) (bp *GenBlockPool[T], err error) { +func NewGenBlockPool[T GenBlock](blockSize int64, maxBlocks int64, reservedBlocks int64, globalMaxBlocksSem *semaphore.Weighted, createBlockFunc func(blockSize int64) (T, error)) (bp *GenBlockPool[T], err error) { if blockSize <= 0 || maxBlocks <= 0 { err = fmt.Errorf("invalid configuration provided for blockPool, blocksize: %d, maxBlocks: %d", blockSize, maxBlocks) return } - bp = &GenBlockPool[T]{ + if reservedBlocks < 0 || reservedBlocks > maxBlocks { + err = fmt.Errorf("invalid reserved blocks count: %d, it should be between 0 and maxBlocks: %d", reservedBlocks, maxBlocks) + return + } + + semAcquired := globalMaxBlocksSem.TryAcquire(reservedBlocks) + if !semAcquired { + return nil, CantAllocateAnyBlockError + } + + return &GenBlockPool[T]{ freeBlocksCh: make(chan T, maxBlocks), blockSize: blockSize, maxBlocks: maxBlocks, + reservedBlocks: reservedBlocks, totalBlocks: 0, globalMaxBlocksSem: globalMaxBlocksSem, createBlockFunc: createBlockFunc, - } - semAcquired := bp.globalMaxBlocksSem.TryAcquire(1) - if !semAcquired { - return nil, CantAllocateAnyBlockError - } - - return bp, nil + }, nil } // Get returns a block. It returns an existing block if it's ready for reuse or @@ -137,9 +154,8 @@ func (bp *GenBlockPool[T]) canAllocateBlock() bool { return false } - // Always allow allocation if this is the first block for the file since it has been reserved at - // the time of block pool creation. - if bp.totalBlocks == 0 { + // Always allow allocation upto reserved number of blocks. + if bp.totalBlocks < bp.reservedBlocks { return true } @@ -162,7 +178,7 @@ func (bp *GenBlockPool[T]) BlockSize() int64 { return bp.blockSize } -func (bp *GenBlockPool[T]) ClearFreeBlockChannel(releaseLastBlock bool) error { +func (bp *GenBlockPool[T]) ClearFreeBlockChannel(releaseReservedBlocks bool) error { for { select { case b := <-bp.freeBlocksCh: @@ -171,16 +187,16 @@ func (bp *GenBlockPool[T]) ClearFreeBlockChannel(releaseLastBlock bool) error { // if we get here, there is likely memory corruption. return fmt.Errorf("munmap error: %v", err) } - bp.totalBlocks-- - // Release semaphore for all but the last block. - if bp.totalBlocks != 0 { + // Release semaphore for all but the reserved blocks. + if bp.totalBlocks > bp.reservedBlocks { bp.globalMaxBlocksSem.Release(1) } + bp.totalBlocks-- default: // We are here, it means there are no more blocks in the free blocks channel. - // Release semaphore for last block iff releaseLastBlock is true. - if releaseLastBlock { - bp.globalMaxBlocksSem.Release(1) + // Release semaphore for the released blocks iff releaseReservedBlocks is true. + if releaseReservedBlocks { + bp.globalMaxBlocksSem.Release(bp.reservedBlocks) } return nil } @@ -194,11 +210,11 @@ func (bp *GenBlockPool[T]) TotalFreeBlocks() int { } // NewBlockPool creates GenBlockPool for block.Block interface. -func NewBlockPool(blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphore.Weighted) (bp *GenBlockPool[Block], err error) { - return NewGenBlockPool(blockSize, maxBlocks, globalMaxBlocksSem, createBlock) +func NewBlockPool(blockSize int64, maxBlocks int64, reservedBlocks int64, globalMaxBlocksSem *semaphore.Weighted) (bp *GenBlockPool[Block], err error) { + return NewGenBlockPool(blockSize, maxBlocks, reservedBlocks, globalMaxBlocksSem, createBlock) } // NewPrefetchBlockPool creates GenBlockPool for block.PrefetchBlock interface. -func NewPrefetchBlockPool(blockSize int64, maxBlocks int64, globalMaxBlocksSem *semaphore.Weighted) (bp *GenBlockPool[PrefetchBlock], err error) { - return NewGenBlockPool(blockSize, maxBlocks, globalMaxBlocksSem, createPrefetchBlock) +func NewPrefetchBlockPool(blockSize int64, maxBlocks int64, reservedBlocks int64, globalMaxBlocksSem *semaphore.Weighted) (bp *GenBlockPool[PrefetchBlock], err error) { + return NewGenBlockPool(blockSize, maxBlocks, reservedBlocks, globalMaxBlocksSem, createPrefetchBlock) } diff --git a/internal/block/block_pool_test.go b/internal/block/block_pool_test.go index 7a1f599615..c3a152a063 100644 --- a/internal/block/block_pool_test.go +++ b/internal/block/block_pool_test.go @@ -36,7 +36,7 @@ func TestBlockPoolTestSuite(t *testing.T) { } func (t *BlockPoolTest) TestInitBlockPool() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) + bp, err := NewGenBlockPool(1024, 10, 0, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) require.NotNil(t.T(), bp) @@ -46,28 +46,28 @@ func (t *BlockPoolTest) TestInitBlockPool() { } func (t *BlockPoolTest) TestInitBlockPoolForZeroBlockSize() { - _, err := NewGenBlockPool(0, 10, semaphore.NewWeighted(10), createBlock) + _, err := NewGenBlockPool(0, 10, 0, semaphore.NewWeighted(10), createBlock) require.NotNil(t.T(), err) assert.Equal(t.T(), fmt.Errorf(invalidConfigError, 0, 10), err) } func (t *BlockPoolTest) TestInitBlockPoolForNegativeBlockSize() { - _, err := NewGenBlockPool(-1, 10, semaphore.NewWeighted(10), createBlock) + _, err := NewGenBlockPool(-1, 10, 0, semaphore.NewWeighted(10), createBlock) require.NotNil(t.T(), err) assert.Equal(t.T(), fmt.Errorf(invalidConfigError, -1, 10), err) } func (t *BlockPoolTest) TestInitBlockPoolForZeroMaxBlocks() { - _, err := NewGenBlockPool(10, 0, semaphore.NewWeighted(10), createBlock) + _, err := NewGenBlockPool(10, 0, 0, semaphore.NewWeighted(10), createBlock) require.NotNil(t.T(), err) assert.Equal(t.T(), fmt.Errorf(invalidConfigError, 10, 0), err) } func (t *BlockPoolTest) TestInitBlockPoolForNegativeMaxBlocks() { - _, err := NewGenBlockPool(10, -1, semaphore.NewWeighted(10), createBlock) + _, err := NewGenBlockPool(10, -1, 0, semaphore.NewWeighted(10), createBlock) require.NotNil(t.T(), err) assert.Equal(t.T(), fmt.Errorf(invalidConfigError, 10, -1), err) @@ -75,7 +75,7 @@ func (t *BlockPoolTest) TestInitBlockPoolForNegativeMaxBlocks() { // Represents when block is available on the freeBlocksCh. func (t *BlockPoolTest) TestTryGetWhenBlockIsAvailableForReuse() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) + bp, err := NewGenBlockPool(1024, 10, 0, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) // Creating a block with some data and send it to blockCh. b, err := createBlock(2) @@ -93,7 +93,7 @@ func (t *BlockPoolTest) TestTryGetWhenBlockIsAvailableForReuse() { } func (t *BlockPoolTest) TestTryGetWhenTotalBlocksIsLessThanThanMaxBlocks() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) + bp, err := NewGenBlockPool(1024, 10, 0, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) block, err := bp.TryGet() @@ -105,7 +105,7 @@ func (t *BlockPoolTest) TestTryGetWhenTotalBlocksIsLessThanThanMaxBlocks() { func (t *BlockPoolTest) TestTryGetToCreateLargeBlock() { // Creating block of size 1TB - bp, err := NewGenBlockPool(1024*1024*1024*1024, 10, semaphore.NewWeighted(10), createBlock) + bp, err := NewGenBlockPool(1024*1024*1024*1024, 10, 0, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) _, err = bp.TryGet() @@ -116,7 +116,7 @@ func (t *BlockPoolTest) TestTryGetToCreateLargeBlock() { // Represents when block is available on the freeBlocksCh. func (t *BlockPoolTest) TestGetWhenBlockIsAvailableForReuse() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) + bp, err := NewGenBlockPool(1024, 10, 0, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) // Creating a block with some data and send it to blockCh. b, err := createBlock(2) @@ -134,7 +134,7 @@ func (t *BlockPoolTest) TestGetWhenBlockIsAvailableForReuse() { } func (t *BlockPoolTest) TestGetWhenTotalBlocksIsLessThanThanMaxBlocks() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) + bp, err := NewGenBlockPool(1024, 10, 0, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) block, err := bp.Get() @@ -146,7 +146,7 @@ func (t *BlockPoolTest) TestGetWhenTotalBlocksIsLessThanThanMaxBlocks() { func (t *BlockPoolTest) TestCreateBlockWithLargeSize() { // Creating block of size 1TB - bp, err := NewGenBlockPool(1024*1024*1024*1024, 10, semaphore.NewWeighted(10), createBlock) + bp, err := NewGenBlockPool(1024*1024*1024*1024, 10, 0, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) _, err = bp.Get() @@ -156,33 +156,39 @@ func (t *BlockPoolTest) TestCreateBlockWithLargeSize() { } func (t *BlockPoolTest) TestBlockSize() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) + bp, err := NewGenBlockPool(1024, 10, 0, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) require.Equal(t.T(), int64(1024), bp.BlockSize()) } -func (t *BlockPoolTest) TestClearFreeBlockChannel() { +func (t *BlockPoolTest) TestClearFreeBlockChannelWithReleaseReservedBlocksTrue() { tests := []struct { - name string - releaseLastBlock bool - possibleSemaphoreAcquire int64 + name string + reservedBlocks int64 + performGetBlock int }{ { - name: "release_last_block_true", - releaseLastBlock: true, - possibleSemaphoreAcquire: 4, + name: "with_0_reserved_blocks", + reservedBlocks: 0, }, { - name: "release_last_block_false", - releaseLastBlock: false, - possibleSemaphoreAcquire: 3, + name: "with_1_reserved_blocks", + reservedBlocks: 1, + }, + { + name: "with_2_reserved_blocks", + reservedBlocks: 2, + }, + { + name: "with_3_reserved_blocks", + reservedBlocks: 3, }, } for _, tt := range tests { t.Run(tt.name, func() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(4), createBlock) + bp, err := NewGenBlockPool(1024, 4, tt.reservedBlocks, semaphore.NewWeighted(4), createBlock) require.Nil(t.T(), err) blocks := make([]Block, 4) for i := 0; i < 4; i++ { @@ -194,22 +200,79 @@ func (t *BlockPoolTest) TestClearFreeBlockChannel() { } require.Equal(t.T(), int64(4), bp.totalBlocks) - err = bp.ClearFreeBlockChannel(tt.releaseLastBlock) + err = bp.ClearFreeBlockChannel(true) require.Nil(t.T(), err) require.EqualValues(t.T(), 0, bp.totalBlocks) for i := 0; i < 4; i++ { require.Nil(t.T(), blocks[i].(*memoryBlock).buffer) } - // Check if semaphore is released correctly. - require.True(t.T(), bp.globalMaxBlocksSem.TryAcquire(tt.possibleSemaphoreAcquire)) + // All 4 semaphore slots should be available to acquire. + require.True(t.T(), bp.globalMaxBlocksSem.TryAcquire(4)) + require.False(t.T(), bp.globalMaxBlocksSem.TryAcquire(1)) + }) + } +} + +func (t *BlockPoolTest) TestClearFreeBlockChannelWithReleaseReservedBlocksFalse() { + tests := []struct { + name string + releaseReservedBlocks bool + reservedBlocks int64 + possibleSemaphoreSlots int64 + }{ + { + name: "with_0_reserved_blocks", + reservedBlocks: 0, + possibleSemaphoreSlots: 4, + }, + { + name: "with_1_reserved_blocks", + reservedBlocks: 1, + possibleSemaphoreSlots: 3, // 4 - 1 reserved + }, + { + name: "with_2_reserved_blocks", + reservedBlocks: 2, + possibleSemaphoreSlots: 2, // 4 - 2 reserved + }, + { + name: "all_4_reserved_blocks", + reservedBlocks: 4, + possibleSemaphoreSlots: 0, // 4 - 4 reserved + }, + } + + for _, tt := range tests { + t.Run(tt.name, func() { + bp, err := NewGenBlockPool(1024, 4, tt.reservedBlocks, semaphore.NewWeighted(4), createBlock) + require.Nil(t.T(), err) + blocks := make([]Block, 4) + for i := 0; i < 4; i++ { + blocks[i] = t.validateGetBlockIsNotBlocked(bp) + } + // Adding all blocks to freeBlocksCh + for i := 0; i < 4; i++ { + bp.freeBlocksCh <- blocks[i] + } + require.Equal(t.T(), int64(4), bp.totalBlocks) + + err = bp.ClearFreeBlockChannel(false) + + require.Nil(t.T(), err) + require.EqualValues(t.T(), 0, bp.totalBlocks) + for i := 0; i < 4; i++ { + require.Nil(t.T(), blocks[i].(*memoryBlock).buffer) + } + // Only reserved blocks semaphore slots should be available to acquire. + require.True(t.T(), bp.globalMaxBlocksSem.TryAcquire(tt.possibleSemaphoreSlots)) require.False(t.T(), bp.globalMaxBlocksSem.TryAcquire(1)) }) } } func (t *BlockPoolTest) TestClearFreeBlockChannelWhenTotalBlocksIsZero() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(1), createBlock) + bp, err := NewGenBlockPool(1024, 10, 0, semaphore.NewWeighted(1), createBlock) require.Nil(t.T(), err) require.Equal(t.T(), int64(0), bp.totalBlocks) @@ -225,7 +288,7 @@ func (t *BlockPoolTest) TestClearFreeBlockChannelWhenTotalBlocksIsZero() { func (t *BlockPoolTest) TestBlockPoolCreationAcquiresGlobalSem() { globalBlocksSem := semaphore.NewWeighted(1) - bp, err := NewGenBlockPool(1024, 3, globalBlocksSem, createBlock) + bp, err := NewGenBlockPool(1024, 3, 1, globalBlocksSem, createBlock) require.Nil(t.T(), err) // Validate that semaphore got acquired. @@ -249,9 +312,9 @@ func (t *BlockPoolTest) TestBlockPoolCreationAcquiresGlobalSem() { func (t *BlockPoolTest) TestClearFreeBlockChannelWithMultipleBlockPools() { globalMaxBlocksSem := semaphore.NewWeighted(3) - bp1, err := NewGenBlockPool(1024, 3, globalMaxBlocksSem, createBlock) + bp1, err := NewGenBlockPool(1024, 3, 1, globalMaxBlocksSem, createBlock) require.Nil(t.T(), err) - bp2, err := NewGenBlockPool(1024, 3, globalMaxBlocksSem, createBlock) + bp2, err := NewGenBlockPool(1024, 3, 1, globalMaxBlocksSem, createBlock) require.Nil(t.T(), err) // Create 2 blocks in bp1. b1 := t.validateGetBlockIsNotBlocked(bp1) @@ -282,7 +345,7 @@ func (t *BlockPoolTest) TestClearFreeBlockChannelWithMultipleBlockPools() { } func (t *BlockPoolTest) TestBlockPoolCreationFailsWhenGlobalMaxBlocksIsZero() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(0), createBlock) + bp, err := NewGenBlockPool(1024, 10, 1, semaphore.NewWeighted(0), createBlock) require.Error(t.T(), err) assert.Nil(t.T(), bp) @@ -290,7 +353,7 @@ func (t *BlockPoolTest) TestBlockPoolCreationFailsWhenGlobalMaxBlocksIsZero() { } func (t *BlockPoolTest) TestTryGetWhenLimitedByGlobalBlocks() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(2), createBlock) + bp, err := NewGenBlockPool(1024, 10, 1, semaphore.NewWeighted(2), createBlock) require.Nil(t.T(), err) // 2 blocks can be created. b1, err1 := bp.TryGet() @@ -309,7 +372,7 @@ func (t *BlockPoolTest) TestTryGetWhenLimitedByGlobalBlocks() { } func (t *BlockPoolTest) TestTryGetWhenTotalBlocksEqualToMaxBlocks() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) + bp, err := NewGenBlockPool(1024, 10, 0, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) bp.totalBlocks = 10 @@ -321,7 +384,7 @@ func (t *BlockPoolTest) TestTryGetWhenTotalBlocksEqualToMaxBlocks() { } func (t *BlockPoolTest) TestGetWhenLimitedByGlobalBlocks() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(2), createBlock) + bp, err := NewGenBlockPool(1024, 10, 1, semaphore.NewWeighted(2), createBlock) require.Nil(t.T(), err) // 2 blocks can be created. @@ -334,7 +397,7 @@ func (t *BlockPoolTest) TestGetWhenLimitedByGlobalBlocks() { } func (t *BlockPoolTest) TestGetWhenTotalBlocksEqualToMaxBlocks() { - bp, err := NewGenBlockPool(1024, 10, semaphore.NewWeighted(10), createBlock) + bp, err := NewGenBlockPool(1024, 10, 0, semaphore.NewWeighted(10), createBlock) require.Nil(t.T(), err) bp.totalBlocks = 10 @@ -379,53 +442,100 @@ func (t *BlockPoolTest) validateGetBlockIsNotBlocked(bp *GenBlockPool[Block]) Bl func (t *BlockPoolTest) TestCanAllocateBlock() { tests := []struct { - name string - maxBlocks int64 - totalBlocks int64 - globalSem *semaphore.Weighted - expected bool + name string + maxBlocks int64 + totalBlocks int64 + reservedBlocks int64 + globalSem *semaphore.Weighted + expected bool }{ { - name: "max_blocks_reached", - maxBlocks: 10, - totalBlocks: 10, - globalSem: semaphore.NewWeighted(0), - expected: false, + name: "max_blocks_reached", + maxBlocks: 10, + totalBlocks: 10, + reservedBlocks: 0, + globalSem: semaphore.NewWeighted(0), + expected: false, + }, + { + name: "first_block", + maxBlocks: 10, + totalBlocks: 0, + reservedBlocks: 0, + globalSem: semaphore.NewWeighted(1), + expected: true, + }, + { + name: "semaphore_acquirable", + maxBlocks: 10, + totalBlocks: 5, + reservedBlocks: 0, + globalSem: semaphore.NewWeighted(1), + expected: true, + }, + { + name: "semaphore_not_acquirable", + maxBlocks: 10, + totalBlocks: 5, + reservedBlocks: 0, + globalSem: semaphore.NewWeighted(0), + expected: false, }, { - name: "first_block", - maxBlocks: 10, - totalBlocks: 0, - globalSem: semaphore.NewWeighted(0), - expected: true, + name: "equal_max_blocks_and_total_blocks_0", + maxBlocks: 0, + totalBlocks: 0, + reservedBlocks: 0, + globalSem: semaphore.NewWeighted(0), + expected: false, }, { - name: "semaphore_acquirable", - maxBlocks: 10, - totalBlocks: 5, - globalSem: semaphore.NewWeighted(1), - expected: true, + name: "total_blocks_more_than_max_blocks", + maxBlocks: 0, + totalBlocks: 1, + reservedBlocks: 0, + globalSem: semaphore.NewWeighted(0), + expected: false, }, { - name: "semaphore_not_acquirable", - maxBlocks: 10, - totalBlocks: 5, - globalSem: semaphore.NewWeighted(0), - expected: false, + name: "reserved_blocks_equal_to_max_blocks", + maxBlocks: 10, + totalBlocks: 0, + reservedBlocks: 10, + globalSem: semaphore.NewWeighted(10), + expected: true, }, { - name: "equal_max_blocks_and_total_blocks_0", - maxBlocks: 0, - totalBlocks: 0, - globalSem: semaphore.NewWeighted(0), - expected: false, + name: "reserved_blocks_less_than_max_blocks", + maxBlocks: 10, + totalBlocks: 0, + reservedBlocks: 5, + globalSem: semaphore.NewWeighted(10), + expected: true, }, { - name: "total_blocks_more_than_max_blocks", - maxBlocks: 0, - totalBlocks: 1, - globalSem: semaphore.NewWeighted(0), - expected: false, + name: "reserved_blocks_equal_to_total_blocks", + maxBlocks: 10, + totalBlocks: 5, + reservedBlocks: 5, + globalSem: semaphore.NewWeighted(0), + expected: false, + }, + { + name: "reserved_blocks_less_than_total_blocks", + maxBlocks: 10, + totalBlocks: 6, + reservedBlocks: 5, + globalSem: semaphore.NewWeighted(0), + expected: false, + }, + { + name: "reserved_blocks_more_than_total_blocks", + maxBlocks: 10, + totalBlocks: 4, + reservedBlocks: 5, + globalSem: semaphore.NewWeighted(0), + expected: false, }, } @@ -443,3 +553,83 @@ func (t *BlockPoolTest) TestCanAllocateBlock() { }) } } + +func (t *BlockPoolTest) TestBlockPoolCreationWithReservedBlocksSuccess() { + tests := []struct { + name string + reservedBlocks int64 + maxBlocks int64 + }{ + { + name: "zero_reserved_blocks", + reservedBlocks: 0, + maxBlocks: 10, + }, + { + name: "one_reserved_block", + reservedBlocks: 1, + maxBlocks: 10, + }, + { + name: "two_reserved_blocks", + reservedBlocks: 2, + maxBlocks: 10, + }, + { + name: "max_blocks_equal_to_reserved_blocks", + reservedBlocks: 10, + maxBlocks: 10, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func() { + bp, err := NewGenBlockPool(1024, 10, tt.reservedBlocks, semaphore.NewWeighted(20), createBlock) + + require.NoError(t.T(), err) + require.NotNil(t.T(), bp) + }) + } +} + +func (t *BlockPoolTest) TestBlockPoolCreationWithReservedBlocksFailure() { + tests := []struct { + name string + reservedBlocks int64 + maxBlocks int64 + globalMaxBlocks int64 + expectedErrMsg string + }{ + { + name: "reserved_blocks_greater_than_max_blocks", + reservedBlocks: 11, + maxBlocks: 10, + globalMaxBlocks: 20, + expectedErrMsg: "invalid reserved blocks count: 11, it should be between 0 and maxBlocks: 10", + }, + { + name: "negative_reserved_blocks", + reservedBlocks: -1, + maxBlocks: 10, + globalMaxBlocks: 20, + expectedErrMsg: "invalid reserved blocks count: -1, it should be between 0 and maxBlocks: 10", + }, + { + name: "reserved_blocks_greater_than_global_max_blocks", + reservedBlocks: 7, + maxBlocks: 7, + globalMaxBlocks: 6, + expectedErrMsg: "cant allocate any block as global max blocks limit is reached", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func() { + bp, err := NewGenBlockPool(1024, 10, tt.reservedBlocks, semaphore.NewWeighted(tt.globalMaxBlocks), createBlock) + + require.Error(t.T(), err) + assert.Nil(t.T(), bp) + assert.EqualError(t.T(), err, tt.expectedErrMsg) + }) + } +} diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index b874d24c23..694a980ed7 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -94,7 +94,8 @@ type BufferedReader struct { // NewBufferedReader returns a new bufferedReader instance. func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *BufferedReadConfig, globalMaxBlocksSem *semaphore.Weighted, workerPool workerpool.WorkerPool, metricHandle metrics.MetricHandle) (*BufferedReader, error) { - blockpool, err := block.NewPrefetchBlockPool(config.PrefetchBlockSizeBytes, config.MaxPrefetchBlockCnt, globalMaxBlocksSem) + // TODO: To pass the minimum required block-count based on the block-size. + blockpool, err := block.NewPrefetchBlockPool(config.PrefetchBlockSizeBytes, config.MaxPrefetchBlockCnt, 2, globalMaxBlocksSem) if err != nil { return nil, fmt.Errorf("NewBufferedReader: failed to create block-pool: %w", err) } diff --git a/internal/bufferedread/download_task_test.go b/internal/bufferedread/download_task_test.go index 11be9171c3..5f9df5539c 100644 --- a/internal/bufferedread/download_task_test.go +++ b/internal/bufferedread/download_task_test.go @@ -59,7 +59,7 @@ func (dts *DownloadTaskTestSuite) SetupTest() { } dts.mockBucket = new(storage.TestifyMockBucket) var err error - dts.blockPool, err = block.NewPrefetchBlockPool(testBlockSize, 10, semaphore.NewWeighted(100)) + dts.blockPool, err = block.NewPrefetchBlockPool(testBlockSize, 10, 1, semaphore.NewWeighted(100)) require.NoError(dts.T(), err, "Failed to create block pool") } diff --git a/internal/bufferedwrites/buffered_write_handler.go b/internal/bufferedwrites/buffered_write_handler.go index cd09295cbe..a484698c6c 100644 --- a/internal/bufferedwrites/buffered_write_handler.go +++ b/internal/bufferedwrites/buffered_write_handler.go @@ -101,7 +101,7 @@ type CreateBWHandlerRequest struct { // NewBWHandler creates the bufferedWriteHandler struct. func NewBWHandler(req *CreateBWHandlerRequest) (bwh BufferedWriteHandler, err error) { - bp, err := block.NewBlockPool(req.BlockSize, req.MaxBlocksPerFile, req.GlobalMaxBlocksSem) + bp, err := block.NewBlockPool(req.BlockSize, req.MaxBlocksPerFile, 1, req.GlobalMaxBlocksSem) if err != nil { return } diff --git a/internal/bufferedwrites/upload_handler_test.go b/internal/bufferedwrites/upload_handler_test.go index 33b2bd4881..ab04aaec30 100644 --- a/internal/bufferedwrites/upload_handler_test.go +++ b/internal/bufferedwrites/upload_handler_test.go @@ -54,7 +54,7 @@ func TestUploadHandlerTestSuite(t *testing.T) { func (t *UploadHandlerTest) SetupTest() { t.mockBucket = new(storagemock.TestifyMockBucket) var err error - t.blockPool, err = block.NewBlockPool(blockSize, maxBlocks, semaphore.NewWeighted(maxBlocks)) + t.blockPool, err = block.NewBlockPool(blockSize, maxBlocks, 1, semaphore.NewWeighted(maxBlocks)) require.NoError(t.T(), err) t.uh = newUploadHandler(&CreateUploadHandlerRequest{ Object: nil, From d4944c7634ab35d971d31973acb09d8abfc5b5ec Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 7 Aug 2025 17:29:31 +0530 Subject: [PATCH 0659/1298] Pin the versions of timm and safetensors (#3653) --- perfmetrics/scripts/ml_tests/pytorch/v2/dino/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/perfmetrics/scripts/ml_tests/pytorch/v2/dino/Dockerfile b/perfmetrics/scripts/ml_tests/pytorch/v2/dino/Dockerfile index b92732b215..bccb26bdeb 100644 --- a/perfmetrics/scripts/ml_tests/pytorch/v2/dino/Dockerfile +++ b/perfmetrics/scripts/ml_tests/pytorch/v2/dino/Dockerfile @@ -18,7 +18,7 @@ FROM gcr.io/deeplearning-platform-release/pytorch-gpu.2-0.py310 # Allow non-root users to specify the allow_other or allow_root mount options RUN echo "user_allow_other" > /etc/fuse.conf -RUN pip3 install timm setuptools==69.5.1 +RUN pip3 install timm==1.0.19 safetensors==0.5.3 setuptools==69.5.1 WORKDIR "/pytorch_dino/" From 854746aad5adc3f8892f255808652df2ed76bd6c Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Thu, 7 Aug 2025 17:35:57 +0530 Subject: [PATCH 0660/1298] test(buffered-read): adding log parser utility required for buffered-read e2e test (#3629) * added initial template for parsing * fixing header checks --- .../read_logs/buffered_read_log_parser.go | 257 ++++++++++++++++++ .../buffered_read_log_parser_test.go | 203 ++++++++++++++ .../json_parser/read_logs/helpers.go | 14 +- .../read_logs/json_read_log_parser_test.go | 32 ++- .../json_parser/read_logs/structure.go | 25 +- 5 files changed, 509 insertions(+), 22 deletions(-) create mode 100644 tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go create mode 100644 tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser_test.go diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go new file mode 100644 index 0000000000..30a40f0d6c --- /dev/null +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go @@ -0,0 +1,257 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_logs + +import ( + "encoding/json" + "fmt" + "io" + "log" + "regexp" + "strings" +) + +var readFileRegex = regexp.MustCompile(`fuse_debug: Op (0x[0-9a-fA-F]+)\s+connection\.go:\d+\] <- ReadFile \(inode (\d+), PID (\d+), handle (\d+), offset (\d+), (\d+) bytes\)`) +var readAtReqRegex = regexp.MustCompile(`([a-f0-9-]+) <- ReadAt\(([^:]+):/([^,]+), (\d+), (\d+), (\d+), (\d+)\)`) +var readAtSimpleRespRegex = regexp.MustCompile(`([a-f0-9-]+) -> ReadAt\(\): Ok\(([0-9.]+(?:s|ms|µs))\)`) + +// ParseBufferedReadLogsFromLogReader parses buffered read logs from an io.Reader and +// returns a map of BufferedReadLogEntry keyed by file handle. +// BufferedReadLogEntry contains the common read log information and a slice of +// BufferedReadChunkData representing the chunk read from buffered reader. +// Example: +// +// { +// "25": { +// "CommonReadLog": { +// "Handle": 25, +// "StartTimeSeconds": 1704444226, +// "StartTimeNanos": 937309952, +// "ProcessID": 2270282, +// "InodeID": 2, +// "BucketName": "bucket_name", +// "ObjectName": "object/name" +// }, +// "Chunks": [ +// { +// "StartTimeSeconds": 1704444226, +// "StartTimeNanos": 937457664, +// "RequestID": "310f589d-20bf", +// "Offset": 0, +// "Size": 26214, +// "BlockIndex": 0, +// "ExecutionTime": "1.907320375s" +// }, +// ... +// ] +// }, +// ... +// } +func ParseBufferedReadLogsFromLogReader(reader io.Reader) (map[int64]*BufferedReadLogEntry, error) { + // file-handle to BufferedReadLogEntry map + bufferedReadLogsMap := make(map[int64]*BufferedReadLogEntry) + + // opReverseMap is used to map request ID to handle and chunk index. + opReverseMap := make(map[string]*handleAndChunkIndex) + + lines, err := loadLogLines(reader) + if err != nil { + return nil, fmt.Errorf("failed to load log lines: %v", err) + } + + for _, line := range lines { + if err := filterAndParseLogLineForBufferedRead(line, bufferedReadLogsMap, opReverseMap); err != nil { + return nil, fmt.Errorf("filterAndParseLogLineForBufferedRead failed for %s: %v", line, err) + } + } + + return bufferedReadLogsMap, nil +} + +// filterAndParseLogLineForBufferedRead filters and parses a log line for buffered read logs. +func filterAndParseLogLineForBufferedRead( + logLine string, + bufferedReadLogsMap map[int64]*BufferedReadLogEntry, + opReverseMap map[string]*handleAndChunkIndex) error { + + jsonLog := make(map[string]interface{}) + if err := json.Unmarshal([]byte(logLine), &jsonLog); err != nil { + log.Printf("filterAndParseLogLineForBufferedRead: failed to unmarshal log line: %s, error: %v", logLine, err) + return nil // Silently ignore the bufferedReadLogsMap which are not in JSON format. + } + + if _, ok := jsonLog["timestamp"]; !ok { + return fmt.Errorf("filterAndParseLogLineForBufferedRead: log line does not contain timestamp: %s", logLine) + } + timestampSeconds := int64(jsonLog["timestamp"].(map[string]interface{})["seconds"].(float64)) + timestampNanos := int64(jsonLog["timestamp"].(map[string]interface{})["nanos"].(float64)) + + // Log message is expected to be in the "message" field. + if _, ok := jsonLog["message"]; !ok { + return fmt.Errorf("filterAndParseLogLineForBufferedRead: log line does not contain message: %s", logLine) + } + logMessage := jsonLog["message"].(string) + + // Parse the logs based on type. + switch { + case strings.Contains(logMessage, "<- ReadFile"): + if err := parseReadFileLogsUsingRegex(timestampSeconds, timestampNanos, logMessage, bufferedReadLogsMap); err != nil { + return fmt.Errorf("parseReadFileLog failed: %v", err) + } + case strings.Contains(logMessage, "<- ReadAt("): + if err := parseReadAtRequestLog(timestampSeconds, timestampNanos, logMessage, bufferedReadLogsMap, opReverseMap); err != nil { + return fmt.Errorf("parseReadAtLog failed: %v", err) + } + case strings.Contains(logMessage, "-> ReadAt("): + if err := parseReadAtResponseLog(logMessage, bufferedReadLogsMap, opReverseMap); err != nil { + return fmt.Errorf("parseReadAtResponseLog failed: %v", err) + } + } + return nil +} + +// parseReadFileLogsUsingRegex parses the ReadFile log using regex and updates the bufferedReadLogsMap map. +// It extracts the handle, PID, inode ID from the log message. +func parseReadFileLogsUsingRegex( + startTimeStampSec, startTimeStampNanos int64, + logMessage string, + bufferedReadLogsMap map[int64]*BufferedReadLogEntry) error { + + matches := readFileRegex.FindStringSubmatch(logMessage) + if len(matches) != 7 { + return fmt.Errorf("invalid ReadFile log format: %s", logMessage) + } + + handle, err := parseToInt64(matches[4]) + if err != nil { + return fmt.Errorf("invalid handle: %v", err) + } + pid, err := parseToInt64(matches[3]) + if err != nil { + return fmt.Errorf("invalid process ID: %v", err) + } + inodeID, err := parseToInt64(matches[2]) + if err != nil { + return fmt.Errorf("invalid inode ID: %v", err) + } + + // ReadFile log entries can come multiple times. + // Check if log entry exists in the map for file handle. + // If log entry doesn't exist, add it to the map. + _, ok := bufferedReadLogsMap[handle] + if !ok { + bufferedReadLogsMap[handle] = &BufferedReadLogEntry{ + CommonReadLog: CommonReadLog{ + Handle: handle, + StartTimeSeconds: startTimeStampSec, + StartTimeNanos: startTimeStampNanos, + ProcessID: pid, + InodeID: inodeID, + }, + Chunks: []BufferedReadChunkData{}, + } + } + return nil +} + +// parseReadAtRequestLog parses the ReadAt request log and updates the bufferedReadLogsMap map. +// It extracts the request ID, offset, size, and block index from the log message. +// It also populates the bucket and object name if they are not already set in the BufferedReadLogEntry. +func parseReadAtRequestLog( + startTimeStampSec, startTimeStampNanos int64, + logMessage string, + bufferedReadLogsMap map[int64]*BufferedReadLogEntry, + opReverseMap map[string]*handleAndChunkIndex) error { + + matches := readAtReqRegex.FindStringSubmatch(logMessage) + if len(matches) != 8 { + return fmt.Errorf("invalid ReadAt log format: %s", logMessage) + } + + handle, err := parseToInt64(matches[4]) // "1072693248" + if err != nil { + return fmt.Errorf("invalid handle: %v", err) + } + + logEntry, ok := bufferedReadLogsMap[handle] + if !ok || logEntry == nil { + return fmt.Errorf("BufferedReadLogEntry for handle %d not found", handle) + } + + if logEntry.BucketName == "" || logEntry.ObjectName == "" { + logEntry.BucketName = matches[2] // "bucket_name" + logEntry.ObjectName = matches[3] // "object/name" + } + + requestID := matches[1] // "37623d67-b6ee" + + offset, err := parseToInt64(matches[5]) // "0" + if err != nil { + return fmt.Errorf("invalid offset: %v", err) + } + size, err := parseToInt64(matches[6]) // "1048576" + if err != nil { + return fmt.Errorf("invalid size: %v", err) + } + blockIndex, err := parseToInt64(matches[7]) // "63" + if err != nil { + return fmt.Errorf("invalid block index: %v", err) + } + + chunkData := BufferedReadChunkData{ + StartTimeSeconds: startTimeStampSec, + StartTimeNanos: startTimeStampNanos, + RequestID: requestID, + Offset: offset, + Size: size, + BlockIndex: blockIndex, + ExecutionTime: "", // Execution time will be filled in the response log. + } + logEntry.Chunks = append(logEntry.Chunks, chunkData) + opReverseMap[requestID] = &handleAndChunkIndex{handle: handle, chunkIndex: len(logEntry.Chunks) - 1} + return nil +} + +// parseReadAtResponseLog parses the ReadAt response log and updates the bufferedReadLogsMap map. +// It extracts the request ID and execution time from the log message. +// It updates the corresponding chunk in the bufferedReadLogsMap map with the execution time. +// The request ID is looked up in the opReverseMap to find the corresponding handle and chunk index. +func parseReadAtResponseLog( + logMessage string, + bufferedReadLogsMap map[int64]*BufferedReadLogEntry, + opReverseMap map[string]*handleAndChunkIndex) error { + + matches := readAtSimpleRespRegex.FindStringSubmatch(logMessage) + if len(matches) != 3 { + return fmt.Errorf("invalid simple ReadAt response log format: %s", logMessage) + } + + requestID := matches[1] // "d88d347c-1b8c" + executionTime := matches[2] // "179.94µs" + + // Look up the request in the reverse map + handleAndChunk, exists := opReverseMap[requestID] + if !exists { + return fmt.Errorf("request ID %s not found in reverse map", requestID) + } + + // Update the execution time in the corresponding chunk + logEntry := bufferedReadLogsMap[handleAndChunk.handle] + if logEntry != nil && handleAndChunk.chunkIndex < len(logEntry.Chunks) { + logEntry.Chunks[handleAndChunk.chunkIndex].ExecutionTime = executionTime + } + + return nil +} diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser_test.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser_test.go new file mode 100644 index 0000000000..36da3a4ac1 --- /dev/null +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser_test.go @@ -0,0 +1,203 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package read_logs_test + +import ( + "bytes" + "fmt" + "io" + "strings" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseBufferedReadLogsFromLogReaderSuccessful(t *testing.T) { + setup.IgnoreTestIfIntegrationTestFlagIsSet(t) + + tests := []struct { + name string // Name of the test case + reader io.Reader + expected map[int64]*read_logs.BufferedReadLogEntry + }{ + { + name: "Test buffered read logs with 1 chunk", + reader: bytes.NewReader([]byte(`{"timestamp":{"seconds":1754207548,"nanos":733110719},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:453] <- ReadFile (inode 2, PID 564246, handle 0, offset 34603008, 1048576 bytes)"} +{"timestamp":{"seconds":1754207548,"nanos":733199657},"severity":"TRACE","message":"2e4645d9-19a8 <- ReadAt(princer-working-dirs:/10G_file, 0, 34603008, 1048576, 2)"} +{"timestamp":{"seconds":1754207548,"nanos":733417812},"severity":"TRACE","message":"2e4645d9-19a8 -> ReadAt(): Ok(223.643µs)"} +{"timestamp":{"seconds":1754207548,"nanos":733444394},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:548] -> ReadFile ()"}`), + ), + expected: map[int64]*read_logs.BufferedReadLogEntry{ + 0: { + CommonReadLog: read_logs.CommonReadLog{ + Handle: 0, + StartTimeSeconds: 1754207548, + StartTimeNanos: 733110719, + ProcessID: 564246, + InodeID: 2, + BucketName: "princer-working-dirs", + ObjectName: "10G_file", + }, + Chunks: []read_logs.BufferedReadChunkData{ + { + StartTimeSeconds: 1754207548, + StartTimeNanos: 733199657, + RequestID: "2e4645d9-19a8", + Offset: 34603008, + Size: 1048576, + BlockIndex: 2, + ExecutionTime: "223.643µs", + }, + }, + }, + }, + }, + { + name: "Test buffered read logs with multiple chunks", + reader: bytes.NewReader([]byte(`{"timestamp":{"seconds":1754207548,"nanos":733110719},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:453] <- ReadFile (inode 2, PID 564246, handle 0, offset 34603008, 1048576 bytes)"} +{"timestamp":{"seconds":1754207548,"nanos":733199657},"severity":"TRACE","message":"2e4645d9-19a8 <- ReadAt(princer-working-dirs:/10G_file, 0, 34603008, 1048576, 2)"} +{"timestamp":{"seconds":1754207548,"nanos":733417812},"severity":"TRACE","message":"2e4645d9-19a8 -> ReadAt(): Ok(223.643µs)"} +{"timestamp":{"seconds":1754207548,"nanos":733444394},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:548] -> ReadFile ()"} +{"timestamp":{"seconds":1754207548,"nanos":733776221},"severity":"TRACE","message":"fuse_debug: Op 0x00000050 connection.go:453] <- ReadFile (inode 2, PID 564246, handle 0, offset 35651584, 1048576 bytes)"} +{"timestamp":{"seconds":1754207548,"nanos":733853084},"severity":"TRACE","message":"a8517095-54c0 <- ReadAt(princer-working-dirs:/10G_file, 0, 35651584, 1048576, 2)"} +{"timestamp":{"seconds":1754207548,"nanos":734027808},"severity":"TRACE","message":"a8517095-54c0 -> ReadAt(): Ok(173.476µs)"} +{"timestamp":{"seconds":1754207548,"nanos":734048914},"severity":"TRACE","message":"fuse_debug: Op 0x00000050 connection.go:548] -> ReadFile ()"} +{"timestamp":{"seconds":1754207548,"nanos":734299231},"severity":"TRACE","message":"fuse_debug: Op 0x00000052 connection.go:453] <- ReadFile (inode 2, PID 564246, handle 0, offset 36700160, 1048576 bytes)"} +{"timestamp":{"seconds":1754207548,"nanos":734358133},"severity":"TRACE","message":"4e8b1c9c-0012 <- ReadAt(princer-working-dirs:/10G_file, 0, 36700160, 1048576, 2)"} +{"timestamp":{"seconds":1754207548,"nanos":734532341},"severity":"TRACE","message":"4e8b1c9c-0012 -> ReadAt(): Ok(179.8µs)"}`), + ), + expected: map[int64]*read_logs.BufferedReadLogEntry{ + 0: { + CommonReadLog: read_logs.CommonReadLog{ + Handle: 0, + StartTimeSeconds: 1754207548, + StartTimeNanos: 733110719, + ProcessID: 564246, + InodeID: 2, + BucketName: "princer-working-dirs", + ObjectName: "10G_file", + }, + Chunks: []read_logs.BufferedReadChunkData{ + { + StartTimeSeconds: 1754207548, + StartTimeNanos: 733199657, + RequestID: "2e4645d9-19a8", + Offset: 34603008, + Size: 1048576, + BlockIndex: 2, + ExecutionTime: "223.643µs", + }, + { + StartTimeSeconds: 1754207548, + StartTimeNanos: 733853084, + RequestID: "a8517095-54c0", + Offset: 35651584, + Size: 1048576, + BlockIndex: 2, + ExecutionTime: "173.476µs", + }, + { + StartTimeSeconds: 1754207548, + StartTimeNanos: 734358133, + RequestID: "4e8b1c9c-0012", + Offset: 36700160, + Size: 1048576, + BlockIndex: 2, + ExecutionTime: "179.8µs", + }, + }, + }, + }, + }, + { + name: "Test buffered read logs with no parsable logs", + reader: bytes.NewReader([]byte(`{"timestamp":{"seconds":1754207548,"nanos":742190759},"severity":"TRACE","message":"Scheduling block: (10G_file, 9, false)."} +{"timestamp":{"seconds":1754207548,"nanos":742296187},"severity":"TRACE","message":"Scheduling block: (10G_file, 10, false)."} +{"timestamp":{"seconds":1754207548,"nanos":742300356},"severity":"TRACE","message":"Download: <- block (10G_file, 9)."} +{"timestamp":{"seconds":1754207548,"nanos":742315339},"severity":"TRACE","message":"Scheduling block: (10G_file, 11, false)."} +{"timestamp":{"seconds":1754207548,"nanos":742323114},"severity":"TRACE","message":"Scheduling block: (10G_file, 12, false)."}`), + ), + expected: make(map[int64]*read_logs.BufferedReadLogEntry), + }, + { + name: "Test buffered read logs with no JSON logs", + reader: bytes.NewReader([]byte(`hello 123`)), + expected: make(map[int64]*read_logs.BufferedReadLogEntry), + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + actual, err := read_logs.ParseBufferedReadLogsFromLogReader(tc.reader) + + require.NoError(t, err) + assert.Equal(t, tc.expected, actual, fmt.Sprintf("Expected: %v, Actual: %v", tc.expected, actual)) + }) + } +} + +func TestBufferedReadLogsFromLogReaderUnsuccessful(t *testing.T) { + setup.IgnoreTestIfIntegrationTestFlagIsSet(t) + + tests := []struct { + name string // Name of the test case + reader io.Reader + errorString string + }{ + { + name: "Test buffered read logs without Read File log", + reader: bytes.NewReader([]byte(`{"timestamp":{"seconds":1754207548,"nanos":733199657},"severity":"TRACE","message":"2e4645d9-19a8 <- ReadAt(princer-working-dirs:/10G_file, 0, 34603008, 1048576, 2)"} +{"timestamp":{"seconds":1754207548,"nanos":733417812},"severity":"TRACE","message":"2e4645d9-19a8 -> ReadAt(): Ok(223.643µs)"} +{"timestamp":{"seconds":1754207548,"nanos":733444394},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:548] -> ReadFile ()"}`), + ), + errorString: "BufferedReadLogEntry for handle 0 not found", + }, + { + name: "Test buffered read logs response log without request log", + reader: bytes.NewReader([]byte(`{"timestamp":{"seconds":1754207548,"nanos":733110719},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:453] <- ReadFile (inode 2, PID 564246, handle 0, offset 34603008, 1048576 bytes)"} +{"timestamp":{"seconds":1754207548,"nanos":733417812},"severity":"TRACE","message":"2e4645d9-19a8 -> ReadAt(): Ok(223.643µs)"} +{"timestamp":{"seconds":1754207548,"nanos":733444394},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:548] -> ReadFile ()"}`), + ), + errorString: "request ID 2e4645d9-19a8 not found in reverse map", + }, + { + name: "Test invalid read file log - invalid Inode ID", + reader: bytes.NewReader([]byte(`{"timestamp":{"seconds":1754207548,"nanos":733110719},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:453] <- ReadFile (inode abc, PID 564246, handle 0, offset 34603008, 1048576 bytes)"}`)), + errorString: "parseReadFileLog failed: invalid ReadFile log", + }, + { + name: "Test invalid read file log - invalid PID", + reader: bytes.NewReader([]byte(`{"timestamp": {"seconds": 1704458059, "nanos": 975956234}, "severity": "TRACE", "message": "fuse_debug: Op 0x00000182 connection.go:415] <- ReadFile (inode 6, PID abc, handle 29, offset 0, 4096 bytes)"}`)), + errorString: "parseReadFileLog failed: invalid ReadFile log format", + }, + { + name: "Test invalid read file log - invalid Handle", + reader: bytes.NewReader([]byte(`{"timestamp": {"seconds": 1704458059, "nanos": 975956234}, "severity": "TRACE", "message": "fuse_debug: Op 0x00000182 connection.go:415] <- ReadFile (inode 6, PID 2382526, handle abc, offset 0, 4096 bytes)"}`)), + errorString: "parseReadFileLog failed: invalid ReadFile log format", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + _, err := read_logs.ParseBufferedReadLogsFromLogReader(tc.reader) + + require.Error(t, err) + assert.True(t, strings.Contains(err.Error(), tc.errorString), fmt.Sprintf("Unexpected error: %s", err)) + }) + } +} diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/helpers.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/helpers.go index bbb8b6aaed..31f2b5f51c 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/helpers.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/helpers.go @@ -64,12 +64,14 @@ func parseReadFileLog(startTimeStampSec, startTimeStampNanos int64, logs []strin _, ok := structuredLogs[handle] if !ok { structuredLogs[handle] = &StructuredReadLogEntry{ - Handle: handle, - StartTimeSeconds: startTimeStampSec, - StartTimeNanos: startTimeStampNanos, - ProcessID: pid, - InodeID: inodeID, - Chunks: []ReadChunkData{}, + CommonReadLog: CommonReadLog{ + Handle: handle, + StartTimeSeconds: startTimeStampSec, + StartTimeNanos: startTimeStampNanos, + ProcessID: pid, + InodeID: inodeID, + }, + Chunks: []ReadChunkData{}, } } return nil diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go index f3f34ebe74..ade081d129 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/json_read_log_parser_test.go @@ -75,13 +75,15 @@ func TestParseLogFileSuccessful(t *testing.T) { ), expected: map[int64]*read_logs.StructuredReadLogEntry{ handleId: { - Handle: handleId, - StartTimeSeconds: readTimestampSeconds, - StartTimeNanos: readTimestampNanos, - ProcessID: pid, - InodeID: inodeId, - BucketName: bucketName, - ObjectName: fileName, + CommonReadLog: read_logs.CommonReadLog{ + Handle: handleId, + StartTimeSeconds: readTimestampSeconds, + StartTimeNanos: readTimestampNanos, + ProcessID: pid, + InodeID: inodeId, + BucketName: bucketName, + ObjectName: fileName, + }, Chunks: []read_logs.ReadChunkData{ chunkData, }, @@ -105,13 +107,15 @@ func TestParseLogFileSuccessful(t *testing.T) { ), expected: map[int64]*read_logs.StructuredReadLogEntry{ 29: { - Handle: 29, - StartTimeSeconds: readTimestampSeconds, - StartTimeNanos: readTimestampNanos, - ProcessID: pid, - InodeID: inodeId, - BucketName: bucketName, - ObjectName: fileName, + CommonReadLog: read_logs.CommonReadLog{ + Handle: 29, + StartTimeSeconds: readTimestampSeconds, + StartTimeNanos: readTimestampNanos, + ProcessID: pid, + InodeID: inodeId, + BucketName: bucketName, + ObjectName: fileName, + }, Chunks: []read_logs.ReadChunkData{ chunkData, chunkData, chunkData, }, diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go index 373bf348a5..6726887c9d 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go @@ -18,8 +18,7 @@ import ( "time" ) -// StructuredReadLogEntry stores the structured format to be created from logs. -type StructuredReadLogEntry struct { +type CommonReadLog struct { Handle int64 StartTimeSeconds int64 StartTimeNanos int64 @@ -27,6 +26,12 @@ type StructuredReadLogEntry struct { InodeID int64 BucketName string ObjectName string +} + +// StructuredReadLogEntry stores the structured format to be created from logs. +type StructuredReadLogEntry struct { + CommonReadLog + // It can be safely assumed that the Chunks will be sorted on timestamp as logs // are parsed in the order of timestamps. Chunks []ReadChunkData @@ -74,3 +79,19 @@ type LogEntry struct { Timestamp time.Time `json:"time"` Message string `json:"message"` } + +type BufferedReadLogEntry struct { + CommonReadLog + + Chunks []BufferedReadChunkData +} + +type BufferedReadChunkData struct { + StartTimeSeconds int64 + StartTimeNanos int64 + RequestID string + Offset int64 + Size int64 + BlockIndex int64 + ExecutionTime string +} From 38d7db4b662cedfcfccbfb436fa354bafb8cad3f Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Thu, 7 Aug 2025 21:17:59 +0530 Subject: [PATCH 0661/1298] ci: improve debugging logging for e2e debug logs (#3609) * print specific filename in delete error * save gcsfuse logfile to GCS in case benchmarking test fails * more useful debug logs --- .../benchmarking/benchmark_delete_test.go | 41 ++++++++++++++++--- .../benchmarking/benchmark_rename_test.go | 40 +++++++++++++++--- .../benchmarking/benchmark_stat_test.go | 41 ++++++++++++++++--- .../benchmarking/setup_test.go | 5 ++- 4 files changed, 108 insertions(+), 19 deletions(-) diff --git a/tools/integration_tests/benchmarking/benchmark_delete_test.go b/tools/integration_tests/benchmarking/benchmark_delete_test.go index 5ad1a2bdfd..b8c92ff6bb 100644 --- a/tools/integration_tests/benchmarking/benchmark_delete_test.go +++ b/tools/integration_tests/benchmarking/benchmark_delete_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -48,15 +48,44 @@ func (s *benchmarkDeleteTest) TeardownB(b *testing.B) { func (s *benchmarkDeleteTest) Benchmark_Delete(b *testing.B) { createFiles(b) + var maxElapsedDuration time.Duration + maxElapsedIteration := -1 b.ResetTimer() - for i := 0; i < b.N; i++ { - if err := os.Remove(path.Join(testDirPath, fmt.Sprintf("a%d.txt", i))); err != nil { - b.Errorf("testing error: %v", err) + // Don't start the timer yet. + b.StopTimer() + + for i := range b.N { + filePath := path.Join(testDirPath, fmt.Sprintf("a%d.txt", i)) + + // Manually time the operation to find the maximum latency with highest accuracy. + // This happens while the benchmark's timer is paused and will not affect the average. + startTime := time.Now() + + // Start the benchmark timer just for the os.Remove call. + b.StartTimer() + err := os.Remove(filePath) + b.StopTimer() // Stop the timer immediately after the operation. + + timeElapsedThisIter := time.Since(startTime) + + // The remaining checks and calculations also happen while the timer is paused. + if err != nil { + b.Errorf("error while deleting %q: %v", filePath, err) + } + + if maxElapsedDuration < timeElapsedThisIter { + maxElapsedDuration = timeElapsedThisIter + maxElapsedIteration = i } } - averageDeleteLatency := time.Duration(int(b.Elapsed()) / b.N) + + // b.Elapsed() is the sum of the time spent only on os.Remove calls, + // leading to a highly accurate average latency. + averageDeleteLatency := b.Elapsed() / time.Duration(b.N) + if averageDeleteLatency > expectedDeleteLatency { - b.Errorf("DeleteFile took more time (%d msec) than expected (%d msec)", averageDeleteLatency.Milliseconds(), expectedDeleteLatency.Milliseconds()) + b.Errorf("DeleteFile took more time on average (%v) than expected (%v).", averageDeleteLatency, expectedDeleteLatency) + b.Errorf("Maximum time taken by a single iteration = %v, in iteration # %v.", maxElapsedDuration, maxElapsedIteration) } } diff --git a/tools/integration_tests/benchmarking/benchmark_rename_test.go b/tools/integration_tests/benchmarking/benchmark_rename_test.go index 2a7764b2f8..891a8dc996 100644 --- a/tools/integration_tests/benchmarking/benchmark_rename_test.go +++ b/tools/integration_tests/benchmarking/benchmark_rename_test.go @@ -48,15 +48,45 @@ func (s *benchmarkRenameTest) TeardownB(b *testing.B) { func (s *benchmarkRenameTest) Benchmark_Rename(b *testing.B) { createFiles(b) + var maxElapsedDuration time.Duration + maxElapsedIteration := -1 b.ResetTimer() - for i := 0; i < b.N; i++ { - if err := os.Rename(path.Join(testDirPath, fmt.Sprintf("a%d.txt", i)), path.Join(testDirPath, fmt.Sprintf("b%d.txt", i))); err != nil { - b.Errorf("testing error: %v", err) + // Don't start the timer yet. + b.StopTimer() + + for i := range b.N { + sourceFilePath := path.Join(testDirPath, fmt.Sprintf("a%d.txt", i)) + dstFilePath := path.Join(testDirPath, fmt.Sprintf("b%d.txt", i)) + + // Manually time the operation to find the maximum latency with highest accuracy. + // This happens while the benchmark's timer is paused and will not affect the average. + startTime := time.Now() + + // Start the benchmark timer just for the os.Rename call. + b.StartTimer() + err := os.Rename(sourceFilePath, dstFilePath) + b.StopTimer() // Stop the timer immediately after the operation. + + timeElapsedThisIter := time.Since(startTime) + + // The remaining checks and calculations also happen while the timer is paused. + if err != nil { + b.Errorf("failed to rename %q to %q: %v", sourceFilePath, dstFilePath, err) + } + + if maxElapsedDuration < timeElapsedThisIter { + maxElapsedDuration = timeElapsedThisIter + maxElapsedIteration = i } } - averageRenameLatency := time.Duration(int(b.Elapsed()) / b.N) + + // b.Elapsed() is the sum of the time spent only on os.Rename calls, + // leading to a highly accurate average latency. + averageRenameLatency := b.Elapsed() / time.Duration(b.N) + if averageRenameLatency > expectedRenameLatency { - b.Errorf("RenameFile took more time (%d msec) than expected (%d msec)", averageRenameLatency.Milliseconds(), expectedRenameLatency.Milliseconds()) + b.Errorf("RenameFile took more time on average (%v) than expected %v", averageRenameLatency, expectedRenameLatency) + b.Errorf("Maximum time taken by a single iteration = %v, in iteration # %v.", maxElapsedDuration, maxElapsedIteration) } } diff --git a/tools/integration_tests/benchmarking/benchmark_stat_test.go b/tools/integration_tests/benchmarking/benchmark_stat_test.go index c8341cc320..264a7cb742 100644 --- a/tools/integration_tests/benchmarking/benchmark_stat_test.go +++ b/tools/integration_tests/benchmarking/benchmark_stat_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -57,15 +57,44 @@ func createFilesToStat(b *testing.B) { func (s *benchmarkStatTest) Benchmark_Stat(b *testing.B) { createFilesToStat(b) + var maxElapsedDuration time.Duration + maxElapsedIteration := -1 b.ResetTimer() - for i := 0; i < b.N; i++ { - if _, err := operations.StatFile(path.Join(testDirPath, "a.txt")); err != nil { - b.Errorf("testing error: %v", err) + // Don't start the timer yet. + b.StopTimer() + + filePath := path.Join(testDirPath, "a.txt") + + for i := range b.N { + // Manually time the operation to find the maximum latency with highest accuracy. + // This happens while the benchmark's timer is paused and will not affect the average. + startTime := time.Now() + + // Start the benchmark timer just for the operations.StatFile call. + b.StartTimer() + _, err := operations.StatFile(filePath) + b.StopTimer() // Stop the timer immediately after the operation. + + timeElapsedThisIter := time.Since(startTime) + + // The remaining checks and calculations also happen while the timer is paused. + if err != nil { + b.Errorf("failed to stat %q: %v", filePath, err) + } + + if maxElapsedDuration < timeElapsedThisIter { + maxElapsedDuration = timeElapsedThisIter + maxElapsedIteration = i } } - averageStatLatency := time.Duration(int(b.Elapsed()) / b.N) + + // b.Elapsed() is the sum of the time spent only on stat calls, + // leading to a highly accurate average latency. + averageStatLatency := b.Elapsed() / time.Duration(b.N) + if averageStatLatency > expectedStatLatency { - b.Errorf("StatFile took more time (%d msec) than expected (%d msec)", averageStatLatency.Milliseconds(), expectedStatLatency.Milliseconds()) + b.Errorf("StatFile took more time on average (%v) than expected (%v)", averageStatLatency, expectedStatLatency) + b.Errorf("Maximum time taken by a single iteration = %v, in iteration # %v.", maxElapsedDuration, maxElapsedIteration) } } diff --git a/tools/integration_tests/benchmarking/setup_test.go b/tools/integration_tests/benchmarking/setup_test.go index f194ca3916..5de9490057 100644 --- a/tools/integration_tests/benchmarking/setup_test.go +++ b/tools/integration_tests/benchmarking/setup_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -65,7 +65,7 @@ func mountGCSFuseAndSetupTestDir(flags []string, testDirName string) { // createFiles creates the below objects in the bucket. // benchmarking/a{i}.txt where i is a counter based on the benchtime value. func createFiles(b *testing.B) { - for i := 0; i < b.N; i++ { + for i := range b.N { operations.CreateFileOfSize(1, path.Join(testDirPath, fmt.Sprintf("a%d.txt", i)), b) } } @@ -96,6 +96,7 @@ func TestMain(m *testing.M) { log.Println("Running static mounting tests...") mountFunc = static_mounting.MountGcsfuseWithStaticMounting successCode := m.Run() + setup.SaveLogFileInCaseOfFailure(successCode) // Clean up test directory created. setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) From ac1df10fdfb7790b11836b2eb828645507dd2e90 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Fri, 8 Aug 2025 09:56:41 +0530 Subject: [PATCH 0662/1298] chore(metrics): sampled logging for undeclared metric attributes (#3651) * sampled logging for undeclared attribute * Attempt to fix format-test * Update optimizedmetrics/otel_metrics.go Co-authored-by: Kislay Kishore * Update optimizedmetrics/otel_metrics.go Co-authored-by: Kislay Kishore * Update tools/metrics-gen/otel_metrics.tpl Co-authored-by: Kislay Kishore * avoid dropping logs * use swap to atomically read-and-reset --------- Co-authored-by: Kislay Kishore --- optimizedmetrics/otel_metrics.go | 94 ++++++++++++++++++++++++++++++ tools/metrics-gen/main.go | 8 +++ tools/metrics-gen/otel_metrics.tpl | 38 ++++++++++++ 3 files changed, 140 insertions(+) diff --git a/optimizedmetrics/otel_metrics.go b/optimizedmetrics/otel_metrics.go index c61cdd29e2..cdfa6905b6 100644 --- a/optimizedmetrics/otel_metrics.go +++ b/optimizedmetrics/otel_metrics.go @@ -23,12 +23,16 @@ import ( "sync/atomic" "time" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) +const logInterval = 5 * time.Minute + var ( + unrecognizedAttr atomic.Value fileCacheReadBytesCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) fileCacheReadBytesCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) fileCacheReadBytesCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) @@ -1191,6 +1195,8 @@ func (o *otelMetrics) FileCacheReadBytesCount( o.fileCacheReadBytesCountReadTypeRandomAtomic.Add(inc) case "Sequential": o.fileCacheReadBytesCountReadTypeSequentialAtomic.Add(inc) + default: + updateUnrecognizedAttribute(readType) } } @@ -1206,6 +1212,8 @@ func (o *otelMetrics) FileCacheReadCount( o.fileCacheReadCountCacheHitTrueReadTypeRandomAtomic.Add(inc) case "Sequential": o.fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic.Add(inc) + default: + updateUnrecognizedAttribute(readType) } case false: switch readType { @@ -1215,6 +1223,8 @@ func (o *otelMetrics) FileCacheReadCount( o.fileCacheReadCountCacheHitFalseReadTypeRandomAtomic.Add(inc) case "Sequential": o.fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic.Add(inc) + default: + updateUnrecognizedAttribute(readType) } } @@ -1299,6 +1309,8 @@ func (o *otelMetrics) FsOpsCount( o.fsOpsCountFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsCountFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } } @@ -1368,6 +1380,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "DIR_NOT_EMPTY": switch fsOp { @@ -1431,6 +1445,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "FILE_DIR_ERROR": switch fsOp { @@ -1494,6 +1510,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "FILE_EXISTS": switch fsOp { @@ -1557,6 +1575,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "INTERRUPT_ERROR": switch fsOp { @@ -1620,6 +1640,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "INVALID_ARGUMENT": switch fsOp { @@ -1683,6 +1705,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "INVALID_OPERATION": switch fsOp { @@ -1746,6 +1770,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "IO_ERROR": switch fsOp { @@ -1809,6 +1835,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "MISC_ERROR": switch fsOp { @@ -1872,6 +1900,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "NETWORK_ERROR": switch fsOp { @@ -1935,6 +1965,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "NOT_A_DIR": switch fsOp { @@ -1998,6 +2030,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "NOT_IMPLEMENTED": switch fsOp { @@ -2061,6 +2095,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "NO_FILE_OR_DIR": switch fsOp { @@ -2124,6 +2160,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "PERM_ERROR": switch fsOp { @@ -2187,6 +2225,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "PROCESS_RESOURCE_MGMT_ERROR": switch fsOp { @@ -2250,6 +2290,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } case "TOO_MANY_OPEN_FILES": switch fsOp { @@ -2313,7 +2355,11 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic.Add(inc) case "WriteFile": o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) } + default: + updateUnrecognizedAttribute(fsErrorCategory) } } @@ -2382,6 +2428,8 @@ func (o *otelMetrics) FsOpsLatency( record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpUnlinkAttrSet} case "WriteFile": record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpWriteFileAttrSet} + default: + updateUnrecognizedAttribute(fsOp) } select { @@ -2399,6 +2447,8 @@ func (o *otelMetrics) GcsDownloadBytesCount( o.gcsDownloadBytesCountReadTypeRandomAtomic.Add(inc) case "Sequential": o.gcsDownloadBytesCountReadTypeSequentialAtomic.Add(inc) + default: + updateUnrecognizedAttribute(readType) } } @@ -2418,6 +2468,8 @@ func (o *otelMetrics) GcsReadCount( o.gcsReadCountReadTypeRandomAtomic.Add(inc) case "Sequential": o.gcsReadCountReadTypeSequentialAtomic.Add(inc) + default: + updateUnrecognizedAttribute(readType) } } @@ -2429,6 +2481,8 @@ func (o *otelMetrics) GcsReaderCount( o.gcsReaderCountIoMethodClosedAtomic.Add(inc) case "opened": o.gcsReaderCountIoMethodOpenedAtomic.Add(inc) + default: + updateUnrecognizedAttribute(ioMethod) } } @@ -2466,6 +2520,8 @@ func (o *otelMetrics) GcsRequestCount( o.gcsRequestCountGcsMethodStatObjectAtomic.Add(inc) case "UpdateObject": o.gcsRequestCountGcsMethodUpdateObjectAtomic.Add(inc) + default: + updateUnrecognizedAttribute(gcsMethod) } } @@ -2504,6 +2560,8 @@ func (o *otelMetrics) GcsRequestLatencies( record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodStatObjectAttrSet} case "UpdateObject": record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodUpdateObjectAttrSet} + default: + updateUnrecognizedAttribute(gcsMethod) } select { @@ -2519,6 +2577,8 @@ func (o *otelMetrics) GcsRetryCount( o.gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic.Add(inc) case "STALLED_READ_REQUEST": o.gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic.Add(inc) + default: + updateUnrecognizedAttribute(retryErrorCategory) } } @@ -2526,6 +2586,7 @@ func (o *otelMetrics) GcsRetryCount( func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetrics, error) { ch := make(chan histogramRecord, bufferSize) var wg sync.WaitGroup + startSampledLogging(ctx) for range workers { wg.Add(1) go func() { @@ -4290,3 +4351,36 @@ func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, obs obsrv.Observe(val, obsrvOptions...) } } + +func updateUnrecognizedAttribute(newValue string) { + unrecognizedAttr.CompareAndSwap("", newValue) +} + +// StartSampledLogging starts a goroutine that logs unrecognized attributes periodically. +func startSampledLogging(ctx context.Context) { + // Init the atomic.Value + unrecognizedAttr.Store("") + + go func() { + ticker := time.NewTicker(logInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + logUnrecognizedAttribute() + } + } + }() +} + +// logUnrecognizedAttribute retrieves and logs any unrecognized attributes. +func logUnrecognizedAttribute() { + // Atomically load and reset the attribute name, then generate a log + // if an unrecognized attribute was encountered. + if currentAttr := unrecognizedAttr.Swap("").(string); currentAttr != "" { + logger.Tracef("Attribute %s is not declared", currentAttr) + } +} diff --git a/tools/metrics-gen/main.go b/tools/metrics-gen/main.go index c8c9a5b598..3ba577a4b9 100644 --- a/tools/metrics-gen/main.go +++ b/tools/metrics-gen/main.go @@ -250,6 +250,11 @@ func generateCombinations(attributes []Attribute) []AttrCombination { return result } +func handleDefaultInSwitchCase(level int, attrName string, builder *strings.Builder) { + builder.WriteString(fmt.Sprintf("%sdefault:\n", strings.Repeat("\t", level+2))) + builder.WriteString(fmt.Sprintf("%supdateUnrecognizedAttribute(%s)\n", strings.Repeat("\t", level+3), toCamel(attrName))) +} + func validateMetric(m Metric) error { if m.Name == "" { return fmt.Errorf("metric-name is required") @@ -381,6 +386,9 @@ func buildSwitches(metric Metric) string { currentCombo := append(combo, AttrValuePair{Name: attr.Name, Type: attr.Type, Value: val}) recorder(level+1, currentCombo) } + if attr.Type == "string" { + handleDefaultInSwitchCase(level, attr.Name, &builder) + } builder.WriteString(fmt.Sprintf("%s}\n", indent)) } diff --git a/tools/metrics-gen/otel_metrics.tpl b/tools/metrics-gen/otel_metrics.tpl index f24eb39106..ca94213224 100644 --- a/tools/metrics-gen/otel_metrics.tpl +++ b/tools/metrics-gen/otel_metrics.tpl @@ -23,12 +23,16 @@ import ( "sync/atomic" "time" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) +const logInterval = 5 * time.Minute + var ( + unrecognizedAttr atomic.Value {{- range $metric := .Metrics -}} {{- if .Attributes}} {{- range $combination := (index $.AttrCombinations $metric.Name)}} @@ -93,6 +97,7 @@ func (o *otelMetrics) {{toPascal .Name}}( func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetrics, error) { ch := make(chan histogramRecord, bufferSize) var wg sync.WaitGroup + startSampledLogging(ctx) for range workers { wg.Add(1) go func() { @@ -168,3 +173,36 @@ func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, obs obsrv.Observe(val, obsrvOptions...) } } + +func updateUnrecognizedAttribute(newValue string) { + unrecognizedAttr.CompareAndSwap("", newValue) +} + +// StartSampledLogging starts a goroutine that logs unrecognized attributes periodically. +func startSampledLogging(ctx context.Context) { + // Init the atomic.Value + unrecognizedAttr.Store("") + + go func() { + ticker := time.NewTicker(logInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + logUnrecognizedAttribute() + } + } + }() +} + +// logUnrecognizedAttribute retrieves and logs any unrecognized attributes. +func logUnrecognizedAttribute() { + // Atomically load and reset the attribute name, then generate a log + // if an unrecognized attribute was encountered. + if currentAttr := unrecognizedAttr.Swap("").(string); currentAttr != "" { + logger.Tracef("Attribute %s is not declared", currentAttr) + } +} From bef76bf1ad17da37da970093acabee7d483bf849 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 8 Aug 2025 10:22:02 +0530 Subject: [PATCH 0663/1298] ci: Skip not-applicable-test for zonal/rapid bucket (#3644) * skip not-applicable-test for ZB * empty commit --- .../implicit_dir/local_file_test.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tools/integration_tests/implicit_dir/local_file_test.go b/tools/integration_tests/implicit_dir/local_file_test.go index 1056f59998..119597b8d6 100644 --- a/tools/integration_tests/implicit_dir/local_file_test.go +++ b/tools/integration_tests/implicit_dir/local_file_test.go @@ -22,6 +22,7 @@ import ( . "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/require" ) const ( @@ -40,7 +41,19 @@ func TestNewFileUnderImplicitDirectoryShouldNotGetSyncedToGCSTillClose(t *testin _, fh := CreateLocalFileInTestDir(testEnv.ctx, testEnv.storageClient, testEnv.testDirPath, fileName, t) operations.WriteWithoutClose(fh, FileContents, t) - ValidateObjectNotFoundErrOnGCS(testEnv.ctx, testEnv.storageClient, testBaseDirName, fileName, t) + if !setup.IsZonalBucketRun() { + // For non-zonal buckets, the object is not visible until the file is closed. + ValidateObjectNotFoundErrOnGCS(testEnv.ctx, testEnv.storageClient, testBaseDirName, fileName, t) + } else { + // For zonal buckets, the object is unfinalized, but visible. + // A zonal bucket object written without sync would be recognized as having zero-size. + ValidateObjectContentsFromGCS(testEnv.ctx, testEnv.storageClient, testBaseDirName, fileName, "", t) + + // A zonal bucket object written with sync can be fully read. + err := fh.Sync() + require.NoError(t, err) + ValidateObjectContentsFromGCS(testEnv.ctx, testEnv.storageClient, testBaseDirName, fileName, FileContents, t) + } // Validate. CloseFileAndValidateContentFromGCS(testEnv.ctx, testEnv.storageClient, fh, testBaseDirName, fileName, FileContents, t) From 7de3ac962c72113388784309e927783aa9a43fab Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 8 Aug 2025 10:44:54 +0530 Subject: [PATCH 0664/1298] test: Add wget installation in the beginning of e2e script for installation of gcloud (#3655) * Add wget installation in e2e script * Autheticated using gh auth login * Add wget installation in starterscript --- tools/cd_scripts/e2e_test.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index cc58a038aa..6c079f794f 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -18,6 +18,17 @@ set -x # Exit immediately if a command exits with a non-zero status. set -e +# Install wget +if command -v apt-get &> /dev/null; then + # For Debian/Ubuntu-based systems + sudo apt-get update && sudo apt-get install -y wget +elif command -v yum &> /dev/null; then + # For RHEL/CentOS-based systems + sudo yum install -y wget +else + exit 1 +fi + # Upgrade gcloud echo "Upgrade gcloud version" gcloud version From 06383cc3d8a2417bdd8b2a269e81b70010c6e671 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 8 Aug 2025 11:49:59 +0530 Subject: [PATCH 0665/1298] Add information regarding ESTALE error in troubleshooting guide (#3654) --- docs/troubleshooting.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 021e5744b9..f19cae8786 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -31,6 +31,12 @@ Run ```gcloud auth application-default login``` command to fetch default credent It’s a generic error, but the most probable culprit is the bucket not having the right permission for Cloud Storage FUSE to operate on. Ref - [here](https://stackoverflow.com/questions/36382704/gcsfuse-input-output-error) +### Stale File Handle Error (ESTALE) + +This error occurs when GCSFuse detects that a file has been modified or deleted on GCS by another process or mount since it was opened. This is a data integrity feature that prioritizes providing clear indications of potential conflicts, preventing silent data loss. + +To avoid this, it's best to prevent multiple sources from modifying the same object simultaneously. For a detailed explanation of the scenarios that can cause this error, please refer to the [Stale File Handle Errors](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/semantics.md#stale-file-handle-errors) section in our semantics documentation. + ### Generic NO_PUBKEY Error - while installing Cloud Storage FUSE on ubuntu 22.04 It happens while running - ```sudo apt-get update``` - working on installing Cloud Storage FUSE. You just have to add the pubkey you get in the error using the below command: ```sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys ``` And then try running ```sudo apt-get update``` From 64b24c7c7838a2a1a091eb7a64863698e5793ebd Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Fri, 8 Aug 2025 13:40:15 +0530 Subject: [PATCH 0666/1298] refactor(metrics): use optimized metrics implementation (#3638) * delete older metrics implm * fix method invocation * unified method for updating read metrics * use helper function to update gcs read metrics Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * create non-gen helper method to capture read metrics * update fakemetrics method * remove unnecessary ctx from readercount method * auto-gen updates --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- cmd/legacy_main.go | 2 +- internal/cache/file/downloader/job.go | 2 +- .../file/downloader/parallel_downloads_job.go | 2 +- internal/fs/wrappers/monitoring.go | 9 +- internal/gcsx/client_readers/range_reader.go | 4 +- internal/gcsx/file_cache_reader.go | 10 +- internal/gcsx/random_reader.go | 4 +- internal/monitor/bucket.go | 16 +- internal/storage/storageutil/custom_retry.go | 2 +- .../storage/storageutil/custom_retry_test.go | 2 +- main.go | 2 +- metrics/helper.go | 22 + .../metric_handle.go | 2 +- {optimizedmetrics => metrics}/metrics.yaml | 0 metrics/noop_metrics.go | 51 +- metrics/otel_metrics.go | 4462 +++++++++++- metrics/otel_metrics_test.go | 5960 +++++++++++++++-- metrics/telemetry.go | 80 - metrics/telemetry_test.go | 69 - optimizedmetrics/noop_metrics.go | 56 - optimizedmetrics/otel_metrics.go | 4386 ------------ optimizedmetrics/otel_metrics_test.go | 5741 ---------------- tools/metrics-gen/metric_handle.tpl | 2 +- tools/metrics-gen/noop_metrics.tpl | 2 +- tools/metrics-gen/otel_metrics.tpl | 2 +- tools/metrics-gen/otel_metrics_test.tpl | 2 +- 26 files changed, 9795 insertions(+), 11097 deletions(-) create mode 100644 metrics/helper.go rename {optimizedmetrics => metrics}/metric_handle.go (99%) rename {optimizedmetrics => metrics}/metrics.yaml (100%) delete mode 100644 metrics/telemetry.go delete mode 100644 metrics/telemetry_test.go delete mode 100644 optimizedmetrics/noop_metrics.go delete mode 100644 optimizedmetrics/otel_metrics.go delete mode 100644 optimizedmetrics/otel_metrics_test.go diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 17126c5d64..6611c87500 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -397,7 +397,7 @@ func Mount(newConfig *cfg.Config, bucketName, mountPoint string) (err error) { metricHandle := metrics.NewNoopMetrics() if cfg.IsMetricsEnabled(&newConfig.Metrics) { metricExporterShutdownFn = monitor.SetupOTelMetricExporters(ctx, newConfig) - if metricHandle, err = metrics.NewOTelMetrics(); err != nil { + if metricHandle, err = metrics.NewOTelMetrics(ctx, int(newConfig.Metrics.Workers), int(newConfig.Metrics.BufferSize)); err != nil { metricHandle = metrics.NewNoopMetrics() } } diff --git a/internal/cache/file/downloader/job.go b/internal/cache/file/downloader/job.go index 16e19e17da..4e7e154db6 100644 --- a/internal/cache/file/downloader/job.go +++ b/internal/cache/file/downloader/job.go @@ -326,7 +326,7 @@ func (job *Job) downloadObjectToFile(cacheFile *os.File) (err error) { if newReader != nil { readHandle = newReader.ReadHandle() } - metrics.CaptureGCSReadMetrics(job.cancelCtx, job.metricsHandle, metrics.ReadTypeNames[metrics.ReadTypeSequential], newReaderLimit-start) + metrics.CaptureGCSReadMetrics(job.metricsHandle, metrics.ReadTypeNames[metrics.ReadTypeSequential], newReaderLimit-start) } maxRead := min(ReadChunkSize, newReaderLimit-start) diff --git a/internal/cache/file/downloader/parallel_downloads_job.go b/internal/cache/file/downloader/parallel_downloads_job.go index 676720a2a8..89bdb20167 100644 --- a/internal/cache/file/downloader/parallel_downloads_job.go +++ b/internal/cache/file/downloader/parallel_downloads_job.go @@ -60,7 +60,7 @@ func (job *Job) downloadRange(ctx context.Context, dstWriter io.Writer, start, e } }() - metrics.CaptureGCSReadMetrics(ctx, job.metricsHandle, metrics.ReadTypeNames[metrics.ReadTypeParallel], end-start) + metrics.CaptureGCSReadMetrics(job.metricsHandle, metrics.ReadTypeNames[metrics.ReadTypeParallel], end-start) // Use standard copy function if O_DIRECT is disabled and memory aligned // buffer otherwise. diff --git a/internal/fs/wrappers/monitoring.go b/internal/fs/wrappers/monitoring.go index ec047a8276..95ffe08341 100644 --- a/internal/fs/wrappers/monitoring.go +++ b/internal/fs/wrappers/monitoring.go @@ -226,17 +226,14 @@ func categorize(err error) string { // Records file system operation count, failed operation count and the operation latency. func recordOp(ctx context.Context, metricHandle metrics.MetricHandle, method string, start time.Time, fsErr error) { - metricHandle.OpsCount(ctx, 1, method) + metricHandle.FsOpsCount(1, method) // Recording opErrorCount. if fsErr != nil { errCategory := categorize(fsErr) - metricHandle.OpsErrorCount(ctx, 1, metrics.FSOpsErrorCategory{ - FSOps: method, - ErrorCategory: errCategory, - }) + metricHandle.FsOpsErrorCount(1, errCategory, method) } - metricHandle.OpsLatency(ctx, time.Since(start), method) + metricHandle.FsOpsLatency(ctx, time.Since(start), method) } // WithMonitoring takes a FileSystem, returns a FileSystem with monitoring diff --git a/internal/gcsx/client_readers/range_reader.go b/internal/gcsx/client_readers/range_reader.go index 44d65fba95..00d60ff219 100644 --- a/internal/gcsx/client_readers/range_reader.go +++ b/internal/gcsx/client_readers/range_reader.go @@ -184,7 +184,7 @@ func (rr *RangeReader) readFromRangeReader(ctx context.Context, p []byte, offset } requestedDataSize := end - offset - metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeNames[readType], requestedDataSize) + metrics.CaptureGCSReadMetrics(rr.metricHandle, metrics.ReadTypeNames[readType], requestedDataSize) return n, err } @@ -279,7 +279,7 @@ func (rr *RangeReader) startRead(start int64, end int64) error { rr.limit = end requestedDataSize := end - start - metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeNames[metrics.ReadTypeSequential], requestedDataSize) + metrics.CaptureGCSReadMetrics(rr.metricHandle, metrics.ReadTypeNames[metrics.ReadTypeSequential], requestedDataSize) return nil } diff --git a/internal/gcsx/file_cache_reader.go b/internal/gcsx/file_cache_reader.go index 03ba9c7d49..c5d26bef63 100644 --- a/internal/gcsx/file_cache_reader.go +++ b/internal/gcsx/file_cache_reader.go @@ -208,13 +208,9 @@ func (fc *FileCacheReader) ReadAt(ctx context.Context, p []byte, offset int64) ( } func captureFileCacheMetrics(ctx context.Context, metricHandle metrics.MetricHandle, readType string, readDataSize int, cacheHit bool, readLatency time.Duration) { - metricHandle.FileCacheReadCount(ctx, 1, metrics.CacheHitReadType{ - ReadType: readType, - CacheHit: cacheHit, - }) - - metricHandle.FileCacheReadBytesCount(ctx, int64(readDataSize), readType) - metricHandle.FileCacheReadLatency(ctx, readLatency, cacheHit) + metricHandle.FileCacheReadCount(1, cacheHit, readType) + metricHandle.FileCacheReadBytesCount(int64(readDataSize), readType) + metricHandle.FileCacheReadLatencies(ctx, readLatency, cacheHit) } func (fc *FileCacheReader) Destroy() { diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index ad7e272eff..c4149cfa00 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -516,7 +516,7 @@ func (rr *randomReader) startRead(start int64, end int64) (err error) { rr.limit = end requestedDataSize := end - start - metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeNames[metrics.ReadTypeSequential], requestedDataSize) + metrics.CaptureGCSReadMetrics(rr.metricHandle, metrics.ReadTypeNames[metrics.ReadTypeSequential], requestedDataSize) return } @@ -735,7 +735,7 @@ func (rr *randomReader) readFromRangeReader(ctx context.Context, p []byte, offse } requestedDataSize := end - offset - metrics.CaptureGCSReadMetrics(ctx, rr.metricHandle, metrics.ReadTypeNames[readType], requestedDataSize) + metrics.CaptureGCSReadMetrics(rr.metricHandle, metrics.ReadTypeNames[readType], requestedDataSize) rr.updateExpectedOffset(offset + int64(n)) return diff --git a/internal/monitor/bucket.go b/internal/monitor/bucket.go index 1e1193a440..2474f34dff 100644 --- a/internal/monitor/bucket.go +++ b/internal/monitor/bucket.go @@ -26,9 +26,9 @@ import ( // recordRequest records a request and its latency. func recordRequest(ctx context.Context, metricHandle metrics.MetricHandle, method string, start time.Time) { - metricHandle.GCSRequestCount(ctx, 1, method) + metricHandle.GcsRequestCount(1, method) - metricHandle.GCSRequestLatency(ctx, time.Since(start), method) + metricHandle.GcsRequestLatencies(ctx, time.Since(start), method) } func CaptureMultiRangeDownloaderMetrics(ctx context.Context, metricHandle metrics.MetricHandle, method string, start time.Time) { @@ -216,13 +216,13 @@ func (mb *monitoringBucket) GCSName(obj *gcs.MinObject) string { } // recordReader increments the reader count when it's opened or closed. -func recordReader(ctx context.Context, metricHandle metrics.MetricHandle, ioMethod string) { - metricHandle.GCSReaderCount(ctx, 1, ioMethod) +func recordReader(metricHandle metrics.MetricHandle, ioMethod string) { + metricHandle.GcsReaderCount(1, ioMethod) } // Monitoring on the object reader func newMonitoringReadCloser(ctx context.Context, object string, rc gcs.StorageReader, metricHandle metrics.MetricHandle) gcs.StorageReader { - recordReader(ctx, metricHandle, "opened") + recordReader(metricHandle, "opened") return &monitoringReadCloser{ ctx: ctx, object: object, @@ -240,7 +240,7 @@ type monitoringReadCloser struct { func (mrc *monitoringReadCloser) Read(p []byte) (n int, err error) { n, err = mrc.wrapped.Read(p) - mrc.metricHandle.GCSReadBytesCount(mrc.ctx, int64(n)) + mrc.metricHandle.GcsReadBytesCount(int64(n)) return } @@ -249,12 +249,12 @@ func (mrc *monitoringReadCloser) Close() (err error) { if err != nil { return fmt.Errorf("close reader: %w", err) } - recordReader(mrc.ctx, mrc.metricHandle, "closed") + recordReader(mrc.metricHandle, "closed") return } func (mrc *monitoringReadCloser) ReadHandle() (rh storagev2.ReadHandle) { rh = mrc.wrapped.ReadHandle() - recordReader(mrc.ctx, mrc.metricHandle, "ReadHandle") + recordReader(mrc.metricHandle, "ReadHandle") return } diff --git a/internal/storage/storageutil/custom_retry.go b/internal/storage/storageutil/custom_retry.go index 99c6698e5c..21938c7bdc 100644 --- a/internal/storage/storageutil/custom_retry.go +++ b/internal/storage/storageutil/custom_retry.go @@ -76,6 +76,6 @@ func ShouldRetryWithMonitoring(ctx context.Context, err error, metricHandle metr val = "STALLED_READ_REQUEST" } - metricHandle.GCSRetryCount(ctx, 1, val) + metricHandle.GcsRetryCount(1, val) return retry } diff --git a/internal/storage/storageutil/custom_retry_test.go b/internal/storage/storageutil/custom_retry_test.go index 9e12d35654..726d7cab21 100644 --- a/internal/storage/storageutil/custom_retry_test.go +++ b/internal/storage/storageutil/custom_retry_test.go @@ -157,7 +157,7 @@ type fakeMetricHandle struct { gcsRetryCountVal string } -func (m *fakeMetricHandle) GCSRetryCount(ctx context.Context, inc int64, val string) { +func (m *fakeMetricHandle) GcsRetryCount(inc int64, val string) { m.gcsRetryCountCalled = true m.gcsRetryCountInc = inc m.gcsRetryCountVal = val diff --git a/main.go b/main.go index aca1052744..ed342471c1 100644 --- a/main.go +++ b/main.go @@ -40,7 +40,7 @@ func logPanic() { // Refer https://go.dev/blog/generate for details. // //go:generate go run -C tools/config-gen . --paramsFile=../../cfg/params.yaml --outDir=../../cfg --templateDir=templates -//go:generate go run -C tools/metrics-gen . --input=../../optimizedmetrics/metrics.yaml --outDir=../../optimizedmetrics +//go:generate go run -C tools/metrics-gen . --input=../../metrics/metrics.yaml --outDir=../../metrics func main() { // Common configuration for all commands defer logPanic() diff --git a/metrics/helper.go b/metrics/helper.go new file mode 100644 index 0000000000..3d6fe04e10 --- /dev/null +++ b/metrics/helper.go @@ -0,0 +1,22 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metrics + +// CaptureGCSReadMetrics is a helper function to encapsulate the logic for recording +// GCS read-related metrics. +func CaptureGCSReadMetrics(mh MetricHandle, readType string, downloadBytes int64) { + mh.GcsReadCount(1, readType) + mh.GcsDownloadBytesCount(downloadBytes, readType) +} diff --git a/optimizedmetrics/metric_handle.go b/metrics/metric_handle.go similarity index 99% rename from optimizedmetrics/metric_handle.go rename to metrics/metric_handle.go index 59b131c899..c21e6aa96d 100644 --- a/optimizedmetrics/metric_handle.go +++ b/metrics/metric_handle.go @@ -13,7 +13,7 @@ // limitations under the License. // **** DO NOT EDIT - FILE IS AUTO-GENERATED **** -package optimizedmetrics +package metrics import ( "context" diff --git a/optimizedmetrics/metrics.yaml b/metrics/metrics.yaml similarity index 100% rename from optimizedmetrics/metrics.yaml rename to metrics/metrics.yaml diff --git a/metrics/noop_metrics.go b/metrics/noop_metrics.go index 000b1b3064..4336a6b064 100644 --- a/metrics/noop_metrics.go +++ b/metrics/noop_metrics.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** package metrics import ( @@ -19,25 +20,37 @@ import ( "time" ) +type noopMetrics struct{} + +func (*noopMetrics) FileCacheReadBytesCount(inc int64, readType string) {} + +func (*noopMetrics) FileCacheReadCount(inc int64, cacheHit bool, readType string) {} + +func (*noopMetrics) FileCacheReadLatencies(ctx context.Context, duration time.Duration, cacheHit bool) { +} + +func (*noopMetrics) FsOpsCount(inc int64, fsOp string) {} + +func (*noopMetrics) FsOpsErrorCount(inc int64, fsErrorCategory string, fsOp string) {} + +func (*noopMetrics) FsOpsLatency(ctx context.Context, duration time.Duration, fsOp string) {} + +func (*noopMetrics) GcsDownloadBytesCount(inc int64, readType string) {} + +func (*noopMetrics) GcsReadBytesCount(inc int64) {} + +func (*noopMetrics) GcsReadCount(inc int64, readType string) {} + +func (*noopMetrics) GcsReaderCount(inc int64, ioMethod string) {} + +func (*noopMetrics) GcsRequestCount(inc int64, gcsMethod string) {} + +func (*noopMetrics) GcsRequestLatencies(ctx context.Context, duration time.Duration, gcsMethod string) { +} + +func (*noopMetrics) GcsRetryCount(inc int64, retryErrorCategory string) {} + func NewNoopMetrics() MetricHandle { var n noopMetrics return &n } - -type noopMetrics struct{} - -func (*noopMetrics) GCSReadBytesCount(_ context.Context, _ int64) {} -func (*noopMetrics) GCSReaderCount(_ context.Context, _ int64, _ string) {} -func (*noopMetrics) GCSRequestCount(_ context.Context, _ int64, _ string) {} -func (*noopMetrics) GCSRequestLatency(_ context.Context, _ time.Duration, _ string) {} -func (*noopMetrics) GCSReadCount(_ context.Context, _ int64, _ string) {} -func (*noopMetrics) GCSDownloadBytesCount(_ context.Context, _ int64, _ string) {} -func (*noopMetrics) GCSRetryCount(_ context.Context, _ int64, _ string) {} - -func (*noopMetrics) OpsCount(_ context.Context, _ int64, _ string) {} -func (*noopMetrics) OpsLatency(_ context.Context, _ time.Duration, _ string) {} -func (*noopMetrics) OpsErrorCount(_ context.Context, _ int64, _ FSOpsErrorCategory) {} - -func (*noopMetrics) FileCacheReadCount(_ context.Context, _ int64, attrs CacheHitReadType) {} -func (*noopMetrics) FileCacheReadBytesCount(_ context.Context, _ int64, _ string) {} -func (*noopMetrics) FileCacheReadLatency(_ context.Context, _ time.Duration, _ bool) {} diff --git a/metrics/otel_metrics.go b/metrics/otel_metrics.go index 319bd1eea1..a89a7a4655 100644 --- a/metrics/otel_metrics.go +++ b/metrics/otel_metrics.go @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** + package metrics import ( @@ -21,228 +23,4364 @@ import ( "sync/atomic" "time" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" ) +const logInterval = 5 * time.Minute + var ( - // Attribute Keys - // ioMethodKey specifies the I/O method attribute (e.g., opened, closed). - ioMethodKey = attribute.Key("io_method") - // gcsMethodKey specifies the name of the GCS method - gcsMethodKey = attribute.Key("gcs_method") - // fsOpKey specifies the FS operation like LookupInode, ReadFile etc. - fsOpKey = attribute.Key("fs_op") - // fsErrCategoryKey specifies the error category. The intention is to reduce the cardinality of FSError by grouping errors together. - fsErrCategoryKey = attribute.Key("fs_error_category") - // readTypeKey specifies the read operation like whether it's Sequential or Random - readTypeKey = attribute.Key("read_type") - // cacheHitKey specifies whether the read operation from file cache resulted in a cache-hit or miss. - cacheHitKey = attribute.Key("cache_hit") - // retryErrCategoryKey specifies the category of the error that triggered a retry. - retryErrCategoryKey = attribute.Key("retry_error_category") - - fsOpsOptionCache, - readTypeOptionCache, - ioMethodOptionCache, - gcsMethodOptionCache, - retryErrCategoryOptionCache, - cacheHitOptionCache, - cacheHitReadTypeOptionCache, - fsOpsErrorCategoryOptionCache sync.Map + unrecognizedAttr atomic.Value + fileCacheReadBytesCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) + fileCacheReadBytesCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) + fileCacheReadBytesCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) + fileCacheReadCountCacheHitTrueReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Parallel"))) + fileCacheReadCountCacheHitTrueReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Random"))) + fileCacheReadCountCacheHitTrueReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Sequential"))) + fileCacheReadCountCacheHitFalseReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Parallel"))) + fileCacheReadCountCacheHitFalseReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Random"))) + fileCacheReadCountCacheHitFalseReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Sequential"))) + fileCacheReadLatenciesCacheHitTrueAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true))) + fileCacheReadLatenciesCacheHitFalseAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false))) + fsOpsCountFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "BatchForget"))) + fsOpsCountFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateFile"))) + fsOpsCountFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateLink"))) + fsOpsCountFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateSymlink"))) + fsOpsCountFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Fallocate"))) + fsOpsCountFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "FlushFile"))) + fsOpsCountFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ForgetInode"))) + fsOpsCountFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsCountFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetXattr"))) + fsOpsCountFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ListXattr"))) + fsOpsCountFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "LookUpInode"))) + fsOpsCountFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkDir"))) + fsOpsCountFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkNode"))) + fsOpsCountFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenDir"))) + fsOpsCountFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenFile"))) + fsOpsCountFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadDir"))) + fsOpsCountFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadFile"))) + fsOpsCountFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadSymlink"))) + fsOpsCountFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsCountFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsCountFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RemoveXattr"))) + fsOpsCountFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Rename"))) + fsOpsCountFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RmDir"))) + fsOpsCountFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsCountFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetXattr"))) + fsOpsCountFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "StatFS"))) + fsOpsCountFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFS"))) + fsOpsCountFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFile"))) + fsOpsCountFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Unlink"))) + fsOpsCountFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "WriteFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "BatchForget"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateLink"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateSymlink"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Fallocate"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "FlushFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ForgetInode"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetXattr"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ListXattr"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "LookUpInode"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkDir"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkNode"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenDir"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadSymlink"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RemoveXattr"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Rename"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RmDir"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetXattr"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "StatFS"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFS"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFile"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Unlink"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "WriteFile"))) + fsOpsLatencyFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "BatchForget"))) + fsOpsLatencyFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateFile"))) + fsOpsLatencyFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateLink"))) + fsOpsLatencyFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateSymlink"))) + fsOpsLatencyFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Fallocate"))) + fsOpsLatencyFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "FlushFile"))) + fsOpsLatencyFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ForgetInode"))) + fsOpsLatencyFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetInodeAttributes"))) + fsOpsLatencyFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetXattr"))) + fsOpsLatencyFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ListXattr"))) + fsOpsLatencyFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "LookUpInode"))) + fsOpsLatencyFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkDir"))) + fsOpsLatencyFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkNode"))) + fsOpsLatencyFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenDir"))) + fsOpsLatencyFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenFile"))) + fsOpsLatencyFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadDir"))) + fsOpsLatencyFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadFile"))) + fsOpsLatencyFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadSymlink"))) + fsOpsLatencyFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseDirHandle"))) + fsOpsLatencyFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseFileHandle"))) + fsOpsLatencyFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RemoveXattr"))) + fsOpsLatencyFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Rename"))) + fsOpsLatencyFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RmDir"))) + fsOpsLatencyFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetInodeAttributes"))) + fsOpsLatencyFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetXattr"))) + fsOpsLatencyFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "StatFS"))) + fsOpsLatencyFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFS"))) + fsOpsLatencyFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFile"))) + fsOpsLatencyFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Unlink"))) + fsOpsLatencyFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "WriteFile"))) + gcsDownloadBytesCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) + gcsDownloadBytesCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) + gcsDownloadBytesCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) + gcsReadCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) + gcsReadCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) + gcsReadCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) + gcsReaderCountIoMethodClosedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("io_method", "closed"))) + gcsReaderCountIoMethodOpenedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("io_method", "opened"))) + gcsRequestCountGcsMethodComposeObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ComposeObjects"))) + gcsRequestCountGcsMethodCreateFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateFolder"))) + gcsRequestCountGcsMethodCreateObjectChunkWriterAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateObjectChunkWriter"))) + gcsRequestCountGcsMethodDeleteFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteFolder"))) + gcsRequestCountGcsMethodDeleteObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteObject"))) + gcsRequestCountGcsMethodFinalizeUploadAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload"))) + gcsRequestCountGcsMethodGetFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "GetFolder"))) + gcsRequestCountGcsMethodListObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ListObjects"))) + gcsRequestCountGcsMethodMoveObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MoveObject"))) + gcsRequestCountGcsMethodMultiRangeDownloaderAddAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MultiRangeDownloader::Add"))) + gcsRequestCountGcsMethodNewMultiRangeDownloaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewMultiRangeDownloader"))) + gcsRequestCountGcsMethodNewReaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewReader"))) + gcsRequestCountGcsMethodRenameFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "RenameFolder"))) + gcsRequestCountGcsMethodStatObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "StatObject"))) + gcsRequestCountGcsMethodUpdateObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "UpdateObject"))) + gcsRequestLatenciesGcsMethodComposeObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ComposeObjects"))) + gcsRequestLatenciesGcsMethodCreateFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateFolder"))) + gcsRequestLatenciesGcsMethodCreateObjectChunkWriterAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateObjectChunkWriter"))) + gcsRequestLatenciesGcsMethodDeleteFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteFolder"))) + gcsRequestLatenciesGcsMethodDeleteObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteObject"))) + gcsRequestLatenciesGcsMethodFinalizeUploadAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload"))) + gcsRequestLatenciesGcsMethodGetFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "GetFolder"))) + gcsRequestLatenciesGcsMethodListObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ListObjects"))) + gcsRequestLatenciesGcsMethodMoveObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MoveObject"))) + gcsRequestLatenciesGcsMethodMultiRangeDownloaderAddAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MultiRangeDownloader::Add"))) + gcsRequestLatenciesGcsMethodNewMultiRangeDownloaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewMultiRangeDownloader"))) + gcsRequestLatenciesGcsMethodNewReaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewReader"))) + gcsRequestLatenciesGcsMethodRenameFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "RenameFolder"))) + gcsRequestLatenciesGcsMethodStatObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "StatObject"))) + gcsRequestLatenciesGcsMethodUpdateObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "UpdateObject"))) + gcsRetryCountRetryErrorCategoryOTHERERRORSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS"))) + gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST"))) ) -func loadOrStoreAttrOption[K comparable](mp *sync.Map, key K, attrSetGenFunc func() attribute.Set) metric.MeasurementOption { - attrSet, ok := mp.Load(key) - if ok { - return attrSet.(metric.MeasurementOption) - } - v, _ := mp.LoadOrStore(key, metric.WithAttributeSet(attrSetGenFunc())) - return v.(metric.MeasurementOption) +type histogramRecord struct { + ctx context.Context + instrument metric.Int64Histogram + value int64 + attributes metric.RecordOption } -func ioMethodAttrOption(ioMethod string) metric.MeasurementOption { - return loadOrStoreAttrOption(&ioMethodOptionCache, ioMethod, - func() attribute.Set { - return attribute.NewSet(ioMethodKey.String(ioMethod)) - }) +type otelMetrics struct { + ch chan histogramRecord + wg *sync.WaitGroup + fileCacheReadBytesCountReadTypeParallelAtomic *atomic.Int64 + fileCacheReadBytesCountReadTypeRandomAtomic *atomic.Int64 + fileCacheReadBytesCountReadTypeSequentialAtomic *atomic.Int64 + fileCacheReadCountCacheHitTrueReadTypeParallelAtomic *atomic.Int64 + fileCacheReadCountCacheHitTrueReadTypeRandomAtomic *atomic.Int64 + fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic *atomic.Int64 + fileCacheReadCountCacheHitFalseReadTypeParallelAtomic *atomic.Int64 + fileCacheReadCountCacheHitFalseReadTypeRandomAtomic *atomic.Int64 + fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic *atomic.Int64 + fsOpsCountFsOpBatchForgetAtomic *atomic.Int64 + fsOpsCountFsOpCreateFileAtomic *atomic.Int64 + fsOpsCountFsOpCreateLinkAtomic *atomic.Int64 + fsOpsCountFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsCountFsOpFallocateAtomic *atomic.Int64 + fsOpsCountFsOpFlushFileAtomic *atomic.Int64 + fsOpsCountFsOpForgetInodeAtomic *atomic.Int64 + fsOpsCountFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsCountFsOpGetXattrAtomic *atomic.Int64 + fsOpsCountFsOpListXattrAtomic *atomic.Int64 + fsOpsCountFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsCountFsOpMkDirAtomic *atomic.Int64 + fsOpsCountFsOpMkNodeAtomic *atomic.Int64 + fsOpsCountFsOpOpenDirAtomic *atomic.Int64 + fsOpsCountFsOpOpenFileAtomic *atomic.Int64 + fsOpsCountFsOpReadDirAtomic *atomic.Int64 + fsOpsCountFsOpReadFileAtomic *atomic.Int64 + fsOpsCountFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsCountFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsCountFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsCountFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsCountFsOpRenameAtomic *atomic.Int64 + fsOpsCountFsOpRmDirAtomic *atomic.Int64 + fsOpsCountFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsCountFsOpSetXattrAtomic *atomic.Int64 + fsOpsCountFsOpStatFSAtomic *atomic.Int64 + fsOpsCountFsOpSyncFSAtomic *atomic.Int64 + fsOpsCountFsOpSyncFileAtomic *atomic.Int64 + fsOpsCountFsOpUnlinkAtomic *atomic.Int64 + fsOpsCountFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic *atomic.Int64 + gcsDownloadBytesCountReadTypeParallelAtomic *atomic.Int64 + gcsDownloadBytesCountReadTypeRandomAtomic *atomic.Int64 + gcsDownloadBytesCountReadTypeSequentialAtomic *atomic.Int64 + gcsReadBytesCountAtomic *atomic.Int64 + gcsReadCountReadTypeParallelAtomic *atomic.Int64 + gcsReadCountReadTypeRandomAtomic *atomic.Int64 + gcsReadCountReadTypeSequentialAtomic *atomic.Int64 + gcsReaderCountIoMethodClosedAtomic *atomic.Int64 + gcsReaderCountIoMethodOpenedAtomic *atomic.Int64 + gcsRequestCountGcsMethodComposeObjectsAtomic *atomic.Int64 + gcsRequestCountGcsMethodCreateFolderAtomic *atomic.Int64 + gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic *atomic.Int64 + gcsRequestCountGcsMethodDeleteFolderAtomic *atomic.Int64 + gcsRequestCountGcsMethodDeleteObjectAtomic *atomic.Int64 + gcsRequestCountGcsMethodFinalizeUploadAtomic *atomic.Int64 + gcsRequestCountGcsMethodGetFolderAtomic *atomic.Int64 + gcsRequestCountGcsMethodListObjectsAtomic *atomic.Int64 + gcsRequestCountGcsMethodMoveObjectAtomic *atomic.Int64 + gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic *atomic.Int64 + gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic *atomic.Int64 + gcsRequestCountGcsMethodNewReaderAtomic *atomic.Int64 + gcsRequestCountGcsMethodRenameFolderAtomic *atomic.Int64 + gcsRequestCountGcsMethodStatObjectAtomic *atomic.Int64 + gcsRequestCountGcsMethodUpdateObjectAtomic *atomic.Int64 + gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic *atomic.Int64 + gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic *atomic.Int64 + fileCacheReadLatencies metric.Int64Histogram + fsOpsLatency metric.Int64Histogram + gcsRequestLatencies metric.Int64Histogram } -func readTypeAttrOption(readType string) metric.MeasurementOption { - return loadOrStoreAttrOption(&readTypeOptionCache, readType, - func() attribute.Set { - return attribute.NewSet(readTypeKey.String(readType)) - }) -} +func (o *otelMetrics) FileCacheReadBytesCount( + inc int64, readType string) { + switch readType { + case "Parallel": + o.fileCacheReadBytesCountReadTypeParallelAtomic.Add(inc) + case "Random": + o.fileCacheReadBytesCountReadTypeRandomAtomic.Add(inc) + case "Sequential": + o.fileCacheReadBytesCountReadTypeSequentialAtomic.Add(inc) + default: + updateUnrecognizedAttribute(readType) + } -func fsOpsAttrOption(fsOps string) metric.MeasurementOption { - return loadOrStoreAttrOption(&fsOpsOptionCache, fsOps, - func() attribute.Set { - return attribute.NewSet(fsOpKey.String(fsOps)) - }) } -func getFsOpsErrorCategoryAttributeOption(attr FSOpsErrorCategory) metric.MeasurementOption { - return loadOrStoreAttrOption(&fsOpsErrorCategoryOptionCache, attr, - func() attribute.Set { - return attribute.NewSet(fsOpKey.String(attr.FSOps), fsErrCategoryKey.String(attr.ErrorCategory)) - }) -} +func (o *otelMetrics) FileCacheReadCount( + inc int64, cacheHit bool, readType string) { + switch cacheHit { + case true: + switch readType { + case "Parallel": + o.fileCacheReadCountCacheHitTrueReadTypeParallelAtomic.Add(inc) + case "Random": + o.fileCacheReadCountCacheHitTrueReadTypeRandomAtomic.Add(inc) + case "Sequential": + o.fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic.Add(inc) + default: + updateUnrecognizedAttribute(readType) + } + case false: + switch readType { + case "Parallel": + o.fileCacheReadCountCacheHitFalseReadTypeParallelAtomic.Add(inc) + case "Random": + o.fileCacheReadCountCacheHitFalseReadTypeRandomAtomic.Add(inc) + case "Sequential": + o.fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic.Add(inc) + default: + updateUnrecognizedAttribute(readType) + } + } -func cacheHitAttrOption(cacheHit bool) metric.MeasurementOption { - return loadOrStoreAttrOption(&cacheHitOptionCache, cacheHit, - func() attribute.Set { - return attribute.NewSet(cacheHitKey.Bool(cacheHit)) - }) } -func gcsMethodAttrOption(gcsMethod string) metric.MeasurementOption { - return loadOrStoreAttrOption(&gcsMethodOptionCache, gcsMethod, - func() attribute.Set { - return attribute.NewSet(gcsMethodKey.String(gcsMethod)) - }) +func (o *otelMetrics) FileCacheReadLatencies( + ctx context.Context, latency time.Duration, cacheHit bool) { + var record histogramRecord + switch cacheHit { + case true: + record = histogramRecord{ctx: ctx, instrument: o.fileCacheReadLatencies, value: latency.Microseconds(), attributes: fileCacheReadLatenciesCacheHitTrueAttrSet} + case false: + record = histogramRecord{ctx: ctx, instrument: o.fileCacheReadLatencies, value: latency.Microseconds(), attributes: fileCacheReadLatenciesCacheHitFalseAttrSet} + } + + select { + case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + default: // Unblock writes to channel if it's full. + } } -func retryErrCategoryAttrOption(retryErrCategory string) metric.MeasurementOption { - return loadOrStoreAttrOption(&retryErrCategoryOptionCache, retryErrCategory, - func() attribute.Set { - return attribute.NewSet(retryErrCategoryKey.String(retryErrCategory)) - }) +func (o *otelMetrics) FsOpsCount( + inc int64, fsOp string) { + switch fsOp { + case "BatchForget": + o.fsOpsCountFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsCountFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsCountFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsCountFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsCountFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsCountFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsCountFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsCountFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsCountFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsCountFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsCountFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsCountFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsCountFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsCountFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsCountFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsCountFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsCountFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsCountFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsCountFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsCountFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsCountFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsCountFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsCountFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsCountFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsCountFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsCountFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsCountFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsCountFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsCountFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsCountFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + } -func cacheHitReadTypeAttrOption(attr CacheHitReadType) metric.MeasurementOption { - return loadOrStoreAttrOption(&cacheHitReadTypeOptionCache, attr, func() attribute.Set { - return attribute.NewSet(cacheHitKey.Bool(attr.CacheHit), readTypeKey.String(attr.ReadType)) - }) +func (o *otelMetrics) FsOpsErrorCount( + inc int64, fsErrorCategory string, fsOp string) { + switch fsErrorCategory { + case "DEVICE_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "DIR_NOT_EMPTY": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "FILE_DIR_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "FILE_EXISTS": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "INTERRUPT_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "INVALID_ARGUMENT": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "INVALID_OPERATION": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "IO_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "MISC_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "NETWORK_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "NOT_A_DIR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "NOT_IMPLEMENTED": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "NO_FILE_OR_DIR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "PERM_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "PROCESS_RESOURCE_MGMT_ERROR": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + case "TOO_MANY_OPEN_FILES": + switch fsOp { + case "BatchForget": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic.Add(inc) + case "CreateFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic.Add(inc) + case "CreateLink": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic.Add(inc) + case "CreateSymlink": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic.Add(inc) + case "Fallocate": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic.Add(inc) + case "FlushFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic.Add(inc) + case "ForgetInode": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic.Add(inc) + case "GetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic.Add(inc) + case "GetXattr": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic.Add(inc) + case "ListXattr": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic.Add(inc) + case "LookUpInode": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic.Add(inc) + case "MkDir": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic.Add(inc) + case "MkNode": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic.Add(inc) + case "OpenDir": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic.Add(inc) + case "OpenFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic.Add(inc) + case "ReadDir": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic.Add(inc) + case "ReadFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic.Add(inc) + case "ReadSymlink": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic.Add(inc) + case "ReleaseDirHandle": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic.Add(inc) + case "ReleaseFileHandle": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic.Add(inc) + case "RemoveXattr": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic.Add(inc) + case "Rename": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic.Add(inc) + case "RmDir": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic.Add(inc) + case "SetInodeAttributes": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic.Add(inc) + case "SetXattr": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic.Add(inc) + case "StatFS": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic.Add(inc) + case "SyncFS": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic.Add(inc) + case "SyncFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic.Add(inc) + case "Unlink": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic.Add(inc) + case "WriteFile": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic.Add(inc) + default: + updateUnrecognizedAttribute(fsOp) + } + default: + updateUnrecognizedAttribute(fsErrorCategory) + } + } -// otelMetrics maintains the list of all metrics computed in GCSFuse. -type otelMetrics struct { - fsOpsCount metric.Int64Counter - fsOpsErrorCount metric.Int64Counter - fsOpsLatency metric.Int64Histogram +func (o *otelMetrics) FsOpsLatency( + ctx context.Context, latency time.Duration, fsOp string) { + var record histogramRecord + switch fsOp { + case "BatchForget": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpBatchForgetAttrSet} + case "CreateFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpCreateFileAttrSet} + case "CreateLink": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpCreateLinkAttrSet} + case "CreateSymlink": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpCreateSymlinkAttrSet} + case "Fallocate": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpFallocateAttrSet} + case "FlushFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpFlushFileAttrSet} + case "ForgetInode": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpForgetInodeAttrSet} + case "GetInodeAttributes": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpGetInodeAttributesAttrSet} + case "GetXattr": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpGetXattrAttrSet} + case "ListXattr": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpListXattrAttrSet} + case "LookUpInode": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpLookUpInodeAttrSet} + case "MkDir": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpMkDirAttrSet} + case "MkNode": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpMkNodeAttrSet} + case "OpenDir": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpOpenDirAttrSet} + case "OpenFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpOpenFileAttrSet} + case "ReadDir": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadDirAttrSet} + case "ReadFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadFileAttrSet} + case "ReadSymlink": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadSymlinkAttrSet} + case "ReleaseDirHandle": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReleaseDirHandleAttrSet} + case "ReleaseFileHandle": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReleaseFileHandleAttrSet} + case "RemoveXattr": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpRemoveXattrAttrSet} + case "Rename": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpRenameAttrSet} + case "RmDir": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpRmDirAttrSet} + case "SetInodeAttributes": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSetInodeAttributesAttrSet} + case "SetXattr": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSetXattrAttrSet} + case "StatFS": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpStatFSAttrSet} + case "SyncFS": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSyncFSAttrSet} + case "SyncFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSyncFileAttrSet} + case "Unlink": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpUnlinkAttrSet} + case "WriteFile": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpWriteFileAttrSet} + default: + updateUnrecognizedAttribute(fsOp) + } - gcsReadCount metric.Int64Counter - gcsReadBytesCountAtomic *atomic.Int64 - gcsReaderCount metric.Int64Counter - gcsRequestCount metric.Int64Counter - gcsRequestLatency metric.Int64Histogram - gcsDownloadBytesCount metric.Int64Counter - gcsRetryCount metric.Int64Counter + select { + case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + default: // Unblock writes to channel if it's full. + } +} + +func (o *otelMetrics) GcsDownloadBytesCount( + inc int64, readType string) { + switch readType { + case "Parallel": + o.gcsDownloadBytesCountReadTypeParallelAtomic.Add(inc) + case "Random": + o.gcsDownloadBytesCountReadTypeRandomAtomic.Add(inc) + case "Sequential": + o.gcsDownloadBytesCountReadTypeSequentialAtomic.Add(inc) + default: + updateUnrecognizedAttribute(readType) + } - fileCacheReadCount metric.Int64Counter - fileCacheReadBytesCount metric.Int64Counter - fileCacheReadLatency metric.Int64Histogram } -func (o *otelMetrics) GCSReadBytesCount(_ context.Context, inc int64) { +func (o *otelMetrics) GcsReadBytesCount( + inc int64) { o.gcsReadBytesCountAtomic.Add(inc) -} -func (o *otelMetrics) GCSReaderCount(ctx context.Context, inc int64, ioMethod string) { - o.gcsReaderCount.Add(ctx, inc, ioMethodAttrOption(ioMethod)) } -func (o *otelMetrics) GCSRequestCount(ctx context.Context, inc int64, gcsMethod string) { - o.gcsRequestCount.Add(ctx, inc, gcsMethodAttrOption(gcsMethod)) -} +func (o *otelMetrics) GcsReadCount( + inc int64, readType string) { + switch readType { + case "Parallel": + o.gcsReadCountReadTypeParallelAtomic.Add(inc) + case "Random": + o.gcsReadCountReadTypeRandomAtomic.Add(inc) + case "Sequential": + o.gcsReadCountReadTypeSequentialAtomic.Add(inc) + default: + updateUnrecognizedAttribute(readType) + } -func (o *otelMetrics) GCSRequestLatency(ctx context.Context, latency time.Duration, gcsMethod string) { - o.gcsRequestLatency.Record(ctx, latency.Milliseconds(), gcsMethodAttrOption(gcsMethod)) } -func (o *otelMetrics) GCSReadCount(ctx context.Context, inc int64, readType string) { - o.gcsReadCount.Add(ctx, inc, readTypeAttrOption(readType)) -} +func (o *otelMetrics) GcsReaderCount( + inc int64, ioMethod string) { + switch ioMethod { + case "closed": + o.gcsReaderCountIoMethodClosedAtomic.Add(inc) + case "opened": + o.gcsReaderCountIoMethodOpenedAtomic.Add(inc) + default: + updateUnrecognizedAttribute(ioMethod) + } -func (o *otelMetrics) GCSDownloadBytesCount(ctx context.Context, inc int64, readType string) { - o.gcsDownloadBytesCount.Add(ctx, inc, readTypeAttrOption(readType)) } -func (o *otelMetrics) GCSRetryCount(ctx context.Context, inc int64, retryErrCategory string) { - o.gcsRetryCount.Add(ctx, inc, retryErrCategoryAttrOption(retryErrCategory)) -} +func (o *otelMetrics) GcsRequestCount( + inc int64, gcsMethod string) { + switch gcsMethod { + case "ComposeObjects": + o.gcsRequestCountGcsMethodComposeObjectsAtomic.Add(inc) + case "CreateFolder": + o.gcsRequestCountGcsMethodCreateFolderAtomic.Add(inc) + case "CreateObjectChunkWriter": + o.gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic.Add(inc) + case "DeleteFolder": + o.gcsRequestCountGcsMethodDeleteFolderAtomic.Add(inc) + case "DeleteObject": + o.gcsRequestCountGcsMethodDeleteObjectAtomic.Add(inc) + case "FinalizeUpload": + o.gcsRequestCountGcsMethodFinalizeUploadAtomic.Add(inc) + case "GetFolder": + o.gcsRequestCountGcsMethodGetFolderAtomic.Add(inc) + case "ListObjects": + o.gcsRequestCountGcsMethodListObjectsAtomic.Add(inc) + case "MoveObject": + o.gcsRequestCountGcsMethodMoveObjectAtomic.Add(inc) + case "MultiRangeDownloader::Add": + o.gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic.Add(inc) + case "NewMultiRangeDownloader": + o.gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic.Add(inc) + case "NewReader": + o.gcsRequestCountGcsMethodNewReaderAtomic.Add(inc) + case "RenameFolder": + o.gcsRequestCountGcsMethodRenameFolderAtomic.Add(inc) + case "StatObject": + o.gcsRequestCountGcsMethodStatObjectAtomic.Add(inc) + case "UpdateObject": + o.gcsRequestCountGcsMethodUpdateObjectAtomic.Add(inc) + default: + updateUnrecognizedAttribute(gcsMethod) + } -func (o *otelMetrics) OpsCount(ctx context.Context, inc int64, fsOp string) { - o.fsOpsCount.Add(ctx, inc, fsOpsAttrOption(fsOp)) } -func (o *otelMetrics) OpsLatency(ctx context.Context, latency time.Duration, fsOp string) { - o.fsOpsLatency.Record(ctx, latency.Microseconds(), fsOpsAttrOption(fsOp)) -} +func (o *otelMetrics) GcsRequestLatencies( + ctx context.Context, latency time.Duration, gcsMethod string) { + var record histogramRecord + switch gcsMethod { + case "ComposeObjects": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodComposeObjectsAttrSet} + case "CreateFolder": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodCreateFolderAttrSet} + case "CreateObjectChunkWriter": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodCreateObjectChunkWriterAttrSet} + case "DeleteFolder": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodDeleteFolderAttrSet} + case "DeleteObject": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodDeleteObjectAttrSet} + case "FinalizeUpload": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodFinalizeUploadAttrSet} + case "GetFolder": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodGetFolderAttrSet} + case "ListObjects": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodListObjectsAttrSet} + case "MoveObject": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodMoveObjectAttrSet} + case "MultiRangeDownloader::Add": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodMultiRangeDownloaderAddAttrSet} + case "NewMultiRangeDownloader": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodNewMultiRangeDownloaderAttrSet} + case "NewReader": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodNewReaderAttrSet} + case "RenameFolder": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodRenameFolderAttrSet} + case "StatObject": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodStatObjectAttrSet} + case "UpdateObject": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodUpdateObjectAttrSet} + default: + updateUnrecognizedAttribute(gcsMethod) + } -func (o *otelMetrics) OpsErrorCount(ctx context.Context, inc int64, attrs FSOpsErrorCategory) { - o.fsOpsErrorCount.Add(ctx, inc, getFsOpsErrorCategoryAttributeOption(attrs)) + select { + case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + default: // Unblock writes to channel if it's full. + } } -func (o *otelMetrics) FileCacheReadCount(ctx context.Context, inc int64, attrs CacheHitReadType) { - o.fileCacheReadCount.Add(ctx, inc, cacheHitReadTypeAttrOption(attrs)) -} +func (o *otelMetrics) GcsRetryCount( + inc int64, retryErrorCategory string) { + switch retryErrorCategory { + case "OTHER_ERRORS": + o.gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic.Add(inc) + case "STALLED_READ_REQUEST": + o.gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic.Add(inc) + default: + updateUnrecognizedAttribute(retryErrorCategory) + } -func (o *otelMetrics) FileCacheReadBytesCount(ctx context.Context, inc int64, readType string) { - o.fileCacheReadBytesCount.Add(ctx, inc, readTypeAttrOption(readType)) } -func (o *otelMetrics) FileCacheReadLatency(ctx context.Context, latency time.Duration, cacheHit bool) { - o.fileCacheReadLatency.Record(ctx, latency.Microseconds(), cacheHitAttrOption(cacheHit)) -} +func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetrics, error) { + ch := make(chan histogramRecord, bufferSize) + var wg sync.WaitGroup + startSampledLogging(ctx) + for range workers { + wg.Add(1) + go func() { + defer wg.Done() + for record := range ch { + record.instrument.Record(record.ctx, record.value, record.attributes) + } + }() + } + meter := otel.Meter("gcsfuse") + var fileCacheReadBytesCountReadTypeParallelAtomic, + fileCacheReadBytesCountReadTypeRandomAtomic, + fileCacheReadBytesCountReadTypeSequentialAtomic atomic.Int64 -func NewOTelMetrics() (*otelMetrics, error) { - fsOpsMeter := otel.Meter("fs_op") - gcsMeter := otel.Meter("gcs") - fileCacheMeter := otel.Meter("file_cache") - fsOpsCount, err1 := fsOpsMeter.Int64Counter("fs/ops_count", metric.WithDescription("The cumulative number of ops processed by the file system.")) - fsOpsLatency, err2 := fsOpsMeter.Int64Histogram("fs/ops_latency", metric.WithDescription("The cumulative distribution of file system operation latencies"), metric.WithUnit("us"), - defaultLatencyDistribution) - fsOpsErrorCount, err3 := fsOpsMeter.Int64Counter("fs/ops_error_count", metric.WithDescription("The cumulative number of errors generated by file system operations")) + var fileCacheReadCountCacheHitTrueReadTypeParallelAtomic, + fileCacheReadCountCacheHitTrueReadTypeRandomAtomic, + fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic, + fileCacheReadCountCacheHitFalseReadTypeParallelAtomic, + fileCacheReadCountCacheHitFalseReadTypeRandomAtomic, + fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic atomic.Int64 - gcsReadCount, err4 := gcsMeter.Int64Counter("gcs/read_count", metric.WithDescription("Specifies the number of gcs reads made along with type - Sequential/Random")) + var fsOpsCountFsOpBatchForgetAtomic, + fsOpsCountFsOpCreateFileAtomic, + fsOpsCountFsOpCreateLinkAtomic, + fsOpsCountFsOpCreateSymlinkAtomic, + fsOpsCountFsOpFallocateAtomic, + fsOpsCountFsOpFlushFileAtomic, + fsOpsCountFsOpForgetInodeAtomic, + fsOpsCountFsOpGetInodeAttributesAtomic, + fsOpsCountFsOpGetXattrAtomic, + fsOpsCountFsOpListXattrAtomic, + fsOpsCountFsOpLookUpInodeAtomic, + fsOpsCountFsOpMkDirAtomic, + fsOpsCountFsOpMkNodeAtomic, + fsOpsCountFsOpOpenDirAtomic, + fsOpsCountFsOpOpenFileAtomic, + fsOpsCountFsOpReadDirAtomic, + fsOpsCountFsOpReadFileAtomic, + fsOpsCountFsOpReadSymlinkAtomic, + fsOpsCountFsOpReleaseDirHandleAtomic, + fsOpsCountFsOpReleaseFileHandleAtomic, + fsOpsCountFsOpRemoveXattrAtomic, + fsOpsCountFsOpRenameAtomic, + fsOpsCountFsOpRmDirAtomic, + fsOpsCountFsOpSetInodeAttributesAtomic, + fsOpsCountFsOpSetXattrAtomic, + fsOpsCountFsOpStatFSAtomic, + fsOpsCountFsOpSyncFSAtomic, + fsOpsCountFsOpSyncFileAtomic, + fsOpsCountFsOpUnlinkAtomic, + fsOpsCountFsOpWriteFileAtomic atomic.Int64 - gcsDownloadBytesCount, err5 := gcsMeter.Int64Counter("gcs/download_bytes_count", - metric.WithDescription("The cumulative number of bytes downloaded from GCS along with type - Sequential/Random"), - metric.WithUnit("By")) + var fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic atomic.Int64 + + var gcsDownloadBytesCountReadTypeParallelAtomic, + gcsDownloadBytesCountReadTypeRandomAtomic, + gcsDownloadBytesCountReadTypeSequentialAtomic atomic.Int64 var gcsReadBytesCountAtomic atomic.Int64 - _, err6 := gcsMeter.Int64ObservableCounter("gcs/read_bytes_count", - metric.WithDescription("The cumulative number of bytes read from GCS objects."), + + var gcsReadCountReadTypeParallelAtomic, + gcsReadCountReadTypeRandomAtomic, + gcsReadCountReadTypeSequentialAtomic atomic.Int64 + + var gcsReaderCountIoMethodClosedAtomic, + gcsReaderCountIoMethodOpenedAtomic atomic.Int64 + + var gcsRequestCountGcsMethodComposeObjectsAtomic, + gcsRequestCountGcsMethodCreateFolderAtomic, + gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, + gcsRequestCountGcsMethodDeleteFolderAtomic, + gcsRequestCountGcsMethodDeleteObjectAtomic, + gcsRequestCountGcsMethodFinalizeUploadAtomic, + gcsRequestCountGcsMethodGetFolderAtomic, + gcsRequestCountGcsMethodListObjectsAtomic, + gcsRequestCountGcsMethodMoveObjectAtomic, + gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic, + gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic, + gcsRequestCountGcsMethodNewReaderAtomic, + gcsRequestCountGcsMethodRenameFolderAtomic, + gcsRequestCountGcsMethodStatObjectAtomic, + gcsRequestCountGcsMethodUpdateObjectAtomic atomic.Int64 + + var gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, + gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic atomic.Int64 + + _, err0 := meter.Int64ObservableCounter("file_cache/read_bytes_count", + metric.WithDescription("The cumulative number of bytes read from file cache along with read type - Sequential/Random"), metric.WithUnit("By"), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - obsrv.Observe(gcsReadBytesCountAtomic.Load()) + conditionallyObserve(obsrv, &fileCacheReadBytesCountReadTypeParallelAtomic, fileCacheReadBytesCountReadTypeParallelAttrSet) + conditionallyObserve(obsrv, &fileCacheReadBytesCountReadTypeRandomAtomic, fileCacheReadBytesCountReadTypeRandomAttrSet) + conditionallyObserve(obsrv, &fileCacheReadBytesCountReadTypeSequentialAtomic, fileCacheReadBytesCountReadTypeSequentialAttrSet) return nil })) - gcsReaderCount, err7 := gcsMeter.Int64Counter("gcs/reader_count", metric.WithDescription("The cumulative number of GCS object readers opened or closed.")) - gcsRequestCount, err8 := gcsMeter.Int64Counter("gcs/request_count", metric.WithDescription("The cumulative number of GCS requests processed along with the GCS method.")) - gcsRequestLatency, err9 := gcsMeter.Int64Histogram("gcs/request_latencies", metric.WithDescription("The cumulative distribution of the GCS request latencies."), metric.WithUnit("ms")) - gcsRetryCount, err10 := gcsMeter.Int64Counter("gcs/retry_count", metric.WithDescription("The cumulative number of retry requests made to GCS.")) - - fileCacheReadCount, err11 := fileCacheMeter.Int64Counter("file_cache/read_count", - metric.WithDescription("Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false")) - fileCacheReadBytesCount, err12 := fileCacheMeter.Int64Counter("file_cache/read_bytes_count", - metric.WithDescription("The cumulative number of bytes read from file cache along with read type - Sequential/Random"), - metric.WithUnit("By")) - fileCacheReadLatency, err13 := fileCacheMeter.Int64Histogram("file_cache/read_latencies", - metric.WithDescription("The cumulative distribution of the file cache read latencies along with cache hit - true/false"), + + _, err1 := meter.Int64ObservableCounter("file_cache/read_count", + metric.WithDescription("Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false"), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitTrueReadTypeParallelAtomic, fileCacheReadCountCacheHitTrueReadTypeParallelAttrSet) + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitTrueReadTypeRandomAtomic, fileCacheReadCountCacheHitTrueReadTypeRandomAttrSet) + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic, fileCacheReadCountCacheHitTrueReadTypeSequentialAttrSet) + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitFalseReadTypeParallelAtomic, fileCacheReadCountCacheHitFalseReadTypeParallelAttrSet) + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitFalseReadTypeRandomAtomic, fileCacheReadCountCacheHitFalseReadTypeRandomAttrSet) + conditionallyObserve(obsrv, &fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic, fileCacheReadCountCacheHitFalseReadTypeSequentialAttrSet) + return nil + })) + + fileCacheReadLatencies, err2 := meter.Int64Histogram("file_cache/read_latencies", + metric.WithDescription("The cumulative distribution of the file cache read latencies along with cache hit - true/false."), metric.WithUnit("us"), - defaultLatencyDistribution) + metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) - if err := errors.Join(err1, err2, err3, err4, err5, err6, err7, err8, err9, err10, err11, err12, err13); err != nil { + _, err3 := meter.Int64ObservableCounter("fs/ops_count", + metric.WithDescription("The cumulative number of ops processed by the file system."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &fsOpsCountFsOpBatchForgetAtomic, fsOpsCountFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpCreateFileAtomic, fsOpsCountFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpCreateLinkAtomic, fsOpsCountFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpCreateSymlinkAtomic, fsOpsCountFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpFallocateAtomic, fsOpsCountFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpFlushFileAtomic, fsOpsCountFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpForgetInodeAtomic, fsOpsCountFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpGetInodeAttributesAtomic, fsOpsCountFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpGetXattrAtomic, fsOpsCountFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpListXattrAtomic, fsOpsCountFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpLookUpInodeAtomic, fsOpsCountFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpMkDirAtomic, fsOpsCountFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpMkNodeAtomic, fsOpsCountFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpOpenDirAtomic, fsOpsCountFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpOpenFileAtomic, fsOpsCountFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpReadDirAtomic, fsOpsCountFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpReadFileAtomic, fsOpsCountFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpReadSymlinkAtomic, fsOpsCountFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpReleaseDirHandleAtomic, fsOpsCountFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpReleaseFileHandleAtomic, fsOpsCountFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpRemoveXattrAtomic, fsOpsCountFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpRenameAtomic, fsOpsCountFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpRmDirAtomic, fsOpsCountFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpSetInodeAttributesAtomic, fsOpsCountFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpSetXattrAtomic, fsOpsCountFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpStatFSAtomic, fsOpsCountFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpSyncFSAtomic, fsOpsCountFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpSyncFileAtomic, fsOpsCountFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpUnlinkAtomic, fsOpsCountFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpWriteFileAtomic, fsOpsCountFsOpWriteFileAttrSet) + return nil + })) + + _, err4 := meter.Int64ObservableCounter("fs/ops_error_count", + metric.WithDescription("The cumulative number of errors generated by file system operations."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAttrSet) + return nil + })) + + fsOpsLatency, err5 := meter.Int64Histogram("fs/ops_latency", + metric.WithDescription("The cumulative distribution of file system operation latencies"), + metric.WithUnit("us"), + metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) + + _, err6 := meter.Int64ObservableCounter("gcs/download_bytes_count", + metric.WithDescription("The cumulative number of bytes downloaded from GCS along with type - Sequential/Random"), + metric.WithUnit("By"), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsDownloadBytesCountReadTypeParallelAtomic, gcsDownloadBytesCountReadTypeParallelAttrSet) + conditionallyObserve(obsrv, &gcsDownloadBytesCountReadTypeRandomAtomic, gcsDownloadBytesCountReadTypeRandomAttrSet) + conditionallyObserve(obsrv, &gcsDownloadBytesCountReadTypeSequentialAtomic, gcsDownloadBytesCountReadTypeSequentialAttrSet) + return nil + })) + + _, err7 := meter.Int64ObservableCounter("gcs/read_bytes_count", + metric.WithDescription("The cumulative number of bytes read from GCS objects."), + metric.WithUnit("By"), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsReadBytesCountAtomic) + return nil + })) + + _, err8 := meter.Int64ObservableCounter("gcs/read_count", + metric.WithDescription("Specifies the number of gcs reads made along with type - Sequential/Random"), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsReadCountReadTypeParallelAtomic, gcsReadCountReadTypeParallelAttrSet) + conditionallyObserve(obsrv, &gcsReadCountReadTypeRandomAtomic, gcsReadCountReadTypeRandomAttrSet) + conditionallyObserve(obsrv, &gcsReadCountReadTypeSequentialAtomic, gcsReadCountReadTypeSequentialAttrSet) + return nil + })) + + _, err9 := meter.Int64ObservableCounter("gcs/reader_count", + metric.WithDescription("The cumulative number of GCS object readers opened or closed."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsReaderCountIoMethodClosedAtomic, gcsReaderCountIoMethodClosedAttrSet) + conditionallyObserve(obsrv, &gcsReaderCountIoMethodOpenedAtomic, gcsReaderCountIoMethodOpenedAttrSet) + return nil + })) + + _, err10 := meter.Int64ObservableCounter("gcs/request_count", + metric.WithDescription("The cumulative number of GCS requests processed along with the GCS method."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodComposeObjectsAtomic, gcsRequestCountGcsMethodComposeObjectsAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodCreateFolderAtomic, gcsRequestCountGcsMethodCreateFolderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, gcsRequestCountGcsMethodCreateObjectChunkWriterAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodDeleteFolderAtomic, gcsRequestCountGcsMethodDeleteFolderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodDeleteObjectAtomic, gcsRequestCountGcsMethodDeleteObjectAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodFinalizeUploadAtomic, gcsRequestCountGcsMethodFinalizeUploadAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodGetFolderAtomic, gcsRequestCountGcsMethodGetFolderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodListObjectsAtomic, gcsRequestCountGcsMethodListObjectsAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodMoveObjectAtomic, gcsRequestCountGcsMethodMoveObjectAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic, gcsRequestCountGcsMethodMultiRangeDownloaderAddAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic, gcsRequestCountGcsMethodNewMultiRangeDownloaderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodNewReaderAtomic, gcsRequestCountGcsMethodNewReaderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodRenameFolderAtomic, gcsRequestCountGcsMethodRenameFolderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodStatObjectAtomic, gcsRequestCountGcsMethodStatObjectAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodUpdateObjectAtomic, gcsRequestCountGcsMethodUpdateObjectAttrSet) + return nil + })) + + gcsRequestLatencies, err11 := meter.Int64Histogram("gcs/request_latencies", + metric.WithDescription("The cumulative distribution of the GCS request latencies."), + metric.WithUnit("ms"), + metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) + + _, err12 := meter.Int64ObservableCounter("gcs/retry_count", + metric.WithDescription("The cumulative number of retry requests made to GCS."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, gcsRetryCountRetryErrorCategoryOTHERERRORSAttrSet) + conditionallyObserve(obsrv, &gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic, gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAttrSet) + return nil + })) + + errs := []error{err0, err1, err2, err3, err4, err5, err6, err7, err8, err9, err10, err11, err12} + if err := errors.Join(errs...); err != nil { return nil, err } return &otelMetrics{ - fsOpsCount: fsOpsCount, - fsOpsErrorCount: fsOpsErrorCount, - fsOpsLatency: fsOpsLatency, - gcsReadCount: gcsReadCount, - gcsReadBytesCountAtomic: &gcsReadBytesCountAtomic, - gcsReaderCount: gcsReaderCount, - gcsRequestCount: gcsRequestCount, - gcsRequestLatency: gcsRequestLatency, - gcsDownloadBytesCount: gcsDownloadBytesCount, - gcsRetryCount: gcsRetryCount, - fileCacheReadCount: fileCacheReadCount, - fileCacheReadBytesCount: fileCacheReadBytesCount, - fileCacheReadLatency: fileCacheReadLatency, + ch: ch, + wg: &wg, + fileCacheReadBytesCountReadTypeParallelAtomic: &fileCacheReadBytesCountReadTypeParallelAtomic, + fileCacheReadBytesCountReadTypeRandomAtomic: &fileCacheReadBytesCountReadTypeRandomAtomic, + fileCacheReadBytesCountReadTypeSequentialAtomic: &fileCacheReadBytesCountReadTypeSequentialAtomic, + fileCacheReadCountCacheHitTrueReadTypeParallelAtomic: &fileCacheReadCountCacheHitTrueReadTypeParallelAtomic, + fileCacheReadCountCacheHitTrueReadTypeRandomAtomic: &fileCacheReadCountCacheHitTrueReadTypeRandomAtomic, + fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic: &fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic, + fileCacheReadCountCacheHitFalseReadTypeParallelAtomic: &fileCacheReadCountCacheHitFalseReadTypeParallelAtomic, + fileCacheReadCountCacheHitFalseReadTypeRandomAtomic: &fileCacheReadCountCacheHitFalseReadTypeRandomAtomic, + fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic: &fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic, + fileCacheReadLatencies: fileCacheReadLatencies, + fsOpsCountFsOpBatchForgetAtomic: &fsOpsCountFsOpBatchForgetAtomic, + fsOpsCountFsOpCreateFileAtomic: &fsOpsCountFsOpCreateFileAtomic, + fsOpsCountFsOpCreateLinkAtomic: &fsOpsCountFsOpCreateLinkAtomic, + fsOpsCountFsOpCreateSymlinkAtomic: &fsOpsCountFsOpCreateSymlinkAtomic, + fsOpsCountFsOpFallocateAtomic: &fsOpsCountFsOpFallocateAtomic, + fsOpsCountFsOpFlushFileAtomic: &fsOpsCountFsOpFlushFileAtomic, + fsOpsCountFsOpForgetInodeAtomic: &fsOpsCountFsOpForgetInodeAtomic, + fsOpsCountFsOpGetInodeAttributesAtomic: &fsOpsCountFsOpGetInodeAttributesAtomic, + fsOpsCountFsOpGetXattrAtomic: &fsOpsCountFsOpGetXattrAtomic, + fsOpsCountFsOpListXattrAtomic: &fsOpsCountFsOpListXattrAtomic, + fsOpsCountFsOpLookUpInodeAtomic: &fsOpsCountFsOpLookUpInodeAtomic, + fsOpsCountFsOpMkDirAtomic: &fsOpsCountFsOpMkDirAtomic, + fsOpsCountFsOpMkNodeAtomic: &fsOpsCountFsOpMkNodeAtomic, + fsOpsCountFsOpOpenDirAtomic: &fsOpsCountFsOpOpenDirAtomic, + fsOpsCountFsOpOpenFileAtomic: &fsOpsCountFsOpOpenFileAtomic, + fsOpsCountFsOpReadDirAtomic: &fsOpsCountFsOpReadDirAtomic, + fsOpsCountFsOpReadFileAtomic: &fsOpsCountFsOpReadFileAtomic, + fsOpsCountFsOpReadSymlinkAtomic: &fsOpsCountFsOpReadSymlinkAtomic, + fsOpsCountFsOpReleaseDirHandleAtomic: &fsOpsCountFsOpReleaseDirHandleAtomic, + fsOpsCountFsOpReleaseFileHandleAtomic: &fsOpsCountFsOpReleaseFileHandleAtomic, + fsOpsCountFsOpRemoveXattrAtomic: &fsOpsCountFsOpRemoveXattrAtomic, + fsOpsCountFsOpRenameAtomic: &fsOpsCountFsOpRenameAtomic, + fsOpsCountFsOpRmDirAtomic: &fsOpsCountFsOpRmDirAtomic, + fsOpsCountFsOpSetInodeAttributesAtomic: &fsOpsCountFsOpSetInodeAttributesAtomic, + fsOpsCountFsOpSetXattrAtomic: &fsOpsCountFsOpSetXattrAtomic, + fsOpsCountFsOpStatFSAtomic: &fsOpsCountFsOpStatFSAtomic, + fsOpsCountFsOpSyncFSAtomic: &fsOpsCountFsOpSyncFSAtomic, + fsOpsCountFsOpSyncFileAtomic: &fsOpsCountFsOpSyncFileAtomic, + fsOpsCountFsOpUnlinkAtomic: &fsOpsCountFsOpUnlinkAtomic, + fsOpsCountFsOpWriteFileAtomic: &fsOpsCountFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic, + fsOpsLatency: fsOpsLatency, + gcsDownloadBytesCountReadTypeParallelAtomic: &gcsDownloadBytesCountReadTypeParallelAtomic, + gcsDownloadBytesCountReadTypeRandomAtomic: &gcsDownloadBytesCountReadTypeRandomAtomic, + gcsDownloadBytesCountReadTypeSequentialAtomic: &gcsDownloadBytesCountReadTypeSequentialAtomic, + gcsReadBytesCountAtomic: &gcsReadBytesCountAtomic, + gcsReadCountReadTypeParallelAtomic: &gcsReadCountReadTypeParallelAtomic, + gcsReadCountReadTypeRandomAtomic: &gcsReadCountReadTypeRandomAtomic, + gcsReadCountReadTypeSequentialAtomic: &gcsReadCountReadTypeSequentialAtomic, + gcsReaderCountIoMethodClosedAtomic: &gcsReaderCountIoMethodClosedAtomic, + gcsReaderCountIoMethodOpenedAtomic: &gcsReaderCountIoMethodOpenedAtomic, + gcsRequestCountGcsMethodComposeObjectsAtomic: &gcsRequestCountGcsMethodComposeObjectsAtomic, + gcsRequestCountGcsMethodCreateFolderAtomic: &gcsRequestCountGcsMethodCreateFolderAtomic, + gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic: &gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, + gcsRequestCountGcsMethodDeleteFolderAtomic: &gcsRequestCountGcsMethodDeleteFolderAtomic, + gcsRequestCountGcsMethodDeleteObjectAtomic: &gcsRequestCountGcsMethodDeleteObjectAtomic, + gcsRequestCountGcsMethodFinalizeUploadAtomic: &gcsRequestCountGcsMethodFinalizeUploadAtomic, + gcsRequestCountGcsMethodGetFolderAtomic: &gcsRequestCountGcsMethodGetFolderAtomic, + gcsRequestCountGcsMethodListObjectsAtomic: &gcsRequestCountGcsMethodListObjectsAtomic, + gcsRequestCountGcsMethodMoveObjectAtomic: &gcsRequestCountGcsMethodMoveObjectAtomic, + gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic: &gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic, + gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic: &gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic, + gcsRequestCountGcsMethodNewReaderAtomic: &gcsRequestCountGcsMethodNewReaderAtomic, + gcsRequestCountGcsMethodRenameFolderAtomic: &gcsRequestCountGcsMethodRenameFolderAtomic, + gcsRequestCountGcsMethodStatObjectAtomic: &gcsRequestCountGcsMethodStatObjectAtomic, + gcsRequestCountGcsMethodUpdateObjectAtomic: &gcsRequestCountGcsMethodUpdateObjectAtomic, + gcsRequestLatencies: gcsRequestLatencies, + gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic: &gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, + gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic: &gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic, }, nil } + +func (o *otelMetrics) Close() { + close(o.ch) + o.wg.Wait() +} + +func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, obsrvOptions ...metric.ObserveOption) { + if val := counter.Load(); val > 0 { + obsrv.Observe(val, obsrvOptions...) + } +} + +func updateUnrecognizedAttribute(newValue string) { + unrecognizedAttr.CompareAndSwap("", newValue) +} + +// StartSampledLogging starts a goroutine that logs unrecognized attributes periodically. +func startSampledLogging(ctx context.Context) { + // Init the atomic.Value + unrecognizedAttr.Store("") + + go func() { + ticker := time.NewTicker(logInterval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + logUnrecognizedAttribute() + } + } + }() +} + +// logUnrecognizedAttribute retrieves and logs any unrecognized attributes. +func logUnrecognizedAttribute() { + // Atomically load and reset the attribute name, then generate a log + // if an unrecognized attribute was encountered. + if currentAttr := unrecognizedAttr.Swap("").(string); currentAttr != "" { + logger.Tracef("Attribute %s is not declared", currentAttr) + } +} diff --git a/metrics/otel_metrics_test.go b/metrics/otel_metrics_test.go index 670bdc7f63..1e6b4edeb2 100644 --- a/metrics/otel_metrics_test.go +++ b/metrics/otel_metrics_test.go @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** + package metrics import ( "context" - "fmt" "testing" "time" @@ -28,33 +29,39 @@ import ( "go.opentelemetry.io/otel/sdk/metric/metricdata" ) +// metricValueMap maps attribute sets to metric values. +type metricValueMap map[string]int64 + +// metricHistogramMap maps attribute sets to histogram data points. +type metricHistogramMap map[string]metricdata.HistogramDataPoint[int64] + func waitForMetricsProcessing() { - // Currently the metrics processing is synchronous, so there is no waiting. - // This will change as we will move to asynchronous metrics. + time.Sleep(time.Millisecond) } -func setupOTel(t *testing.T) (*otelMetrics, *metric.ManualReader) { +func setupOTel(ctx context.Context, t *testing.T) (*otelMetrics, *metric.ManualReader) { t.Helper() reader := metric.NewManualReader() provider := metric.NewMeterProvider(metric.WithReader(reader)) otel.SetMeterProvider(provider) - m, err := NewOTelMetrics() + m, err := NewOTelMetrics(ctx, 10, 100) require.NoError(t, err) return m, reader } // gatherHistogramMetrics collects all histogram metrics from the reader. // It returns a map where the key is the metric name, and the value is another map. -// The inner map's key is the set of attributes, -// and the value is the HistogramDataPoint. -func gatherHistogramMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[attribute.Set]metricdata.HistogramDataPoint[int64] { +// The inner map's key is a string representation of the attributes, +// and the value is the metricdata.HistogramDataPoint. +func gatherHistogramMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[string]metricdata.HistogramDataPoint[int64] { t.Helper() var rm metricdata.ResourceMetrics err := rd.Collect(ctx, &rm) require.NoError(t, err) - results := make(map[string]map[attribute.Set]metricdata.HistogramDataPoint[int64]) + results := make(map[string]map[string]metricdata.HistogramDataPoint[int64]) + encoder := attribute.DefaultEncoder() // Using default encoder for _, sm := range rm.ScopeMetrics { for _, m := range sm.Metrics { @@ -64,13 +71,13 @@ func gatherHistogramMetrics(ctx context.Context, t *testing.T, rd *metric.Manual continue } - metricMap := make(map[attribute.Set]metricdata.HistogramDataPoint[int64]) + metricMap := make(metricHistogramMap) for _, dp := range hist.DataPoints { if dp.Count == 0 { continue } - metricMap[dp.Attributes] = dp + metricMap[dp.Attributes.Encoded(encoder)] = dp } if len(metricMap) > 0 { @@ -84,15 +91,16 @@ func gatherHistogramMetrics(ctx context.Context, t *testing.T, rd *metric.Manual // gatherNonZeroCounterMetrics collects all non-zero counter metrics from the reader. // It returns a map where the key is the metric name, and the value is another map. -// The inner map's key is the set of attributes, -// and the value is the counter's value. -func gatherNonZeroCounterMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[attribute.Set]int64 { +// The inner map's key is a string representation of the attributes, +// and the value is the metric's value. +func gatherNonZeroCounterMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[string]int64 { t.Helper() var rm metricdata.ResourceMetrics err := rd.Collect(ctx, &rm) require.NoError(t, err) - results := make(map[string]map[attribute.Set]int64) + results := make(map[string]map[string]int64) + encoder := attribute.DefaultEncoder() for _, sm := range rm.ScopeMetrics { for _, m := range sm.Metrics { @@ -102,14 +110,13 @@ func gatherNonZeroCounterMetrics(ctx context.Context, t *testing.T, rd *metric.M continue } - metricMap := make(map[attribute.Set]int64) + metricMap := make(metricValueMap) for _, dp := range sum.DataPoints { if dp.Value == 0 { continue } - // The attribute.Set can be used as a map key directly. - metricMap[dp.Attributes] = dp.Value + metricMap[dp.Attributes.Encoded(encoder)] = dp.Value } if len(metricMap) > 0 { @@ -121,255 +128,47 @@ func gatherNonZeroCounterMetrics(ctx context.Context, t *testing.T, rd *metric.M return results } -func TestFsOpsCount(t *testing.T) { - fsOps := []string{ - "StatFS", "LookUpInode", "GetInodeAttributes", "SetInodeAttributes", "ForgetInode", - "BatchForget", "MkDir", "MkNode", "CreateFile", "CreateLink", "CreateSymlink", - "Rename", "RmDir", "Unlink", "OpenDir", "ReadDir", "ReleaseDirHandle", - "OpenFile", "ReadFile", "WriteFile", "SyncFile", "FlushFile", "ReleaseFileHandle", - "ReadSymlink", "RemoveXattr", "GetXattr", "ListXattr", "SetXattr", "Fallocate", "SyncFS", - } - - for _, op := range fsOps { - t.Run(op, func(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - m.OpsCount(context.TODO(), 3, op) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - opsCount, ok := metrics["fs/ops_count"] - expected := map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", op)): 3, - } - assert.True(t, ok, "fs/ops_count metric not found") - assert.Equal(t, expected, opsCount) - }) - } - -} - -func TestMultipleFSOpsSummed(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - m.OpsCount(context.TODO(), 5, "BatchForget") - m.OpsCount(context.TODO(), 2, "CreateFile") - m.OpsCount(context.TODO(), 3, "BatchForget") - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - opsCount, ok := metrics["fs/ops_count"] - assert.True(t, ok, "fs/ops_count metric not found") - expected := map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "BatchForget")): 8, - attribute.NewSet(attribute.String("fs_op", "CreateFile")): 2, - } - assert.Equal(t, expected, opsCount) - -} - -func TestFsOpsErrorCount(t *testing.T) { - fsOps := []string{ - "StatFS", "LookUpInode", "GetInodeAttributes", "SetInodeAttributes", "ForgetInode", - "BatchForget", "MkDir", "MkNode", "CreateFile", "CreateLink", "CreateSymlink", - "Rename", "RmDir", "Unlink", "OpenDir", "ReadDir", "ReleaseDirHandle", - "OpenFile", "ReadFile", "WriteFile", "SyncFile", "FlushFile", "ReleaseFileHandle", - "ReadSymlink", "RemoveXattr", "GetXattr", "ListXattr", "SetXattr", "Fallocate", "SyncFS", - } - fsErrorCategories := []string{ - "DEVICE_ERROR", "DIR_NOT_EMPTY", "FILE_EXISTS", "FILE_DIR_ERROR", "NOT_IMPLEMENTED", - "IO_ERROR", "INTERRUPT_ERROR", "INVALID_ARGUMENT", "INVALID_OPERATION", "MISC_ERROR", - "NETWORK_ERROR", "NO_FILE_OR_DIR", "NOT_A_DIR", "PERM_ERROR", - "PROCESS_RESOURCE_MGMT_ERROR", "TOO_MANY_OPEN_FILES", - } - - for _, op := range fsOps { - for _, category := range fsErrorCategories { - op, category := op, category - t.Run(fmt.Sprintf("%s_%s", op, category), func(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - m.OpsErrorCount(context.TODO(), 5, FSOpsErrorCategory{ErrorCategory: category, FSOps: op}) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - opsErrorCount, ok := metrics["fs/ops_error_count"] - require.True(t, ok, "fs/ops_error_count metric not found") - expectedKey := attribute.NewSet( - attribute.String("fs_error_category", category), - attribute.String("fs_op", op)) - expected := map[attribute.Set]int64{ - expectedKey: 5, - } - assert.Equal(t, expected, opsErrorCount) - }) - } - } - -} - -func TestFsOpsErrorCountSummed(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - m.OpsErrorCount(context.TODO(), 5, FSOpsErrorCategory{ErrorCategory: "IO_ERROR", FSOps: "ReadFile"}) - m.OpsErrorCount(context.TODO(), 3, FSOpsErrorCategory{ErrorCategory: "IO_ERROR", FSOps: "ReadFile"}) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - opsErrorCount, ok := metrics["fs/ops_error_count"] - assert.True(t, ok, "fs/ops_error_count metric not found") - expectedKey := attribute.NewSet( - attribute.String("fs_error_category", "IO_ERROR"), - attribute.String("fs_op", "ReadFile"), - ) - assert.Equal(t, map[attribute.Set]int64{expectedKey: 8}, opsErrorCount) -} - -func TestFsOpsErrorCountDifferentErrors(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - m.OpsErrorCount(context.TODO(), 5, FSOpsErrorCategory{ErrorCategory: "IO_ERROR", FSOps: "ReadFile"}) - m.OpsErrorCount(context.TODO(), 2, FSOpsErrorCategory{ErrorCategory: "NETWORK_ERROR", FSOps: "WriteFile"}) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - opsErrorCount, ok := metrics["fs/ops_error_count"] - assert.True(t, ok, "fs/ops_error_count metric not found") - expected := map[attribute.Set]int64{ - attribute.NewSet( - attribute.String("fs_error_category", "IO_ERROR"), - attribute.String("fs_op", "ReadFile"), - ): 5, - attribute.NewSet( - attribute.String("fs_error_category", "NETWORK_ERROR"), - attribute.String("fs_op", "WriteFile"), - ): 2, - } - assert.Equal(t, expected, opsErrorCount) -} - -func TestFsOpsErrorCountDifferentErrorsSummed(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - m.OpsErrorCount(context.TODO(), 5, FSOpsErrorCategory{ErrorCategory: "IO_ERROR", FSOps: "ReadFile"}) - m.OpsErrorCount(context.TODO(), 2, FSOpsErrorCategory{ErrorCategory: "NETWORK_ERROR", FSOps: "WriteFile"}) - m.OpsErrorCount(context.TODO(), 3, FSOpsErrorCategory{ErrorCategory: "IO_ERROR", FSOps: "ReadFile"}) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - opsErrorCount, ok := metrics["fs/ops_error_count"] - assert.True(t, ok, "fs/ops_error_count metric not found") - expected := map[attribute.Set]int64{ - attribute.NewSet( - attribute.String("fs_error_category", "IO_ERROR"), - attribute.String("fs_op", "ReadFile"), - ): 8, - attribute.NewSet( - attribute.String("fs_error_category", "NETWORK_ERROR"), - attribute.String("fs_op", "WriteFile"), - ): 2, - } - assert.Equal(t, expected, opsErrorCount) -} - -func TestFsOpsLatency(t *testing.T) { - fsOps := []string{ - "StatFS", "LookUpInode", "GetInodeAttributes", "SetInodeAttributes", "ForgetInode", - "BatchForget", "MkDir", "MkNode", "CreateFile", "CreateLink", "CreateSymlink", - "Rename", "RmDir", "Unlink", "OpenDir", "ReadDir", "ReleaseDirHandle", - "OpenFile", "ReadFile", "WriteFile", "SyncFile", "FlushFile", "ReleaseFileHandle", - "ReadSymlink", "RemoveXattr", "GetXattr", "ListXattr", "SetXattr", "Fallocate", "SyncFS", - } - - for _, op := range fsOps { - op := op - t.Run(op, func(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - latency := 123 * time.Microsecond - - m.OpsLatency(ctx, latency, op) - waitForMetricsProcessing() - - metrics := gatherHistogramMetrics(ctx, t, rd) - opsLatency, ok := metrics["fs/ops_latency"] - require.True(t, ok, "fs/ops_latency metric not found") - expectedKey := attribute.NewSet(attribute.String("fs_op", op)) - dp, ok := opsLatency[expectedKey] - require.True(t, ok, "DataPoint not found for key: %s", expectedKey) - assert.Equal(t, uint64(1), dp.Count) - assert.Equal(t, latency.Microseconds(), dp.Sum) - }) - } -} - -func TestFsOpsLatencySummed(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - latency1 := 100 * time.Microsecond - latency2 := 200 * time.Microsecond - - m.OpsLatency(ctx, latency1, "ReadFile") - m.OpsLatency(ctx, latency2, "ReadFile") - waitForMetricsProcessing() - - metrics := gatherHistogramMetrics(ctx, t, rd) - opsLatency, ok := metrics["fs/ops_latency"] - require.True(t, ok, "fs/ops_latency metric not found") - dp, ok := opsLatency[attribute.NewSet(attribute.String("fs_op", "ReadFile"))] - require.True(t, ok, "DataPoint not found for key: fs_op=ReadFile") - assert.Equal(t, uint64(2), dp.Count) - assert.Equal(t, latency1.Microseconds()+latency2.Microseconds(), dp.Sum) -} - -func TestGcsReadCount(t *testing.T) { +func TestFileCacheReadBytesCount(t *testing.T) { tests := []struct { name string f func(m *otelMetrics) expected map[attribute.Set]int64 }{ { - name: "Sequential", + name: "read_type_Parallel", f: func(m *otelMetrics) { - m.GCSReadCount(context.TODO(), 5, "Sequential") + m.FileCacheReadBytesCount(5, "Parallel") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Sequential")): 5, + attribute.NewSet(attribute.String("read_type", "Parallel")): 5, }, }, { - name: "Random", + name: "read_type_Random", f: func(m *otelMetrics) { - m.GCSReadCount(context.TODO(), 3, "Random") + m.FileCacheReadBytesCount(5, "Random") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Random")): 3, + attribute.NewSet(attribute.String("read_type", "Random")): 5, }, }, { - name: "Parallel", + name: "read_type_Sequential", f: func(m *otelMetrics) { - m.GCSReadCount(context.TODO(), 2, "Parallel") + m.FileCacheReadBytesCount(5, "Sequential") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Parallel")): 2, + attribute.NewSet(attribute.String("read_type", "Sequential")): 5, }, - }, - { + }, { name: "multiple_attributes_summed", f: func(m *otelMetrics) { - m.GCSReadCount(context.TODO(), 5, "Sequential") - m.GCSReadCount(context.TODO(), 2, "Random") - m.GCSReadCount(context.TODO(), 3, "Sequential") + m.FileCacheReadBytesCount(5, "Parallel") + m.FileCacheReadBytesCount(2, "Random") + m.FileCacheReadBytesCount(3, "Parallel") }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Sequential")): 8, - attribute.NewSet(attribute.String("read_type", "Random")): 2, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("read_type", "Parallel")): 8, + attribute.NewSet(attribute.String("read_type", "Random")): 2, }, }, } @@ -377,130 +176,92 @@ func TestGcsReadCount(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctx := context.Background() - m, rd := setupOTel(t) + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) tc.f(m) waitForMetricsProcessing() metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - readCount, ok := metrics["gcs/read_count"] - assert.True(t, ok, "gcs/read_count metric not found") - assert.Equal(t, tc.expected, readCount) + metric, ok := metrics["file_cache/read_bytes_count"] + assert.True(t, ok, "file_cache/read_bytes_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) }) } } -func TestGcsDownloadBytesCount(t *testing.T) { +func TestFileCacheReadCount(t *testing.T) { tests := []struct { name string f func(m *otelMetrics) expected map[attribute.Set]int64 }{ { - name: "Sequential", + name: "cache_hit_true_read_type_Parallel", f: func(m *otelMetrics) { - m.GCSDownloadBytesCount(context.TODO(), 500, "Sequential") + m.FileCacheReadCount(5, true, "Parallel") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Sequential")): 500, + attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Parallel")): 5, }, }, { - name: "Random", + name: "cache_hit_true_read_type_Random", f: func(m *otelMetrics) { - m.GCSDownloadBytesCount(context.TODO(), 300, "Random") + m.FileCacheReadCount(5, true, "Random") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Random")): 300, + attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Random")): 5, }, }, { - name: "Parallel", + name: "cache_hit_true_read_type_Sequential", f: func(m *otelMetrics) { - m.GCSDownloadBytesCount(context.TODO(), 200, "Parallel") + m.FileCacheReadCount(5, true, "Sequential") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Parallel")): 200, + attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Sequential")): 5, }, }, { - name: "multiple_attributes_summed", + name: "cache_hit_false_read_type_Parallel", f: func(m *otelMetrics) { - m.GCSDownloadBytesCount(context.TODO(), 500, "Sequential") - m.GCSDownloadBytesCount(context.TODO(), 200, "Random") - m.GCSDownloadBytesCount(context.TODO(), 300, "Sequential") + m.FileCacheReadCount(5, false, "Parallel") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Sequential")): 800, - attribute.NewSet(attribute.String("read_type", "Random")): 200, + attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Parallel")): 5, }, }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - downloadBytes, ok := metrics["gcs/download_bytes_count"] - assert.True(t, ok, "gcs/download_bytes_count metric not found") - assert.Equal(t, tc.expected, downloadBytes) - }) - } -} - -func TestGcsReadBytesCount(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - m.GCSReadBytesCount(context.TODO(), 1024) - m.GCSReadBytesCount(context.TODO(), 2048) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - readBytes, ok := metrics["gcs/read_bytes_count"] - require.True(t, ok, "gcs/read_bytes_count metric not found") - assert.Equal(t, map[attribute.Set]int64{attribute.NewSet(): 3072}, readBytes) -} - -func TestGcsReaderCount(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ { - name: "opened", + name: "cache_hit_false_read_type_Random", f: func(m *otelMetrics) { - m.GCSReaderCount(context.TODO(), 5, "opened") + m.FileCacheReadCount(5, false, "Random") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("io_method", "opened")): 5, + attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Random")): 5, }, }, { - name: "closed", + name: "cache_hit_false_read_type_Sequential", f: func(m *otelMetrics) { - m.GCSReaderCount(context.TODO(), 3, "closed") + m.FileCacheReadCount(5, false, "Sequential") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("io_method", "closed")): 3, + attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Sequential")): 5, }, - }, - { + }, { name: "multiple_attributes_summed", f: func(m *otelMetrics) { - m.GCSReaderCount(context.TODO(), 5, "opened") - m.GCSReaderCount(context.TODO(), 2, "closed") - m.GCSReaderCount(context.TODO(), 3, "opened") + m.FileCacheReadCount(5, true, "Parallel") + m.FileCacheReadCount(2, true, "Random") + m.FileCacheReadCount(3, true, "Parallel") }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("io_method", "opened")): 8, - attribute.NewSet(attribute.String("io_method", "closed")): 2, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Parallel")): 8, + attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Random")): 2, }, }, } @@ -508,370 +269,5473 @@ func TestGcsReaderCount(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctx := context.Background() - m, rd := setupOTel(t) + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) tc.f(m) waitForMetricsProcessing() metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - readerCount, ok := metrics["gcs/reader_count"] - assert.True(t, ok, "gcs/reader_count metric not found") - assert.Equal(t, tc.expected, readerCount) - }) - } -} - -func TestGcsRequestCount(t *testing.T) { - gcsMethods := []string{ - "MultiRangeDownloader::Add", "ComposeObjects", "CreateFolder", "CreateObjectChunkWriter", - "DeleteFolder", "DeleteObject", "FinalizeUpload", "GetFolder", "ListObjects", - "MoveObject", "NewReader", "RenameFolder", "StatObject", "UpdateObject", - } - - for _, method := range gcsMethods { - method := method - t.Run(method, func(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - m.GCSRequestCount(context.TODO(), 5, method) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - requestCount, ok := metrics["gcs/request_count"] - require.True(t, ok, "gcs/request_count metric not found") - expectedKey := attribute.NewSet(attribute.String("gcs_method", method)) - expected := map[attribute.Set]int64{ - expectedKey: 5, + metric, ok := metrics["file_cache/read_count"] + assert.True(t, ok, "file_cache/read_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v } - assert.Equal(t, expected, requestCount) + assert.Equal(t, expectedMap, metric) }) } } -func TestGcsRequestCountSummed(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - m.GCSRequestCount(context.TODO(), 5, "StatObject") - m.GCSRequestCount(context.TODO(), 3, "StatObject") - m.GCSRequestCount(context.TODO(), 2, "ListObjects") - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - requestCount, ok := metrics["gcs/request_count"] - assert.True(t, ok, "gcs/request_count metric not found") - assert.Equal(t, map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "StatObject")): 8, - attribute.NewSet(attribute.String("gcs_method", "ListObjects")): 2, - }, requestCount) -} - -func TestGcsRequestLatencies(t *testing.T) { - gcsMethods := []string{ - "MultiRangeDownloader::Add", "ComposeObjects", "CreateFolder", "CreateObjectChunkWriter", - "DeleteFolder", "DeleteObject", "FinalizeUpload", "GetFolder", "ListObjects", - "MoveObject", "NewReader", "RenameFolder", "StatObject", "UpdateObject", +func TestFileCacheReadLatencies(t *testing.T) { + tests := []struct { + name string + latencies []time.Duration + cacheHit bool + }{ + { + name: "cache_hit_true", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + cacheHit: true, + }, + { + name: "cache_hit_false", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + cacheHit: false, + }, } - for _, method := range gcsMethods { - method := method - t.Run(method, func(t *testing.T) { + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { ctx := context.Background() - m, rd := setupOTel(t) - latency := 123 * time.Millisecond + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + var totalLatency time.Duration - m.GCSRequestLatency(ctx, latency, method) + for _, latency := range tc.latencies { + m.FileCacheReadLatencies(ctx, latency, tc.cacheHit) + totalLatency += latency + } waitForMetricsProcessing() metrics := gatherHistogramMetrics(ctx, t, rd) - requestLatencies, ok := metrics["gcs/request_latencies"] - require.True(t, ok, "gcs/request_latencies metric not found") - expectedKey := attribute.NewSet(attribute.String("gcs_method", method)) - dp, ok := requestLatencies[expectedKey] + metric, ok := metrics["file_cache/read_latencies"] + require.True(t, ok, "file_cache/read_latencies metric not found") + + attrs := []attribute.KeyValue{ + attribute.Bool("cache_hit", tc.cacheHit), + } + s := attribute.NewSet(attrs...) + expectedKey := s.Encoded(encoder) + dp, ok := metric[expectedKey] require.True(t, ok, "DataPoint not found for key: %s", expectedKey) - assert.Equal(t, uint64(1), dp.Count) - assert.Equal(t, latency.Milliseconds(), dp.Sum) + assert.Equal(t, uint64(len(tc.latencies)), dp.Count) + assert.Equal(t, totalLatency.Microseconds(), dp.Sum) }) } } -func TestGcsRequestLatenciesSummed(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - latency1 := 100 * time.Millisecond - latency2 := 200 * time.Millisecond - - m.GCSRequestLatency(ctx, latency1, "StatObject") - m.GCSRequestLatency(ctx, latency2, "StatObject") - waitForMetricsProcessing() - - metrics := gatherHistogramMetrics(ctx, t, rd) - requestLatencies, ok := metrics["gcs/request_latencies"] - require.True(t, ok, "gcs/request_latencies metric not found") - dp, ok := requestLatencies[attribute.NewSet(attribute.String("gcs_method", "StatObject"))] - require.True(t, ok, "DataPoint not found for key: gcs_method=StatObject") - assert.Equal(t, uint64(2), dp.Count) - assert.Equal(t, latency1.Milliseconds()+latency2.Milliseconds(), dp.Sum) -} - -func TestGcsRetryCount(t *testing.T) { +func TestFsOpsCount(t *testing.T) { tests := []struct { name string f func(m *otelMetrics) expected map[attribute.Set]int64 }{ { - name: "STALLED_READ_REQUEST", + name: "fs_op_BatchForget", f: func(m *otelMetrics) { - m.GCSRetryCount(context.TODO(), 5, "STALLED_READ_REQUEST") + m.FsOpsCount(5, "BatchForget") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST")): 5, + attribute.NewSet(attribute.String("fs_op", "BatchForget")): 5, }, }, { - name: "OTHER_ERRORS", + name: "fs_op_CreateFile", f: func(m *otelMetrics) { - m.GCSRetryCount(context.TODO(), 3, "OTHER_ERRORS") + m.FsOpsCount(5, "CreateFile") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS")): 3, + attribute.NewSet(attribute.String("fs_op", "CreateFile")): 5, }, }, { - name: "multiple_attributes_summed", + name: "fs_op_CreateLink", f: func(m *otelMetrics) { - m.GCSRetryCount(context.TODO(), 5, "STALLED_READ_REQUEST") - m.GCSRetryCount(context.TODO(), 2, "OTHER_ERRORS") - m.GCSRetryCount(context.TODO(), 3, "STALLED_READ_REQUEST") + m.FsOpsCount(5, "CreateLink") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST")): 8, - attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS")): 2, + attribute.NewSet(attribute.String("fs_op", "CreateLink")): 5, }, }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - retryCount, ok := metrics["gcs/retry_count"] - assert.True(t, ok, "gcs/retry_count metric not found") - assert.Equal(t, tc.expected, retryCount) - }) - } -} - -func TestFileCacheReadCountNew(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ { - name: "cache_hit_true_sequential", + name: "fs_op_CreateSymlink", f: func(m *otelMetrics) { - m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: true, ReadType: "Sequential"}) + m.FsOpsCount(5, "CreateSymlink") }, expected: map[attribute.Set]int64{ - attribute.NewSet( - attribute.Bool("cache_hit", true), - attribute.String("read_type", "Sequential")): 5, + attribute.NewSet(attribute.String("fs_op", "CreateSymlink")): 5, }, }, { - name: "cache_hit_true_random", + name: "fs_op_Fallocate", f: func(m *otelMetrics) { - m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: true, ReadType: "Random"}) + m.FsOpsCount(5, "Fallocate") }, expected: map[attribute.Set]int64{ - attribute.NewSet( - attribute.Bool("cache_hit", true), - attribute.String("read_type", "Random")): 5, + attribute.NewSet(attribute.String("fs_op", "Fallocate")): 5, }, }, { - name: "cache_hit_true_parallel", + name: "fs_op_FlushFile", f: func(m *otelMetrics) { - m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: true, ReadType: "Parallel"}) + m.FsOpsCount(5, "FlushFile") }, expected: map[attribute.Set]int64{ - attribute.NewSet( - attribute.Bool("cache_hit", true), - attribute.String("read_type", "Parallel")): 5, + attribute.NewSet(attribute.String("fs_op", "FlushFile")): 5, }, }, { - name: "cache_hit_false_sequential", + name: "fs_op_ForgetInode", f: func(m *otelMetrics) { - m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: false, ReadType: "Sequential"}) + m.FsOpsCount(5, "ForgetInode") }, expected: map[attribute.Set]int64{ - attribute.NewSet( - attribute.Bool("cache_hit", false), - attribute.String("read_type", "Sequential")): 5, + attribute.NewSet(attribute.String("fs_op", "ForgetInode")): 5, }, }, { - name: "cache_hit_false_random", + name: "fs_op_GetInodeAttributes", f: func(m *otelMetrics) { - m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: false, ReadType: "Random"}) + m.FsOpsCount(5, "GetInodeAttributes") }, expected: map[attribute.Set]int64{ - attribute.NewSet( - attribute.Bool("cache_hit", false), - attribute.String("read_type", "Random")): 5, + attribute.NewSet(attribute.String("fs_op", "GetInodeAttributes")): 5, }, }, { - name: "cache_hit_false_parallel", + name: "fs_op_GetXattr", f: func(m *otelMetrics) { - m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: false, ReadType: "Parallel"}) + m.FsOpsCount(5, "GetXattr") }, expected: map[attribute.Set]int64{ - attribute.NewSet( - attribute.Bool("cache_hit", false), - attribute.String("read_type", "Parallel")): 5, + attribute.NewSet(attribute.String("fs_op", "GetXattr")): 5, }, }, { - name: "multiple_attributes_summed", + name: "fs_op_ListXattr", f: func(m *otelMetrics) { - m.FileCacheReadCount(context.TODO(), 5, CacheHitReadType{CacheHit: true, ReadType: "Sequential"}) - m.FileCacheReadCount(context.TODO(), 2, CacheHitReadType{CacheHit: false, ReadType: "Random"}) - m.FileCacheReadCount(context.TODO(), 3, CacheHitReadType{CacheHit: true, ReadType: "Sequential"}) + m.FsOpsCount(5, "ListXattr") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Random")): 2, - attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Sequential")): 8, + attribute.NewSet(attribute.String("fs_op", "ListXattr")): 5, }, }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - readCount, ok := metrics["file_cache/read_count"] - assert.True(t, ok, "file_cache/read_count metric not found") - assert.Equal(t, tc.expected, readCount) - }) - } -} - -func TestFileCacheReadBytesCountNew(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ { - name: "Sequential", + name: "fs_op_LookUpInode", f: func(m *otelMetrics) { - m.FileCacheReadBytesCount(context.TODO(), 500, "Sequential") + m.FsOpsCount(5, "LookUpInode") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Sequential")): 500, + attribute.NewSet(attribute.String("fs_op", "LookUpInode")): 5, }, }, { - name: "Random", + name: "fs_op_MkDir", f: func(m *otelMetrics) { - m.FileCacheReadBytesCount(context.TODO(), 300, "Random") + m.FsOpsCount(5, "MkDir") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Random")): 300, + attribute.NewSet(attribute.String("fs_op", "MkDir")): 5, }, }, { - name: "Parallel", + name: "fs_op_MkNode", f: func(m *otelMetrics) { - m.FileCacheReadBytesCount(context.TODO(), 200, "Parallel") + m.FsOpsCount(5, "MkNode") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Parallel")): 200, + attribute.NewSet(attribute.String("fs_op", "MkNode")): 5, }, }, { - name: "multiple_attributes_summed", + name: "fs_op_OpenDir", f: func(m *otelMetrics) { - m.FileCacheReadBytesCount(context.TODO(), 500, "Sequential") - m.FileCacheReadBytesCount(context.TODO(), 200, "Random") - m.FileCacheReadBytesCount(context.TODO(), 300, "Sequential") + m.FsOpsCount(5, "OpenDir") }, expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Random")): 200, - attribute.NewSet(attribute.String("read_type", "Sequential")): 800, + attribute.NewSet(attribute.String("fs_op", "OpenDir")): 5, }, }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - m, rd := setupOTel(t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - readBytesCount, ok := metrics["file_cache/read_bytes_count"] - assert.True(t, ok, "file_cache/read_bytes_count metric not found") - assert.Equal(t, tc.expected, readBytesCount) - }) - } -} - -func TestFileCacheReadLatencies(t *testing.T) { - tests := []struct { - name string - cacheHit bool - latencies []time.Duration - }{ { - name: "cache_hit_true", - cacheHit: true, - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + name: "fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "OpenFile")): 5, + }, }, { - name: "cache_hit_false", - cacheHit: false, - latencies: []time.Duration{300 * time.Microsecond, 400 * time.Microsecond}, + name: "fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "WriteFile")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "BatchForget") + m.FsOpsCount(2, "CreateFile") + m.FsOpsCount(3, "BatchForget") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("fs_op", "BatchForget")): 8, + attribute.NewSet(attribute.String("fs_op", "CreateFile")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["fs/ops_count"] + assert.True(t, ok, "fs/ops_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestFsOpsErrorCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "fs_error_category_DEVICE_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_IO_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_MISC_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_PERM_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_BatchForget", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "BatchForget") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "BatchForget")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_CreateFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "CreateFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_CreateLink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "CreateLink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateLink")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_CreateSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "CreateSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateSymlink")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_Fallocate", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "Fallocate") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Fallocate")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_FlushFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "FlushFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "FlushFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ForgetInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ForgetInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ForgetInode")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_GetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "GetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_GetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "GetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetXattr")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ListXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ListXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ListXattr")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_LookUpInode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "LookUpInode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "LookUpInode")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_MkDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "MkDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkDir")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_MkNode", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "MkNode") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkNode")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_OpenDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "OpenDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenDir")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_OpenFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "OpenFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReadDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReadDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadDir")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReadFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReadFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReadSymlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReadSymlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadSymlink")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReleaseDirHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReleaseDirHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseDirHandle")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReleaseFileHandle", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReleaseFileHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseFileHandle")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_RemoveXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "RemoveXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RemoveXattr")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_Rename", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "Rename") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Rename")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_RmDir", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "RmDir") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RmDir")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SetInodeAttributes", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SetInodeAttributes") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetInodeAttributes")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SetXattr", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SetXattr") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetXattr")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_StatFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "StatFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "StatFS")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SyncFS", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SyncFS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFS")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SyncFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SyncFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFile")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_Unlink", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "Unlink") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Unlink")): 5, + }, + }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_WriteFile", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "WriteFile") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "WriteFile")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "BatchForget") + m.FsOpsErrorCount(2, "DEVICE_ERROR", "CreateFile") + m.FsOpsErrorCount(3, "DEVICE_ERROR", "BatchForget") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "BatchForget")): 8, + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateFile")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["fs/ops_error_count"] + assert.True(t, ok, "fs/ops_error_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestFsOpsLatency(t *testing.T) { + tests := []struct { + name string + latencies []time.Duration + fsOp string + }{ + { + name: "fs_op_BatchForget", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "BatchForget", + }, + { + name: "fs_op_CreateFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "CreateFile", + }, + { + name: "fs_op_CreateLink", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "CreateLink", + }, + { + name: "fs_op_CreateSymlink", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "CreateSymlink", + }, + { + name: "fs_op_Fallocate", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "Fallocate", + }, + { + name: "fs_op_FlushFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "FlushFile", + }, + { + name: "fs_op_ForgetInode", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ForgetInode", + }, + { + name: "fs_op_GetInodeAttributes", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "GetInodeAttributes", + }, + { + name: "fs_op_GetXattr", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "GetXattr", + }, + { + name: "fs_op_ListXattr", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ListXattr", + }, + { + name: "fs_op_LookUpInode", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "LookUpInode", + }, + { + name: "fs_op_MkDir", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "MkDir", + }, + { + name: "fs_op_MkNode", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "MkNode", + }, + { + name: "fs_op_OpenDir", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "OpenDir", + }, + { + name: "fs_op_OpenFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "OpenFile", + }, + { + name: "fs_op_ReadDir", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ReadDir", + }, + { + name: "fs_op_ReadFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ReadFile", + }, + { + name: "fs_op_ReadSymlink", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ReadSymlink", + }, + { + name: "fs_op_ReleaseDirHandle", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ReleaseDirHandle", + }, + { + name: "fs_op_ReleaseFileHandle", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ReleaseFileHandle", + }, + { + name: "fs_op_RemoveXattr", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "RemoveXattr", + }, + { + name: "fs_op_Rename", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "Rename", + }, + { + name: "fs_op_RmDir", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "RmDir", + }, + { + name: "fs_op_SetInodeAttributes", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "SetInodeAttributes", + }, + { + name: "fs_op_SetXattr", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "SetXattr", + }, + { + name: "fs_op_StatFS", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "StatFS", + }, + { + name: "fs_op_SyncFS", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "SyncFS", + }, + { + name: "fs_op_SyncFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "SyncFile", + }, + { + name: "fs_op_Unlink", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "Unlink", + }, + { + name: "fs_op_WriteFile", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "WriteFile", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + var totalLatency time.Duration + + for _, latency := range tc.latencies { + m.FsOpsLatency(ctx, latency, tc.fsOp) + totalLatency += latency + } + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + metric, ok := metrics["fs/ops_latency"] + require.True(t, ok, "fs/ops_latency metric not found") + + attrs := []attribute.KeyValue{ + attribute.String("fs_op", tc.fsOp), + } + s := attribute.NewSet(attrs...) + expectedKey := s.Encoded(encoder) + dp, ok := metric[expectedKey] + require.True(t, ok, "DataPoint not found for key: %s", expectedKey) + assert.Equal(t, uint64(len(tc.latencies)), dp.Count) + assert.Equal(t, totalLatency.Microseconds(), dp.Sum) + }) + } +} + +func TestGcsDownloadBytesCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "read_type_Parallel", + f: func(m *otelMetrics) { + m.GcsDownloadBytesCount(5, "Parallel") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Parallel")): 5, + }, + }, + { + name: "read_type_Random", + f: func(m *otelMetrics) { + m.GcsDownloadBytesCount(5, "Random") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Random")): 5, + }, + }, + { + name: "read_type_Sequential", + f: func(m *otelMetrics) { + m.GcsDownloadBytesCount(5, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Sequential")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GcsDownloadBytesCount(5, "Parallel") + m.GcsDownloadBytesCount(2, "Random") + m.GcsDownloadBytesCount(3, "Parallel") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("read_type", "Parallel")): 8, + attribute.NewSet(attribute.String("read_type", "Random")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/download_bytes_count"] + assert.True(t, ok, "gcs/download_bytes_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestGcsReadBytesCount(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + m.GcsReadBytesCount(1024) + m.GcsReadBytesCount(2048) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/read_bytes_count"] + require.True(t, ok, "gcs/read_bytes_count metric not found") + s := attribute.NewSet() + assert.Equal(t, map[string]int64{s.Encoded(encoder): 3072}, metric) +} + +func TestGcsReadCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "read_type_Parallel", + f: func(m *otelMetrics) { + m.GcsReadCount(5, "Parallel") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Parallel")): 5, + }, + }, + { + name: "read_type_Random", + f: func(m *otelMetrics) { + m.GcsReadCount(5, "Random") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Random")): 5, + }, + }, + { + name: "read_type_Sequential", + f: func(m *otelMetrics) { + m.GcsReadCount(5, "Sequential") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("read_type", "Sequential")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GcsReadCount(5, "Parallel") + m.GcsReadCount(2, "Random") + m.GcsReadCount(3, "Parallel") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("read_type", "Parallel")): 8, + attribute.NewSet(attribute.String("read_type", "Random")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/read_count"] + assert.True(t, ok, "gcs/read_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestGcsReaderCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "io_method_closed", + f: func(m *otelMetrics) { + m.GcsReaderCount(5, "closed") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("io_method", "closed")): 5, + }, + }, + { + name: "io_method_opened", + f: func(m *otelMetrics) { + m.GcsReaderCount(5, "opened") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("io_method", "opened")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GcsReaderCount(5, "closed") + m.GcsReaderCount(2, "opened") + m.GcsReaderCount(3, "closed") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("io_method", "closed")): 8, + attribute.NewSet(attribute.String("io_method", "opened")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/reader_count"] + assert.True(t, ok, "gcs/reader_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestGcsRequestCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "gcs_method_ComposeObjects", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "ComposeObjects") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "ComposeObjects")): 5, + }, + }, + { + name: "gcs_method_CreateFolder", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "CreateFolder") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "CreateFolder")): 5, + }, + }, + { + name: "gcs_method_CreateObjectChunkWriter", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "CreateObjectChunkWriter") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "CreateObjectChunkWriter")): 5, + }, + }, + { + name: "gcs_method_DeleteFolder", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "DeleteFolder") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "DeleteFolder")): 5, + }, + }, + { + name: "gcs_method_DeleteObject", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "DeleteObject") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "DeleteObject")): 5, + }, + }, + { + name: "gcs_method_FinalizeUpload", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "FinalizeUpload") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload")): 5, + }, + }, + { + name: "gcs_method_GetFolder", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "GetFolder") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "GetFolder")): 5, + }, + }, + { + name: "gcs_method_ListObjects", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "ListObjects") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "ListObjects")): 5, + }, + }, + { + name: "gcs_method_MoveObject", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "MoveObject") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "MoveObject")): 5, + }, + }, + { + name: "gcs_method_MultiRangeDownloader::Add", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "MultiRangeDownloader::Add") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "MultiRangeDownloader::Add")): 5, + }, + }, + { + name: "gcs_method_NewMultiRangeDownloader", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "NewMultiRangeDownloader") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "NewMultiRangeDownloader")): 5, + }, + }, + { + name: "gcs_method_NewReader", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "NewReader") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "NewReader")): 5, + }, + }, + { + name: "gcs_method_RenameFolder", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "RenameFolder") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "RenameFolder")): 5, + }, + }, + { + name: "gcs_method_StatObject", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "StatObject") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "StatObject")): 5, + }, + }, + { + name: "gcs_method_UpdateObject", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "UpdateObject") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "UpdateObject")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "ComposeObjects") + m.GcsRequestCount(2, "CreateFolder") + m.GcsRequestCount(3, "ComposeObjects") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("gcs_method", "ComposeObjects")): 8, + attribute.NewSet(attribute.String("gcs_method", "CreateFolder")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/request_count"] + assert.True(t, ok, "gcs/request_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestGcsRequestLatencies(t *testing.T) { + tests := []struct { + name string + latencies []time.Duration + gcsMethod string + }{ + { + name: "gcs_method_ComposeObjects", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "ComposeObjects", + }, + { + name: "gcs_method_CreateFolder", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "CreateFolder", + }, + { + name: "gcs_method_CreateObjectChunkWriter", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "CreateObjectChunkWriter", + }, + { + name: "gcs_method_DeleteFolder", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "DeleteFolder", + }, + { + name: "gcs_method_DeleteObject", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "DeleteObject", + }, + { + name: "gcs_method_FinalizeUpload", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "FinalizeUpload", + }, + { + name: "gcs_method_GetFolder", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "GetFolder", + }, + { + name: "gcs_method_ListObjects", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "ListObjects", + }, + { + name: "gcs_method_MoveObject", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "MoveObject", + }, + { + name: "gcs_method_MultiRangeDownloader::Add", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "MultiRangeDownloader::Add", + }, + { + name: "gcs_method_NewMultiRangeDownloader", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "NewMultiRangeDownloader", + }, + { + name: "gcs_method_NewReader", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "NewReader", + }, + { + name: "gcs_method_RenameFolder", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "RenameFolder", + }, + { + name: "gcs_method_StatObject", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "StatObject", + }, + { + name: "gcs_method_UpdateObject", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "UpdateObject", }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { ctx := context.Background() - m, rd := setupOTel(t) + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) var totalLatency time.Duration for _, latency := range tc.latencies { - m.FileCacheReadLatency(ctx, latency, tc.cacheHit) + m.GcsRequestLatencies(ctx, latency, tc.gcsMethod) totalLatency += latency } waitForMetricsProcessing() metrics := gatherHistogramMetrics(ctx, t, rd) - readLatencies, ok := metrics["file_cache/read_latencies"] - require.True(t, ok, "file_cache/read_latencies metric not found") - expectedKey := attribute.NewSet(attribute.Bool("cache_hit", tc.cacheHit)) - dp, ok := readLatencies[expectedKey] + metric, ok := metrics["gcs/request_latencies"] + require.True(t, ok, "gcs/request_latencies metric not found") + + attrs := []attribute.KeyValue{ + attribute.String("gcs_method", tc.gcsMethod), + } + s := attribute.NewSet(attrs...) + expectedKey := s.Encoded(encoder) + dp, ok := metric[expectedKey] require.True(t, ok, "DataPoint not found for key: %s", expectedKey) assert.Equal(t, uint64(len(tc.latencies)), dp.Count) - assert.Equal(t, totalLatency.Microseconds(), dp.Sum) + assert.Equal(t, totalLatency.Milliseconds(), dp.Sum) + }) + } +} + +func TestGcsRetryCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "retry_error_category_OTHER_ERRORS", + f: func(m *otelMetrics) { + m.GcsRetryCount(5, "OTHER_ERRORS") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS")): 5, + }, + }, + { + name: "retry_error_category_STALLED_READ_REQUEST", + f: func(m *otelMetrics) { + m.GcsRetryCount(5, "STALLED_READ_REQUEST") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.GcsRetryCount(5, "OTHER_ERRORS") + m.GcsRetryCount(2, "STALLED_READ_REQUEST") + m.GcsRetryCount(3, "OTHER_ERRORS") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS")): 8, + attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["gcs/retry_count"] + assert.True(t, ok, "gcs/retry_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) }) } } diff --git a/metrics/telemetry.go b/metrics/telemetry.go deleted file mode 100644 index b1ffed5950..0000000000 --- a/metrics/telemetry.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - "context" - "fmt" - "time" - - "go.opentelemetry.io/otel/metric" -) - -// The default time buckets for latency metrics. -// The unit can however change for different units i.e. for one metric the unit could be microseconds and for another it could be milliseconds. -var defaultLatencyDistribution = metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000) - -// Pair of CacheHit and ReadType attributes -type CacheHitReadType struct { - CacheHit bool - ReadType string -} - -// Pair of FSOp and ErrorCategory attributes -type FSOpsErrorCategory struct { - FSOps string - ErrorCategory string -} - -// MetricAttr represents the attributes associated with a metric. -type MetricAttr struct { - Key, Value string -} - -func (a *MetricAttr) String() string { - return fmt.Sprintf("Key: %s, Value: %s", a.Key, a.Value) -} - -type GCSMetricHandle interface { - GCSReadBytesCount(ctx context.Context, inc int64) - GCSReaderCount(ctx context.Context, inc int64, ioMethod string) - GCSRequestCount(ctx context.Context, inc int64, gcsMethod string) - GCSRequestLatency(ctx context.Context, latency time.Duration, gcsMethod string) - GCSReadCount(ctx context.Context, inc int64, readType string) - GCSDownloadBytesCount(ctx context.Context, inc int64, readType string) - GCSRetryCount(ctx context.Context, inc int64, gcsRetryErr string) -} - -type OpsMetricHandle interface { - OpsCount(ctx context.Context, inc int64, fsOp string) - OpsLatency(ctx context.Context, latency time.Duration, fsOp string) - OpsErrorCount(ctx context.Context, inc int64, attrs FSOpsErrorCategory) -} - -type FileCacheMetricHandle interface { - FileCacheReadCount(ctx context.Context, inc int64, attrs CacheHitReadType) - FileCacheReadBytesCount(ctx context.Context, inc int64, readType string) - FileCacheReadLatency(ctx context.Context, latency time.Duration, cacheHit bool) -} -type MetricHandle interface { - GCSMetricHandle - OpsMetricHandle - FileCacheMetricHandle -} - -func CaptureGCSReadMetrics(ctx context.Context, metricHandle MetricHandle, readType string, requestedDataSize int64) { - metricHandle.GCSReadCount(ctx, 1, readType) - metricHandle.GCSDownloadBytesCount(ctx, requestedDataSize, readType) -} diff --git a/metrics/telemetry_test.go b/metrics/telemetry_test.go deleted file mode 100644 index 3765ff57fd..0000000000 --- a/metrics/telemetry_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package metrics - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" -) - -type int64DataPoint struct { - v int64 - attr metric.MeasurementOption -} - -type fakeMetricHandle struct { - noopMetrics - GCSReadBytesCounter []int64DataPoint - GCSDownloadBytesCounter []int64DataPoint -} - -func (f *fakeMetricHandle) GCSReadCount(ctx context.Context, inc int64, readType string) { - f.GCSReadBytesCounter = append(f.GCSReadBytesCounter, int64DataPoint{ - v: inc, - attr: metric.WithAttributes(attribute.String("read_type", readType)), - }) -} - -func (f *fakeMetricHandle) GCSDownloadBytesCount(ctx context.Context, requestedDataSize int64, readType string) { - f.GCSDownloadBytesCounter = append(f.GCSDownloadBytesCounter, int64DataPoint{ - v: requestedDataSize, - attr: metric.WithAttributes(attribute.String("read_type", readType)), - }) -} - -func TestCaptureGCSReadMetrics(t *testing.T) { - t.Parallel() - metricHandle := fakeMetricHandle{} - - CaptureGCSReadMetrics(context.Background(), &metricHandle, "Sequential", 64) - - require.Len(t, metricHandle.GCSReadBytesCounter, 1) - require.Len(t, metricHandle.GCSDownloadBytesCounter, 1) - assert.Equal(t, metricHandle.GCSReadBytesCounter[0], int64DataPoint{ - v: 1, - attr: metric.WithAttributes(attribute.String("read_type", "Sequential")), - }) - assert.Equal(t, metricHandle.GCSDownloadBytesCounter[0], int64DataPoint{ - v: 64, - attr: metric.WithAttributes(attribute.String("read_type", "Sequential")), - }) -} diff --git a/optimizedmetrics/noop_metrics.go b/optimizedmetrics/noop_metrics.go deleted file mode 100644 index 5b1424efc3..0000000000 --- a/optimizedmetrics/noop_metrics.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** -package optimizedmetrics - -import ( - "context" - "time" -) - -type noopMetrics struct{} - -func (*noopMetrics) FileCacheReadBytesCount(inc int64, readType string) {} - -func (*noopMetrics) FileCacheReadCount(inc int64, cacheHit bool, readType string) {} - -func (*noopMetrics) FileCacheReadLatencies(ctx context.Context, duration time.Duration, cacheHit bool) { -} - -func (*noopMetrics) FsOpsCount(inc int64, fsOp string) {} - -func (*noopMetrics) FsOpsErrorCount(inc int64, fsErrorCategory string, fsOp string) {} - -func (*noopMetrics) FsOpsLatency(ctx context.Context, duration time.Duration, fsOp string) {} - -func (*noopMetrics) GcsDownloadBytesCount(inc int64, readType string) {} - -func (*noopMetrics) GcsReadBytesCount(inc int64) {} - -func (*noopMetrics) GcsReadCount(inc int64, readType string) {} - -func (*noopMetrics) GcsReaderCount(inc int64, ioMethod string) {} - -func (*noopMetrics) GcsRequestCount(inc int64, gcsMethod string) {} - -func (*noopMetrics) GcsRequestLatencies(ctx context.Context, duration time.Duration, gcsMethod string) { -} - -func (*noopMetrics) GcsRetryCount(inc int64, retryErrorCategory string) {} - -func NewNoopMetrics() MetricHandle { - var n noopMetrics - return &n -} diff --git a/optimizedmetrics/otel_metrics.go b/optimizedmetrics/otel_metrics.go deleted file mode 100644 index cdfa6905b6..0000000000 --- a/optimizedmetrics/otel_metrics.go +++ /dev/null @@ -1,4386 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** - -package optimizedmetrics - -import ( - "context" - "errors" - "sync" - "sync/atomic" - "time" - - "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/metric" -) - -const logInterval = 5 * time.Minute - -var ( - unrecognizedAttr atomic.Value - fileCacheReadBytesCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) - fileCacheReadBytesCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) - fileCacheReadBytesCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) - fileCacheReadCountCacheHitTrueReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Parallel"))) - fileCacheReadCountCacheHitTrueReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Random"))) - fileCacheReadCountCacheHitTrueReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Sequential"))) - fileCacheReadCountCacheHitFalseReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Parallel"))) - fileCacheReadCountCacheHitFalseReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Random"))) - fileCacheReadCountCacheHitFalseReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Sequential"))) - fileCacheReadLatenciesCacheHitTrueAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", true))) - fileCacheReadLatenciesCacheHitFalseAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.Bool("cache_hit", false))) - fsOpsCountFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "BatchForget"))) - fsOpsCountFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateFile"))) - fsOpsCountFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateLink"))) - fsOpsCountFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateSymlink"))) - fsOpsCountFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Fallocate"))) - fsOpsCountFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "FlushFile"))) - fsOpsCountFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ForgetInode"))) - fsOpsCountFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsCountFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetXattr"))) - fsOpsCountFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ListXattr"))) - fsOpsCountFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "LookUpInode"))) - fsOpsCountFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkDir"))) - fsOpsCountFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkNode"))) - fsOpsCountFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenDir"))) - fsOpsCountFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenFile"))) - fsOpsCountFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadDir"))) - fsOpsCountFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadFile"))) - fsOpsCountFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadSymlink"))) - fsOpsCountFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsCountFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsCountFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RemoveXattr"))) - fsOpsCountFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Rename"))) - fsOpsCountFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RmDir"))) - fsOpsCountFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsCountFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetXattr"))) - fsOpsCountFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "StatFS"))) - fsOpsCountFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFS"))) - fsOpsCountFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFile"))) - fsOpsCountFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Unlink"))) - fsOpsCountFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "WriteFile"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "BatchForget"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateFile"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateLink"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateSymlink"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Fallocate"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "FlushFile"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ForgetInode"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetXattr"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ListXattr"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "LookUpInode"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkDir"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkNode"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenDir"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenFile"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadDir"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadFile"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadSymlink"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RemoveXattr"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Rename"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RmDir"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetXattr"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "StatFS"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFS"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFile"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Unlink"))) - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "WriteFile"))) - fsOpsLatencyFsOpBatchForgetAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "BatchForget"))) - fsOpsLatencyFsOpCreateFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateFile"))) - fsOpsLatencyFsOpCreateLinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateLink"))) - fsOpsLatencyFsOpCreateSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "CreateSymlink"))) - fsOpsLatencyFsOpFallocateAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Fallocate"))) - fsOpsLatencyFsOpFlushFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "FlushFile"))) - fsOpsLatencyFsOpForgetInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ForgetInode"))) - fsOpsLatencyFsOpGetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetInodeAttributes"))) - fsOpsLatencyFsOpGetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "GetXattr"))) - fsOpsLatencyFsOpListXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ListXattr"))) - fsOpsLatencyFsOpLookUpInodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "LookUpInode"))) - fsOpsLatencyFsOpMkDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkDir"))) - fsOpsLatencyFsOpMkNodeAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "MkNode"))) - fsOpsLatencyFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenDir"))) - fsOpsLatencyFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenFile"))) - fsOpsLatencyFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadDir"))) - fsOpsLatencyFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadFile"))) - fsOpsLatencyFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadSymlink"))) - fsOpsLatencyFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseDirHandle"))) - fsOpsLatencyFsOpReleaseFileHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseFileHandle"))) - fsOpsLatencyFsOpRemoveXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RemoveXattr"))) - fsOpsLatencyFsOpRenameAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Rename"))) - fsOpsLatencyFsOpRmDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "RmDir"))) - fsOpsLatencyFsOpSetInodeAttributesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetInodeAttributes"))) - fsOpsLatencyFsOpSetXattrAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SetXattr"))) - fsOpsLatencyFsOpStatFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "StatFS"))) - fsOpsLatencyFsOpSyncFSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFS"))) - fsOpsLatencyFsOpSyncFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "SyncFile"))) - fsOpsLatencyFsOpUnlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "Unlink"))) - fsOpsLatencyFsOpWriteFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "WriteFile"))) - gcsDownloadBytesCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) - gcsDownloadBytesCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) - gcsDownloadBytesCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) - gcsReadCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) - gcsReadCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) - gcsReadCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) - gcsReaderCountIoMethodClosedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("io_method", "closed"))) - gcsReaderCountIoMethodOpenedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("io_method", "opened"))) - gcsRequestCountGcsMethodComposeObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ComposeObjects"))) - gcsRequestCountGcsMethodCreateFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateFolder"))) - gcsRequestCountGcsMethodCreateObjectChunkWriterAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateObjectChunkWriter"))) - gcsRequestCountGcsMethodDeleteFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteFolder"))) - gcsRequestCountGcsMethodDeleteObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteObject"))) - gcsRequestCountGcsMethodFinalizeUploadAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload"))) - gcsRequestCountGcsMethodGetFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "GetFolder"))) - gcsRequestCountGcsMethodListObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ListObjects"))) - gcsRequestCountGcsMethodMoveObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MoveObject"))) - gcsRequestCountGcsMethodMultiRangeDownloaderAddAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MultiRangeDownloader::Add"))) - gcsRequestCountGcsMethodNewMultiRangeDownloaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewMultiRangeDownloader"))) - gcsRequestCountGcsMethodNewReaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewReader"))) - gcsRequestCountGcsMethodRenameFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "RenameFolder"))) - gcsRequestCountGcsMethodStatObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "StatObject"))) - gcsRequestCountGcsMethodUpdateObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "UpdateObject"))) - gcsRequestLatenciesGcsMethodComposeObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ComposeObjects"))) - gcsRequestLatenciesGcsMethodCreateFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateFolder"))) - gcsRequestLatenciesGcsMethodCreateObjectChunkWriterAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateObjectChunkWriter"))) - gcsRequestLatenciesGcsMethodDeleteFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteFolder"))) - gcsRequestLatenciesGcsMethodDeleteObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteObject"))) - gcsRequestLatenciesGcsMethodFinalizeUploadAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload"))) - gcsRequestLatenciesGcsMethodGetFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "GetFolder"))) - gcsRequestLatenciesGcsMethodListObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ListObjects"))) - gcsRequestLatenciesGcsMethodMoveObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MoveObject"))) - gcsRequestLatenciesGcsMethodMultiRangeDownloaderAddAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MultiRangeDownloader::Add"))) - gcsRequestLatenciesGcsMethodNewMultiRangeDownloaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewMultiRangeDownloader"))) - gcsRequestLatenciesGcsMethodNewReaderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "NewReader"))) - gcsRequestLatenciesGcsMethodRenameFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "RenameFolder"))) - gcsRequestLatenciesGcsMethodStatObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "StatObject"))) - gcsRequestLatenciesGcsMethodUpdateObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "UpdateObject"))) - gcsRetryCountRetryErrorCategoryOTHERERRORSAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS"))) - gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST"))) -) - -type histogramRecord struct { - ctx context.Context - instrument metric.Int64Histogram - value int64 - attributes metric.RecordOption -} - -type otelMetrics struct { - ch chan histogramRecord - wg *sync.WaitGroup - fileCacheReadBytesCountReadTypeParallelAtomic *atomic.Int64 - fileCacheReadBytesCountReadTypeRandomAtomic *atomic.Int64 - fileCacheReadBytesCountReadTypeSequentialAtomic *atomic.Int64 - fileCacheReadCountCacheHitTrueReadTypeParallelAtomic *atomic.Int64 - fileCacheReadCountCacheHitTrueReadTypeRandomAtomic *atomic.Int64 - fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic *atomic.Int64 - fileCacheReadCountCacheHitFalseReadTypeParallelAtomic *atomic.Int64 - fileCacheReadCountCacheHitFalseReadTypeRandomAtomic *atomic.Int64 - fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic *atomic.Int64 - fsOpsCountFsOpBatchForgetAtomic *atomic.Int64 - fsOpsCountFsOpCreateFileAtomic *atomic.Int64 - fsOpsCountFsOpCreateLinkAtomic *atomic.Int64 - fsOpsCountFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsCountFsOpFallocateAtomic *atomic.Int64 - fsOpsCountFsOpFlushFileAtomic *atomic.Int64 - fsOpsCountFsOpForgetInodeAtomic *atomic.Int64 - fsOpsCountFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsCountFsOpGetXattrAtomic *atomic.Int64 - fsOpsCountFsOpListXattrAtomic *atomic.Int64 - fsOpsCountFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsCountFsOpMkDirAtomic *atomic.Int64 - fsOpsCountFsOpMkNodeAtomic *atomic.Int64 - fsOpsCountFsOpOpenDirAtomic *atomic.Int64 - fsOpsCountFsOpOpenFileAtomic *atomic.Int64 - fsOpsCountFsOpReadDirAtomic *atomic.Int64 - fsOpsCountFsOpReadFileAtomic *atomic.Int64 - fsOpsCountFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsCountFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsCountFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsCountFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsCountFsOpRenameAtomic *atomic.Int64 - fsOpsCountFsOpRmDirAtomic *atomic.Int64 - fsOpsCountFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsCountFsOpSetXattrAtomic *atomic.Int64 - fsOpsCountFsOpStatFSAtomic *atomic.Int64 - fsOpsCountFsOpSyncFSAtomic *atomic.Int64 - fsOpsCountFsOpSyncFileAtomic *atomic.Int64 - fsOpsCountFsOpUnlinkAtomic *atomic.Int64 - fsOpsCountFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic *atomic.Int64 - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic *atomic.Int64 - gcsDownloadBytesCountReadTypeParallelAtomic *atomic.Int64 - gcsDownloadBytesCountReadTypeRandomAtomic *atomic.Int64 - gcsDownloadBytesCountReadTypeSequentialAtomic *atomic.Int64 - gcsReadBytesCountAtomic *atomic.Int64 - gcsReadCountReadTypeParallelAtomic *atomic.Int64 - gcsReadCountReadTypeRandomAtomic *atomic.Int64 - gcsReadCountReadTypeSequentialAtomic *atomic.Int64 - gcsReaderCountIoMethodClosedAtomic *atomic.Int64 - gcsReaderCountIoMethodOpenedAtomic *atomic.Int64 - gcsRequestCountGcsMethodComposeObjectsAtomic *atomic.Int64 - gcsRequestCountGcsMethodCreateFolderAtomic *atomic.Int64 - gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic *atomic.Int64 - gcsRequestCountGcsMethodDeleteFolderAtomic *atomic.Int64 - gcsRequestCountGcsMethodDeleteObjectAtomic *atomic.Int64 - gcsRequestCountGcsMethodFinalizeUploadAtomic *atomic.Int64 - gcsRequestCountGcsMethodGetFolderAtomic *atomic.Int64 - gcsRequestCountGcsMethodListObjectsAtomic *atomic.Int64 - gcsRequestCountGcsMethodMoveObjectAtomic *atomic.Int64 - gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic *atomic.Int64 - gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic *atomic.Int64 - gcsRequestCountGcsMethodNewReaderAtomic *atomic.Int64 - gcsRequestCountGcsMethodRenameFolderAtomic *atomic.Int64 - gcsRequestCountGcsMethodStatObjectAtomic *atomic.Int64 - gcsRequestCountGcsMethodUpdateObjectAtomic *atomic.Int64 - gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic *atomic.Int64 - gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic *atomic.Int64 - fileCacheReadLatencies metric.Int64Histogram - fsOpsLatency metric.Int64Histogram - gcsRequestLatencies metric.Int64Histogram -} - -func (o *otelMetrics) FileCacheReadBytesCount( - inc int64, readType string) { - switch readType { - case "Parallel": - o.fileCacheReadBytesCountReadTypeParallelAtomic.Add(inc) - case "Random": - o.fileCacheReadBytesCountReadTypeRandomAtomic.Add(inc) - case "Sequential": - o.fileCacheReadBytesCountReadTypeSequentialAtomic.Add(inc) - default: - updateUnrecognizedAttribute(readType) - } - -} - -func (o *otelMetrics) FileCacheReadCount( - inc int64, cacheHit bool, readType string) { - switch cacheHit { - case true: - switch readType { - case "Parallel": - o.fileCacheReadCountCacheHitTrueReadTypeParallelAtomic.Add(inc) - case "Random": - o.fileCacheReadCountCacheHitTrueReadTypeRandomAtomic.Add(inc) - case "Sequential": - o.fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic.Add(inc) - default: - updateUnrecognizedAttribute(readType) - } - case false: - switch readType { - case "Parallel": - o.fileCacheReadCountCacheHitFalseReadTypeParallelAtomic.Add(inc) - case "Random": - o.fileCacheReadCountCacheHitFalseReadTypeRandomAtomic.Add(inc) - case "Sequential": - o.fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic.Add(inc) - default: - updateUnrecognizedAttribute(readType) - } - } - -} - -func (o *otelMetrics) FileCacheReadLatencies( - ctx context.Context, latency time.Duration, cacheHit bool) { - var record histogramRecord - switch cacheHit { - case true: - record = histogramRecord{ctx: ctx, instrument: o.fileCacheReadLatencies, value: latency.Microseconds(), attributes: fileCacheReadLatenciesCacheHitTrueAttrSet} - case false: - record = histogramRecord{ctx: ctx, instrument: o.fileCacheReadLatencies, value: latency.Microseconds(), attributes: fileCacheReadLatenciesCacheHitFalseAttrSet} - } - - select { - case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing - default: // Unblock writes to channel if it's full. - } -} - -func (o *otelMetrics) FsOpsCount( - inc int64, fsOp string) { - switch fsOp { - case "BatchForget": - o.fsOpsCountFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsCountFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsCountFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsCountFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsCountFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsCountFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsCountFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsCountFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsCountFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsCountFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsCountFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsCountFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsCountFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsCountFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsCountFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsCountFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsCountFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsCountFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsCountFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsCountFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsCountFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsCountFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsCountFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsCountFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsCountFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsCountFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsCountFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsCountFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsCountFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsCountFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - -} - -func (o *otelMetrics) FsOpsErrorCount( - inc int64, fsErrorCategory string, fsOp string) { - switch fsErrorCategory { - case "DEVICE_ERROR": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "DIR_NOT_EMPTY": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "FILE_DIR_ERROR": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "FILE_EXISTS": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "INTERRUPT_ERROR": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "INVALID_ARGUMENT": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "INVALID_OPERATION": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "IO_ERROR": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "MISC_ERROR": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "NETWORK_ERROR": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "NOT_A_DIR": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "NOT_IMPLEMENTED": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "NO_FILE_OR_DIR": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "PERM_ERROR": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "PROCESS_RESOURCE_MGMT_ERROR": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - case "TOO_MANY_OPEN_FILES": - switch fsOp { - case "BatchForget": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic.Add(inc) - case "CreateFile": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic.Add(inc) - case "CreateLink": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic.Add(inc) - case "CreateSymlink": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic.Add(inc) - case "Fallocate": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic.Add(inc) - case "FlushFile": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic.Add(inc) - case "ForgetInode": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic.Add(inc) - case "GetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic.Add(inc) - case "GetXattr": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic.Add(inc) - case "ListXattr": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic.Add(inc) - case "LookUpInode": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic.Add(inc) - case "MkDir": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic.Add(inc) - case "MkNode": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic.Add(inc) - case "OpenDir": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic.Add(inc) - case "OpenFile": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic.Add(inc) - case "ReadDir": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic.Add(inc) - case "ReadFile": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic.Add(inc) - case "ReadSymlink": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic.Add(inc) - case "ReleaseDirHandle": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic.Add(inc) - case "ReleaseFileHandle": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic.Add(inc) - case "RemoveXattr": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic.Add(inc) - case "Rename": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic.Add(inc) - case "RmDir": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic.Add(inc) - case "SetInodeAttributes": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic.Add(inc) - case "SetXattr": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic.Add(inc) - case "StatFS": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic.Add(inc) - case "SyncFS": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic.Add(inc) - case "SyncFile": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic.Add(inc) - case "Unlink": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic.Add(inc) - case "WriteFile": - o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic.Add(inc) - default: - updateUnrecognizedAttribute(fsOp) - } - default: - updateUnrecognizedAttribute(fsErrorCategory) - } - -} - -func (o *otelMetrics) FsOpsLatency( - ctx context.Context, latency time.Duration, fsOp string) { - var record histogramRecord - switch fsOp { - case "BatchForget": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpBatchForgetAttrSet} - case "CreateFile": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpCreateFileAttrSet} - case "CreateLink": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpCreateLinkAttrSet} - case "CreateSymlink": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpCreateSymlinkAttrSet} - case "Fallocate": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpFallocateAttrSet} - case "FlushFile": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpFlushFileAttrSet} - case "ForgetInode": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpForgetInodeAttrSet} - case "GetInodeAttributes": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpGetInodeAttributesAttrSet} - case "GetXattr": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpGetXattrAttrSet} - case "ListXattr": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpListXattrAttrSet} - case "LookUpInode": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpLookUpInodeAttrSet} - case "MkDir": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpMkDirAttrSet} - case "MkNode": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpMkNodeAttrSet} - case "OpenDir": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpOpenDirAttrSet} - case "OpenFile": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpOpenFileAttrSet} - case "ReadDir": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadDirAttrSet} - case "ReadFile": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadFileAttrSet} - case "ReadSymlink": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadSymlinkAttrSet} - case "ReleaseDirHandle": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReleaseDirHandleAttrSet} - case "ReleaseFileHandle": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReleaseFileHandleAttrSet} - case "RemoveXattr": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpRemoveXattrAttrSet} - case "Rename": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpRenameAttrSet} - case "RmDir": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpRmDirAttrSet} - case "SetInodeAttributes": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSetInodeAttributesAttrSet} - case "SetXattr": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSetXattrAttrSet} - case "StatFS": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpStatFSAttrSet} - case "SyncFS": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSyncFSAttrSet} - case "SyncFile": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpSyncFileAttrSet} - case "Unlink": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpUnlinkAttrSet} - case "WriteFile": - record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpWriteFileAttrSet} - default: - updateUnrecognizedAttribute(fsOp) - } - - select { - case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing - default: // Unblock writes to channel if it's full. - } -} - -func (o *otelMetrics) GcsDownloadBytesCount( - inc int64, readType string) { - switch readType { - case "Parallel": - o.gcsDownloadBytesCountReadTypeParallelAtomic.Add(inc) - case "Random": - o.gcsDownloadBytesCountReadTypeRandomAtomic.Add(inc) - case "Sequential": - o.gcsDownloadBytesCountReadTypeSequentialAtomic.Add(inc) - default: - updateUnrecognizedAttribute(readType) - } - -} - -func (o *otelMetrics) GcsReadBytesCount( - inc int64) { - o.gcsReadBytesCountAtomic.Add(inc) - -} - -func (o *otelMetrics) GcsReadCount( - inc int64, readType string) { - switch readType { - case "Parallel": - o.gcsReadCountReadTypeParallelAtomic.Add(inc) - case "Random": - o.gcsReadCountReadTypeRandomAtomic.Add(inc) - case "Sequential": - o.gcsReadCountReadTypeSequentialAtomic.Add(inc) - default: - updateUnrecognizedAttribute(readType) - } - -} - -func (o *otelMetrics) GcsReaderCount( - inc int64, ioMethod string) { - switch ioMethod { - case "closed": - o.gcsReaderCountIoMethodClosedAtomic.Add(inc) - case "opened": - o.gcsReaderCountIoMethodOpenedAtomic.Add(inc) - default: - updateUnrecognizedAttribute(ioMethod) - } - -} - -func (o *otelMetrics) GcsRequestCount( - inc int64, gcsMethod string) { - switch gcsMethod { - case "ComposeObjects": - o.gcsRequestCountGcsMethodComposeObjectsAtomic.Add(inc) - case "CreateFolder": - o.gcsRequestCountGcsMethodCreateFolderAtomic.Add(inc) - case "CreateObjectChunkWriter": - o.gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic.Add(inc) - case "DeleteFolder": - o.gcsRequestCountGcsMethodDeleteFolderAtomic.Add(inc) - case "DeleteObject": - o.gcsRequestCountGcsMethodDeleteObjectAtomic.Add(inc) - case "FinalizeUpload": - o.gcsRequestCountGcsMethodFinalizeUploadAtomic.Add(inc) - case "GetFolder": - o.gcsRequestCountGcsMethodGetFolderAtomic.Add(inc) - case "ListObjects": - o.gcsRequestCountGcsMethodListObjectsAtomic.Add(inc) - case "MoveObject": - o.gcsRequestCountGcsMethodMoveObjectAtomic.Add(inc) - case "MultiRangeDownloader::Add": - o.gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic.Add(inc) - case "NewMultiRangeDownloader": - o.gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic.Add(inc) - case "NewReader": - o.gcsRequestCountGcsMethodNewReaderAtomic.Add(inc) - case "RenameFolder": - o.gcsRequestCountGcsMethodRenameFolderAtomic.Add(inc) - case "StatObject": - o.gcsRequestCountGcsMethodStatObjectAtomic.Add(inc) - case "UpdateObject": - o.gcsRequestCountGcsMethodUpdateObjectAtomic.Add(inc) - default: - updateUnrecognizedAttribute(gcsMethod) - } - -} - -func (o *otelMetrics) GcsRequestLatencies( - ctx context.Context, latency time.Duration, gcsMethod string) { - var record histogramRecord - switch gcsMethod { - case "ComposeObjects": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodComposeObjectsAttrSet} - case "CreateFolder": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodCreateFolderAttrSet} - case "CreateObjectChunkWriter": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodCreateObjectChunkWriterAttrSet} - case "DeleteFolder": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodDeleteFolderAttrSet} - case "DeleteObject": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodDeleteObjectAttrSet} - case "FinalizeUpload": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodFinalizeUploadAttrSet} - case "GetFolder": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodGetFolderAttrSet} - case "ListObjects": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodListObjectsAttrSet} - case "MoveObject": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodMoveObjectAttrSet} - case "MultiRangeDownloader::Add": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodMultiRangeDownloaderAddAttrSet} - case "NewMultiRangeDownloader": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodNewMultiRangeDownloaderAttrSet} - case "NewReader": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodNewReaderAttrSet} - case "RenameFolder": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodRenameFolderAttrSet} - case "StatObject": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodStatObjectAttrSet} - case "UpdateObject": - record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodUpdateObjectAttrSet} - default: - updateUnrecognizedAttribute(gcsMethod) - } - - select { - case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing - default: // Unblock writes to channel if it's full. - } -} - -func (o *otelMetrics) GcsRetryCount( - inc int64, retryErrorCategory string) { - switch retryErrorCategory { - case "OTHER_ERRORS": - o.gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic.Add(inc) - case "STALLED_READ_REQUEST": - o.gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic.Add(inc) - default: - updateUnrecognizedAttribute(retryErrorCategory) - } - -} - -func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetrics, error) { - ch := make(chan histogramRecord, bufferSize) - var wg sync.WaitGroup - startSampledLogging(ctx) - for range workers { - wg.Add(1) - go func() { - defer wg.Done() - for record := range ch { - record.instrument.Record(record.ctx, record.value, record.attributes) - } - }() - } - meter := otel.Meter("gcsfuse") - var fileCacheReadBytesCountReadTypeParallelAtomic, - fileCacheReadBytesCountReadTypeRandomAtomic, - fileCacheReadBytesCountReadTypeSequentialAtomic atomic.Int64 - - var fileCacheReadCountCacheHitTrueReadTypeParallelAtomic, - fileCacheReadCountCacheHitTrueReadTypeRandomAtomic, - fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic, - fileCacheReadCountCacheHitFalseReadTypeParallelAtomic, - fileCacheReadCountCacheHitFalseReadTypeRandomAtomic, - fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic atomic.Int64 - - var fsOpsCountFsOpBatchForgetAtomic, - fsOpsCountFsOpCreateFileAtomic, - fsOpsCountFsOpCreateLinkAtomic, - fsOpsCountFsOpCreateSymlinkAtomic, - fsOpsCountFsOpFallocateAtomic, - fsOpsCountFsOpFlushFileAtomic, - fsOpsCountFsOpForgetInodeAtomic, - fsOpsCountFsOpGetInodeAttributesAtomic, - fsOpsCountFsOpGetXattrAtomic, - fsOpsCountFsOpListXattrAtomic, - fsOpsCountFsOpLookUpInodeAtomic, - fsOpsCountFsOpMkDirAtomic, - fsOpsCountFsOpMkNodeAtomic, - fsOpsCountFsOpOpenDirAtomic, - fsOpsCountFsOpOpenFileAtomic, - fsOpsCountFsOpReadDirAtomic, - fsOpsCountFsOpReadFileAtomic, - fsOpsCountFsOpReadSymlinkAtomic, - fsOpsCountFsOpReleaseDirHandleAtomic, - fsOpsCountFsOpReleaseFileHandleAtomic, - fsOpsCountFsOpRemoveXattrAtomic, - fsOpsCountFsOpRenameAtomic, - fsOpsCountFsOpRmDirAtomic, - fsOpsCountFsOpSetInodeAttributesAtomic, - fsOpsCountFsOpSetXattrAtomic, - fsOpsCountFsOpStatFSAtomic, - fsOpsCountFsOpSyncFSAtomic, - fsOpsCountFsOpSyncFileAtomic, - fsOpsCountFsOpUnlinkAtomic, - fsOpsCountFsOpWriteFileAtomic atomic.Int64 - - var fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic atomic.Int64 - - var gcsDownloadBytesCountReadTypeParallelAtomic, - gcsDownloadBytesCountReadTypeRandomAtomic, - gcsDownloadBytesCountReadTypeSequentialAtomic atomic.Int64 - - var gcsReadBytesCountAtomic atomic.Int64 - - var gcsReadCountReadTypeParallelAtomic, - gcsReadCountReadTypeRandomAtomic, - gcsReadCountReadTypeSequentialAtomic atomic.Int64 - - var gcsReaderCountIoMethodClosedAtomic, - gcsReaderCountIoMethodOpenedAtomic atomic.Int64 - - var gcsRequestCountGcsMethodComposeObjectsAtomic, - gcsRequestCountGcsMethodCreateFolderAtomic, - gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, - gcsRequestCountGcsMethodDeleteFolderAtomic, - gcsRequestCountGcsMethodDeleteObjectAtomic, - gcsRequestCountGcsMethodFinalizeUploadAtomic, - gcsRequestCountGcsMethodGetFolderAtomic, - gcsRequestCountGcsMethodListObjectsAtomic, - gcsRequestCountGcsMethodMoveObjectAtomic, - gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic, - gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic, - gcsRequestCountGcsMethodNewReaderAtomic, - gcsRequestCountGcsMethodRenameFolderAtomic, - gcsRequestCountGcsMethodStatObjectAtomic, - gcsRequestCountGcsMethodUpdateObjectAtomic atomic.Int64 - - var gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, - gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic atomic.Int64 - - _, err0 := meter.Int64ObservableCounter("file_cache/read_bytes_count", - metric.WithDescription("The cumulative number of bytes read from file cache along with read type - Sequential/Random"), - metric.WithUnit("By"), - metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - conditionallyObserve(obsrv, &fileCacheReadBytesCountReadTypeParallelAtomic, fileCacheReadBytesCountReadTypeParallelAttrSet) - conditionallyObserve(obsrv, &fileCacheReadBytesCountReadTypeRandomAtomic, fileCacheReadBytesCountReadTypeRandomAttrSet) - conditionallyObserve(obsrv, &fileCacheReadBytesCountReadTypeSequentialAtomic, fileCacheReadBytesCountReadTypeSequentialAttrSet) - return nil - })) - - _, err1 := meter.Int64ObservableCounter("file_cache/read_count", - metric.WithDescription("Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false"), - metric.WithUnit(""), - metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - conditionallyObserve(obsrv, &fileCacheReadCountCacheHitTrueReadTypeParallelAtomic, fileCacheReadCountCacheHitTrueReadTypeParallelAttrSet) - conditionallyObserve(obsrv, &fileCacheReadCountCacheHitTrueReadTypeRandomAtomic, fileCacheReadCountCacheHitTrueReadTypeRandomAttrSet) - conditionallyObserve(obsrv, &fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic, fileCacheReadCountCacheHitTrueReadTypeSequentialAttrSet) - conditionallyObserve(obsrv, &fileCacheReadCountCacheHitFalseReadTypeParallelAtomic, fileCacheReadCountCacheHitFalseReadTypeParallelAttrSet) - conditionallyObserve(obsrv, &fileCacheReadCountCacheHitFalseReadTypeRandomAtomic, fileCacheReadCountCacheHitFalseReadTypeRandomAttrSet) - conditionallyObserve(obsrv, &fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic, fileCacheReadCountCacheHitFalseReadTypeSequentialAttrSet) - return nil - })) - - fileCacheReadLatencies, err2 := meter.Int64Histogram("file_cache/read_latencies", - metric.WithDescription("The cumulative distribution of the file cache read latencies along with cache hit - true/false."), - metric.WithUnit("us"), - metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) - - _, err3 := meter.Int64ObservableCounter("fs/ops_count", - metric.WithDescription("The cumulative number of ops processed by the file system."), - metric.WithUnit(""), - metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - conditionallyObserve(obsrv, &fsOpsCountFsOpBatchForgetAtomic, fsOpsCountFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpCreateFileAtomic, fsOpsCountFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpCreateLinkAtomic, fsOpsCountFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpCreateSymlinkAtomic, fsOpsCountFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpFallocateAtomic, fsOpsCountFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpFlushFileAtomic, fsOpsCountFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpForgetInodeAtomic, fsOpsCountFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpGetInodeAttributesAtomic, fsOpsCountFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpGetXattrAtomic, fsOpsCountFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpListXattrAtomic, fsOpsCountFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpLookUpInodeAtomic, fsOpsCountFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpMkDirAtomic, fsOpsCountFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpMkNodeAtomic, fsOpsCountFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpOpenDirAtomic, fsOpsCountFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpOpenFileAtomic, fsOpsCountFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpReadDirAtomic, fsOpsCountFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpReadFileAtomic, fsOpsCountFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpReadSymlinkAtomic, fsOpsCountFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpReleaseDirHandleAtomic, fsOpsCountFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpReleaseFileHandleAtomic, fsOpsCountFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpRemoveXattrAtomic, fsOpsCountFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpRenameAtomic, fsOpsCountFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpRmDirAtomic, fsOpsCountFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpSetInodeAttributesAtomic, fsOpsCountFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpSetXattrAtomic, fsOpsCountFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpStatFSAtomic, fsOpsCountFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpSyncFSAtomic, fsOpsCountFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpSyncFileAtomic, fsOpsCountFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpUnlinkAtomic, fsOpsCountFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsCountFsOpWriteFileAtomic, fsOpsCountFsOpWriteFileAttrSet) - return nil - })) - - _, err4 := meter.Int64ObservableCounter("fs/ops_error_count", - metric.WithDescription("The cumulative number of errors generated by file system operations."), - metric.WithUnit(""), - metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAttrSet) - conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAttrSet) - return nil - })) - - fsOpsLatency, err5 := meter.Int64Histogram("fs/ops_latency", - metric.WithDescription("The cumulative distribution of file system operation latencies"), - metric.WithUnit("us"), - metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) - - _, err6 := meter.Int64ObservableCounter("gcs/download_bytes_count", - metric.WithDescription("The cumulative number of bytes downloaded from GCS along with type - Sequential/Random"), - metric.WithUnit("By"), - metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - conditionallyObserve(obsrv, &gcsDownloadBytesCountReadTypeParallelAtomic, gcsDownloadBytesCountReadTypeParallelAttrSet) - conditionallyObserve(obsrv, &gcsDownloadBytesCountReadTypeRandomAtomic, gcsDownloadBytesCountReadTypeRandomAttrSet) - conditionallyObserve(obsrv, &gcsDownloadBytesCountReadTypeSequentialAtomic, gcsDownloadBytesCountReadTypeSequentialAttrSet) - return nil - })) - - _, err7 := meter.Int64ObservableCounter("gcs/read_bytes_count", - metric.WithDescription("The cumulative number of bytes read from GCS objects."), - metric.WithUnit("By"), - metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - conditionallyObserve(obsrv, &gcsReadBytesCountAtomic) - return nil - })) - - _, err8 := meter.Int64ObservableCounter("gcs/read_count", - metric.WithDescription("Specifies the number of gcs reads made along with type - Sequential/Random"), - metric.WithUnit(""), - metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - conditionallyObserve(obsrv, &gcsReadCountReadTypeParallelAtomic, gcsReadCountReadTypeParallelAttrSet) - conditionallyObserve(obsrv, &gcsReadCountReadTypeRandomAtomic, gcsReadCountReadTypeRandomAttrSet) - conditionallyObserve(obsrv, &gcsReadCountReadTypeSequentialAtomic, gcsReadCountReadTypeSequentialAttrSet) - return nil - })) - - _, err9 := meter.Int64ObservableCounter("gcs/reader_count", - metric.WithDescription("The cumulative number of GCS object readers opened or closed."), - metric.WithUnit(""), - metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - conditionallyObserve(obsrv, &gcsReaderCountIoMethodClosedAtomic, gcsReaderCountIoMethodClosedAttrSet) - conditionallyObserve(obsrv, &gcsReaderCountIoMethodOpenedAtomic, gcsReaderCountIoMethodOpenedAttrSet) - return nil - })) - - _, err10 := meter.Int64ObservableCounter("gcs/request_count", - metric.WithDescription("The cumulative number of GCS requests processed along with the GCS method."), - metric.WithUnit(""), - metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodComposeObjectsAtomic, gcsRequestCountGcsMethodComposeObjectsAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodCreateFolderAtomic, gcsRequestCountGcsMethodCreateFolderAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, gcsRequestCountGcsMethodCreateObjectChunkWriterAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodDeleteFolderAtomic, gcsRequestCountGcsMethodDeleteFolderAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodDeleteObjectAtomic, gcsRequestCountGcsMethodDeleteObjectAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodFinalizeUploadAtomic, gcsRequestCountGcsMethodFinalizeUploadAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodGetFolderAtomic, gcsRequestCountGcsMethodGetFolderAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodListObjectsAtomic, gcsRequestCountGcsMethodListObjectsAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodMoveObjectAtomic, gcsRequestCountGcsMethodMoveObjectAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic, gcsRequestCountGcsMethodMultiRangeDownloaderAddAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic, gcsRequestCountGcsMethodNewMultiRangeDownloaderAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodNewReaderAtomic, gcsRequestCountGcsMethodNewReaderAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodRenameFolderAtomic, gcsRequestCountGcsMethodRenameFolderAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodStatObjectAtomic, gcsRequestCountGcsMethodStatObjectAttrSet) - conditionallyObserve(obsrv, &gcsRequestCountGcsMethodUpdateObjectAtomic, gcsRequestCountGcsMethodUpdateObjectAttrSet) - return nil - })) - - gcsRequestLatencies, err11 := meter.Int64Histogram("gcs/request_latencies", - metric.WithDescription("The cumulative distribution of the GCS request latencies."), - metric.WithUnit("ms"), - metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) - - _, err12 := meter.Int64ObservableCounter("gcs/retry_count", - metric.WithDescription("The cumulative number of retry requests made to GCS."), - metric.WithUnit(""), - metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { - conditionallyObserve(obsrv, &gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, gcsRetryCountRetryErrorCategoryOTHERERRORSAttrSet) - conditionallyObserve(obsrv, &gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic, gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAttrSet) - return nil - })) - - errs := []error{err0, err1, err2, err3, err4, err5, err6, err7, err8, err9, err10, err11, err12} - if err := errors.Join(errs...); err != nil { - return nil, err - } - - return &otelMetrics{ - ch: ch, - wg: &wg, - fileCacheReadBytesCountReadTypeParallelAtomic: &fileCacheReadBytesCountReadTypeParallelAtomic, - fileCacheReadBytesCountReadTypeRandomAtomic: &fileCacheReadBytesCountReadTypeRandomAtomic, - fileCacheReadBytesCountReadTypeSequentialAtomic: &fileCacheReadBytesCountReadTypeSequentialAtomic, - fileCacheReadCountCacheHitTrueReadTypeParallelAtomic: &fileCacheReadCountCacheHitTrueReadTypeParallelAtomic, - fileCacheReadCountCacheHitTrueReadTypeRandomAtomic: &fileCacheReadCountCacheHitTrueReadTypeRandomAtomic, - fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic: &fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic, - fileCacheReadCountCacheHitFalseReadTypeParallelAtomic: &fileCacheReadCountCacheHitFalseReadTypeParallelAtomic, - fileCacheReadCountCacheHitFalseReadTypeRandomAtomic: &fileCacheReadCountCacheHitFalseReadTypeRandomAtomic, - fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic: &fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic, - fileCacheReadLatencies: fileCacheReadLatencies, - fsOpsCountFsOpBatchForgetAtomic: &fsOpsCountFsOpBatchForgetAtomic, - fsOpsCountFsOpCreateFileAtomic: &fsOpsCountFsOpCreateFileAtomic, - fsOpsCountFsOpCreateLinkAtomic: &fsOpsCountFsOpCreateLinkAtomic, - fsOpsCountFsOpCreateSymlinkAtomic: &fsOpsCountFsOpCreateSymlinkAtomic, - fsOpsCountFsOpFallocateAtomic: &fsOpsCountFsOpFallocateAtomic, - fsOpsCountFsOpFlushFileAtomic: &fsOpsCountFsOpFlushFileAtomic, - fsOpsCountFsOpForgetInodeAtomic: &fsOpsCountFsOpForgetInodeAtomic, - fsOpsCountFsOpGetInodeAttributesAtomic: &fsOpsCountFsOpGetInodeAttributesAtomic, - fsOpsCountFsOpGetXattrAtomic: &fsOpsCountFsOpGetXattrAtomic, - fsOpsCountFsOpListXattrAtomic: &fsOpsCountFsOpListXattrAtomic, - fsOpsCountFsOpLookUpInodeAtomic: &fsOpsCountFsOpLookUpInodeAtomic, - fsOpsCountFsOpMkDirAtomic: &fsOpsCountFsOpMkDirAtomic, - fsOpsCountFsOpMkNodeAtomic: &fsOpsCountFsOpMkNodeAtomic, - fsOpsCountFsOpOpenDirAtomic: &fsOpsCountFsOpOpenDirAtomic, - fsOpsCountFsOpOpenFileAtomic: &fsOpsCountFsOpOpenFileAtomic, - fsOpsCountFsOpReadDirAtomic: &fsOpsCountFsOpReadDirAtomic, - fsOpsCountFsOpReadFileAtomic: &fsOpsCountFsOpReadFileAtomic, - fsOpsCountFsOpReadSymlinkAtomic: &fsOpsCountFsOpReadSymlinkAtomic, - fsOpsCountFsOpReleaseDirHandleAtomic: &fsOpsCountFsOpReleaseDirHandleAtomic, - fsOpsCountFsOpReleaseFileHandleAtomic: &fsOpsCountFsOpReleaseFileHandleAtomic, - fsOpsCountFsOpRemoveXattrAtomic: &fsOpsCountFsOpRemoveXattrAtomic, - fsOpsCountFsOpRenameAtomic: &fsOpsCountFsOpRenameAtomic, - fsOpsCountFsOpRmDirAtomic: &fsOpsCountFsOpRmDirAtomic, - fsOpsCountFsOpSetInodeAttributesAtomic: &fsOpsCountFsOpSetInodeAttributesAtomic, - fsOpsCountFsOpSetXattrAtomic: &fsOpsCountFsOpSetXattrAtomic, - fsOpsCountFsOpStatFSAtomic: &fsOpsCountFsOpStatFSAtomic, - fsOpsCountFsOpSyncFSAtomic: &fsOpsCountFsOpSyncFSAtomic, - fsOpsCountFsOpSyncFileAtomic: &fsOpsCountFsOpSyncFileAtomic, - fsOpsCountFsOpUnlinkAtomic: &fsOpsCountFsOpUnlinkAtomic, - fsOpsCountFsOpWriteFileAtomic: &fsOpsCountFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpBatchForgetAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateLinkAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpCreateSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFallocateAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpFlushFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpForgetInodeAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpGetXattrAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpListXattrAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpLookUpInodeAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkDirAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpMkNodeAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseFileHandleAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRemoveXattrAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRenameAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpRmDirAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetInodeAttributesAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSetXattrAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpStatFSAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFSAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpSyncFileAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic, - fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic, - fsOpsLatency: fsOpsLatency, - gcsDownloadBytesCountReadTypeParallelAtomic: &gcsDownloadBytesCountReadTypeParallelAtomic, - gcsDownloadBytesCountReadTypeRandomAtomic: &gcsDownloadBytesCountReadTypeRandomAtomic, - gcsDownloadBytesCountReadTypeSequentialAtomic: &gcsDownloadBytesCountReadTypeSequentialAtomic, - gcsReadBytesCountAtomic: &gcsReadBytesCountAtomic, - gcsReadCountReadTypeParallelAtomic: &gcsReadCountReadTypeParallelAtomic, - gcsReadCountReadTypeRandomAtomic: &gcsReadCountReadTypeRandomAtomic, - gcsReadCountReadTypeSequentialAtomic: &gcsReadCountReadTypeSequentialAtomic, - gcsReaderCountIoMethodClosedAtomic: &gcsReaderCountIoMethodClosedAtomic, - gcsReaderCountIoMethodOpenedAtomic: &gcsReaderCountIoMethodOpenedAtomic, - gcsRequestCountGcsMethodComposeObjectsAtomic: &gcsRequestCountGcsMethodComposeObjectsAtomic, - gcsRequestCountGcsMethodCreateFolderAtomic: &gcsRequestCountGcsMethodCreateFolderAtomic, - gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic: &gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, - gcsRequestCountGcsMethodDeleteFolderAtomic: &gcsRequestCountGcsMethodDeleteFolderAtomic, - gcsRequestCountGcsMethodDeleteObjectAtomic: &gcsRequestCountGcsMethodDeleteObjectAtomic, - gcsRequestCountGcsMethodFinalizeUploadAtomic: &gcsRequestCountGcsMethodFinalizeUploadAtomic, - gcsRequestCountGcsMethodGetFolderAtomic: &gcsRequestCountGcsMethodGetFolderAtomic, - gcsRequestCountGcsMethodListObjectsAtomic: &gcsRequestCountGcsMethodListObjectsAtomic, - gcsRequestCountGcsMethodMoveObjectAtomic: &gcsRequestCountGcsMethodMoveObjectAtomic, - gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic: &gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic, - gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic: &gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic, - gcsRequestCountGcsMethodNewReaderAtomic: &gcsRequestCountGcsMethodNewReaderAtomic, - gcsRequestCountGcsMethodRenameFolderAtomic: &gcsRequestCountGcsMethodRenameFolderAtomic, - gcsRequestCountGcsMethodStatObjectAtomic: &gcsRequestCountGcsMethodStatObjectAtomic, - gcsRequestCountGcsMethodUpdateObjectAtomic: &gcsRequestCountGcsMethodUpdateObjectAtomic, - gcsRequestLatencies: gcsRequestLatencies, - gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic: &gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, - gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic: &gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic, - }, nil -} - -func (o *otelMetrics) Close() { - close(o.ch) - o.wg.Wait() -} - -func conditionallyObserve(obsrv metric.Int64Observer, counter *atomic.Int64, obsrvOptions ...metric.ObserveOption) { - if val := counter.Load(); val > 0 { - obsrv.Observe(val, obsrvOptions...) - } -} - -func updateUnrecognizedAttribute(newValue string) { - unrecognizedAttr.CompareAndSwap("", newValue) -} - -// StartSampledLogging starts a goroutine that logs unrecognized attributes periodically. -func startSampledLogging(ctx context.Context) { - // Init the atomic.Value - unrecognizedAttr.Store("") - - go func() { - ticker := time.NewTicker(logInterval) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - logUnrecognizedAttribute() - } - } - }() -} - -// logUnrecognizedAttribute retrieves and logs any unrecognized attributes. -func logUnrecognizedAttribute() { - // Atomically load and reset the attribute name, then generate a log - // if an unrecognized attribute was encountered. - if currentAttr := unrecognizedAttr.Swap("").(string); currentAttr != "" { - logger.Tracef("Attribute %s is not declared", currentAttr) - } -} diff --git a/optimizedmetrics/otel_metrics_test.go b/optimizedmetrics/otel_metrics_test.go deleted file mode 100644 index c4bb1583f3..0000000000 --- a/optimizedmetrics/otel_metrics_test.go +++ /dev/null @@ -1,5741 +0,0 @@ -// Copyright 2025 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// **** DO NOT EDIT - FILE IS AUTO-GENERATED **** - -package optimizedmetrics - -import ( - "context" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel" - "go.opentelemetry.io/otel/attribute" - "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/metric/metricdata" -) - -// metricValueMap maps attribute sets to metric values. -type metricValueMap map[string]int64 - -// metricHistogramMap maps attribute sets to histogram data points. -type metricHistogramMap map[string]metricdata.HistogramDataPoint[int64] - -func waitForMetricsProcessing() { - time.Sleep(time.Millisecond) -} - -func setupOTel(ctx context.Context, t *testing.T) (*otelMetrics, *metric.ManualReader) { - t.Helper() - reader := metric.NewManualReader() - provider := metric.NewMeterProvider(metric.WithReader(reader)) - otel.SetMeterProvider(provider) - - m, err := NewOTelMetrics(ctx, 10, 100) - require.NoError(t, err) - return m, reader -} - -// gatherHistogramMetrics collects all histogram metrics from the reader. -// It returns a map where the key is the metric name, and the value is another map. -// The inner map's key is a string representation of the attributes, -// and the value is the metricdata.HistogramDataPoint. -func gatherHistogramMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[string]metricdata.HistogramDataPoint[int64] { - t.Helper() - var rm metricdata.ResourceMetrics - err := rd.Collect(ctx, &rm) - require.NoError(t, err) - - results := make(map[string]map[string]metricdata.HistogramDataPoint[int64]) - encoder := attribute.DefaultEncoder() // Using default encoder - - for _, sm := range rm.ScopeMetrics { - for _, m := range sm.Metrics { - // We are interested in Histogram[int64]. - hist, ok := m.Data.(metricdata.Histogram[int64]) - if !ok { - continue - } - - metricMap := make(metricHistogramMap) - for _, dp := range hist.DataPoints { - if dp.Count == 0 { - continue - } - - metricMap[dp.Attributes.Encoded(encoder)] = dp - } - - if len(metricMap) > 0 { - results[m.Name] = metricMap - } - } - } - - return results -} - -// gatherNonZeroCounterMetrics collects all non-zero counter metrics from the reader. -// It returns a map where the key is the metric name, and the value is another map. -// The inner map's key is a string representation of the attributes, -// and the value is the metric's value. -func gatherNonZeroCounterMetrics(ctx context.Context, t *testing.T, rd *metric.ManualReader) map[string]map[string]int64 { - t.Helper() - var rm metricdata.ResourceMetrics - err := rd.Collect(ctx, &rm) - require.NoError(t, err) - - results := make(map[string]map[string]int64) - encoder := attribute.DefaultEncoder() - - for _, sm := range rm.ScopeMetrics { - for _, m := range sm.Metrics { - // We are interested in Sum[int64] which corresponds to int_counter. - sum, ok := m.Data.(metricdata.Sum[int64]) - if !ok { - continue - } - - metricMap := make(metricValueMap) - for _, dp := range sum.DataPoints { - if dp.Value == 0 { - continue - } - - metricMap[dp.Attributes.Encoded(encoder)] = dp.Value - } - - if len(metricMap) > 0 { - results[m.Name] = metricMap - } - } - } - - return results -} - -func TestFileCacheReadBytesCount(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ - { - name: "read_type_Parallel", - f: func(m *otelMetrics) { - m.FileCacheReadBytesCount(5, "Parallel") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Parallel")): 5, - }, - }, - { - name: "read_type_Random", - f: func(m *otelMetrics) { - m.FileCacheReadBytesCount(5, "Random") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Random")): 5, - }, - }, - { - name: "read_type_Sequential", - f: func(m *otelMetrics) { - m.FileCacheReadBytesCount(5, "Sequential") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Sequential")): 5, - }, - }, { - name: "multiple_attributes_summed", - f: func(m *otelMetrics) { - m.FileCacheReadBytesCount(5, "Parallel") - m.FileCacheReadBytesCount(2, "Random") - m.FileCacheReadBytesCount(3, "Parallel") - }, - expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("read_type", "Parallel")): 8, - attribute.NewSet(attribute.String("read_type", "Random")): 2, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - metric, ok := metrics["file_cache/read_bytes_count"] - assert.True(t, ok, "file_cache/read_bytes_count metric not found") - expectedMap := make(map[string]int64) - for k, v := range tc.expected { - expectedMap[k.Encoded(encoder)] = v - } - assert.Equal(t, expectedMap, metric) - }) - } -} - -func TestFileCacheReadCount(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ - { - name: "cache_hit_true_read_type_Parallel", - f: func(m *otelMetrics) { - m.FileCacheReadCount(5, true, "Parallel") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Parallel")): 5, - }, - }, - { - name: "cache_hit_true_read_type_Random", - f: func(m *otelMetrics) { - m.FileCacheReadCount(5, true, "Random") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Random")): 5, - }, - }, - { - name: "cache_hit_true_read_type_Sequential", - f: func(m *otelMetrics) { - m.FileCacheReadCount(5, true, "Sequential") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Sequential")): 5, - }, - }, - { - name: "cache_hit_false_read_type_Parallel", - f: func(m *otelMetrics) { - m.FileCacheReadCount(5, false, "Parallel") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Parallel")): 5, - }, - }, - { - name: "cache_hit_false_read_type_Random", - f: func(m *otelMetrics) { - m.FileCacheReadCount(5, false, "Random") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Random")): 5, - }, - }, - { - name: "cache_hit_false_read_type_Sequential", - f: func(m *otelMetrics) { - m.FileCacheReadCount(5, false, "Sequential") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.Bool("cache_hit", false), attribute.String("read_type", "Sequential")): 5, - }, - }, { - name: "multiple_attributes_summed", - f: func(m *otelMetrics) { - m.FileCacheReadCount(5, true, "Parallel") - m.FileCacheReadCount(2, true, "Random") - m.FileCacheReadCount(3, true, "Parallel") - }, - expected: map[attribute.Set]int64{attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Parallel")): 8, - attribute.NewSet(attribute.Bool("cache_hit", true), attribute.String("read_type", "Random")): 2, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - metric, ok := metrics["file_cache/read_count"] - assert.True(t, ok, "file_cache/read_count metric not found") - expectedMap := make(map[string]int64) - for k, v := range tc.expected { - expectedMap[k.Encoded(encoder)] = v - } - assert.Equal(t, expectedMap, metric) - }) - } -} - -func TestFileCacheReadLatencies(t *testing.T) { - tests := []struct { - name string - latencies []time.Duration - cacheHit bool - }{ - { - name: "cache_hit_true", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - cacheHit: true, - }, - { - name: "cache_hit_false", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - cacheHit: false, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - var totalLatency time.Duration - - for _, latency := range tc.latencies { - m.FileCacheReadLatencies(ctx, latency, tc.cacheHit) - totalLatency += latency - } - waitForMetricsProcessing() - - metrics := gatherHistogramMetrics(ctx, t, rd) - metric, ok := metrics["file_cache/read_latencies"] - require.True(t, ok, "file_cache/read_latencies metric not found") - - attrs := []attribute.KeyValue{ - attribute.Bool("cache_hit", tc.cacheHit), - } - s := attribute.NewSet(attrs...) - expectedKey := s.Encoded(encoder) - dp, ok := metric[expectedKey] - require.True(t, ok, "DataPoint not found for key: %s", expectedKey) - assert.Equal(t, uint64(len(tc.latencies)), dp.Count) - assert.Equal(t, totalLatency.Microseconds(), dp.Sum) - }) - } -} - -func TestFsOpsCount(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ - { - name: "fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_op", "WriteFile")): 5, - }, - }, { - name: "multiple_attributes_summed", - f: func(m *otelMetrics) { - m.FsOpsCount(5, "BatchForget") - m.FsOpsCount(2, "CreateFile") - m.FsOpsCount(3, "BatchForget") - }, - expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("fs_op", "BatchForget")): 8, - attribute.NewSet(attribute.String("fs_op", "CreateFile")): 2, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - metric, ok := metrics["fs/ops_count"] - assert.True(t, ok, "fs/ops_count metric not found") - expectedMap := make(map[string]int64) - for k, v := range tc.expected { - expectedMap[k.Encoded(encoder)] = v - } - assert.Equal(t, expectedMap, metric) - }) - } -} - -func TestFsOpsErrorCount(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ - { - name: "fs_error_category_DEVICE_ERROR_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_DEVICE_ERROR_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_DIR_NOT_EMPTY_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_FILE_DIR_ERROR_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_FILE_EXISTS_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "FILE_EXISTS", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_INTERRUPT_ERROR_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_INVALID_ARGUMENT_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_INVALID_OPERATION_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "INVALID_OPERATION", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_IO_ERROR_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "IO_ERROR", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_MISC_ERROR_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "MISC_ERROR", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_NETWORK_ERROR_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NETWORK_ERROR", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_NOT_A_DIR_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_A_DIR", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_NOT_IMPLEMENTED_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_NO_FILE_OR_DIR_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_PERM_ERROR_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PERM_ERROR", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_BatchForget", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "BatchForget") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "BatchForget")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_CreateFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "CreateFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateFile")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_CreateLink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "CreateLink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateLink")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_CreateSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "CreateSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "CreateSymlink")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_Fallocate", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "Fallocate") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Fallocate")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_FlushFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "FlushFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "FlushFile")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ForgetInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ForgetInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ForgetInode")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_GetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "GetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_GetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "GetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "GetXattr")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ListXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ListXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ListXattr")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_LookUpInode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "LookUpInode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "LookUpInode")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_MkDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "MkDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkDir")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_MkNode", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "MkNode") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "MkNode")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_OpenDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "OpenDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenDir")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_OpenFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "OpenFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenFile")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReadDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReadDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadDir")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReadFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReadFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadFile")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReadSymlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReadSymlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadSymlink")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReleaseDirHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReleaseDirHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseDirHandle")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReleaseFileHandle", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReleaseFileHandle") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseFileHandle")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_RemoveXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "RemoveXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RemoveXattr")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_Rename", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "Rename") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Rename")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_RmDir", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "RmDir") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "RmDir")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SetInodeAttributes", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SetInodeAttributes") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetInodeAttributes")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SetXattr", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SetXattr") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SetXattr")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_StatFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "StatFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "StatFS")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SyncFS", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SyncFS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFS")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_SyncFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "SyncFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "SyncFile")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_Unlink", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "Unlink") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "Unlink")): 5, - }, - }, - { - name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_WriteFile", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "WriteFile") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "WriteFile")): 5, - }, - }, { - name: "multiple_attributes_summed", - f: func(m *otelMetrics) { - m.FsOpsErrorCount(5, "DEVICE_ERROR", "BatchForget") - m.FsOpsErrorCount(2, "DEVICE_ERROR", "CreateFile") - m.FsOpsErrorCount(3, "DEVICE_ERROR", "BatchForget") - }, - expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "BatchForget")): 8, - attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "CreateFile")): 2, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - metric, ok := metrics["fs/ops_error_count"] - assert.True(t, ok, "fs/ops_error_count metric not found") - expectedMap := make(map[string]int64) - for k, v := range tc.expected { - expectedMap[k.Encoded(encoder)] = v - } - assert.Equal(t, expectedMap, metric) - }) - } -} - -func TestFsOpsLatency(t *testing.T) { - tests := []struct { - name string - latencies []time.Duration - fsOp string - }{ - { - name: "fs_op_BatchForget", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "BatchForget", - }, - { - name: "fs_op_CreateFile", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "CreateFile", - }, - { - name: "fs_op_CreateLink", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "CreateLink", - }, - { - name: "fs_op_CreateSymlink", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "CreateSymlink", - }, - { - name: "fs_op_Fallocate", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "Fallocate", - }, - { - name: "fs_op_FlushFile", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "FlushFile", - }, - { - name: "fs_op_ForgetInode", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "ForgetInode", - }, - { - name: "fs_op_GetInodeAttributes", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "GetInodeAttributes", - }, - { - name: "fs_op_GetXattr", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "GetXattr", - }, - { - name: "fs_op_ListXattr", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "ListXattr", - }, - { - name: "fs_op_LookUpInode", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "LookUpInode", - }, - { - name: "fs_op_MkDir", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "MkDir", - }, - { - name: "fs_op_MkNode", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "MkNode", - }, - { - name: "fs_op_OpenDir", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "OpenDir", - }, - { - name: "fs_op_OpenFile", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "OpenFile", - }, - { - name: "fs_op_ReadDir", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "ReadDir", - }, - { - name: "fs_op_ReadFile", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "ReadFile", - }, - { - name: "fs_op_ReadSymlink", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "ReadSymlink", - }, - { - name: "fs_op_ReleaseDirHandle", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "ReleaseDirHandle", - }, - { - name: "fs_op_ReleaseFileHandle", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "ReleaseFileHandle", - }, - { - name: "fs_op_RemoveXattr", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "RemoveXattr", - }, - { - name: "fs_op_Rename", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "Rename", - }, - { - name: "fs_op_RmDir", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "RmDir", - }, - { - name: "fs_op_SetInodeAttributes", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "SetInodeAttributes", - }, - { - name: "fs_op_SetXattr", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "SetXattr", - }, - { - name: "fs_op_StatFS", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "StatFS", - }, - { - name: "fs_op_SyncFS", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "SyncFS", - }, - { - name: "fs_op_SyncFile", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "SyncFile", - }, - { - name: "fs_op_Unlink", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "Unlink", - }, - { - name: "fs_op_WriteFile", - latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, - fsOp: "WriteFile", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - var totalLatency time.Duration - - for _, latency := range tc.latencies { - m.FsOpsLatency(ctx, latency, tc.fsOp) - totalLatency += latency - } - waitForMetricsProcessing() - - metrics := gatherHistogramMetrics(ctx, t, rd) - metric, ok := metrics["fs/ops_latency"] - require.True(t, ok, "fs/ops_latency metric not found") - - attrs := []attribute.KeyValue{ - attribute.String("fs_op", tc.fsOp), - } - s := attribute.NewSet(attrs...) - expectedKey := s.Encoded(encoder) - dp, ok := metric[expectedKey] - require.True(t, ok, "DataPoint not found for key: %s", expectedKey) - assert.Equal(t, uint64(len(tc.latencies)), dp.Count) - assert.Equal(t, totalLatency.Microseconds(), dp.Sum) - }) - } -} - -func TestGcsDownloadBytesCount(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ - { - name: "read_type_Parallel", - f: func(m *otelMetrics) { - m.GcsDownloadBytesCount(5, "Parallel") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Parallel")): 5, - }, - }, - { - name: "read_type_Random", - f: func(m *otelMetrics) { - m.GcsDownloadBytesCount(5, "Random") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Random")): 5, - }, - }, - { - name: "read_type_Sequential", - f: func(m *otelMetrics) { - m.GcsDownloadBytesCount(5, "Sequential") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Sequential")): 5, - }, - }, { - name: "multiple_attributes_summed", - f: func(m *otelMetrics) { - m.GcsDownloadBytesCount(5, "Parallel") - m.GcsDownloadBytesCount(2, "Random") - m.GcsDownloadBytesCount(3, "Parallel") - }, - expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("read_type", "Parallel")): 8, - attribute.NewSet(attribute.String("read_type", "Random")): 2, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - metric, ok := metrics["gcs/download_bytes_count"] - assert.True(t, ok, "gcs/download_bytes_count metric not found") - expectedMap := make(map[string]int64) - for k, v := range tc.expected { - expectedMap[k.Encoded(encoder)] = v - } - assert.Equal(t, expectedMap, metric) - }) - } -} - -func TestGcsReadBytesCount(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - - m.GcsReadBytesCount(1024) - m.GcsReadBytesCount(2048) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - metric, ok := metrics["gcs/read_bytes_count"] - require.True(t, ok, "gcs/read_bytes_count metric not found") - s := attribute.NewSet() - assert.Equal(t, map[string]int64{s.Encoded(encoder): 3072}, metric) -} - -func TestGcsReadCount(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ - { - name: "read_type_Parallel", - f: func(m *otelMetrics) { - m.GcsReadCount(5, "Parallel") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Parallel")): 5, - }, - }, - { - name: "read_type_Random", - f: func(m *otelMetrics) { - m.GcsReadCount(5, "Random") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Random")): 5, - }, - }, - { - name: "read_type_Sequential", - f: func(m *otelMetrics) { - m.GcsReadCount(5, "Sequential") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("read_type", "Sequential")): 5, - }, - }, { - name: "multiple_attributes_summed", - f: func(m *otelMetrics) { - m.GcsReadCount(5, "Parallel") - m.GcsReadCount(2, "Random") - m.GcsReadCount(3, "Parallel") - }, - expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("read_type", "Parallel")): 8, - attribute.NewSet(attribute.String("read_type", "Random")): 2, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - metric, ok := metrics["gcs/read_count"] - assert.True(t, ok, "gcs/read_count metric not found") - expectedMap := make(map[string]int64) - for k, v := range tc.expected { - expectedMap[k.Encoded(encoder)] = v - } - assert.Equal(t, expectedMap, metric) - }) - } -} - -func TestGcsReaderCount(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ - { - name: "io_method_closed", - f: func(m *otelMetrics) { - m.GcsReaderCount(5, "closed") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("io_method", "closed")): 5, - }, - }, - { - name: "io_method_opened", - f: func(m *otelMetrics) { - m.GcsReaderCount(5, "opened") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("io_method", "opened")): 5, - }, - }, { - name: "multiple_attributes_summed", - f: func(m *otelMetrics) { - m.GcsReaderCount(5, "closed") - m.GcsReaderCount(2, "opened") - m.GcsReaderCount(3, "closed") - }, - expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("io_method", "closed")): 8, - attribute.NewSet(attribute.String("io_method", "opened")): 2, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - metric, ok := metrics["gcs/reader_count"] - assert.True(t, ok, "gcs/reader_count metric not found") - expectedMap := make(map[string]int64) - for k, v := range tc.expected { - expectedMap[k.Encoded(encoder)] = v - } - assert.Equal(t, expectedMap, metric) - }) - } -} - -func TestGcsRequestCount(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ - { - name: "gcs_method_ComposeObjects", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "ComposeObjects") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "ComposeObjects")): 5, - }, - }, - { - name: "gcs_method_CreateFolder", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "CreateFolder") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "CreateFolder")): 5, - }, - }, - { - name: "gcs_method_CreateObjectChunkWriter", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "CreateObjectChunkWriter") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "CreateObjectChunkWriter")): 5, - }, - }, - { - name: "gcs_method_DeleteFolder", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "DeleteFolder") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "DeleteFolder")): 5, - }, - }, - { - name: "gcs_method_DeleteObject", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "DeleteObject") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "DeleteObject")): 5, - }, - }, - { - name: "gcs_method_FinalizeUpload", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "FinalizeUpload") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload")): 5, - }, - }, - { - name: "gcs_method_GetFolder", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "GetFolder") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "GetFolder")): 5, - }, - }, - { - name: "gcs_method_ListObjects", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "ListObjects") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "ListObjects")): 5, - }, - }, - { - name: "gcs_method_MoveObject", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "MoveObject") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "MoveObject")): 5, - }, - }, - { - name: "gcs_method_MultiRangeDownloader::Add", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "MultiRangeDownloader::Add") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "MultiRangeDownloader::Add")): 5, - }, - }, - { - name: "gcs_method_NewMultiRangeDownloader", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "NewMultiRangeDownloader") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "NewMultiRangeDownloader")): 5, - }, - }, - { - name: "gcs_method_NewReader", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "NewReader") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "NewReader")): 5, - }, - }, - { - name: "gcs_method_RenameFolder", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "RenameFolder") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "RenameFolder")): 5, - }, - }, - { - name: "gcs_method_StatObject", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "StatObject") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "StatObject")): 5, - }, - }, - { - name: "gcs_method_UpdateObject", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "UpdateObject") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("gcs_method", "UpdateObject")): 5, - }, - }, { - name: "multiple_attributes_summed", - f: func(m *otelMetrics) { - m.GcsRequestCount(5, "ComposeObjects") - m.GcsRequestCount(2, "CreateFolder") - m.GcsRequestCount(3, "ComposeObjects") - }, - expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("gcs_method", "ComposeObjects")): 8, - attribute.NewSet(attribute.String("gcs_method", "CreateFolder")): 2, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - metric, ok := metrics["gcs/request_count"] - assert.True(t, ok, "gcs/request_count metric not found") - expectedMap := make(map[string]int64) - for k, v := range tc.expected { - expectedMap[k.Encoded(encoder)] = v - } - assert.Equal(t, expectedMap, metric) - }) - } -} - -func TestGcsRequestLatencies(t *testing.T) { - tests := []struct { - name string - latencies []time.Duration - gcsMethod string - }{ - { - name: "gcs_method_ComposeObjects", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "ComposeObjects", - }, - { - name: "gcs_method_CreateFolder", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "CreateFolder", - }, - { - name: "gcs_method_CreateObjectChunkWriter", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "CreateObjectChunkWriter", - }, - { - name: "gcs_method_DeleteFolder", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "DeleteFolder", - }, - { - name: "gcs_method_DeleteObject", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "DeleteObject", - }, - { - name: "gcs_method_FinalizeUpload", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "FinalizeUpload", - }, - { - name: "gcs_method_GetFolder", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "GetFolder", - }, - { - name: "gcs_method_ListObjects", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "ListObjects", - }, - { - name: "gcs_method_MoveObject", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "MoveObject", - }, - { - name: "gcs_method_MultiRangeDownloader::Add", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "MultiRangeDownloader::Add", - }, - { - name: "gcs_method_NewMultiRangeDownloader", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "NewMultiRangeDownloader", - }, - { - name: "gcs_method_NewReader", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "NewReader", - }, - { - name: "gcs_method_RenameFolder", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "RenameFolder", - }, - { - name: "gcs_method_StatObject", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "StatObject", - }, - { - name: "gcs_method_UpdateObject", - latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, - gcsMethod: "UpdateObject", - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - var totalLatency time.Duration - - for _, latency := range tc.latencies { - m.GcsRequestLatencies(ctx, latency, tc.gcsMethod) - totalLatency += latency - } - waitForMetricsProcessing() - - metrics := gatherHistogramMetrics(ctx, t, rd) - metric, ok := metrics["gcs/request_latencies"] - require.True(t, ok, "gcs/request_latencies metric not found") - - attrs := []attribute.KeyValue{ - attribute.String("gcs_method", tc.gcsMethod), - } - s := attribute.NewSet(attrs...) - expectedKey := s.Encoded(encoder) - dp, ok := metric[expectedKey] - require.True(t, ok, "DataPoint not found for key: %s", expectedKey) - assert.Equal(t, uint64(len(tc.latencies)), dp.Count) - assert.Equal(t, totalLatency.Milliseconds(), dp.Sum) - }) - } -} - -func TestGcsRetryCount(t *testing.T) { - tests := []struct { - name string - f func(m *otelMetrics) - expected map[attribute.Set]int64 - }{ - { - name: "retry_error_category_OTHER_ERRORS", - f: func(m *otelMetrics) { - m.GcsRetryCount(5, "OTHER_ERRORS") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS")): 5, - }, - }, - { - name: "retry_error_category_STALLED_READ_REQUEST", - f: func(m *otelMetrics) { - m.GcsRetryCount(5, "STALLED_READ_REQUEST") - }, - expected: map[attribute.Set]int64{ - attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST")): 5, - }, - }, { - name: "multiple_attributes_summed", - f: func(m *otelMetrics) { - m.GcsRetryCount(5, "OTHER_ERRORS") - m.GcsRetryCount(2, "STALLED_READ_REQUEST") - m.GcsRetryCount(3, "OTHER_ERRORS") - }, - expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("retry_error_category", "OTHER_ERRORS")): 8, - attribute.NewSet(attribute.String("retry_error_category", "STALLED_READ_REQUEST")): 2, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - ctx := context.Background() - encoder := attribute.DefaultEncoder() - m, rd := setupOTel(ctx, t) - - tc.f(m) - waitForMetricsProcessing() - - metrics := gatherNonZeroCounterMetrics(ctx, t, rd) - metric, ok := metrics["gcs/retry_count"] - assert.True(t, ok, "gcs/retry_count metric not found") - expectedMap := make(map[string]int64) - for k, v := range tc.expected { - expectedMap[k.Encoded(encoder)] = v - } - assert.Equal(t, expectedMap, metric) - }) - } -} diff --git a/tools/metrics-gen/metric_handle.tpl b/tools/metrics-gen/metric_handle.tpl index 2331450a23..3a253521e5 100644 --- a/tools/metrics-gen/metric_handle.tpl +++ b/tools/metrics-gen/metric_handle.tpl @@ -13,7 +13,7 @@ // limitations under the License. // **** DO NOT EDIT - FILE IS AUTO-GENERATED **** -package optimizedmetrics +package metrics import ( "context" diff --git a/tools/metrics-gen/noop_metrics.tpl b/tools/metrics-gen/noop_metrics.tpl index ae9d870aa1..09ec9cae11 100644 --- a/tools/metrics-gen/noop_metrics.tpl +++ b/tools/metrics-gen/noop_metrics.tpl @@ -13,7 +13,7 @@ // limitations under the License. // **** DO NOT EDIT - FILE IS AUTO-GENERATED **** -package optimizedmetrics +package metrics import ( "context" diff --git a/tools/metrics-gen/otel_metrics.tpl b/tools/metrics-gen/otel_metrics.tpl index ca94213224..d9e8fde8fb 100644 --- a/tools/metrics-gen/otel_metrics.tpl +++ b/tools/metrics-gen/otel_metrics.tpl @@ -14,7 +14,7 @@ // **** DO NOT EDIT - FILE IS AUTO-GENERATED **** -package optimizedmetrics +package metrics import ( "context" diff --git a/tools/metrics-gen/otel_metrics_test.tpl b/tools/metrics-gen/otel_metrics_test.tpl index e2ccf5d6ef..102dbc8aab 100644 --- a/tools/metrics-gen/otel_metrics_test.tpl +++ b/tools/metrics-gen/otel_metrics_test.tpl @@ -14,7 +14,7 @@ // **** DO NOT EDIT - FILE IS AUTO-GENERATED **** -package optimizedmetrics +package metrics import ( "context" From 2fafa2352a890fa0fde598cfbe0f7eabb6fc64a0 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Fri, 8 Aug 2025 15:15:23 +0530 Subject: [PATCH 0667/1298] Delete shadow-review workflow. (#3657) --- .github/workflows/shadow.yml | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 .github/workflows/shadow.yml diff --git a/.github/workflows/shadow.yml b/.github/workflows/shadow.yml deleted file mode 100644 index 1b114eebe8..0000000000 --- a/.github/workflows/shadow.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: "Shadow reviews" - -on: - pull_request: - types: - - opened - - ready_for_review - - reopened - branches: - - master - -jobs: - shadow-reviewer: - runs-on: ubuntu-latest - if: github.event.pull_request.draft == false - timeout-minutes: 15 - permissions: - pull-requests: write - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Add shadow reviewer - run: echo "PR_URL=\"${PR_URL}\"" && gh pr edit --add-reviewer @GoogleCloudPlatform/gcsfuse-shadow-reviewers "$PR_URL" - env: - GH_TOKEN: ${{ secrets.SHADOW_REVIEWER_CLASSIC }} - PR_URL: ${{github.event.pull_request.html_url}} From 6f4a28d2951c265143f1a874e438638c878c5640 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:09:25 +0530 Subject: [PATCH 0668/1298] chore(metrics): disable exemplar filter for metrics (#3656) * turn off exemplar filter * trigger CI --- internal/monitor/otelexporters.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/monitor/otelexporters.go b/internal/monitor/otelexporters.go index 6075d95412..1d637049ab 100644 --- a/internal/monitor/otelexporters.go +++ b/internal/monitor/otelexporters.go @@ -31,6 +31,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/prometheus" "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/exemplar" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.26.0" @@ -61,7 +62,7 @@ func SetupOTelMetricExporters(ctx context.Context, c *cfg.Config) (shutdownFn co options = append(options, metric.WithResource(res)) } - options = append(options, metric.WithView(dropDisallowedMetricsView)) + options = append(options, metric.WithView(dropDisallowedMetricsView), metric.WithExemplarFilter(exemplar.AlwaysOffFilter)) meterProvider := metric.NewMeterProvider(options...) shutdownFns = append(shutdownFns, meterProvider.Shutdown) From 8c512d2472539c43c65514f733c78964b0aa6322 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Fri, 8 Aug 2025 17:48:16 +0530 Subject: [PATCH 0669/1298] chore: clean up timeout from MRD (#3646) * clean up timeout from MRD * fix unit test * review comment --- .../gcsx/client_readers/multi_range_reader.go | 12 ++--- .../client_readers/multi_range_reader_test.go | 47 ++--------------- .../gcsx/multi_range_downloader_wrapper.go | 5 +- .../multi_range_downloader_wrapper_test.go | 22 +++----- internal/gcsx/random_reader.go | 2 +- internal/gcsx/random_reader_stretchr_test.go | 50 ------------------- 6 files changed, 14 insertions(+), 124 deletions(-) diff --git a/internal/gcsx/client_readers/multi_range_reader.go b/internal/gcsx/client_readers/multi_range_reader.go index e78a0f8f0d..096b283e9c 100644 --- a/internal/gcsx/client_readers/multi_range_reader.go +++ b/internal/gcsx/client_readers/multi_range_reader.go @@ -19,7 +19,6 @@ import ( "fmt" "io" "sync/atomic" - "time" "github.com/googlecloudplatform/gcsfuse/v3/internal/gcsx" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" @@ -27,11 +26,6 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/metrics" ) -// TimeoutForMultiRangeRead is the timeout value for multi-range read operations. -// -// TODO(b/385826024): Revert timeout to an appropriate value. This value is currently a placeholder and needs to be adjusted. -const TimeoutForMultiRangeRead = time.Hour - type MultiRangeReader struct { gcsx.GCSReader @@ -69,7 +63,7 @@ func NewMultiRangeReader(object *gcs.MinObject, metricHandle metrics.MetricHandl // Returns: // - int: The number of bytes read. // - error: An error if the read operation fails. -func (mrd *MultiRangeReader) readFromMultiRangeReader(ctx context.Context, p []byte, offset, end int64, timeout time.Duration) (int, error) { +func (mrd *MultiRangeReader) readFromMultiRangeReader(ctx context.Context, p []byte, offset, end int64) (int, error) { if mrd.mrdWrapper == nil { return 0, fmt.Errorf("readFromMultiRangeReader: Invalid MultiRangeDownloaderWrapper") } @@ -78,7 +72,7 @@ func (mrd *MultiRangeReader) readFromMultiRangeReader(ctx context.Context, p []b mrd.mrdWrapper.IncrementRefCount() } - return mrd.mrdWrapper.Read(ctx, p, offset, end, timeout, mrd.metricHandle) + return mrd.mrdWrapper.Read(ctx, p, offset, end, mrd.metricHandle) } func (mrd *MultiRangeReader) ReadAt(ctx context.Context, req *gcsx.GCSReaderRequest) (gcsx.ReaderResponse, error) { @@ -93,7 +87,7 @@ func (mrd *MultiRangeReader) ReadAt(ctx context.Context, req *gcsx.GCSReaderRequ return readerResponse, err } - readerResponse.Size, err = mrd.readFromMultiRangeReader(ctx, req.Buffer, req.Offset, req.EndOffset, TimeoutForMultiRangeRead) + readerResponse.Size, err = mrd.readFromMultiRangeReader(ctx, req.Buffer, req.Offset, req.EndOffset) return readerResponse, err } diff --git a/internal/gcsx/client_readers/multi_range_reader_test.go b/internal/gcsx/client_readers/multi_range_reader_test.go index c7c60a7df6..235d5b18c8 100644 --- a/internal/gcsx/client_readers/multi_range_reader_test.go +++ b/internal/gcsx/client_readers/multi_range_reader_test.go @@ -105,7 +105,7 @@ func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_ReadFull() { t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) buf := make([]byte, tc.dataSize+tc.extraSize) - bytesRead, err := t.multiRangeReader.readFromMultiRangeReader(t.ctx, buf, 0, int64(t.object.Size), TestTimeoutForMultiRangeRead) + bytesRead, err := t.multiRangeReader.readFromMultiRangeReader(t.ctx, buf, 0, int64(t.object.Size)) assert.NoError(t.T(), err) assert.Equal(t.T(), tc.dataSize, bytesRead) @@ -114,51 +114,10 @@ func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_ReadFull() { } } -func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_TimeoutExceeded() { - t.multiRangeReader.isMRDInUse.Store(false) - dataSize := 100 - t.object.Size = uint64(dataSize) - testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) - require.NoError(t.T(), err, "Error in creating MRDWrapper") - t.multiRangeReader.mrdWrapper = &fakeMRDWrapper - sleepTime := 10 * time.Millisecond - timeout := 5 * time.Millisecond - t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, sleepTime)).Once() - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Once() - buf := make([]byte, dataSize) - - _, err = t.multiRangeReader.readFromMultiRangeReader(t.ctx, buf, 0, int64(t.object.Size), timeout) - - assert.Error(t.T(), err) - assert.ErrorContains(t.T(), err, "timeout") -} - -func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_TimeoutNotExceeded() { - t.multiRangeReader.isMRDInUse.Store(false) - dataSize := 100 - t.object.Size = uint64(dataSize) - testContent := testUtil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := gcsx.NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) - require.NoError(t.T(), err, "Error in creating MRDWrapper") - t.multiRangeReader.mrdWrapper = &fakeMRDWrapper - sleepTime := 2 * time.Millisecond - timeout := 5 * time.Millisecond - t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, sleepTime)).Once() - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Once() - buf := make([]byte, dataSize) - - bytesRead, err := t.multiRangeReader.readFromMultiRangeReader(t.ctx, buf, 0, int64(t.object.Size), timeout) - - assert.NoError(t.T(), err) - assert.Equal(t.T(), dataSize, bytesRead) - assert.Equal(t.T(), testContent[:dataSize], buf[:bytesRead]) -} - func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_NilMRDWrapper() { t.multiRangeReader.mrdWrapper = nil - bytesRead, err := t.multiRangeReader.readFromMultiRangeReader(t.ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), TestTimeoutForMultiRangeRead) + bytesRead, err := t.multiRangeReader.readFromMultiRangeReader(t.ctx, make([]byte, t.object.Size), 0, int64(t.object.Size)) assert.Error(t.T(), err) assert.ErrorContains(t.T(), err, "readFromMultiRangeReader: Invalid MultiRangeDownloaderWrapper") @@ -190,7 +149,7 @@ func (t *multiRangeReaderTest) Test_ReadFromMultiRangeReader_ReadChunk() { t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Times(1) buf := make([]byte, tc.end-tc.start) - bytesRead, err := t.multiRangeReader.readFromMultiRangeReader(t.ctx, buf, int64(tc.start), int64(tc.end), TestTimeoutForMultiRangeRead) + bytesRead, err := t.multiRangeReader.readFromMultiRangeReader(t.ctx, buf, int64(tc.start), int64(tc.end)) assert.NoError(t.T(), err) assert.Equal(t.T(), tc.end-tc.start, bytesRead) diff --git a/internal/gcsx/multi_range_downloader_wrapper.go b/internal/gcsx/multi_range_downloader_wrapper.go index 5d64585c21..4717e43114 100644 --- a/internal/gcsx/multi_range_downloader_wrapper.go +++ b/internal/gcsx/multi_range_downloader_wrapper.go @@ -194,8 +194,7 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) ensureMultiRangeDownloader() (err } // Reads the data using MultiRangeDownloader. -func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []byte, - startOffset int64, endOffset int64, timeout time.Duration, metricHandle metrics.MetricHandle) (bytesRead int, err error) { +func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []byte, startOffset int64, endOffset int64, metricHandle metrics.MetricHandle) (bytesRead int, err error) { // Bidi Api with 0 as read_limit means no limit whereas we do not want to read anything with empty buffer. // Hence, handling it separately. if len(buf) == 0 { @@ -242,8 +241,6 @@ func (mrdWrapper *MultiRangeDownloaderWrapper) Read(ctx context.Context, buf []b if !mrdWrapper.config.FileSystem.IgnoreInterrupts { select { - case <-time.After(timeout): - err = fmt.Errorf("timeout") case <-ctx.Done(): err = ctx.Err() case res := <-done: diff --git a/internal/gcsx/multi_range_downloader_wrapper_test.go b/internal/gcsx/multi_range_downloader_wrapper_test.go index f3f0849ec7..67b96aa8b0 100644 --- a/internal/gcsx/multi_range_downloader_wrapper_test.go +++ b/internal/gcsx/multi_range_downloader_wrapper_test.go @@ -159,7 +159,7 @@ func (t *mrdWrapperTest) Test_Read() { t.mrdWrapper.Wrapped = nil t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, time.Microsecond)) - bytesRead, err := t.mrdWrapper.Read(context.Background(), buf, int64(tc.start), int64(tc.end), 10*time.Millisecond, metrics.NewNoopMetrics()) + bytesRead, err := t.mrdWrapper.Read(context.Background(), buf, int64(tc.start), int64(tc.end), metrics.NewNoopMetrics()) assert.NoError(t.T(), err) assert.Equal(t.T(), tc.end-tc.start, bytesRead) @@ -172,22 +172,12 @@ func (t *mrdWrapperTest) Test_Read_ErrorInCreatingMRD() { t.mrdWrapper.Wrapped = nil t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(nil, fmt.Errorf("Error in creating MRD")).Once() - bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) + bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), metrics.NewNoopMetrics()) assert.ErrorContains(t.T(), err, "MultiRangeDownloaderWrapper::Read: Error in creating MultiRangeDownloader") assert.Equal(t.T(), 0, bytesRead) } -func (t *mrdWrapperTest) Test_Read_Timeout() { - t.mrdWrapper.Wrapped = nil - t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, t.objectData, t.mrdTimeout+2*time.Millisecond), nil).Once() - - bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) - - assert.ErrorContains(t.T(), err, "timeout") - assert.Equal(t.T(), 0, bytesRead) -} - func (t *mrdWrapperTest) TestReadContextCancelledWithInterruptsEnabled() { t.mrdWrapper.Wrapped = nil t.mrdWrapper.config = &cfg.Config{FileSystem: cfg.FileSystemConfig{IgnoreInterrupts: false}} @@ -195,7 +185,7 @@ func (t *mrdWrapperTest) TestReadContextCancelledWithInterruptsEnabled() { ctx, cancel := context.WithCancel(context.Background()) cancel() - bytesRead, err := t.mrdWrapper.Read(ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) + bytesRead, err := t.mrdWrapper.Read(ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), metrics.NewNoopMetrics()) require.Error(t.T(), err) assert.ErrorContains(t.T(), err, "context canceled") @@ -208,7 +198,7 @@ func (t *mrdWrapperTest) TestReadContextCancelledWithInterruptsDisabled() { ctx, cancel := context.WithCancel(context.Background()) cancel() - bytesRead, err := t.mrdWrapper.Read(ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) + bytesRead, err := t.mrdWrapper.Read(ctx, make([]byte, t.object.Size), 0, int64(t.object.Size), metrics.NewNoopMetrics()) require.NoError(t.T(), err) assert.Equal(t.T(), 100, bytesRead) @@ -218,7 +208,7 @@ func (t *mrdWrapperTest) Test_Read_EOF() { t.mrdWrapper.Wrapped = nil t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleepAndDefaultError(t.object, t.objectData, time.Microsecond, io.EOF), nil).Once() - _, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) + _, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), metrics.NewNoopMetrics()) assert.ErrorIs(t.T(), err, io.EOF) } @@ -227,7 +217,7 @@ func (t *mrdWrapperTest) Test_Read_Error() { t.mrdWrapper.Wrapped = nil t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleepAndDefaultError(t.object, t.objectData, time.Microsecond, fmt.Errorf("Error")), nil).Once() - bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), t.mrdTimeout, metrics.NewNoopMetrics()) + bytesRead, err := t.mrdWrapper.Read(context.Background(), make([]byte, t.object.Size), 0, int64(t.object.Size), metrics.NewNoopMetrics()) assert.ErrorContains(t.T(), err, "error in Add call") assert.Equal(t.T(), 0, bytesRead) diff --git a/internal/gcsx/random_reader.go b/internal/gcsx/random_reader.go index c4149cfa00..4f1485188d 100644 --- a/internal/gcsx/random_reader.go +++ b/internal/gcsx/random_reader.go @@ -750,7 +750,7 @@ func (rr *randomReader) readFromMultiRangeReader(ctx context.Context, p []byte, rr.mrdWrapper.IncrementRefCount() } - bytesRead, err = rr.mrdWrapper.Read(ctx, p, offset, end, timeout, rr.metricHandle) + bytesRead, err = rr.mrdWrapper.Read(ctx, p, offset, end, rr.metricHandle) rr.totalReadBytes.Add(uint64(bytesRead)) rr.updateExpectedOffset(offset + int64(bytesRead)) return diff --git a/internal/gcsx/random_reader_stretchr_test.go b/internal/gcsx/random_reader_stretchr_test.go index 0f7e9e57b8..27704bd5f4 100644 --- a/internal/gcsx/random_reader_stretchr_test.go +++ b/internal/gcsx/random_reader_stretchr_test.go @@ -1173,56 +1173,6 @@ func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_NilMRDWrapper() assert.Equal(t.T(), 0, bytesRead) } -func (t *RandomReaderStretchrTest) Test_ReadFromMultiRangeReader_ValidateTimeout() { - testCases := []struct { - name string - dataSize int - timeout time.Duration - sleepTime time.Duration - expectedErrKeyword string - }{ - { - name: "TimeoutPlusFiveMilliSecond", - dataSize: 100, - timeout: 5 * time.Millisecond, - sleepTime: 10 * time.Millisecond, - expectedErrKeyword: "timeout", - }, - { - name: "TimeoutValue", - dataSize: 100, - timeout: 5 * time.Millisecond, - sleepTime: 5 * time.Millisecond, - expectedErrKeyword: "timeout", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func() { - t.rr.wrapped.reader = nil - t.rr.wrapped.isMRDInUse.Store(false) - t.object.Size = uint64(tc.dataSize) - testContent := testutil.GenerateRandomBytes(int(t.object.Size)) - fakeMRDWrapper, err := NewMultiRangeDownloaderWrapperWithClock(t.mockBucket, t.object, &clock.FakeClock{}, &cfg.Config{}) - assert.Nil(t.T(), err, "Error in creating MRDWrapper") - t.rr.wrapped.mrdWrapper = &fakeMRDWrapper - t.mockBucket.On("NewMultiRangeDownloader", mock.Anything, mock.Anything).Return(fake.NewFakeMultiRangeDownloaderWithSleep(t.object, testContent, tc.sleepTime)).Once() - t.mockBucket.On("BucketType", mock.Anything).Return(gcs.BucketType{Zonal: true}).Once() - buf := make([]byte, tc.dataSize) - - bytesRead, err := t.rr.wrapped.readFromMultiRangeReader(t.rr.ctx, buf, 0, int64(t.object.Size), tc.timeout) - - if tc.name == "TimeoutValue" && bytesRead != 0 { - assert.NoError(t.T(), err) - assert.Equal(t.T(), tc.dataSize, bytesRead) - assert.Equal(t.T(), testContent[:tc.dataSize], buf[:bytesRead]) - return - } - assert.ErrorContains(t.T(), err, tc.expectedErrKeyword) - }) - } -} - // Validates: // 1. No change in ReadAt behavior based inactiveStreamTimeout config. // 2. Valid timeout config creates InactiveTimeoutReader instance of storage.Reader. From 24cd286ce1926feec95f569dd20c397b560ba13a Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Fri, 8 Aug 2025 20:57:44 +0530 Subject: [PATCH 0670/1298] test: Move proxy server out of integration tests folder (#3650) * run proxy tests as a part of e2e tests * move folder out of directory --- .../{proxy_server => }/configs/config.yaml | 0 .../empty_gcs_file_2nd_chunk_upload_returns412.yaml | 0 .../local_file_2nd_chunk_upload_returns412.yaml | 0 .../{proxy_server => }/configs/read_stall_5s.yaml | 0 .../{proxy_server => }/configs/write_stall_40s.yaml | 0 .../configs/write_stall_twice_40s.yaml | 0 .../emulator_tests/read_stall/read_stall_test.go | 2 +- .../empty_gcs_file_failure_test.go | 2 +- .../new_local_file_failure_test.go | 2 +- .../emulator_tests/util/test_helper.go | 2 +- .../write_stall/writes_stall_on_sync_test.go | 6 +++--- .../emulator_tests => }/proxy_server/config.go | 0 .../emulator_tests => }/proxy_server/config_test.go | 0 .../emulator_tests => }/proxy_server/emulator.go | 0 .../emulator_tests => }/proxy_server/emulator_test.go | 0 .../emulator_tests => }/proxy_server/main.go | 0 .../emulator_tests => }/proxy_server/main_test.go | 10 ---------- .../proxy_server/operation_manager.go | 0 .../proxy_server/operation_manager_test.go | 0 .../emulator_tests => }/proxy_server/request_mapper.go | 0 .../proxy_server/request_mappper_test.go | 0 21 files changed, 7 insertions(+), 17 deletions(-) rename tools/integration_tests/emulator_tests/{proxy_server => }/configs/config.yaml (100%) rename tools/integration_tests/emulator_tests/{proxy_server => }/configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml (100%) rename tools/integration_tests/emulator_tests/{proxy_server => }/configs/local_file_2nd_chunk_upload_returns412.yaml (100%) rename tools/integration_tests/emulator_tests/{proxy_server => }/configs/read_stall_5s.yaml (100%) rename tools/integration_tests/emulator_tests/{proxy_server => }/configs/write_stall_40s.yaml (100%) rename tools/integration_tests/emulator_tests/{proxy_server => }/configs/write_stall_twice_40s.yaml (100%) rename tools/{integration_tests/emulator_tests => }/proxy_server/config.go (100%) rename tools/{integration_tests/emulator_tests => }/proxy_server/config_test.go (100%) rename tools/{integration_tests/emulator_tests => }/proxy_server/emulator.go (100%) rename tools/{integration_tests/emulator_tests => }/proxy_server/emulator_test.go (100%) rename tools/{integration_tests/emulator_tests => }/proxy_server/main.go (100%) rename tools/{integration_tests/emulator_tests => }/proxy_server/main_test.go (84%) rename tools/{integration_tests/emulator_tests => }/proxy_server/operation_manager.go (100%) rename tools/{integration_tests/emulator_tests => }/proxy_server/operation_manager_test.go (100%) rename tools/{integration_tests/emulator_tests => }/proxy_server/request_mapper.go (100%) rename tools/{integration_tests/emulator_tests => }/proxy_server/request_mappper_test.go (100%) diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/config.yaml b/tools/integration_tests/emulator_tests/configs/config.yaml similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/configs/config.yaml rename to tools/integration_tests/emulator_tests/configs/config.yaml diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml b/tools/integration_tests/emulator_tests/configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml rename to tools/integration_tests/emulator_tests/configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/local_file_2nd_chunk_upload_returns412.yaml b/tools/integration_tests/emulator_tests/configs/local_file_2nd_chunk_upload_returns412.yaml similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/configs/local_file_2nd_chunk_upload_returns412.yaml rename to tools/integration_tests/emulator_tests/configs/local_file_2nd_chunk_upload_returns412.yaml diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/read_stall_5s.yaml b/tools/integration_tests/emulator_tests/configs/read_stall_5s.yaml similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/configs/read_stall_5s.yaml rename to tools/integration_tests/emulator_tests/configs/read_stall_5s.yaml diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_40s.yaml b/tools/integration_tests/emulator_tests/configs/write_stall_40s.yaml similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_40s.yaml rename to tools/integration_tests/emulator_tests/configs/write_stall_40s.yaml diff --git a/tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_twice_40s.yaml b/tools/integration_tests/emulator_tests/configs/write_stall_twice_40s.yaml similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/configs/write_stall_twice_40s.yaml rename to tools/integration_tests/emulator_tests/configs/write_stall_twice_40s.yaml diff --git a/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go b/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go index 773bdda397..adf77dadd1 100644 --- a/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go +++ b/tools/integration_tests/emulator_tests/read_stall/read_stall_test.go @@ -50,7 +50,7 @@ type readStall struct { } func (r *readStall) SetupTest() { - r.configPath = "../proxy_server/configs/read_stall_5s.yaml" + r.configPath = "../configs/read_stall_5s.yaml" r.proxyServerLogFile = setup.CreateProxyServerLogFile(r.T()) var err error r.port, r.proxyProcessId, err = emulator_tests.StartProxyServer(r.configPath, r.proxyServerLogFile) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go index 95c5085fde..b69a45459c 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/empty_gcs_file_failure_test.go @@ -36,7 +36,7 @@ type emptyGcsFileFailureTestSuite struct { // ////////////////////////////////////////////////////////////////////// func (t *emptyGcsFileFailureTestSuite) SetupTest() { - t.configPath = "../proxy_server/configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml" + t.configPath = "../configs/empty_gcs_file_2nd_chunk_upload_returns412.yaml" t.setupTest() // Create an empty file on GCS. CreateObjectInGCSTestDir(t.ctx, t.storageClient, testDirName, FileName1, "", t.T()) diff --git a/tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go b/tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go index 9850def934..bc8f198a23 100644 --- a/tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go +++ b/tools/integration_tests/emulator_tests/streaming_writes_failure/new_local_file_failure_test.go @@ -34,7 +34,7 @@ type newLocalFileFailureTestSuite struct { // ////////////////////////////////////////////////////////////////////// func (t *newLocalFileFailureTestSuite) SetupTest() { - t.configPath = "../proxy_server/configs/local_file_2nd_chunk_upload_returns412.yaml" + t.configPath = "../configs/local_file_2nd_chunk_upload_returns412.yaml" t.setupTest() // Create local file. t.filePath, t.fh1 = CreateLocalFileInTestDir(t.ctx, t.storageClient, t.testDirPath, FileName1, t.T()) diff --git a/tools/integration_tests/emulator_tests/util/test_helper.go b/tools/integration_tests/emulator_tests/util/test_helper.go index 352bd66f30..0cc858e0ff 100644 --- a/tools/integration_tests/emulator_tests/util/test_helper.go +++ b/tools/integration_tests/emulator_tests/util/test_helper.go @@ -49,7 +49,7 @@ const PortAndProxyProcessIdInfoRegex = `Listening Proxy Server On Port \[(\d+)\] // - int: Proxy Server Process ID. // - error: An error if any error occurs in setting up Proxy Server. func StartProxyServer(configPath, logFilePath string) (int, int, error) { - cmd := exec.Command("go", "run", "../proxy_server/.", "--config-path="+configPath, "--log-file="+logFilePath) + cmd := exec.Command("go", "run", "../../../proxy_server/.", "--config-path="+configPath, "--log-file="+logFilePath) cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr err := cmd.Start() if err != nil { diff --git a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go index 248fd6bb5d..9373ad084c 100644 --- a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go @@ -45,7 +45,7 @@ type chunkTransferTimeoutInfinity struct { } func (s *chunkTransferTimeoutInfinity) Setup(t *testing.T) { - configPath := "../proxy_server/configs/write_stall_40s.yaml" + configPath := "../configs/write_stall_40s.yaml" s.proxyServerLogFile = setup.CreateProxyServerLogFile(t) var err error s.port, s.proxyProcessId, err = emulator_tests.StartProxyServer(configPath, s.proxyServerLogFile) @@ -114,14 +114,14 @@ func TestChunkTransferTimeout(t *testing.T) { }{ { name: "SingleStall", - configPath: "../proxy_server/configs/write_stall_40s.yaml", + configPath: "../configs/write_stall_40s.yaml", expectedTimeout: func(chunkTransferTimeoutSecs int) time.Duration { return time.Duration(chunkTransferTimeoutSecs) * time.Second }, }, { name: "MultipleStalls", - configPath: "../proxy_server/configs/write_stall_twice_40s.yaml", // 2 stalls + configPath: "../configs/write_stall_twice_40s.yaml", // 2 stalls // Expect total time to be greater than the timeout multiplied by the number of stalls (2 in this case). expectedTimeout: func(chunkTransferTimeoutSecs int) time.Duration { return time.Duration(chunkTransferTimeoutSecs*2) * time.Second diff --git a/tools/integration_tests/emulator_tests/proxy_server/config.go b/tools/proxy_server/config.go similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/config.go rename to tools/proxy_server/config.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/config_test.go b/tools/proxy_server/config_test.go similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/config_test.go rename to tools/proxy_server/config_test.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/emulator.go b/tools/proxy_server/emulator.go similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/emulator.go rename to tools/proxy_server/emulator.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/emulator_test.go b/tools/proxy_server/emulator_test.go similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/emulator_test.go rename to tools/proxy_server/emulator_test.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/main.go b/tools/proxy_server/main.go similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/main.go rename to tools/proxy_server/main.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/main_test.go b/tools/proxy_server/main_test.go similarity index 84% rename from tools/integration_tests/emulator_tests/proxy_server/main_test.go rename to tools/proxy_server/main_test.go index 1c834bac4a..4006cd1014 100644 --- a/tools/integration_tests/emulator_tests/proxy_server/main_test.go +++ b/tools/proxy_server/main_test.go @@ -20,7 +20,6 @@ import ( "net/http/httptest" "testing" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" ) @@ -50,12 +49,3 @@ func TestAddRetryID(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "test-id-123", req.Header.Get("x-retry-test-id"), "Unexpected x-retry-test-id header value") } - -func TestMain(m *testing.M) { - setup.ParseSetUpFlags() - - // Skip running unit tests as part of the integration tests. Although running these tests will not cause any failures. - if setup.IsIntegrationTest() { - return - } -} diff --git a/tools/integration_tests/emulator_tests/proxy_server/operation_manager.go b/tools/proxy_server/operation_manager.go similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/operation_manager.go rename to tools/proxy_server/operation_manager.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/operation_manager_test.go b/tools/proxy_server/operation_manager_test.go similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/operation_manager_test.go rename to tools/proxy_server/operation_manager_test.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/request_mapper.go b/tools/proxy_server/request_mapper.go similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/request_mapper.go rename to tools/proxy_server/request_mapper.go diff --git a/tools/integration_tests/emulator_tests/proxy_server/request_mappper_test.go b/tools/proxy_server/request_mappper_test.go similarity index 100% rename from tools/integration_tests/emulator_tests/proxy_server/request_mappper_test.go rename to tools/proxy_server/request_mappper_test.go From 9c0560fc13e8df5f75c7227583c79b36d430a422 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Sat, 9 Aug 2025 00:09:20 +0530 Subject: [PATCH 0671/1298] adding missing fields (#3660) --- internal/fs/handle/file.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/fs/handle/file.go b/internal/fs/handle/file.go index 3904b075e1..cd00b1c392 100644 --- a/internal/fs/handle/file.go +++ b/internal/fs/handle/file.go @@ -199,6 +199,8 @@ func (fh *FileHandle) ReadWithReadManager(ctx context.Context, dst []byte, offse MetricHandle: fh.metricHandle, MrdWrapper: mrdWrapper, Config: fh.config, + GlobalMaxBlocksSem: fh.globalMaxReadBlocksSem, + WorkerPool: fh.bufferedReadWorkerPool, }) // Release RWLock and take RLock on file handle again. Inode lock is not needed now. From bb492ab83716363fc53f3ffb21398a4324985ac4 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 11 Aug 2025 08:57:34 +0530 Subject: [PATCH 0672/1298] fix script (#3662) --- .../scripts/build_and_install_gcsfuse.sh | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/perfmetrics/scripts/build_and_install_gcsfuse.sh b/perfmetrics/scripts/build_and_install_gcsfuse.sh index 7fa9ad87dd..bf3f84d00d 100755 --- a/perfmetrics/scripts/build_and_install_gcsfuse.sh +++ b/perfmetrics/scripts/build_and_install_gcsfuse.sh @@ -20,18 +20,18 @@ set -e # --- Determine architecture (e.g., amd64, arm64) --- architecture=$(dpkg --print-architecture) -# --- Install Docker only if not already installed --- -if ! command -v docker &> /dev/null; then - echo "Installing docker..." - sudo mkdir -p /etc/apt/keyrings - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg - echo \ - "deb [arch=${architecture} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ - $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null - sudo apt-get update - sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y +# Install Docker if it's not already present or if this is a Kokoro environment as it has old docker version. +if ! command -v docker &> /dev/null || [[ -n "${KOKORO_ARTIFACTS_DIR}" ]]; then + echo "Installing Docker..." + sudo mkdir -p /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg + echo \ + "deb [arch=${architecture} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y else - echo "Docker is already installed. Skipping Docker installation." + echo "Docker is already installed. Skipping Docker installation." fi # --- Build and install gcsfuse --- From 2fad2de7dda2474a4ec9ee40d385fb1c62cb2b7e Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:43:00 +0530 Subject: [PATCH 0673/1298] fix(metrics): Prevent panic from unrecognized histogram attributes (#3666) * return if histogram record is nil due to undeclared attr * add return after logging for all attributes --- metrics/otel_metrics.go | 28 ++++++++++++++++++++++++++++ tools/metrics-gen/main.go | 1 + 2 files changed, 29 insertions(+) diff --git a/metrics/otel_metrics.go b/metrics/otel_metrics.go index a89a7a4655..3b22e2abd6 100644 --- a/metrics/otel_metrics.go +++ b/metrics/otel_metrics.go @@ -1197,6 +1197,7 @@ func (o *otelMetrics) FileCacheReadBytesCount( o.fileCacheReadBytesCountReadTypeSequentialAtomic.Add(inc) default: updateUnrecognizedAttribute(readType) + return } } @@ -1214,6 +1215,7 @@ func (o *otelMetrics) FileCacheReadCount( o.fileCacheReadCountCacheHitTrueReadTypeSequentialAtomic.Add(inc) default: updateUnrecognizedAttribute(readType) + return } case false: switch readType { @@ -1225,6 +1227,7 @@ func (o *otelMetrics) FileCacheReadCount( o.fileCacheReadCountCacheHitFalseReadTypeSequentialAtomic.Add(inc) default: updateUnrecognizedAttribute(readType) + return } } @@ -1311,6 +1314,7 @@ func (o *otelMetrics) FsOpsCount( o.fsOpsCountFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } } @@ -1382,6 +1386,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "DIR_NOT_EMPTY": switch fsOp { @@ -1447,6 +1452,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "FILE_DIR_ERROR": switch fsOp { @@ -1512,6 +1518,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "FILE_EXISTS": switch fsOp { @@ -1577,6 +1584,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "INTERRUPT_ERROR": switch fsOp { @@ -1642,6 +1650,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "INVALID_ARGUMENT": switch fsOp { @@ -1707,6 +1716,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "INVALID_OPERATION": switch fsOp { @@ -1772,6 +1782,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "IO_ERROR": switch fsOp { @@ -1837,6 +1848,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "MISC_ERROR": switch fsOp { @@ -1902,6 +1914,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "NETWORK_ERROR": switch fsOp { @@ -1967,6 +1980,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "NOT_A_DIR": switch fsOp { @@ -2032,6 +2046,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "NOT_IMPLEMENTED": switch fsOp { @@ -2097,6 +2112,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "NO_FILE_OR_DIR": switch fsOp { @@ -2162,6 +2178,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "PERM_ERROR": switch fsOp { @@ -2227,6 +2244,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "PROCESS_RESOURCE_MGMT_ERROR": switch fsOp { @@ -2292,6 +2310,7 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } case "TOO_MANY_OPEN_FILES": switch fsOp { @@ -2357,9 +2376,11 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic.Add(inc) default: updateUnrecognizedAttribute(fsOp) + return } default: updateUnrecognizedAttribute(fsErrorCategory) + return } } @@ -2430,6 +2451,7 @@ func (o *otelMetrics) FsOpsLatency( record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpWriteFileAttrSet} default: updateUnrecognizedAttribute(fsOp) + return } select { @@ -2449,6 +2471,7 @@ func (o *otelMetrics) GcsDownloadBytesCount( o.gcsDownloadBytesCountReadTypeSequentialAtomic.Add(inc) default: updateUnrecognizedAttribute(readType) + return } } @@ -2470,6 +2493,7 @@ func (o *otelMetrics) GcsReadCount( o.gcsReadCountReadTypeSequentialAtomic.Add(inc) default: updateUnrecognizedAttribute(readType) + return } } @@ -2483,6 +2507,7 @@ func (o *otelMetrics) GcsReaderCount( o.gcsReaderCountIoMethodOpenedAtomic.Add(inc) default: updateUnrecognizedAttribute(ioMethod) + return } } @@ -2522,6 +2547,7 @@ func (o *otelMetrics) GcsRequestCount( o.gcsRequestCountGcsMethodUpdateObjectAtomic.Add(inc) default: updateUnrecognizedAttribute(gcsMethod) + return } } @@ -2562,6 +2588,7 @@ func (o *otelMetrics) GcsRequestLatencies( record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodUpdateObjectAttrSet} default: updateUnrecognizedAttribute(gcsMethod) + return } select { @@ -2579,6 +2606,7 @@ func (o *otelMetrics) GcsRetryCount( o.gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic.Add(inc) default: updateUnrecognizedAttribute(retryErrorCategory) + return } } diff --git a/tools/metrics-gen/main.go b/tools/metrics-gen/main.go index 3ba577a4b9..1565fa0b12 100644 --- a/tools/metrics-gen/main.go +++ b/tools/metrics-gen/main.go @@ -253,6 +253,7 @@ func generateCombinations(attributes []Attribute) []AttrCombination { func handleDefaultInSwitchCase(level int, attrName string, builder *strings.Builder) { builder.WriteString(fmt.Sprintf("%sdefault:\n", strings.Repeat("\t", level+2))) builder.WriteString(fmt.Sprintf("%supdateUnrecognizedAttribute(%s)\n", strings.Repeat("\t", level+3), toCamel(attrName))) + builder.WriteString(fmt.Sprintf("%sreturn\n", strings.Repeat("\t", level+3))) } func validateMetric(m Metric) error { From 2081d9eb6cd14b924ca74e3b1e3dfc739265f6b5 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:49:29 +0530 Subject: [PATCH 0674/1298] add missing attribute values (#3671) --- metrics/metrics.yaml | 6 + metrics/otel_metrics.go | 225 ++++++++++++++++++++++++++++----- metrics/otel_metrics_test.go | 237 +++++++++++++++++++++++++++++++++-- 3 files changed, 433 insertions(+), 35 deletions(-) diff --git a/metrics/metrics.yaml b/metrics/metrics.yaml index 4509c8e4ef..31861c2caa 100644 --- a/metrics/metrics.yaml +++ b/metrics/metrics.yaml @@ -86,6 +86,7 @@ - "OpenDir" - "OpenFile" - "ReadDir" + - "ReadDirPlus" - "ReadFile" - "ReadSymlink" - "ReleaseDirHandle" @@ -169,6 +170,7 @@ values: - "closed" - "opened" + - "ReadHandle" - metric-name: "gcs/request_count" description: "The cumulative number of GCS requests processed along with the GCS method." @@ -178,11 +180,15 @@ attribute-type: string values: &gcs_method_list - "ComposeObjects" + - "CopyObject" + - "CreateAppendableObjectWriter" - "CreateFolder" + - "CreateObject" - "CreateObjectChunkWriter" - "DeleteFolder" - "DeleteObject" - "FinalizeUpload" + - "FlushPendingWrites" - "GetFolder" - "ListObjects" - "MoveObject" diff --git a/metrics/otel_metrics.go b/metrics/otel_metrics.go index 3b22e2abd6..d4ff06fce7 100644 --- a/metrics/otel_metrics.go +++ b/metrics/otel_metrics.go @@ -60,6 +60,7 @@ var ( fsOpsCountFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenDir"))) fsOpsCountFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenFile"))) fsOpsCountFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadDir"))) + fsOpsCountFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadDirPlus"))) fsOpsCountFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadFile"))) fsOpsCountFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadSymlink"))) fsOpsCountFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseDirHandle"))) @@ -90,6 +91,7 @@ var ( fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -120,6 +122,7 @@ var ( fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -150,6 +153,7 @@ var ( fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -180,6 +184,7 @@ var ( fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -210,6 +215,7 @@ var ( fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -240,6 +246,7 @@ var ( fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -270,6 +277,7 @@ var ( fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -300,6 +308,7 @@ var ( fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -330,6 +339,7 @@ var ( fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -360,6 +370,7 @@ var ( fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -390,6 +401,7 @@ var ( fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -420,6 +432,7 @@ var ( fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -450,6 +463,7 @@ var ( fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -480,6 +494,7 @@ var ( fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -510,6 +525,7 @@ var ( fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -540,6 +556,7 @@ var ( fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenDir"))) fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "OpenFile"))) fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadDir"))) + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadDirPlus"))) fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadFile"))) fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadSymlink"))) fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReleaseDirHandle"))) @@ -570,6 +587,7 @@ var ( fsOpsLatencyFsOpOpenDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenDir"))) fsOpsLatencyFsOpOpenFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "OpenFile"))) fsOpsLatencyFsOpReadDirAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadDir"))) + fsOpsLatencyFsOpReadDirPlusAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadDirPlus"))) fsOpsLatencyFsOpReadFileAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadFile"))) fsOpsLatencyFsOpReadSymlinkAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReadSymlink"))) fsOpsLatencyFsOpReleaseDirHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("fs_op", "ReleaseDirHandle"))) @@ -590,14 +608,19 @@ var ( gcsReadCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) gcsReadCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) gcsReadCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) + gcsReaderCountIoMethodReadHandleAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("io_method", "ReadHandle"))) gcsReaderCountIoMethodClosedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("io_method", "closed"))) gcsReaderCountIoMethodOpenedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("io_method", "opened"))) gcsRequestCountGcsMethodComposeObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ComposeObjects"))) + gcsRequestCountGcsMethodCopyObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CopyObject"))) + gcsRequestCountGcsMethodCreateAppendableObjectWriterAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateAppendableObjectWriter"))) gcsRequestCountGcsMethodCreateFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateFolder"))) + gcsRequestCountGcsMethodCreateObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateObject"))) gcsRequestCountGcsMethodCreateObjectChunkWriterAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateObjectChunkWriter"))) gcsRequestCountGcsMethodDeleteFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteFolder"))) gcsRequestCountGcsMethodDeleteObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteObject"))) gcsRequestCountGcsMethodFinalizeUploadAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload"))) + gcsRequestCountGcsMethodFlushPendingWritesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "FlushPendingWrites"))) gcsRequestCountGcsMethodGetFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "GetFolder"))) gcsRequestCountGcsMethodListObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ListObjects"))) gcsRequestCountGcsMethodMoveObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MoveObject"))) @@ -608,11 +631,15 @@ var ( gcsRequestCountGcsMethodStatObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "StatObject"))) gcsRequestCountGcsMethodUpdateObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "UpdateObject"))) gcsRequestLatenciesGcsMethodComposeObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ComposeObjects"))) + gcsRequestLatenciesGcsMethodCopyObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CopyObject"))) + gcsRequestLatenciesGcsMethodCreateAppendableObjectWriterAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateAppendableObjectWriter"))) gcsRequestLatenciesGcsMethodCreateFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateFolder"))) + gcsRequestLatenciesGcsMethodCreateObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateObject"))) gcsRequestLatenciesGcsMethodCreateObjectChunkWriterAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "CreateObjectChunkWriter"))) gcsRequestLatenciesGcsMethodDeleteFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteFolder"))) gcsRequestLatenciesGcsMethodDeleteObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "DeleteObject"))) gcsRequestLatenciesGcsMethodFinalizeUploadAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload"))) + gcsRequestLatenciesGcsMethodFlushPendingWritesAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "FlushPendingWrites"))) gcsRequestLatenciesGcsMethodGetFolderAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "GetFolder"))) gcsRequestLatenciesGcsMethodListObjectsAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "ListObjects"))) gcsRequestLatenciesGcsMethodMoveObjectAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("gcs_method", "MoveObject"))) @@ -661,6 +688,7 @@ type otelMetrics struct { fsOpsCountFsOpOpenDirAtomic *atomic.Int64 fsOpsCountFsOpOpenFileAtomic *atomic.Int64 fsOpsCountFsOpReadDirAtomic *atomic.Int64 + fsOpsCountFsOpReadDirPlusAtomic *atomic.Int64 fsOpsCountFsOpReadFileAtomic *atomic.Int64 fsOpsCountFsOpReadSymlinkAtomic *atomic.Int64 fsOpsCountFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -691,6 +719,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -721,6 +750,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -751,6 +781,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -781,6 +812,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -811,6 +843,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -841,6 +874,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -871,6 +905,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -901,6 +936,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -931,6 +967,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -961,6 +998,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -991,6 +1029,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -1021,6 +1060,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -1051,6 +1091,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -1081,6 +1122,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -1111,6 +1153,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -1141,6 +1184,7 @@ type otelMetrics struct { fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic *atomic.Int64 + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirPlusAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic *atomic.Int64 fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic *atomic.Int64 @@ -1162,14 +1206,19 @@ type otelMetrics struct { gcsReadCountReadTypeParallelAtomic *atomic.Int64 gcsReadCountReadTypeRandomAtomic *atomic.Int64 gcsReadCountReadTypeSequentialAtomic *atomic.Int64 + gcsReaderCountIoMethodReadHandleAtomic *atomic.Int64 gcsReaderCountIoMethodClosedAtomic *atomic.Int64 gcsReaderCountIoMethodOpenedAtomic *atomic.Int64 gcsRequestCountGcsMethodComposeObjectsAtomic *atomic.Int64 + gcsRequestCountGcsMethodCopyObjectAtomic *atomic.Int64 + gcsRequestCountGcsMethodCreateAppendableObjectWriterAtomic *atomic.Int64 gcsRequestCountGcsMethodCreateFolderAtomic *atomic.Int64 + gcsRequestCountGcsMethodCreateObjectAtomic *atomic.Int64 gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic *atomic.Int64 gcsRequestCountGcsMethodDeleteFolderAtomic *atomic.Int64 gcsRequestCountGcsMethodDeleteObjectAtomic *atomic.Int64 gcsRequestCountGcsMethodFinalizeUploadAtomic *atomic.Int64 + gcsRequestCountGcsMethodFlushPendingWritesAtomic *atomic.Int64 gcsRequestCountGcsMethodGetFolderAtomic *atomic.Int64 gcsRequestCountGcsMethodListObjectsAtomic *atomic.Int64 gcsRequestCountGcsMethodMoveObjectAtomic *atomic.Int64 @@ -1284,6 +1333,8 @@ func (o *otelMetrics) FsOpsCount( o.fsOpsCountFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsCountFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsCountFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsCountFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -1356,6 +1407,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -1422,6 +1475,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -1488,6 +1543,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -1554,6 +1611,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -1620,6 +1679,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -1686,6 +1747,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -1752,6 +1815,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -1818,6 +1883,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -1884,6 +1951,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -1950,6 +2019,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -2016,6 +2087,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -2082,6 +2155,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -2148,6 +2223,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -2214,6 +2291,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -2280,6 +2359,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -2346,6 +2427,8 @@ func (o *otelMetrics) FsOpsErrorCount( o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic.Add(inc) case "ReadDir": o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic.Add(inc) + case "ReadDirPlus": + o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirPlusAtomic.Add(inc) case "ReadFile": o.fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic.Add(inc) case "ReadSymlink": @@ -2421,6 +2504,8 @@ func (o *otelMetrics) FsOpsLatency( record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpOpenFileAttrSet} case "ReadDir": record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadDirAttrSet} + case "ReadDirPlus": + record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadDirPlusAttrSet} case "ReadFile": record = histogramRecord{ctx: ctx, instrument: o.fsOpsLatency, value: latency.Microseconds(), attributes: fsOpsLatencyFsOpReadFileAttrSet} case "ReadSymlink": @@ -2501,6 +2586,8 @@ func (o *otelMetrics) GcsReadCount( func (o *otelMetrics) GcsReaderCount( inc int64, ioMethod string) { switch ioMethod { + case "ReadHandle": + o.gcsReaderCountIoMethodReadHandleAtomic.Add(inc) case "closed": o.gcsReaderCountIoMethodClosedAtomic.Add(inc) case "opened": @@ -2517,8 +2604,14 @@ func (o *otelMetrics) GcsRequestCount( switch gcsMethod { case "ComposeObjects": o.gcsRequestCountGcsMethodComposeObjectsAtomic.Add(inc) + case "CopyObject": + o.gcsRequestCountGcsMethodCopyObjectAtomic.Add(inc) + case "CreateAppendableObjectWriter": + o.gcsRequestCountGcsMethodCreateAppendableObjectWriterAtomic.Add(inc) case "CreateFolder": o.gcsRequestCountGcsMethodCreateFolderAtomic.Add(inc) + case "CreateObject": + o.gcsRequestCountGcsMethodCreateObjectAtomic.Add(inc) case "CreateObjectChunkWriter": o.gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic.Add(inc) case "DeleteFolder": @@ -2527,6 +2620,8 @@ func (o *otelMetrics) GcsRequestCount( o.gcsRequestCountGcsMethodDeleteObjectAtomic.Add(inc) case "FinalizeUpload": o.gcsRequestCountGcsMethodFinalizeUploadAtomic.Add(inc) + case "FlushPendingWrites": + o.gcsRequestCountGcsMethodFlushPendingWritesAtomic.Add(inc) case "GetFolder": o.gcsRequestCountGcsMethodGetFolderAtomic.Add(inc) case "ListObjects": @@ -2558,8 +2653,14 @@ func (o *otelMetrics) GcsRequestLatencies( switch gcsMethod { case "ComposeObjects": record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodComposeObjectsAttrSet} + case "CopyObject": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodCopyObjectAttrSet} + case "CreateAppendableObjectWriter": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodCreateAppendableObjectWriterAttrSet} case "CreateFolder": record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodCreateFolderAttrSet} + case "CreateObject": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodCreateObjectAttrSet} case "CreateObjectChunkWriter": record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodCreateObjectChunkWriterAttrSet} case "DeleteFolder": @@ -2568,6 +2669,8 @@ func (o *otelMetrics) GcsRequestLatencies( record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodDeleteObjectAttrSet} case "FinalizeUpload": record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodFinalizeUploadAttrSet} + case "FlushPendingWrites": + record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodFlushPendingWritesAttrSet} case "GetFolder": record = histogramRecord{ctx: ctx, instrument: o.gcsRequestLatencies, value: latency.Milliseconds(), attributes: gcsRequestLatenciesGcsMethodGetFolderAttrSet} case "ListObjects": @@ -2652,6 +2755,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsCountFsOpOpenDirAtomic, fsOpsCountFsOpOpenFileAtomic, fsOpsCountFsOpReadDirAtomic, + fsOpsCountFsOpReadDirPlusAtomic, fsOpsCountFsOpReadFileAtomic, fsOpsCountFsOpReadSymlinkAtomic, fsOpsCountFsOpReleaseDirHandleAtomic, @@ -2683,6 +2787,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, @@ -2713,6 +2818,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, @@ -2743,6 +2849,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, @@ -2773,6 +2880,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, @@ -2803,6 +2911,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, @@ -2833,6 +2942,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, @@ -2863,6 +2973,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, @@ -2893,6 +3004,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, @@ -2923,6 +3035,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, @@ -2953,6 +3066,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, @@ -2983,6 +3097,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, @@ -3013,6 +3128,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, @@ -3043,6 +3159,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, @@ -3073,6 +3190,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, @@ -3103,6 +3221,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, @@ -3133,6 +3252,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, @@ -3158,15 +3278,20 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr gcsReadCountReadTypeRandomAtomic, gcsReadCountReadTypeSequentialAtomic atomic.Int64 - var gcsReaderCountIoMethodClosedAtomic, + var gcsReaderCountIoMethodReadHandleAtomic, + gcsReaderCountIoMethodClosedAtomic, gcsReaderCountIoMethodOpenedAtomic atomic.Int64 var gcsRequestCountGcsMethodComposeObjectsAtomic, + gcsRequestCountGcsMethodCopyObjectAtomic, + gcsRequestCountGcsMethodCreateAppendableObjectWriterAtomic, gcsRequestCountGcsMethodCreateFolderAtomic, + gcsRequestCountGcsMethodCreateObjectAtomic, gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, gcsRequestCountGcsMethodDeleteFolderAtomic, gcsRequestCountGcsMethodDeleteObjectAtomic, gcsRequestCountGcsMethodFinalizeUploadAtomic, + gcsRequestCountGcsMethodFlushPendingWritesAtomic, gcsRequestCountGcsMethodGetFolderAtomic, gcsRequestCountGcsMethodListObjectsAtomic, gcsRequestCountGcsMethodMoveObjectAtomic, @@ -3228,6 +3353,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsCountFsOpOpenDirAtomic, fsOpsCountFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsCountFsOpOpenFileAtomic, fsOpsCountFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsCountFsOpReadDirAtomic, fsOpsCountFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsCountFsOpReadDirPlusAtomic, fsOpsCountFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsCountFsOpReadFileAtomic, fsOpsCountFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsCountFsOpReadSymlinkAtomic, fsOpsCountFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsCountFsOpReleaseDirHandleAtomic, fsOpsCountFsOpReleaseDirHandleAttrSet) @@ -3265,6 +3391,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAttrSet) @@ -3295,6 +3422,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAttrSet) @@ -3325,6 +3453,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAttrSet) @@ -3355,6 +3484,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAttrSet) @@ -3385,6 +3515,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAttrSet) @@ -3415,6 +3546,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAttrSet) @@ -3445,6 +3577,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAttrSet) @@ -3475,6 +3608,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAttrSet) @@ -3505,6 +3639,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAttrSet) @@ -3535,6 +3670,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAttrSet) @@ -3565,6 +3701,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAttrSet) @@ -3595,6 +3732,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAttrSet) @@ -3625,6 +3763,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAttrSet) @@ -3655,6 +3794,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAttrSet) @@ -3685,6 +3825,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAttrSet) @@ -3715,6 +3856,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAttrSet) + conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirPlusAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAttrSet) conditionallyObserve(obsrv, &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAttrSet) @@ -3769,6 +3911,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr metric.WithDescription("The cumulative number of GCS object readers opened or closed."), metric.WithUnit(""), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &gcsReaderCountIoMethodReadHandleAtomic, gcsReaderCountIoMethodReadHandleAttrSet) conditionallyObserve(obsrv, &gcsReaderCountIoMethodClosedAtomic, gcsReaderCountIoMethodClosedAttrSet) conditionallyObserve(obsrv, &gcsReaderCountIoMethodOpenedAtomic, gcsReaderCountIoMethodOpenedAttrSet) return nil @@ -3779,11 +3922,15 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr metric.WithUnit(""), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { conditionallyObserve(obsrv, &gcsRequestCountGcsMethodComposeObjectsAtomic, gcsRequestCountGcsMethodComposeObjectsAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodCopyObjectAtomic, gcsRequestCountGcsMethodCopyObjectAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodCreateAppendableObjectWriterAtomic, gcsRequestCountGcsMethodCreateAppendableObjectWriterAttrSet) conditionallyObserve(obsrv, &gcsRequestCountGcsMethodCreateFolderAtomic, gcsRequestCountGcsMethodCreateFolderAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodCreateObjectAtomic, gcsRequestCountGcsMethodCreateObjectAttrSet) conditionallyObserve(obsrv, &gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, gcsRequestCountGcsMethodCreateObjectChunkWriterAttrSet) conditionallyObserve(obsrv, &gcsRequestCountGcsMethodDeleteFolderAtomic, gcsRequestCountGcsMethodDeleteFolderAttrSet) conditionallyObserve(obsrv, &gcsRequestCountGcsMethodDeleteObjectAtomic, gcsRequestCountGcsMethodDeleteObjectAttrSet) conditionallyObserve(obsrv, &gcsRequestCountGcsMethodFinalizeUploadAtomic, gcsRequestCountGcsMethodFinalizeUploadAttrSet) + conditionallyObserve(obsrv, &gcsRequestCountGcsMethodFlushPendingWritesAtomic, gcsRequestCountGcsMethodFlushPendingWritesAttrSet) conditionallyObserve(obsrv, &gcsRequestCountGcsMethodGetFolderAtomic, gcsRequestCountGcsMethodGetFolderAttrSet) conditionallyObserve(obsrv, &gcsRequestCountGcsMethodListObjectsAtomic, gcsRequestCountGcsMethodListObjectsAttrSet) conditionallyObserve(obsrv, &gcsRequestCountGcsMethodMoveObjectAtomic, gcsRequestCountGcsMethodMoveObjectAttrSet) @@ -3844,6 +3991,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsCountFsOpOpenDirAtomic: &fsOpsCountFsOpOpenDirAtomic, fsOpsCountFsOpOpenFileAtomic: &fsOpsCountFsOpOpenFileAtomic, fsOpsCountFsOpReadDirAtomic: &fsOpsCountFsOpReadDirAtomic, + fsOpsCountFsOpReadDirPlusAtomic: &fsOpsCountFsOpReadDirPlusAtomic, fsOpsCountFsOpReadFileAtomic: &fsOpsCountFsOpReadFileAtomic, fsOpsCountFsOpReadSymlinkAtomic: &fsOpsCountFsOpReadSymlinkAtomic, fsOpsCountFsOpReleaseDirHandleAtomic: &fsOpsCountFsOpReleaseDirHandleAtomic, @@ -3874,6 +4022,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryDEVICEERRORFsOpReleaseDirHandleAtomic, @@ -3904,6 +4053,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryDIRNOTEMPTYFsOpReleaseDirHandleAtomic, @@ -3934,6 +4084,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEDIRERRORFsOpReleaseDirHandleAtomic, @@ -3964,6 +4115,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryFILEEXISTSFsOpReleaseDirHandleAtomic, @@ -3994,6 +4146,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINTERRUPTERRORFsOpReleaseDirHandleAtomic, @@ -4024,6 +4177,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDARGUMENTFsOpReleaseDirHandleAtomic, @@ -4054,6 +4208,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryINVALIDOPERATIONFsOpReleaseDirHandleAtomic, @@ -4084,6 +4239,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryIOERRORFsOpReleaseDirHandleAtomic, @@ -4114,6 +4270,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryMISCERRORFsOpReleaseDirHandleAtomic, @@ -4144,6 +4301,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNETWORKERRORFsOpReleaseDirHandleAtomic, @@ -4174,6 +4332,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTADIRFsOpReleaseDirHandleAtomic, @@ -4204,6 +4363,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOTIMPLEMENTEDFsOpReleaseDirHandleAtomic, @@ -4234,6 +4394,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryNOFILEORDIRFsOpReleaseDirHandleAtomic, @@ -4264,6 +4425,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryPERMERRORFsOpReleaseDirHandleAtomic, @@ -4294,6 +4456,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryPROCESSRESOURCEMGMTERRORFsOpReleaseDirHandleAtomic, @@ -4324,6 +4487,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenDirAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpOpenFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirAtomic, + fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirPlusAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadDirPlusAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadFileAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReadSymlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpReleaseDirHandleAtomic, @@ -4339,33 +4503,38 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpUnlinkAtomic, fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic: &fsOpsErrorCountFsErrorCategoryTOOMANYOPENFILESFsOpWriteFileAtomic, fsOpsLatency: fsOpsLatency, - gcsDownloadBytesCountReadTypeParallelAtomic: &gcsDownloadBytesCountReadTypeParallelAtomic, - gcsDownloadBytesCountReadTypeRandomAtomic: &gcsDownloadBytesCountReadTypeRandomAtomic, - gcsDownloadBytesCountReadTypeSequentialAtomic: &gcsDownloadBytesCountReadTypeSequentialAtomic, - gcsReadBytesCountAtomic: &gcsReadBytesCountAtomic, - gcsReadCountReadTypeParallelAtomic: &gcsReadCountReadTypeParallelAtomic, - gcsReadCountReadTypeRandomAtomic: &gcsReadCountReadTypeRandomAtomic, - gcsReadCountReadTypeSequentialAtomic: &gcsReadCountReadTypeSequentialAtomic, - gcsReaderCountIoMethodClosedAtomic: &gcsReaderCountIoMethodClosedAtomic, - gcsReaderCountIoMethodOpenedAtomic: &gcsReaderCountIoMethodOpenedAtomic, - gcsRequestCountGcsMethodComposeObjectsAtomic: &gcsRequestCountGcsMethodComposeObjectsAtomic, - gcsRequestCountGcsMethodCreateFolderAtomic: &gcsRequestCountGcsMethodCreateFolderAtomic, - gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic: &gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, - gcsRequestCountGcsMethodDeleteFolderAtomic: &gcsRequestCountGcsMethodDeleteFolderAtomic, - gcsRequestCountGcsMethodDeleteObjectAtomic: &gcsRequestCountGcsMethodDeleteObjectAtomic, - gcsRequestCountGcsMethodFinalizeUploadAtomic: &gcsRequestCountGcsMethodFinalizeUploadAtomic, - gcsRequestCountGcsMethodGetFolderAtomic: &gcsRequestCountGcsMethodGetFolderAtomic, - gcsRequestCountGcsMethodListObjectsAtomic: &gcsRequestCountGcsMethodListObjectsAtomic, - gcsRequestCountGcsMethodMoveObjectAtomic: &gcsRequestCountGcsMethodMoveObjectAtomic, - gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic: &gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic, - gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic: &gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic, - gcsRequestCountGcsMethodNewReaderAtomic: &gcsRequestCountGcsMethodNewReaderAtomic, - gcsRequestCountGcsMethodRenameFolderAtomic: &gcsRequestCountGcsMethodRenameFolderAtomic, - gcsRequestCountGcsMethodStatObjectAtomic: &gcsRequestCountGcsMethodStatObjectAtomic, - gcsRequestCountGcsMethodUpdateObjectAtomic: &gcsRequestCountGcsMethodUpdateObjectAtomic, - gcsRequestLatencies: gcsRequestLatencies, - gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic: &gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, - gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic: &gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic, + gcsDownloadBytesCountReadTypeParallelAtomic: &gcsDownloadBytesCountReadTypeParallelAtomic, + gcsDownloadBytesCountReadTypeRandomAtomic: &gcsDownloadBytesCountReadTypeRandomAtomic, + gcsDownloadBytesCountReadTypeSequentialAtomic: &gcsDownloadBytesCountReadTypeSequentialAtomic, + gcsReadBytesCountAtomic: &gcsReadBytesCountAtomic, + gcsReadCountReadTypeParallelAtomic: &gcsReadCountReadTypeParallelAtomic, + gcsReadCountReadTypeRandomAtomic: &gcsReadCountReadTypeRandomAtomic, + gcsReadCountReadTypeSequentialAtomic: &gcsReadCountReadTypeSequentialAtomic, + gcsReaderCountIoMethodReadHandleAtomic: &gcsReaderCountIoMethodReadHandleAtomic, + gcsReaderCountIoMethodClosedAtomic: &gcsReaderCountIoMethodClosedAtomic, + gcsReaderCountIoMethodOpenedAtomic: &gcsReaderCountIoMethodOpenedAtomic, + gcsRequestCountGcsMethodComposeObjectsAtomic: &gcsRequestCountGcsMethodComposeObjectsAtomic, + gcsRequestCountGcsMethodCopyObjectAtomic: &gcsRequestCountGcsMethodCopyObjectAtomic, + gcsRequestCountGcsMethodCreateAppendableObjectWriterAtomic: &gcsRequestCountGcsMethodCreateAppendableObjectWriterAtomic, + gcsRequestCountGcsMethodCreateFolderAtomic: &gcsRequestCountGcsMethodCreateFolderAtomic, + gcsRequestCountGcsMethodCreateObjectAtomic: &gcsRequestCountGcsMethodCreateObjectAtomic, + gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic: &gcsRequestCountGcsMethodCreateObjectChunkWriterAtomic, + gcsRequestCountGcsMethodDeleteFolderAtomic: &gcsRequestCountGcsMethodDeleteFolderAtomic, + gcsRequestCountGcsMethodDeleteObjectAtomic: &gcsRequestCountGcsMethodDeleteObjectAtomic, + gcsRequestCountGcsMethodFinalizeUploadAtomic: &gcsRequestCountGcsMethodFinalizeUploadAtomic, + gcsRequestCountGcsMethodFlushPendingWritesAtomic: &gcsRequestCountGcsMethodFlushPendingWritesAtomic, + gcsRequestCountGcsMethodGetFolderAtomic: &gcsRequestCountGcsMethodGetFolderAtomic, + gcsRequestCountGcsMethodListObjectsAtomic: &gcsRequestCountGcsMethodListObjectsAtomic, + gcsRequestCountGcsMethodMoveObjectAtomic: &gcsRequestCountGcsMethodMoveObjectAtomic, + gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic: &gcsRequestCountGcsMethodMultiRangeDownloaderAddAtomic, + gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic: &gcsRequestCountGcsMethodNewMultiRangeDownloaderAtomic, + gcsRequestCountGcsMethodNewReaderAtomic: &gcsRequestCountGcsMethodNewReaderAtomic, + gcsRequestCountGcsMethodRenameFolderAtomic: &gcsRequestCountGcsMethodRenameFolderAtomic, + gcsRequestCountGcsMethodStatObjectAtomic: &gcsRequestCountGcsMethodStatObjectAtomic, + gcsRequestCountGcsMethodUpdateObjectAtomic: &gcsRequestCountGcsMethodUpdateObjectAtomic, + gcsRequestLatencies: gcsRequestLatencies, + gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic: &gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, + gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic: &gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic, }, nil } diff --git a/metrics/otel_metrics_test.go b/metrics/otel_metrics_test.go index 1e6b4edeb2..66952354d7 100644 --- a/metrics/otel_metrics_test.go +++ b/metrics/otel_metrics_test.go @@ -485,6 +485,15 @@ func TestFsOpsCount(t *testing.T) { attribute.NewSet(attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsCount(5, "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_op_ReadFile", f: func(m *otelMetrics) { @@ -794,6 +803,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_DEVICE_ERROR_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DEVICE_ERROR", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DEVICE_ERROR"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_DEVICE_ERROR_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -1064,6 +1082,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "DIR_NOT_EMPTY", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "DIR_NOT_EMPTY"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_DIR_NOT_EMPTY_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -1334,6 +1361,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_DIR_ERROR", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_DIR_ERROR"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_FILE_DIR_ERROR_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -1604,6 +1640,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_FILE_EXISTS_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "FILE_EXISTS", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "FILE_EXISTS"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_FILE_EXISTS_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -1874,6 +1919,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INTERRUPT_ERROR", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INTERRUPT_ERROR"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_INTERRUPT_ERROR_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -2144,6 +2198,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_ARGUMENT", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_ARGUMENT"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_INVALID_ARGUMENT_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -2414,6 +2477,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_INVALID_OPERATION_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "INVALID_OPERATION", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "INVALID_OPERATION"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_INVALID_OPERATION_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -2684,6 +2756,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_IO_ERROR_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "IO_ERROR", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "IO_ERROR"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_IO_ERROR_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -2954,6 +3035,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_MISC_ERROR_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "MISC_ERROR", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "MISC_ERROR"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_MISC_ERROR_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -3224,6 +3314,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_NETWORK_ERROR_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NETWORK_ERROR", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NETWORK_ERROR"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_NETWORK_ERROR_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -3494,6 +3593,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_NOT_A_DIR_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_A_DIR", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_A_DIR"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_NOT_A_DIR_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -3764,6 +3872,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NOT_IMPLEMENTED", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NOT_IMPLEMENTED"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_NOT_IMPLEMENTED_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -4034,6 +4151,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "NO_FILE_OR_DIR", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "NO_FILE_OR_DIR"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_NO_FILE_OR_DIR_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -4304,6 +4430,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_PERM_ERROR_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PERM_ERROR", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PERM_ERROR"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_PERM_ERROR_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -4574,6 +4709,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "PROCESS_RESOURCE_MGMT_ERROR", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "PROCESS_RESOURCE_MGMT_ERROR"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_PROCESS_RESOURCE_MGMT_ERROR_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -4844,6 +4988,15 @@ func TestFsOpsErrorCount(t *testing.T) { attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadDir")): 5, }, }, + { + name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReadDirPlus", + f: func(m *otelMetrics) { + m.FsOpsErrorCount(5, "TOO_MANY_OPEN_FILES", "ReadDirPlus") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("fs_error_category", "TOO_MANY_OPEN_FILES"), attribute.String("fs_op", "ReadDirPlus")): 5, + }, + }, { name: "fs_error_category_TOO_MANY_OPEN_FILES_fs_op_ReadFile", f: func(m *otelMetrics) { @@ -5089,6 +5242,11 @@ func TestFsOpsLatency(t *testing.T) { latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, fsOp: "ReadDir", }, + { + name: "fs_op_ReadDirPlus", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + fsOp: "ReadDirPlus", + }, { name: "fs_op_ReadFile", latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, @@ -5345,6 +5503,15 @@ func TestGcsReaderCount(t *testing.T) { f func(m *otelMetrics) expected map[attribute.Set]int64 }{ + { + name: "io_method_ReadHandle", + f: func(m *otelMetrics) { + m.GcsReaderCount(5, "ReadHandle") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("io_method", "ReadHandle")): 5, + }, + }, { name: "io_method_closed", f: func(m *otelMetrics) { @@ -5365,12 +5532,12 @@ func TestGcsReaderCount(t *testing.T) { }, { name: "multiple_attributes_summed", f: func(m *otelMetrics) { - m.GcsReaderCount(5, "closed") - m.GcsReaderCount(2, "opened") - m.GcsReaderCount(3, "closed") + m.GcsReaderCount(5, "ReadHandle") + m.GcsReaderCount(2, "closed") + m.GcsReaderCount(3, "ReadHandle") }, - expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("io_method", "closed")): 8, - attribute.NewSet(attribute.String("io_method", "opened")): 2, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("io_method", "ReadHandle")): 8, + attribute.NewSet(attribute.String("io_method", "closed")): 2, }, }, } @@ -5411,6 +5578,24 @@ func TestGcsRequestCount(t *testing.T) { attribute.NewSet(attribute.String("gcs_method", "ComposeObjects")): 5, }, }, + { + name: "gcs_method_CopyObject", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "CopyObject") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "CopyObject")): 5, + }, + }, + { + name: "gcs_method_CreateAppendableObjectWriter", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "CreateAppendableObjectWriter") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "CreateAppendableObjectWriter")): 5, + }, + }, { name: "gcs_method_CreateFolder", f: func(m *otelMetrics) { @@ -5420,6 +5605,15 @@ func TestGcsRequestCount(t *testing.T) { attribute.NewSet(attribute.String("gcs_method", "CreateFolder")): 5, }, }, + { + name: "gcs_method_CreateObject", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "CreateObject") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "CreateObject")): 5, + }, + }, { name: "gcs_method_CreateObjectChunkWriter", f: func(m *otelMetrics) { @@ -5456,6 +5650,15 @@ func TestGcsRequestCount(t *testing.T) { attribute.NewSet(attribute.String("gcs_method", "FinalizeUpload")): 5, }, }, + { + name: "gcs_method_FlushPendingWrites", + f: func(m *otelMetrics) { + m.GcsRequestCount(5, "FlushPendingWrites") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("gcs_method", "FlushPendingWrites")): 5, + }, + }, { name: "gcs_method_GetFolder", f: func(m *otelMetrics) { @@ -5540,11 +5743,11 @@ func TestGcsRequestCount(t *testing.T) { name: "multiple_attributes_summed", f: func(m *otelMetrics) { m.GcsRequestCount(5, "ComposeObjects") - m.GcsRequestCount(2, "CreateFolder") + m.GcsRequestCount(2, "CopyObject") m.GcsRequestCount(3, "ComposeObjects") }, expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("gcs_method", "ComposeObjects")): 8, - attribute.NewSet(attribute.String("gcs_method", "CreateFolder")): 2, + attribute.NewSet(attribute.String("gcs_method", "CopyObject")): 2, }, }, } @@ -5581,11 +5784,26 @@ func TestGcsRequestLatencies(t *testing.T) { latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, gcsMethod: "ComposeObjects", }, + { + name: "gcs_method_CopyObject", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "CopyObject", + }, + { + name: "gcs_method_CreateAppendableObjectWriter", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "CreateAppendableObjectWriter", + }, { name: "gcs_method_CreateFolder", latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, gcsMethod: "CreateFolder", }, + { + name: "gcs_method_CreateObject", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "CreateObject", + }, { name: "gcs_method_CreateObjectChunkWriter", latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, @@ -5606,6 +5824,11 @@ func TestGcsRequestLatencies(t *testing.T) { latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, gcsMethod: "FinalizeUpload", }, + { + name: "gcs_method_FlushPendingWrites", + latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, + gcsMethod: "FlushPendingWrites", + }, { name: "gcs_method_GetFolder", latencies: []time.Duration{100 * time.Millisecond, 200 * time.Millisecond}, From c340d5b85da4322f21a301e83a1b811f109ea483 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 11 Aug 2025 16:57:23 +0530 Subject: [PATCH 0675/1298] fix EOFerror handling (#3674) --- internal/bufferedread/buffered_reader.go | 1 - internal/bufferedread/buffered_reader_test.go | 4 +- internal/fs/handle/file_test.go | 37 +++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index 694a980ed7..0138818260 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -307,7 +307,6 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) } if off >= int64(p.object.Size) { - err = io.EOF break } diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 864672d410..5ddb2cb99d 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -928,7 +928,7 @@ func (t *BufferedReaderTest) TestReadAtExactlyToEndOfFile() { // Read the entire file. resp, err := reader.ReadAt(t.ctx, buf, 0) - assert.ErrorIs(t.T(), err, io.EOF) + assert.NoError(t.T(), err) assert.Equal(t.T(), int(t.object.Size), resp.Size) assertBufferContent(t.T(), buf, 0) t.bucket.AssertExpectations(t.T()) @@ -1083,7 +1083,7 @@ func (t *BufferedReaderTest) TestReadAtExceedsObjectSize() { resp, err := reader.ReadAt(t.ctx, buf, readOffset) - assert.ErrorIs(t.T(), err, io.EOF) + assert.NoError(t.T(), err) assert.Equal(t.T(), 512, resp.Size) assertBufferContent(t.T(), buf[:resp.Size], readOffset) t.bucket.AssertExpectations(t.T()) diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 073ab8c733..5114e3998f 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -31,10 +31,12 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/jacobsa/fuse/fuseops" "github.com/jacobsa/timeutil" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "golang.org/x/sync/semaphore" ) @@ -414,3 +416,38 @@ func (t *fileTest) TestFileHandle_CheckInvariants_WithNilReaderAndManager() { fh.checkInvariants() }) } + +func (t *fileTest) Test_ReadWithReadManager_FullReadSuccessWithBufferedRead() { + const ( + fileSize = 1 * 1024 * 1024 // 1 MiB + ) + expectedData := make([]byte, fileSize) + for i := 0; i < fileSize; i++ { + expectedData[i] = byte(i % 256) + } + // Setup for Buffered Read test case + config := &cfg.Config{ + Read: cfg.ReadConfig{ + EnableBufferedRead: true, + MaxBlocksPerHandle: 10, + BlockSizeMb: 1, + StartBlocksPerHandle: 2, + }, + } + workerPool, err := workerpool.NewStaticWorkerPoolForCurrentCPU() + require.NoError(t.T(), err) + defer workerPool.Stop() + globalSemaphore := semaphore.NewWeighted(20) // Sufficient blocks for the test + parent := createDirInode(&t.bucket, &t.clock) + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "read_obj", expectedData, false) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, config, workerPool, globalSemaphore) + fh.inode.Lock() + buf := make([]byte, fileSize) + + // ReadWithReadManager will unlock the inode. + output, n, err := fh.ReadWithReadManager(context.Background(), buf, 0, 200) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), fileSize, n) + assert.Equal(t.T(), expectedData, output) +} From daa9363cfb1029e2a20ea6fc8d67c73e8b4dec1f Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 12 Aug 2025 09:06:47 +0530 Subject: [PATCH 0676/1298] ci: fix broken e2e test in unfinalized_object package (#3670) Fixed test TestUnfinalizedObjectOperationTest/TestUnfinalizedObjectCantBeRenamedIfCreatedFromDifferentMount --- .../unfinalized_object_operations_test.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go index 2be2ce607b..02599d979d 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go @@ -18,7 +18,6 @@ import ( "context" "log" "path" - "syscall" "testing" "cloud.google.com/go/storage" @@ -121,15 +120,14 @@ func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCanBeRenamedIfCreated operations.ValidateESTALEError(t.T(), err) } -func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCantBeRenamedIfCreatedFromDifferentMount() { +func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCanBeRenamedIfCreatedFromDifferentMount() { size := operations.MiB _ = client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), setup.GenerateRandomString(size)) // Overwrite unfinalized object. err := operations.RenameFile(path.Join(t.testDirPath, t.fileName), path.Join(t.testDirPath, "New"+t.fileName)) - require.Error(t.T(), err) - assert.ErrorContains(t.T(), err, syscall.EIO.Error()) + require.NoError(t.T(), err) } //////////////////////////////////////////////////////////////////////// From 7af5343969c6423c4c6ec38aa46a6af7452669b6 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 12 Aug 2025 13:32:42 +0530 Subject: [PATCH 0677/1298] refactor: explicit_dir test package migration [GKE-GCSFuse Test migration] (#3669) * explicit dir test migration * add cleanup todo --- .../explicit_dir/explicit_dir_test.go | 48 +++++++++++++++---- tools/integration_tests/test_config.yaml | 26 ++++++++++ .../perisistent_mounting.go | 25 ++++++---- .../static_mounting/static_mounting.go | 34 ++++++++++--- .../implicit_and_explicit_dir_setup.go | 27 +++++++++++ tools/integration_tests/util/setup/setup.go | 40 ++++++++++++++-- .../util/test_suite/config.go | 32 +++++++++++++ 7 files changed, 203 insertions(+), 29 deletions(-) create mode 100644 tools/integration_tests/test_config.yaml create mode 100644 tools/integration_tests/util/test_suite/config.go diff --git a/tools/integration_tests/explicit_dir/explicit_dir_test.go b/tools/integration_tests/explicit_dir/explicit_dir_test.go index 2620873dfe..f1c0271d35 100644 --- a/tools/integration_tests/explicit_dir/explicit_dir_test.go +++ b/tools/integration_tests/explicit_dir/explicit_dir_test.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -19,16 +19,24 @@ import ( "context" "log" "os" + "strings" "testing" "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" + "gopkg.in/yaml.v3" ) const DirForExplicitDirTests = "dirForExplicitDirTests" +// Config holds all test configurations parsed from the YAML file. +type Config struct { + ExplicitDir []test_suite.TestConfig `yaml:"explicit_dir"` +} + // IMPORTANT: To prevent global variable pollution, enhance code clarity, // and avoid inadvertent errors. We strongly suggest that, all new package-level // variables (which would otherwise be declared with `var` at the package root) should @@ -42,7 +50,29 @@ var testEnv env func TestMain(m *testing.M) { setup.ParseSetUpFlags() - // Create storage client before running tests. + + // 1. Load and parse the common configuration. + var cfg Config + if setup.ConfigFile() != "" { + configData, err := os.ReadFile(setup.ConfigFile()) + if err != nil { + log.Fatalf("could not read test_config.yaml: %v", err) + } + expandedYaml := os.ExpandEnv(string(configData)) + if err := yaml.Unmarshal([]byte(expandedYaml), &cfg); err != nil { + log.Fatalf("Failed to parse config YAML: %v", err) + } + } + if len(cfg.ExplicitDir) == 0 { + log.Println("No configuration found for explicit_dir tests in config. Using flags instead.") + // Populate the config manually. + cfg.ExplicitDir = make([]test_suite.TestConfig, 1) + cfg.ExplicitDir[0].TestBucket = setup.TestBucket() + cfg.ExplicitDir[0].Flags = []string{"--implicit-dirs=false", "--implicit-dirs=false --client-protocol=grpc"} + cfg.ExplicitDir[0].MountedDirectory = setup.MountedDirectory() + } + + // 2. Create storage client before running tests. testEnv.ctx = context.Background() closeStorageClient := client.CreateStorageClientWithCancel(&testEnv.ctx, &testEnv.storageClient) defer func() { @@ -54,18 +84,18 @@ func TestMain(m *testing.M) { // These tests will not run on HNS buckets because the "--implicit-dirs=false" flag does not function similarly to how it does on FLAT buckets. // Note that HNS buckets do not have the concept of implicit directories. - if setup.IsHierarchicalBucket(testEnv.ctx, testEnv.storageClient) { + if setup.ResolveIsHierarchicalBucket(testEnv.ctx, cfg.ExplicitDir[0].TestBucket, testEnv.storageClient) { log.Println("These tests will not run on HNS buckets.") return } - flags := [][]string{{"--implicit-dirs=false"}} - - if !testing.Short() { - flags = append(flags, []string{"--client-protocol=grpc", "--implicit-dirs=false"}) + // 4. Build the flag sets dynamically from the config. + var flags [][]string + for _, flagString := range cfg.ExplicitDir[0].Flags { + flags = append(flags, strings.Fields(flagString)) } - successCode := implicit_and_explicit_dir_setup.RunTestsForImplicitDirAndExplicitDir(flags, m) - + // 5. Run tests with the dynamically generated flags. + successCode := implicit_and_explicit_dir_setup.RunTestsForExplicitDir(&cfg.ExplicitDir[0], flags, m) os.Exit(successCode) } diff --git a/tools/integration_tests/test_config.yaml b/tools/integration_tests/test_config.yaml new file mode 100644 index 0000000000..21f6c71e39 --- /dev/null +++ b/tools/integration_tests/test_config.yaml @@ -0,0 +1,26 @@ +# Copyright 2025 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +explicit_dir: + - mounted_directory: "${MOUNTED_DIR}" # To be passed by GKE after mounting + test_bucket: "${BUCKET_NAME}" # To be passed by both gcsfuse and gke tests + log_file: # Optional flag required by some tests where log parsing is done to validate end behavior + flags: + - "--implicit-dirs=false" + - "--implicit-dirs=false --client-protocol=grpc" + compatible: # Bucket type to run these tests with + - flat: true + - hns: false + - zonal: false + run_on_gke: false diff --git a/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go b/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go index a606d6e2fe..1fd6643f83 100644 --- a/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go +++ b/tools/integration_tests/util/mounting/persistent_mounting/perisistent_mounting.go @@ -22,6 +22,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" ) // Change e.g --log_severity=trace to log_severity=trace @@ -39,8 +40,8 @@ func makePersistentMountingArgs(flags []string) (args []string) { return } -func mountGcsfuseWithPersistentMounting(flags []string) (err error) { - defaultArg := []string{setup.TestBucket(), +func mountGcsfuseWithPersistentMountingWithConfigFile(config *test_suite.TestConfig, flags []string) (err error) { + defaultArg := []string{config.TestBucket, setup.MntDir(), "-o", "log_severity=trace", @@ -60,11 +61,11 @@ func mountGcsfuseWithPersistentMounting(flags []string) (err error) { return err } -func executeTestsForPersistentMounting(flagsSet [][]string, m *testing.M) (successCode int) { +func executeTestsForPersistentMountingWithConfigFile(config *test_suite.TestConfig, flagsSet [][]string, m *testing.M) (successCode int) { var err error for i := 0; i < len(flagsSet); i++ { - if err = mountGcsfuseWithPersistentMounting(flagsSet[i]); err != nil { + if err = mountGcsfuseWithPersistentMountingWithConfigFile(config, flagsSet[i]); err != nil { setup.LogAndExit(fmt.Sprintf("mountGcsfuse: %v\n", err)) } log.Printf("Running persistent mounting tests with flags: %s", flagsSet[i]) @@ -76,12 +77,20 @@ func executeTestsForPersistentMounting(flagsSet [][]string, m *testing.M) (succe return } +// Deprecated: Use RunTestsWithConfigFile instead. +// TODO(b/438068132): cleanup deprecated methods after migration is complete. func RunTests(flagsSet [][]string, m *testing.M) (successCode int) { - log.Println("Running persistent mounting tests...") - - successCode = executeTestsForPersistentMounting(flagsSet, m) + config := &test_suite.TestConfig{ + TestBucket: setup.TestBucket(), + MountedDirectory: setup.MountedDirectory(), + LogFile: setup.LogFile(), + } + return RunTestsWithConfigFile(config, flagsSet, m) +} +func RunTestsWithConfigFile(config *test_suite.TestConfig, flagsSet [][]string, m *testing.M) (successCode int) { + log.Println("Running persistent mounting tests...") + successCode = executeTestsForPersistentMountingWithConfigFile(config, flagsSet, m) log.Printf("Test log: %s\n", setup.LogFile()) - return successCode } diff --git a/tools/integration_tests/util/mounting/static_mounting/static_mounting.go b/tools/integration_tests/util/mounting/static_mounting/static_mounting.go index 37b54931fa..5819894278 100644 --- a/tools/integration_tests/util/mounting/static_mounting/static_mounting.go +++ b/tools/integration_tests/util/mounting/static_mounting/static_mounting.go @@ -21,9 +21,21 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" ) +// Deprecated: Use MountGcsfuseWithStaticMountingWithConfigFile instead. +// TODO(b/438068132): cleanup deprecated methods after migration is complete. func MountGcsfuseWithStaticMounting(flags []string) (err error) { + config := &test_suite.TestConfig{ + TestBucket: setup.TestBucket(), + MountedDirectory: setup.MountedDirectory(), + LogFile: setup.LogFile(), + } + return MountGcsfuseWithStaticMountingWithConfigFile(config, flags) +} + +func MountGcsfuseWithStaticMountingWithConfigFile(config *test_suite.TestConfig, flags []string) (err error) { var defaultArg []string if setup.TestOnTPCEndPoint() { defaultArg = append(defaultArg, "--custom-endpoint=storage.apis-tpczero.goog:443", @@ -32,7 +44,7 @@ func MountGcsfuseWithStaticMounting(flags []string) (err error) { defaultArg = append(defaultArg, "--log-severity=trace", "--log-file="+setup.LogFile(), - setup.TestBucket(), + config.TestBucket, setup.MntDir()) for i := 0; i < len(defaultArg); i++ { @@ -44,11 +56,11 @@ func MountGcsfuseWithStaticMounting(flags []string) (err error) { return err } -func executeTestsForStaticMounting(flagsSet [][]string, m *testing.M) (successCode int) { +func executeTestsForStaticMounting(config *test_suite.TestConfig, flagsSet [][]string, m *testing.M) (successCode int) { var err error for i := 0; i < len(flagsSet); i++ { - if err = MountGcsfuseWithStaticMounting(flagsSet[i]); err != nil { + if err = MountGcsfuseWithStaticMountingWithConfigFile(config, flagsSet[i]); err != nil { setup.LogAndExit(fmt.Sprintf("mountGcsfuse: %v\n", err)) } log.Printf("Running static mounting tests with flags: %s", flagsSet[i]) @@ -60,12 +72,20 @@ func executeTestsForStaticMounting(flagsSet [][]string, m *testing.M) (successCo return } +// Deprecated: Use RunTestsWithConfigFile instead. +// TODO(b/438068132): cleanup deprecated methods after migration is complete. func RunTests(flagsSet [][]string, m *testing.M) (successCode int) { - log.Println("Running static mounting tests...") - - successCode = executeTestsForStaticMounting(flagsSet, m) + config := &test_suite.TestConfig{ + TestBucket: setup.TestBucket(), + MountedDirectory: setup.MountedDirectory(), + LogFile: setup.LogFile(), + } + return RunTestsWithConfigFile(config, flagsSet, m) +} +func RunTestsWithConfigFile(config *test_suite.TestConfig, flagsSet [][]string, m *testing.M) (successCode int) { + log.Println("Running static mounting tests...") + successCode = executeTestsForStaticMounting(config, flagsSet, m) log.Printf("Test log: %s\n", setup.LogFile()) - return successCode } diff --git a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go index bdff8239c0..86da34f457 100644 --- a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go +++ b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go @@ -28,6 +28,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" "github.com/stretchr/testify/require" ) @@ -46,6 +47,32 @@ const SecondFileInExplicitDirectory = "fileInExplicitDir2" const FileInImplicitDirectory = "fileInImplicitDir1" const FileInImplicitSubDirectory = "fileInImplicitDir2" +func RunTestsForExplicitDir(config *test_suite.TestConfig, flags [][]string, m *testing.M) int { + if config == nil { + log.Println("config is nil") + return 1 + } + + if config.MountedDirectory != "" && config.TestBucket != "" { + successCode := setup.RunTestsForMountedDirectory(config.MountedDirectory, m) + return successCode + } + + // Run tests for testBucket only if --testbucket flag is set. + if config.TestBucket == "" { + log.Print("pass test bucket to run the tests") + return 1 + } + setup.SetUpTestDirForTestBucket(config.TestBucket) + + successCode := static_mounting.RunTestsWithConfigFile(config, flags, m) + + if successCode == 0 { + successCode = persistent_mounting.RunTestsWithConfigFile(config, flags, m) + } + return successCode +} + func RunTestsForImplicitDirAndExplicitDir(flags [][]string, m *testing.M) int { setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 4b5d5099c0..823fa3ba21 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -48,6 +48,7 @@ var testInstalledPackage = flag.Bool("testInstalledPackage", false, "[Optional] var testOnTPCEndPoint = flag.Bool("testOnTPCEndPoint", false, "Run tests on TPC endpoint only when the flag value is true.") var gcsfusePreBuiltDir = flag.String("gcsfuse_prebuilt_dir", "", "Path to the pre-built GCSFuse directory containing bin/gcsfuse and sbin/mount.gcsfuse.") var profileLabelForMountedDirTest = flag.String("profile_label", "", "To pass profile-label for the cloud-profile test.") +var configFile = flag.String("config-file", "", "Common GCSFuse config file to run tests with.") const ( FilePermission_0600 = 0600 @@ -339,6 +340,10 @@ func ParseSetUpFlags() { } } +func ConfigFile() string { + return *configFile +} + func IgnoreTestIfIntegrationTestFlagIsSet(t *testing.T) { flag.Parse() @@ -389,17 +394,36 @@ func ExitWithFailureIfMountedDirectoryIsSetOrTestBucketIsNotSet() { } } +// Deprecated: Use RunTestsForMountedDirectory instead. +// TODO(b/438068132): cleanup deprecated methods after migration is complete. func RunTestsForMountedDirectoryFlag(m *testing.M) { // Execute tests for the mounted directory. if *mountedDirectory != "" { - mntDir = *mountedDirectory - successCode := ExecuteTest(m) - os.Exit(successCode) + os.Exit(RunTestsForMountedDirectory(*mountedDirectory, m)) + } +} + +// RunTestsForMountedDirectory executes tests for the mounted directory. +// User is expected to ensure that this function is called when mounted directory is set. +// Returns exit code. +func RunTestsForMountedDirectory(mountedDirectory string, m *testing.M) int { + // Execute tests for the mounted directory. + if mountedDirectory == "" { + log.Println("RunTestsForMountedDirectory failed: Mounted directory is not set.") + return 1 } + mntDir = mountedDirectory + return ExecuteTest(m) } +// Deprecated: Use SetUpTestDirForTestBucket instead. +// TODO(b/438068132): cleanup deprecated methods after migration is complete. func SetUpTestDirForTestBucketFlag() { - testBucketName := TestBucket() + SetUpTestDirForTestBucket(TestBucket()) +} + +func SetUpTestDirForTestBucket(testBucket string) { + testBucketName := testBucket if testBucketName == "" { log.Fatal("Not running TestBucket tests as --testBucket flag is not set.") } @@ -509,8 +533,14 @@ func AreBothMountedDirectoryAndTestBucketFlagsSet() bool { return false } +// Deprecated: use ResolveIsHierarchicalBucket instead. +// TODO(b/438068132): cleanup deprecated methods after migration is complete. func IsHierarchicalBucket(ctx context.Context, storageClient *storage.Client) bool { - attrs, err := storageClient.Bucket(TestBucket()).Attrs(ctx) + return ResolveIsHierarchicalBucket(ctx, TestBucket(), storageClient) +} + +func ResolveIsHierarchicalBucket(ctx context.Context, testBucket string, storageClient *storage.Client) bool { + attrs, err := storageClient.Bucket(testBucket).Attrs(ctx) if err != nil { return false } diff --git a/tools/integration_tests/util/test_suite/config.go b/tools/integration_tests/util/test_suite/config.go new file mode 100644 index 0000000000..dd85f7f72c --- /dev/null +++ b/tools/integration_tests/util/test_suite/config.go @@ -0,0 +1,32 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package test_suite + +// BucketType represents the 'compatible' field. +type BucketType struct { + Flat bool `yaml:"flat"` + Hns bool `yaml:"hns"` + Zonal bool `yaml:"zonal"` +} + +// TestConfig represents the common configuration for test packages. +type TestConfig struct { + MountedDirectory string `yaml:"mounted_directory"` + TestBucket string `yaml:"test_bucket"` + LogFile string `yaml:"log_file,omitempty"` + Flags []string `yaml:"flags"` + Compatible []BucketType `yaml:"compatible"` + RunOnGKE bool `yaml:"run_on_gke"` +} From 04afdef999c33234c6e00a812e65533b3916613a Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Tue, 12 Aug 2025 14:39:41 +0530 Subject: [PATCH 0678/1298] feat(Migrate auth TPC): Make default value of flag true (#3610) * make default value of flag true * updating unit tets * removing custom-endpoint for e2e tests --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/root_test.go | 6 +++--- .../util/mounting/static_mounting/static_mounting.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 2f1b1413de..38a89d27e0 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -399,7 +399,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.BoolP("enable-google-lib-auth", "", false, "Enable google library authentication method to fetch the credentials") + flagSet.BoolP("enable-google-lib-auth", "", true, "Enable google library authentication method to fetch the credentials") if err := flagSet.MarkHidden("enable-google-lib-auth"); err != nil { return err diff --git a/cfg/params.yaml b/cfg/params.yaml index 2905593a02..5a674fb0b4 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -89,7 +89,7 @@ config-path: "enable-google-lib-auth" type: "bool" usage: "Enable google library authentication method to fetch the credentials" - default: false + default: true hide-flag: true - config-path: "enable-hns" diff --git a/cmd/root_test.go b/cmd/root_test.go index 07dbd4f45c..ba35cd0192 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1110,12 +1110,12 @@ func TestArgsParsing_EnableGoogleLibAuthFlag(t *testing.T) { { name: "default", args: []string{"gcsfuse", "abc", "pqr"}, - expectedEnableGoogleLibAuth: false, + expectedEnableGoogleLibAuth: true, }, { name: "normal", - args: []string{"gcsfuse", "--enable-google-lib-auth=true", "abc", "pqr"}, - expectedEnableGoogleLibAuth: true, + args: []string{"gcsfuse", "--enable-google-lib-auth=false", "abc", "pqr"}, + expectedEnableGoogleLibAuth: false, }, } diff --git a/tools/integration_tests/util/mounting/static_mounting/static_mounting.go b/tools/integration_tests/util/mounting/static_mounting/static_mounting.go index 5819894278..112afd6757 100644 --- a/tools/integration_tests/util/mounting/static_mounting/static_mounting.go +++ b/tools/integration_tests/util/mounting/static_mounting/static_mounting.go @@ -38,7 +38,7 @@ func MountGcsfuseWithStaticMounting(flags []string) (err error) { func MountGcsfuseWithStaticMountingWithConfigFile(config *test_suite.TestConfig, flags []string) (err error) { var defaultArg []string if setup.TestOnTPCEndPoint() { - defaultArg = append(defaultArg, "--custom-endpoint=storage.apis-tpczero.goog:443", + defaultArg = append(defaultArg, "--key-file=/tmp/sa.key.json") } From debdb65ecfb4333d50845c7fe6025b8320c2d8d7 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Tue, 12 Aug 2025 19:17:16 +0530 Subject: [PATCH 0679/1298] feat(buffered-read): adding hidden flag to control min-block to start buffered-read (#3679) * Adding flag for min block * minor change * fixing unit tests --- cfg/config.go | 12 ++++++++++ cfg/params.yaml | 9 +++++++ cfg/validate.go | 4 ++++ cfg/validate_test.go | 24 +++++++++++++++++++ cmd/config_validation_test.go | 2 ++ cmd/root_test.go | 18 ++++++++++++++ cmd/testdata/valid_config.yaml | 1 + internal/bufferedread/buffered_reader.go | 4 ++-- internal/bufferedread/buffered_reader_test.go | 22 +++++++++++++++++ internal/gcsx/read_manager/read_manager.go | 1 + .../gcsx/read_manager/read_manager_test.go | 1 + 11 files changed, 96 insertions(+), 2 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 38a89d27e0..493f36e15f 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -272,6 +272,8 @@ type ReadConfig struct { MaxBlocksPerHandle int64 `yaml:"max-blocks-per-handle"` + MinBlocksPerHandle int64 `yaml:"min-blocks-per-handle"` + StartBlocksPerHandle int64 `yaml:"start-blocks-per-handle"` } @@ -659,6 +661,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } + flagSet.IntP("read-min-blocks-per-handle", "", 4, "Specifies the minimum number of blocks required by a file-handle to start reading via buffered reads. The value should be >= 1 or \"read-max-blocks-per-handle\".") + + if err := flagSet.MarkHidden("read-min-blocks-per-handle"); err != nil { + return err + } + flagSet.DurationP("read-stall-initial-req-timeout", "", 20000000000*time.Nanosecond, "Initial value of the read-request dynamic timeout.") if err := flagSet.MarkHidden("read-stall-initial-req-timeout"); err != nil { @@ -1096,6 +1104,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("read.min-blocks-per-handle", flagSet.Lookup("read-min-blocks-per-handle")); err != nil { + return err + } + if err := v.BindPFlag("gcs-retries.read-stall.initial-req-timeout", flagSet.Lookup("read-stall-initial-req-timeout")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index 5a674fb0b4..1d3103f028 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -799,6 +799,15 @@ default: 20 hide-flag: true +- config-path: "read.min-blocks-per-handle" + flag-name: "read-min-blocks-per-handle" + type: "int" + usage: >- + Specifies the minimum number of blocks required by a file-handle to start + reading via buffered reads. The value should be >= 1 or "read-max-blocks-per-handle". + default: 4 + hide-flag: true + - config-path: "read.start-blocks-per-handle" flag-name: "read-start-blocks-per-handle" type: "int" diff --git a/cfg/validate.go b/cfg/validate.go index b14eeb065b..03f1347248 100644 --- a/cfg/validate.go +++ b/cfg/validate.go @@ -242,6 +242,10 @@ func isValidBufferedReadConfig(rc *ReadConfig) error { return fmt.Errorf("invalid value of read-max-blocks-per-handle: %d; should be >=1 or -1 (for infinite)", rc.MaxBlocksPerHandle) } + if rc.MinBlocksPerHandle < 1 || (rc.MaxBlocksPerHandle != -1 && rc.MinBlocksPerHandle > rc.MaxBlocksPerHandle) { + return fmt.Errorf("invalid value of read-min-blocks-per-handle: %d; should be >=1 or less than or equal to read-max-blocks-per-handle: %d", rc.MinBlocksPerHandle, rc.MaxBlocksPerHandle) + } + return nil } diff --git a/cfg/validate_test.go b/cfg/validate_test.go index f44c9963b5..b56ebaa18e 100644 --- a/cfg/validate_test.go +++ b/cfg/validate_test.go @@ -581,6 +581,7 @@ func Test_isValidBufferedReadConfig_ErrorScenarios(t *testing.T) { GlobalMaxBlocks: -1, MaxBlocksPerHandle: -1, StartBlocksPerHandle: 1, + MinBlocksPerHandle: 4, }}, {"zero_block_size", ReadConfig{ BlockSizeMb: 0, @@ -588,6 +589,7 @@ func Test_isValidBufferedReadConfig_ErrorScenarios(t *testing.T) { GlobalMaxBlocks: -1, MaxBlocksPerHandle: -1, StartBlocksPerHandle: 1, + MinBlocksPerHandle: 4, }}, {"negative_global_max_blocks", ReadConfig{ BlockSizeMb: 16, @@ -595,6 +597,7 @@ func Test_isValidBufferedReadConfig_ErrorScenarios(t *testing.T) { GlobalMaxBlocks: -2, MaxBlocksPerHandle: -1, StartBlocksPerHandle: 1, + MinBlocksPerHandle: 4, }}, {"negative_max_blocks_per_handle", ReadConfig{ BlockSizeMb: 16, @@ -602,6 +605,23 @@ func Test_isValidBufferedReadConfig_ErrorScenarios(t *testing.T) { GlobalMaxBlocks: -1, MaxBlocksPerHandle: -2, StartBlocksPerHandle: 1, + MinBlocksPerHandle: 4, + }}, + {"negative_min_blocks_per_handle", ReadConfig{ + BlockSizeMb: 16, + EnableBufferedRead: true, + GlobalMaxBlocks: -1, + MaxBlocksPerHandle: -1, + StartBlocksPerHandle: 1, + MinBlocksPerHandle: -4, + }}, + {"zero_min_blocks_per_handle", ReadConfig{ + BlockSizeMb: 16, + EnableBufferedRead: true, + GlobalMaxBlocks: -1, + MaxBlocksPerHandle: -1, + StartBlocksPerHandle: 1, + MinBlocksPerHandle: 0, }}, } @@ -623,6 +643,7 @@ func Test_isValidBufferedReadConfig_ValidScenarios(t *testing.T) { GlobalMaxBlocks: -1, MaxBlocksPerHandle: -1, StartBlocksPerHandle: 1, + MinBlocksPerHandle: 1, }}, {"valid_config_2", ReadConfig{ BlockSizeMb: 16, @@ -630,6 +651,7 @@ func Test_isValidBufferedReadConfig_ValidScenarios(t *testing.T) { GlobalMaxBlocks: 10, MaxBlocksPerHandle: -1, StartBlocksPerHandle: 1, + MinBlocksPerHandle: 4, }}, {"valid_config_3", ReadConfig{ BlockSizeMb: 16, @@ -637,6 +659,7 @@ func Test_isValidBufferedReadConfig_ValidScenarios(t *testing.T) { GlobalMaxBlocks: 10, MaxBlocksPerHandle: 5, StartBlocksPerHandle: 1, + MinBlocksPerHandle: 5, }}, {"valid_config_4", ReadConfig{ BlockSizeMb: 16, @@ -644,6 +667,7 @@ func Test_isValidBufferedReadConfig_ValidScenarios(t *testing.T) { GlobalMaxBlocks: 10, MaxBlocksPerHandle: 5, StartBlocksPerHandle: 10, + MinBlocksPerHandle: 3, }}, } diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 137fae300f..adda4dbc83 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -242,6 +242,7 @@ func TestValidateConfigFile_ReadConfig(t *testing.T) { GlobalMaxBlocks: 20, MaxBlocksPerHandle: 20, StartBlocksPerHandle: 1, + MinBlocksPerHandle: 4, }, }, }, @@ -256,6 +257,7 @@ func TestValidateConfigFile_ReadConfig(t *testing.T) { MaxBlocksPerHandle: 20, GlobalMaxBlocks: 40, StartBlocksPerHandle: 4, + MinBlocksPerHandle: 2, }, }, }, diff --git a/cmd/root_test.go b/cmd/root_test.go index ba35cd0192..75650a7640 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -401,6 +401,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { expectedReadGlobalMaxBlocks int64 expectedReadMaxBlocksPerHandle int64 expectedReadStartBlocksPerHandle int64 + expectedReadMinBlocksPerHandle int64 }{ { name: "Test default flags.", @@ -409,6 +410,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { expectedReadGlobalMaxBlocks: 20, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, + expectedReadMinBlocksPerHandle: 4, }, { name: "Test enable buffered read flag true.", @@ -417,6 +419,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { expectedReadGlobalMaxBlocks: 20, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, + expectedReadMinBlocksPerHandle: 4, }, { name: "Test enable buffered read flag false.", @@ -425,6 +428,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { expectedReadGlobalMaxBlocks: 20, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, + expectedReadMinBlocksPerHandle: 4, }, { name: "Test positive read-block-size-mb flag.", @@ -433,6 +437,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { expectedReadGlobalMaxBlocks: 20, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, + expectedReadMinBlocksPerHandle: 4, }, { name: "Test positive read-global-max-blocks flag.", @@ -441,6 +446,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { expectedReadGlobalMaxBlocks: 10, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, + expectedReadMinBlocksPerHandle: 4, }, { name: "Test positive read-max-blocks-per-handle flag.", @@ -449,6 +455,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { expectedReadGlobalMaxBlocks: 20, expectedReadMaxBlocksPerHandle: 10, expectedReadStartBlocksPerHandle: 1, + expectedReadMinBlocksPerHandle: 4, }, { name: "Test positive read-start-blocks-per-handle flag.", @@ -457,6 +464,16 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { expectedReadGlobalMaxBlocks: 20, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 10, + expectedReadMinBlocksPerHandle: 4, + }, + { + name: "Test positive read-min-blocks-per-handle flag.", + args: []string{"gcsfuse", "--read-min-blocks-per-handle=10", "abc", "pqr"}, + expectedReadBlockSizeMB: 16, + expectedReadGlobalMaxBlocks: 20, + expectedReadMaxBlocksPerHandle: 20, + expectedReadStartBlocksPerHandle: 1, + expectedReadMinBlocksPerHandle: 10, }, } @@ -477,6 +494,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { assert.Equal(t, tc.expectedReadGlobalMaxBlocks, rc.GlobalMaxBlocks) assert.Equal(t, tc.expectedReadMaxBlocksPerHandle, rc.MaxBlocksPerHandle) assert.Equal(t, tc.expectedReadStartBlocksPerHandle, rc.StartBlocksPerHandle) + assert.Equal(t, tc.expectedReadMinBlocksPerHandle, rc.MinBlocksPerHandle) } }) } diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 3f34ea5936..ac90de2b49 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -6,6 +6,7 @@ read: block-size-mb: 16 start-blocks-per-handle: 4 max-blocks-per-handle: 20 + min-blocks-per-handle: 2 write: create-empty-file: true enable-streaming-writes: true diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index 0138818260..ed99fd0340 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -43,6 +43,7 @@ type BufferedReadConfig struct { MaxPrefetchBlockCnt int64 // Maximum number of blocks that can be prefetched. PrefetchBlockSizeBytes int64 // Size of each block to be prefetched. InitialPrefetchBlockCnt int64 // Number of blocks to prefetch initially. + MinBlocksPerHandle int64 // Minimum number of blocks available in block-pool to start buffered-read. } const ( @@ -94,8 +95,7 @@ type BufferedReader struct { // NewBufferedReader returns a new bufferedReader instance. func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *BufferedReadConfig, globalMaxBlocksSem *semaphore.Weighted, workerPool workerpool.WorkerPool, metricHandle metrics.MetricHandle) (*BufferedReader, error) { - // TODO: To pass the minimum required block-count based on the block-size. - blockpool, err := block.NewPrefetchBlockPool(config.PrefetchBlockSizeBytes, config.MaxPrefetchBlockCnt, 2, globalMaxBlocksSem) + blockpool, err := block.NewPrefetchBlockPool(config.PrefetchBlockSizeBytes, config.MaxPrefetchBlockCnt, config.MinBlocksPerHandle, globalMaxBlocksSem) if err != nil { return nil, fmt.Errorf("NewBufferedReader: failed to create block-pool: %w", err) } diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 5ddb2cb99d..26a85f2aac 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -39,6 +39,7 @@ import ( const ( testMaxPrefetchBlockCnt int64 = 10 + testMinBlocksPerHandle int64 = 2 testGlobalMaxBlocks int64 = 20 testPrefetchBlockSizeBytes int64 = 1024 testInitialPrefetchBlockCnt int64 = 2 @@ -112,6 +113,7 @@ func (t *BufferedReaderTest) SetupTest() { MaxPrefetchBlockCnt: testMaxPrefetchBlockCnt, PrefetchBlockSizeBytes: testPrefetchBlockSizeBytes, InitialPrefetchBlockCnt: testInitialPrefetchBlockCnt, + MinBlocksPerHandle: testMinBlocksPerHandle, } var err error t.workerPool, err = workerpool.NewStaticWorkerPool(5, 10) @@ -143,6 +145,26 @@ func (t *BufferedReaderTest) TestNewBufferedReader() { assert.NotNil(t.T(), reader.cancelFunc) } +func (t *BufferedReaderTest) TestNewBufferedReaderWithMinimumBlockNotAvailableInPool() { + // Simulate no blocks available globally. + t.globalMaxBlocksSem = semaphore.NewWeighted(1) + + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + + assert.Error(t.T(), err) + assert.ErrorIs(t.T(), err, block.CantAllocateAnyBlockError) + assert.Nil(t.T(), reader, "BufferedReader should be nil on error") +} + +func (t *BufferedReaderTest) TestNewBufferedReaderWithZeroBlockSize() { + t.config.PrefetchBlockSizeBytes = 0 + + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + + assert.Error(t.T(), err, "NewBufferedReader should return error with invalid block size") + assert.Nil(t.T(), reader, "BufferedReader should be nil on error") +} + func (t *BufferedReaderTest) TestDestroySuccess() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err, "NewBufferedReader should not return error") diff --git a/internal/gcsx/read_manager/read_manager.go b/internal/gcsx/read_manager/read_manager.go index f455008e2e..97522f91af 100644 --- a/internal/gcsx/read_manager/read_manager.go +++ b/internal/gcsx/read_manager/read_manager.go @@ -80,6 +80,7 @@ func NewReadManager(object *gcs.MinObject, bucket gcs.Bucket, config *ReadManage MaxPrefetchBlockCnt: readConfig.MaxBlocksPerHandle, PrefetchBlockSizeBytes: readConfig.BlockSizeMb * util.MiB, InitialPrefetchBlockCnt: readConfig.StartBlocksPerHandle, + MinBlocksPerHandle: readConfig.MinBlocksPerHandle, } bufferedReader, err := bufferedread.NewBufferedReader( object, diff --git a/internal/gcsx/read_manager/read_manager_test.go b/internal/gcsx/read_manager/read_manager_test.go index e621e0a217..cab81dcd38 100644 --- a/internal/gcsx/read_manager/read_manager_test.go +++ b/internal/gcsx/read_manager/read_manager_test.go @@ -68,6 +68,7 @@ func (t *readManagerTest) readManagerConfig(fileCacheEnable bool, bufferedReadEn MaxBlocksPerHandle: 10, BlockSizeMb: 1, StartBlocksPerHandle: 2, + MinBlocksPerHandle: 2, }, }, GlobalMaxBlocksSem: semaphore.NewWeighted(20), From eea0ca7efe539b0723ad2014db44c02ef0d63471 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 13 Aug 2025 00:19:56 +0530 Subject: [PATCH 0680/1298] feat(bufferedread): Handle edge cases for non-blocking TryGet in Buffered Reader (#3680) * Handle TryGet correctly and cause fatal error only on GCS erros * resolved comments * make the log messages more structured and parseable for integration tests --- internal/bufferedread/buffered_reader.go | 82 ++++++----- internal/bufferedread/buffered_reader_test.go | 136 ++++++++++++++++++ 2 files changed, 184 insertions(+), 34 deletions(-) diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index ed99fd0340..0b7c6d662d 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -97,7 +97,7 @@ type BufferedReader struct { func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *BufferedReadConfig, globalMaxBlocksSem *semaphore.Weighted, workerPool workerpool.WorkerPool, metricHandle metrics.MetricHandle) (*BufferedReader, error) { blockpool, err := block.NewPrefetchBlockPool(config.PrefetchBlockSizeBytes, config.MaxPrefetchBlockCnt, config.MinBlocksPerHandle, globalMaxBlocksSem) if err != nil { - return nil, fmt.Errorf("NewBufferedReader: failed to create block-pool: %w", err) + return nil, fmt.Errorf("NewBufferedReader: creating block-pool: %w", err) } reader := &BufferedReader{ @@ -145,13 +145,13 @@ func (p *BufferedReader) handleRandomRead(offset int64) error { entry := p.blockQueue.Pop() entry.cancel() if _, waitErr := entry.block.AwaitReady(context.Background()); waitErr != nil { - logger.Warnf("handleRandomRead: AwaitReady error during discard (offset=%d): %v", offset, waitErr) + logger.Warnf("handleRandomRead: AwaitReady during discard (offset=%d): %v", offset, waitErr) } p.blockPool.Release(entry.block) } if p.randomSeekCount > p.randomReadsThreshold { - logger.Warnf("handleRandomRead: random seek count %d exceeded threshold %d, falling back to another reader", p.randomSeekCount, p.randomReadsThreshold) + logger.Warnf("Fallback to another reader for object %q. Random seek count %d exceeded threshold %d.", p.object.Name, p.randomSeekCount, p.randomReadsThreshold) return gcsx.FallbackToAnotherReader } @@ -191,7 +191,7 @@ func (p *BufferedReader) prepareQueueForOffset(offset int64) { entry.cancel() if _, waitErr := block.AwaitReady(context.Background()); waitErr != nil { - logger.Warnf("prepareQueueForOffset: AwaitReady error during discard (offset=%d): %v", offset, waitErr) + logger.Warnf("prepareQueueForOffset: AwaitReady during discard (offset=%d): %v", offset, waitErr) } p.blockPool.Release(block) @@ -253,7 +253,7 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) }() if err = p.handleRandomRead(off); err != nil { - err = fmt.Errorf("ReadAt: handleRandomRead failed: %w", err) + err = fmt.Errorf("BufferedReader.ReadAt: handleRandomRead: %w", err) return resp, err } @@ -264,11 +264,8 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) if p.blockQueue.IsEmpty() { if err = p.freshStart(off); err != nil { - if errors.Is(err, ErrPrefetchBlockNotAvailable) { - return resp, gcsx.FallbackToAnotherReader - } - err = fmt.Errorf("BufferedReader.ReadAt: freshStart failed: %w", err) - break + logger.Warnf("Fallback to another reader for object %q due to freshStart failure: %v", p.object.Name, err) + return resp, gcsx.FallbackToAnotherReader } prefetchTriggered = true } @@ -278,7 +275,7 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) status, waitErr := blk.AwaitReady(ctx) if waitErr != nil { - err = fmt.Errorf("BufferedReader.ReadAt: AwaitReady failed: %w", waitErr) + err = fmt.Errorf("BufferedReader.ReadAt: AwaitReady: %w", waitErr) break } @@ -330,32 +327,45 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) // prefetch schedules the next set of blocks for prefetching starting from // the nextBlockIndexToPrefetch. func (p *BufferedReader) prefetch() error { - // Do not schedule more than MaxPrefetchBlockCnt. + // Determine the number of blocks to prefetch in this cycle, respecting the + // MaxPrefetchBlockCnt and the number of blocks remaining in the file. availableSlots := p.config.MaxPrefetchBlockCnt - int64(p.blockQueue.Len()) if availableSlots <= 0 { return nil } - blockCountToPrefetch := min(p.numPrefetchBlocks, availableSlots) + totalBlockCount := (int64(p.object.Size) + p.config.PrefetchBlockSizeBytes - 1) / p.config.PrefetchBlockSizeBytes + remainingBlocksInFile := totalBlockCount - p.nextBlockIndexToPrefetch + blockCountToPrefetch := min(min(p.numPrefetchBlocks, availableSlots), remainingBlocksInFile) if blockCountToPrefetch <= 0 { return nil } - totalBlockCount := (int64(p.object.Size) + p.config.PrefetchBlockSizeBytes - 1) / p.config.PrefetchBlockSizeBytes + allBlocksScheduledSuccessfully := true for i := int64(0); i < blockCountToPrefetch; i++ { - if p.nextBlockIndexToPrefetch >= totalBlockCount { - break - } if err := p.scheduleNextBlock(false); err != nil { - return fmt.Errorf("prefetch: failed to schedule block index %d: %v", p.nextBlockIndexToPrefetch, err) + if errors.Is(err, ErrPrefetchBlockNotAvailable) { + // This is not a critical error for a background prefetch. We just stop + // trying to prefetch more in this cycle. The specific reason has + // already been logged by scheduleNextBlock. + allBlocksScheduledSuccessfully = false + break // Stop prefetching more blocks. + } + return fmt.Errorf("prefetch: scheduling block index %d: %w", p.nextBlockIndexToPrefetch, err) } } - // Set the size for the next multiplicative prefetch. - p.numPrefetchBlocks *= p.prefetchMultiplier - - // Do not prefetch more than MaxPrefetchBlockCnt blocks. - if p.numPrefetchBlocks > p.config.MaxPrefetchBlockCnt { - p.numPrefetchBlocks = p.config.MaxPrefetchBlockCnt + // Only increase the prefetch window size if we successfully scheduled all the + // intended blocks. This is a more conservative approach that prevents the + // window from growing aggressively if block pool is consistently under pressure. + if allBlocksScheduledSuccessfully { + // Set the size for the next multiplicative prefetch. + p.numPrefetchBlocks *= p.prefetchMultiplier + + // Cap the prefetch window size for the next cycle at the configured + // maximum to prevent unbounded growth. + if p.numPrefetchBlocks > p.config.MaxPrefetchBlockCnt { + p.numPrefetchBlocks = p.config.MaxPrefetchBlockCnt + } } return nil } @@ -371,12 +381,14 @@ func (p *BufferedReader) freshStart(currentOffset int64) error { // Schedule the first block as urgent. if err := p.scheduleNextBlock(true); err != nil { - return fmt.Errorf("freshStart: while scheduling first block: %w", err) + return fmt.Errorf("freshStart: scheduling first block: %w", err) } // Prefetch the initial blocks. if err := p.prefetch(); err != nil { - return fmt.Errorf("freshStart: while prefetching: %w", err) + // A failure during the initial prefetch is not fatal, as the first block + // has already been scheduled. Log the error and continue. + logger.Warnf("freshStart: initial prefetch: %v", err) } return nil } @@ -384,16 +396,18 @@ func (p *BufferedReader) freshStart(currentOffset int64) error { // scheduleNextBlock schedules the next block for prefetch. func (p *BufferedReader) scheduleNextBlock(urgent bool) error { b, err := p.blockPool.TryGet() - if err != nil || b == nil { - if err != nil { - logger.Warnf("scheduleNextBlock: failed to get block from pool: %v", err) - } + if err != nil { + // Any error from TryGet (e.g., pool exhausted, mmap failure) means we + // can't get a block. For the buffered reader, this is a recoverable + // condition that should either trigger a fallback to another reader (for + // urgent reads) or be ignored (for background prefetches). + logger.Warnf("scheduleNextBlock: could not get block from pool (urgent=%t): %v", urgent, err) return ErrPrefetchBlockNotAvailable } if err := p.scheduleBlockWithIndex(b, p.nextBlockIndexToPrefetch, urgent); err != nil { p.blockPool.Release(b) - return err + return fmt.Errorf("scheduleNextBlock: %w", err) } p.nextBlockIndexToPrefetch++ return nil @@ -403,7 +417,7 @@ func (p *BufferedReader) scheduleNextBlock(urgent bool) error { func (p *BufferedReader) scheduleBlockWithIndex(b block.PrefetchBlock, blockIndex int64, urgent bool) error { startOffset := blockIndex * p.config.PrefetchBlockSizeBytes if err := b.SetAbsStartOff(startOffset); err != nil { - return fmt.Errorf("scheduleBlockWithIndex: failed to set start offset: %w", err) + return fmt.Errorf("scheduleBlockWithIndex: setting start offset: %w", err) } ctx, cancel := context.WithCancel(p.ctx) @@ -431,14 +445,14 @@ func (p *BufferedReader) Destroy() { // We expect a context.Canceled error here, but we wait to ensure the // block's worker goroutine has finished before releasing the block. if _, err := bqe.block.AwaitReady(p.ctx); err != nil && err != context.Canceled { - logger.Warnf("Destroy: while waiting for block on destroy: %v", err) + logger.Warnf("Destroy: waiting for block on destroy: %v", err) } p.blockPool.Release(bqe.block) } err := p.blockPool.ClearFreeBlockChannel(true) if err != nil { - logger.Warnf("Destroy: while clearing free block channel: %v", err) + logger.Warnf("Destroy: clearing free block channel: %v", err) } p.blockPool = nil } diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 26a85f2aac..1a57793916 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -43,6 +43,7 @@ const ( testGlobalMaxBlocks int64 = 20 testPrefetchBlockSizeBytes int64 = 1024 testInitialPrefetchBlockCnt int64 = 2 + oneTB int64 = 1 << 40 ) type BufferedReaderTest struct { @@ -145,6 +146,15 @@ func (t *BufferedReaderTest) TestNewBufferedReader() { assert.NotNil(t.T(), reader.cancelFunc) } +func (t *BufferedReaderTest) TestNewBufferedReaderFailsWhenPoolAllocationFails() { + t.globalMaxBlocksSem = semaphore.NewWeighted(1) + + _, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + + require.Error(t.T(), err) + assert.ErrorIs(t.T(), err, block.CantAllocateAnyBlockError) +} + func (t *BufferedReaderTest) TestNewBufferedReaderWithMinimumBlockNotAvailableInPool() { // Simulate no blocks available globally. t.globalMaxBlocksSem = semaphore.NewWeighted(1) @@ -697,6 +707,45 @@ func (t *BufferedReaderTest) TestPrefetchLimitedByAvailableSlots() { t.bucket.AssertExpectations(t.T()) } +func (t *BufferedReaderTest) TestPrefetchStopsWhenPoolIsExhausted() { + // Configure a small pool that will be exhausted, to test the case where + // prefetching is not possible. + t.config.MaxPrefetchBlockCnt = 4 + t.config.InitialPrefetchBlockCnt = 2 + // The global semaphore only has enough permits for the reserved blocks. + t.globalMaxBlocksSem = semaphore.NewWeighted(2) + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + // At this point, NewBufferedReader has acquired 2 permits for its reserved blocks. + // The global semaphore is now empty. + // The first prefetch() call will succeed by allocating the 2 reserved blocks. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), 1024, 0), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 1024 })).Return(createFakeReaderWithOffset(t.T(), 1024, 1024), nil).Once() + err = reader.prefetch() + require.NoError(t.T(), err) + require.Equal(t.T(), 2, reader.blockQueue.Len()) + assert.Equal(t.T(), int64(4), reader.numPrefetchBlocks, "numPrefetchBlocks should be multiplied after successful prefetch") + // The pool has now created 2 blocks (totalBlocks=2), which is its max (maxBlocks=2). + // To simulate a state where the pool is exhausted, we drain the queue without + // releasing the blocks back to the pool's free channel. We must wait for the + // downloads to complete before proceeding. + bqe1 := reader.blockQueue.Pop() + _, _ = bqe1.block.AwaitReady(t.ctx) + bqe2 := reader.blockQueue.Pop() + _, _ = bqe2.block.AwaitReady(t.ctx) + // Now the blockQueue and freeBlocksCh are empty, but totalBlocks is at its limit. + + // The next prefetch call should attempt to schedule blocks but fail to get + // any from the exhausted pool. It should not return an error. + err = reader.prefetch() + + require.NoError(t.T(), err, "prefetch should handle block unavailability gracefully") + assert.Equal(t.T(), 0, reader.blockQueue.Len(), "No new blocks should have been scheduled") + assert.Equal(t.T(), int64(2), reader.nextBlockIndexToPrefetch, "The index should not have advanced") + assert.Equal(t.T(), int64(4), reader.numPrefetchBlocks, "numPrefetchBlocks should not increase when prefetch is incomplete") + t.bucket.AssertExpectations(t.T()) +} + func (t *BufferedReaderTest) TestReadAtOffsetBeyondEOF() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) require.NoError(t.T(), err) @@ -1089,6 +1138,38 @@ func (t *BufferedReaderTest) TestReadAtFallsBackAfterRandomReads() { t.bucket.AssertExpectations(t.T()) } +func (t *BufferedReaderTest) TestReadAtFallbackOnFreshStartFailure() { + t.config.MaxPrefetchBlockCnt = 2 + t.config.InitialPrefetchBlockCnt = 2 + t.globalMaxBlocksSem = semaphore.NewWeighted(2) + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + // Manually exhaust the pool's blocks to simulate a scenario where all blocks are in use. + _, err = reader.blockPool.Get() + require.NoError(t.T(), err) + _, err = reader.blockPool.Get() + require.NoError(t.T(), err) + t.bucket.On("Name").Return("test-bucket").Maybe() + + _, err = reader.ReadAt(t.ctx, make([]byte, 10), 0) + + assert.ErrorIs(t.T(), err, gcsx.FallbackToAnotherReader, "ReadAt should fall back when freshStart fails to get a block") +} + +func (t *BufferedReaderTest) TestReadAtFallbackOnMmapFailure() { + // Configure a huge block size that will likely cause mmap to fail. + // This simulates a non-recoverable error during block creation within the + // buffered reader, which should cause a fallback. + t.config.PrefetchBlockSizeBytes = oneTB + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + t.bucket.On("Name").Return("test-bucket").Maybe() + + _, err = reader.ReadAt(t.ctx, make([]byte, 10), 0) + + assert.ErrorIs(t.T(), err, gcsx.FallbackToAnotherReader, "ReadAt should fall back when mmap fails") +} + func (t *BufferedReaderTest) TestReadAtExceedsObjectSize() { objectSize := uint64(1536) // 1.5 blocks readOffset := int64(1024) @@ -1110,3 +1191,58 @@ func (t *BufferedReaderTest) TestReadAtExceedsObjectSize() { assertBufferContent(t.T(), buf[:resp.Size], readOffset) t.bucket.AssertExpectations(t.T()) } + +func (t *BufferedReaderTest) TestReadAtSucceedsWhenBackgroundPrefetchFailsDueToGlobalSem() { + // Configure a scenario where the initial read succeeds, but the subsequent + // background prefetch fails due to an exhausted global semaphore. + t.config.MaxPrefetchBlockCnt = 3 + t.config.InitialPrefetchBlockCnt = 1 + t.globalMaxBlocksSem = semaphore.NewWeighted(2) + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), 1024, 0), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 1024 })).Return(createFakeReaderWithOffset(t.T(), 1024, 1024), nil).Once() + t.bucket.On("Name").Return("test-bucket").Maybe() + buf := make([]byte, 1024) + + // The read should succeed. When this read consumes block 0, it will trigger + // a background prefetch for block 2, which will fail because the global + // semaphore is exhausted. This failure should not affect the foreground read. + resp, err := reader.ReadAt(t.ctx, buf, 0) + + require.NoError(t.T(), err) + assert.Equal(t.T(), 1024, resp.Size) + assertBufferContent(t.T(), buf, 0) + require.Equal(t.T(), 1, reader.blockQueue.Len()) + assert.Equal(t.T(), int64(1024), reader.blockQueue.Peek().block.AbsStartOff()) + t.bucket.AssertExpectations(t.T()) +} + +func (t *BufferedReaderTest) TestReadAtSucceedsWhenBackgroundPrefetchFailsOnGCSError() { + t.config.MaxPrefetchBlockCnt = 2 + t.config.InitialPrefetchBlockCnt = 2 + t.globalMaxBlocksSem = semaphore.NewWeighted(2) + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + // Mock the first block download to succeed, but the second (prefetched) block + // to fail with a GCS error. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 0 })).Return(createFakeReaderWithOffset(t.T(), 1024, 0), nil).Once() + gcsError := errors.New("simulated GCS error") + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 1024 })).Return(nil, gcsError).Once() + t.bucket.On("Name").Return("test-bucket").Maybe() + buf := make([]byte, 10) + + // The initial read should succeed because it reads from the first block, which + // was downloaded successfully. The background prefetch failure for the second + // block should not affect this call. + resp, err := reader.ReadAt(t.ctx, buf, 0) + + require.NoError(t.T(), err) + assert.Equal(t.T(), 10, resp.Size) + assertBufferContent(t.T(), buf, 0) + // A subsequent attempt to read the second block (which failed to prefetch) + // should return the original GCS error. + _, err = reader.ReadAt(t.ctx, buf, 1024) + assert.ErrorIs(t.T(), err, gcsError) + assert.ErrorContains(t.T(), err, "download failed") +} From 07fe416c226e24211f161a30cae8c05ef64a3bbc Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 13 Aug 2025 14:42:28 +0530 Subject: [PATCH 0681/1298] fix(Buffered read): Make buffered read path thread safe (#3677) * write test for concurrent writes * adding locks * temp changes * adding unit test * adding unit test * review comment * fix unit test * rebase * adding comment * review comment --- internal/bufferedread/buffered_reader.go | 47 ++++++++++++-- internal/bufferedread/buffered_reader_test.go | 65 +++++++++++++++++++ internal/fs/handle/file_test.go | 61 +++++++++++++++++ 3 files changed, 168 insertions(+), 5 deletions(-) diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index 0b7c6d662d..58912bc7eb 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "io" + "sync" "time" "github.com/google/uuid" @@ -77,20 +78,37 @@ type BufferedReader struct { // prefetching operation. numPrefetchBlocks int64 - blockQueue common.Queue[*blockQueueEntry] + metricHandle metrics.MetricHandle readHandle []byte // For zonal bucket. - blockPool *block.GenBlockPool[block.PrefetchBlock] - workerPool workerpool.WorkerPool - metricHandle metrics.MetricHandle - ctx context.Context cancelFunc context.CancelFunc prefetchMultiplier int64 // Multiplier for number of blocks to prefetch. randomReadsThreshold int64 // Number of random reads after which the reader falls back to another reader. + + // `mu` synchronizes access to the buffered reader's shared state. + // All shared variables, such as the block pool and queue, require this lock before any operation. + mu sync.Mutex + + // GUARDED by (mu) + workerPool workerpool.WorkerPool + + // blockQueue is the core of the prefetching pipeline, holding blocks that are + // either downloaded or in the process of being downloaded. + // GUARDED by (mu) + blockQueue common.Queue[*blockQueueEntry] + + // blockPool is a pool of blocks that can be reused for prefetching. + // It is used to avoid allocating new blocks for each prefetch operation. + // The pool is initialized with a maximum number of blocks that can be + // prefetched at a time, and it allows for efficient reuse of blocks. + // The pool is also responsible for managing the global limit on the number + // of blocks that can be allocated across all BufferedReader instances. + // GUARDED by (mu) + blockPool *block.GenBlockPool[block.PrefetchBlock] } // NewBufferedReader returns a new bufferedReader instance. @@ -124,6 +142,7 @@ func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *Buffere // If the number of detected random reads exceeds a configured threshold, it // returns a gcsx.FallbackToAnotherReader error to signal that another reader // should be used. +// LOCKS_REQUIRED(p.mu) func (p *BufferedReader) handleRandomRead(offset int64) error { // Exit early if we have already decided to fall back to another reader. // This avoids re-evaluating the read pattern on every call when the random @@ -159,6 +178,7 @@ func (p *BufferedReader) handleRandomRead(offset int64) error { } // isRandomSeek checks if the read for the given offset is random or not. +// LOCKS_REQUIRED(p.mu) func (p *BufferedReader) isRandomSeek(offset int64) bool { if p.blockQueue.IsEmpty() { return offset != 0 @@ -178,6 +198,7 @@ func (p *BufferedReader) isRandomSeek(offset int64) bool { // seeks (both forward and backward) that land outside the current block. // For each discarded block, its download is cancelled, and it is returned to // the block pool. +// LOCKS_REQUIRED(p.mu) func (p *BufferedReader) prepareQueueForOffset(offset int64) { for !p.blockQueue.IsEmpty() { entry := p.blockQueue.Peek() @@ -221,6 +242,8 @@ func (p *BufferedReader) prepareQueueForOffset(offset int64) { // prefetch operation is triggered to keep the pipeline full. // 5. The loop continues until the buffer is full, the end of the file is // reached, or an error occurs. +// +// LOCKS_EXCLUDED(p.mu) func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) (gcsx.ReaderResponse, error) { resp := gcsx.ReaderResponse{DataBuf: inputBuf} reqID := uuid.New() @@ -245,6 +268,9 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) return resp, nil } + p.mu.Lock() + defer p.mu.Unlock() + defer func() { dur := time.Since(start) if err == nil || errors.Is(err, io.EOF) { @@ -326,6 +352,7 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) // prefetch schedules the next set of blocks for prefetching starting from // the nextBlockIndexToPrefetch. +// LOCKS_REQUIRED(p.mu) func (p *BufferedReader) prefetch() error { // Determine the number of blocks to prefetch in this cycle, respecting the // MaxPrefetchBlockCnt and the number of blocks remaining in the file. @@ -372,6 +399,7 @@ func (p *BufferedReader) prefetch() error { // freshStart resets the prefetching state and schedules the initial set of // blocks starting from the given offset. +// LOCKS_REQUIRED(p.mu) func (p *BufferedReader) freshStart(currentOffset int64) error { blockIndex := currentOffset / p.config.PrefetchBlockSizeBytes p.nextBlockIndexToPrefetch = blockIndex @@ -394,6 +422,7 @@ func (p *BufferedReader) freshStart(currentOffset int64) error { } // scheduleNextBlock schedules the next block for prefetch. +// LOCKS_REQUIRED(p.mu) func (p *BufferedReader) scheduleNextBlock(urgent bool) error { b, err := p.blockPool.TryGet() if err != nil { @@ -414,6 +443,7 @@ func (p *BufferedReader) scheduleNextBlock(urgent bool) error { } // scheduleBlockWithIndex schedules a block with a specific index. +// LOCKS_REQUIRED(p.mu) func (p *BufferedReader) scheduleBlockWithIndex(b block.PrefetchBlock, blockIndex int64, urgent bool) error { startOffset := blockIndex * p.config.PrefetchBlockSizeBytes if err := b.SetAbsStartOff(startOffset); err != nil { @@ -432,7 +462,11 @@ func (p *BufferedReader) scheduleBlockWithIndex(b block.PrefetchBlock, blockInde return nil } +// LOCKS_EXCLUDED(p.mu) func (p *BufferedReader) Destroy() { + p.mu.Lock() + defer p.mu.Unlock() + if p.cancelFunc != nil { p.cancelFunc() p.cancelFunc = nil @@ -458,7 +492,10 @@ func (p *BufferedReader) Destroy() { } // CheckInvariants checks for internal consistency of the reader. +// LOCKS_EXCLUDED(p.mu) func (p *BufferedReader) CheckInvariants() { + p.mu.Lock() + defer p.mu.Unlock() // The prefetch block size must be positive. if p.config.PrefetchBlockSizeBytes <= 0 { diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 1a57793916..65962b264d 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -19,6 +19,7 @@ import ( "context" "errors" "io" + "sync" "testing" "github.com/googlecloudplatform/gcsfuse/v3/internal/block" @@ -1246,3 +1247,67 @@ func (t *BufferedReaderTest) TestReadAtSucceedsWhenBackgroundPrefetchFailsOnGCSE assert.ErrorIs(t.T(), err, gcsError) assert.ErrorContains(t.T(), err, "download failed") } + +func (t *BufferedReaderTest) TestReadAtConcurrentReads() { + const ( + fileSize = 10 * util.MiB + numGoroutines = 3 + blockSize = 1 * util.MiB + readSize = 1 * util.MiB + ) + t.object.Size = fileSize + t.config.PrefetchBlockSizeBytes = blockSize + t.config.MaxPrefetchBlockCnt = 10 + t.config.InitialPrefetchBlockCnt = 2 // This will prefetch 2 blocks after the initial one. + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + require.NoError(t.T(), err) + // Set up mocks for all possible block reads. Because the goroutines run + // concurrently, we prepare mocks for all blocks that could be read or + // prefetched (2 blocks) and use .Maybe() to allow them to be called in + // any order. + for i := 0; i <= 8; i++ { + start := uint64(i * blockSize) + // Create content for this block using the A-Z pattern from the test helpers. + blockContent := make([]byte, blockSize) + for j := range blockContent { + blockContent[j] = byte('A' + ((int(start) + j) % 26)) + } + t.bucket.On("NewReaderWithReadHandle", + mock.Anything, + mock.MatchedBy(func(req *gcs.ReadObjectRequest) bool { + return req.Range.Start == start + }), + ).Return(&fake.FakeReader{ReadCloser: io.NopCloser(bytes.NewReader(blockContent))}, nil).Maybe() + } + t.bucket.On("Name").Return("test-bucket").Maybe() + var wg sync.WaitGroup + wg.Add(numGoroutines) + results := make([][]byte, numGoroutines) + + // Each go routine will read different range to avoid duplicate calls for same range. + // That's why we are multiplying by 3 to have offset 3 blocks apart. + var readIndex = 3 + for i := 0; i < numGoroutines; i++ { + go func(index int) { + defer wg.Done() + offset := int64(index * readIndex * readSize) + readBuf := make([]byte, readSize) + + resp, err := reader.ReadAt(t.ctx, readBuf, offset) + + require.NoError(t.T(), err) + require.Equal(t.T(), readSize, resp.Size) + // Copy the result to a new slice to avoid data races on readBuf. + results[index] = make([]byte, readSize) + copy(results[index], readBuf) + }(i) + } + + wg.Wait() + // Verify the results from all goroutines individually. + for i, res := range results { + offset := int64(i * readIndex * readSize) + assertBufferContent(t.T(), res, offset) + } + t.bucket.AssertExpectations(t.T()) +} diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 5114e3998f..5f4b52b122 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -20,6 +20,7 @@ import ( "errors" "fmt" "io" + "sync" "testing" "time" @@ -451,3 +452,63 @@ func (t *fileTest) Test_ReadWithReadManager_FullReadSuccessWithBufferedRead() { assert.Equal(t.T(), fileSize, n) assert.Equal(t.T(), expectedData, output) } + +func (t *fileTest) Test_ReadWithReadManager_ConcurrentReadsWithBufferedReader() { + const ( + fileSize = 9 * 1024 * 1024 // 9 MiB + numGoroutines = 3 + ) + // Create expected data for the file. + expectedData := make([]byte, fileSize) + for i := 0; i < fileSize; i++ { + expectedData[i] = byte(i % 256) + } + // Setup configuration for buffered read. + config := &cfg.Config{ + Read: cfg.ReadConfig{ + EnableBufferedRead: true, + MaxBlocksPerHandle: 10, + StartBlocksPerHandle: 2, + BlockSizeMb: 1, + }, + } + workerPool, err := workerpool.NewStaticWorkerPoolForCurrentCPU() + require.NoError(t.T(), err) + defer workerPool.Stop() + globalSemaphore := semaphore.NewWeighted(20) + // Create mock inode and file handle. + parent := createDirInode(&t.bucket, &t.clock) + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "read_obj", expectedData, false) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, config, workerPool, globalSemaphore) + // Use a WaitGroup to synchronize goroutines. + var wg sync.WaitGroup + wg.Add(numGoroutines) + readSize := fileSize / numGoroutines + results := make([][]byte, numGoroutines) + for i := 0; i < numGoroutines; i++ { + go func(index int) { + defer wg.Done() + offset := int64(index * readSize) + readBuf := make([]byte, readSize) + fh.inode.Lock() + + // Each goroutine use same file handle. + output, n, err := fh.ReadWithReadManager(context.Background(), readBuf, offset, int32(readSize)) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), readSize, n) + results[index] = output + }(i) + } + // Wait for all goroutines to finish. + wg.Wait() + // Combine the results from all goroutines. + combinedResult := make([]byte, 0, fileSize) + for _, res := range results { + combinedResult = append(combinedResult, res...) + } + // Final assertion: compare the combined result with the original expected data. + assert.Equal(t.T(), expectedData, combinedResult, "Combined result should match expected data.") + // Clean up the original file handle. + fh.Destroy() +} From 8b3502299708ddacd1bc253074e0f6d5f31efdaf Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 13 Aug 2025 23:28:41 +0530 Subject: [PATCH 0682/1298] Update block reservation logic (#3689) --- internal/bufferedread/buffered_reader.go | 9 +++- internal/bufferedread/buffered_reader_test.go | 46 ++++++++++++++++++- 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index 58912bc7eb..07600d1843 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -113,7 +113,14 @@ type BufferedReader struct { // NewBufferedReader returns a new bufferedReader instance. func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *BufferedReadConfig, globalMaxBlocksSem *semaphore.Weighted, workerPool workerpool.WorkerPool, metricHandle metrics.MetricHandle) (*BufferedReader, error) { - blockpool, err := block.NewPrefetchBlockPool(config.PrefetchBlockSizeBytes, config.MaxPrefetchBlockCnt, config.MinBlocksPerHandle, globalMaxBlocksSem) + if config.PrefetchBlockSizeBytes <= 0 { + return nil, fmt.Errorf("NewBufferedReader: PrefetchBlockSizeBytes must be positive, but is %d", config.PrefetchBlockSizeBytes) + } + // To optimize resource usage, reserve only the number of blocks required for + // the file, capped by the configured minimum. + blocksInFile := (int64(object.Size) + config.PrefetchBlockSizeBytes - 1) / config.PrefetchBlockSizeBytes + numBlocksToReserve := min(blocksInFile, config.MinBlocksPerHandle) + blockpool, err := block.NewPrefetchBlockPool(config.PrefetchBlockSizeBytes, config.MaxPrefetchBlockCnt, numBlocksToReserve, globalMaxBlocksSem) if err != nil { return nil, fmt.Errorf("NewBufferedReader: creating block-pool: %w", err) } diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 65962b264d..735f0620d4 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -147,6 +147,50 @@ func (t *BufferedReaderTest) TestNewBufferedReader() { assert.NotNil(t.T(), reader.cancelFunc) } +func (t *BufferedReaderTest) TestNewBufferedReaderReservesRequiredBlocks() { + testCases := []struct { + name string + objectSize uint64 + minBlocksPerHandle int64 + expectedReserved int64 + }{ + { + name: "SmallFile", + objectSize: uint64(testPrefetchBlockSizeBytes) / 2, // Requires 1 block + minBlocksPerHandle: 5, + expectedReserved: 1, + }, + { + name: "LargeFile", + objectSize: uint64(testPrefetchBlockSizeBytes) * 10, // Requires 10 blocks + minBlocksPerHandle: 5, + expectedReserved: 5, + }, + { + name: "ZeroSizeFile", + objectSize: 0, // Requires 0 blocks + minBlocksPerHandle: 5, + expectedReserved: 0, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + t.object.Size = tc.objectSize + t.config.MinBlocksPerHandle = tc.minBlocksPerHandle + t.globalMaxBlocksSem = semaphore.NewWeighted(testGlobalMaxBlocks) + + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + + require.NoError(t.T(), err) + require.NotNil(t.T(), reader) + // Verify that the correct number of blocks were reserved by checking the semaphore's state. + assert.True(t.T(), t.globalMaxBlocksSem.TryAcquire(testGlobalMaxBlocks-tc.expectedReserved), "Should acquire remaining permits") + assert.False(t.T(), t.globalMaxBlocksSem.TryAcquire(1), "Should not acquire more permits") + }) + } +} + func (t *BufferedReaderTest) TestNewBufferedReaderFailsWhenPoolAllocationFails() { t.globalMaxBlocksSem = semaphore.NewWeighted(1) @@ -172,7 +216,7 @@ func (t *BufferedReaderTest) TestNewBufferedReaderWithZeroBlockSize() { reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) - assert.Error(t.T(), err, "NewBufferedReader should return error with invalid block size") + assert.ErrorContains(t.T(), err, "PrefetchBlockSizeBytes must be positive") assert.Nil(t.T(), reader, "BufferedReader should be nil on error") } From 896479c4c12a8890f643bae28687905c94b011cd Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 14 Aug 2025 09:31:50 +0530 Subject: [PATCH 0683/1298] test(bufferedread): Add setup and helper methods for Buffered Read integration tests (#3685) * Initial changes * Add setup for e2e tests of buffered reader * Rebased * Add new integration test * Add package to e2e scripts * formatting * Resolved comments * resolved comments * Add chunk sizes to test scenario as well --- internal/bufferedread/buffered_reader.go | 10 +- internal/util/util.go | 1 + tools/cd_scripts/e2e_test.sh | 1 + .../buffered_read/helpers_test.go | 110 +++++++++++++++ .../buffered_read/sequential_read_test.go | 122 +++++++++++++++++ .../buffered_read/setup_test.go | 128 ++++++++++++++++++ .../improved_run_e2e_tests.sh | 1 + tools/integration_tests/run_e2e_tests.sh | 1 + .../read_logs/buffered_read_log_parser.go | 40 +++++- .../buffered_read_log_parser_test.go | 109 +++++++++++++++ .../json_parser/read_logs/structure.go | 4 +- .../util/operations/file_operations.go | 4 +- 12 files changed, 519 insertions(+), 12 deletions(-) create mode 100644 tools/integration_tests/buffered_read/helpers_test.go create mode 100644 tools/integration_tests/buffered_read/sequential_read_test.go create mode 100644 tools/integration_tests/buffered_read/setup_test.go diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index 07600d1843..df537131d0 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -148,9 +148,9 @@ func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *Buffere // random if the requested offset is outside the currently prefetched window. // If the number of detected random reads exceeds a configured threshold, it // returns a gcsx.FallbackToAnotherReader error to signal that another reader -// should be used. +// should be used. It takes handleID for logging purposes. // LOCKS_REQUIRED(p.mu) -func (p *BufferedReader) handleRandomRead(offset int64) error { +func (p *BufferedReader) handleRandomRead(offset int64, handleID int64) error { // Exit early if we have already decided to fall back to another reader. // This avoids re-evaluating the read pattern on every call when the random // read threshold has been met. @@ -177,7 +177,7 @@ func (p *BufferedReader) handleRandomRead(offset int64) error { } if p.randomSeekCount > p.randomReadsThreshold { - logger.Warnf("Fallback to another reader for object %q. Random seek count %d exceeded threshold %d.", p.object.Name, p.randomSeekCount, p.randomReadsThreshold) + logger.Warnf("Fallback to another reader for object %q, handle %d. Random seek count %d exceeded threshold %d.", p.object.Name, handleID, p.randomSeekCount, p.randomReadsThreshold) return gcsx.FallbackToAnotherReader } @@ -285,7 +285,7 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) } }() - if err = p.handleRandomRead(off); err != nil { + if err = p.handleRandomRead(off, handleID); err != nil { err = fmt.Errorf("BufferedReader.ReadAt: handleRandomRead: %w", err) return resp, err } @@ -297,7 +297,7 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) if p.blockQueue.IsEmpty() { if err = p.freshStart(off); err != nil { - logger.Warnf("Fallback to another reader for object %q due to freshStart failure: %v", p.object.Name, err) + logger.Warnf("Fallback to another reader for object %q, handle %d, due to freshStart failure: %v", p.object.Name, handleID, err) return resp, gcsx.FallbackToAnotherReader } prefetchTriggered = true diff --git a/internal/util/util.go b/internal/util/util.go index 748aaf34ac..77dedd9509 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -30,6 +30,7 @@ const ( MaxMiBsInUint64 uint64 = math.MaxUint64 >> 20 MaxMiBsInInt64 int64 = math.MaxInt64 >> 20 MiB = 1024 * 1024 + KiB = 1024 // HeapSizeToRssConversionFactor is a constant factor // which we multiply to the calculated heap-size diff --git a/tools/cd_scripts/e2e_test.sh b/tools/cd_scripts/e2e_test.sh index 6c079f794f..a48d992581 100755 --- a/tools/cd_scripts/e2e_test.sh +++ b/tools/cd_scripts/e2e_test.sh @@ -226,6 +226,7 @@ TEST_DIR_PARALLEL=( "release_version" "readdirplus" "dentry_cache" + "buffered_read" ) # These tests never become parallel as they are changing bucket permissions. diff --git a/tools/integration_tests/buffered_read/helpers_test.go b/tools/integration_tests/buffered_read/helpers_test.go new file mode 100644 index 0000000000..a6c68569d9 --- /dev/null +++ b/tools/integration_tests/buffered_read/helpers_test.go @@ -0,0 +1,110 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package buffered_read + +import ( + "bytes" + "context" + "os" + "path" + "syscall" + "testing" + "time" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Expected is a helper struct that stores list of attributes to be validated from logs. +type Expected struct { + StartTimeStampSeconds int64 + EndTimeStampSeconds int64 + BucketName string + ObjectName string +} + +func readFileAndValidate(ctx context.Context, storageClient *storage.Client, testDir, fileName string, readFullFile bool, offset int64, chunkSizeToRead int64, t *testing.T) *Expected { + expected := &Expected{ + StartTimeStampSeconds: time.Now().Unix(), + BucketName: setup.TestBucket(), + ObjectName: path.Join(path.Base(testDir), fileName), + } + if setup.DynamicBucketMounted() != "" { + expected.BucketName = setup.DynamicBucketMounted() + } + if readFullFile { + content, err := operations.ReadFileSequentially(path.Join(testDir, fileName), chunkSizeToRead) + require.NoError(t, err, "Failed to read file sequentially") + obj := storageClient.Bucket(expected.BucketName).Object(expected.ObjectName) + attrs, err := obj.Attrs(ctx) + require.NoError(t, err, "obj.Attrs") + localCRC32C, err := operations.CalculateCRC32(bytes.NewReader(content)) + require.NoError(t, err, "Error while calculating crc for the content read from mounted file") + assert.Equal(t, attrs.CRC32C, localCRC32C, "CRC32C mismatch. GCS: %d, Local: %d", attrs.CRC32C, localCRC32C) + } else { + content, err := operations.ReadChunkFromFile(path.Join(testDir, fileName), chunkSizeToRead, offset, os.O_RDONLY|syscall.O_DIRECT) + require.NoError(t, err, "Failed to read random file chunk") + client.ValidateObjectChunkFromGCS(ctx, storageClient, path.Base(testDir), fileName, offset, chunkSizeToRead, string(content), t) + } + expected.EndTimeStampSeconds = time.Now().Unix() + return expected +} + +func validate(expected *Expected, logEntry *read_logs.BufferedReadLogEntry, fallback bool, t *testing.T) { + t.Helper() + assert.GreaterOrEqual(t, logEntry.StartTimeSeconds, expected.StartTimeStampSeconds, "start time in logs %d less than actual start time %d.", logEntry.StartTimeSeconds, expected.StartTimeStampSeconds) + + assert.Equal(t, expected.BucketName, logEntry.BucketName, "Bucket names don't match! Expected: %s, Got from logs: %s", + expected.BucketName, logEntry.BucketName) + + assert.Equal(t, expected.ObjectName, logEntry.ObjectName, "Object names don't match! Expected: %s, Got from logs: %s", + expected.ObjectName, logEntry.ObjectName) + + assert.Equal(t, fallback, logEntry.Fallback, "Expected Fallback: %t, Got from logs: %t", fallback, logEntry.Fallback) +} + +func setupFileInTestDir(ctx context.Context, storageClient *storage.Client, testDir string, fileSize int64, t *testing.T) (fileName string) { + fileName = testFileName + setup.GenerateRandomString(4) + client.SetupFileInTestDirectory(ctx, storageClient, path.Base(testDir), fileName, fileSize, t) + return fileName +} + +func parseBufferedReadLogs(t *testing.T) map[int64]*read_logs.BufferedReadLogEntry { + t.Helper() + f := operations.OpenFile(setup.LogFile(), t) + defer operations.CloseFileShouldNotThrowError(t, f) + + logEntries, err := read_logs.ParseBufferedReadLogsFromLogReader(f) + require.NoError(t, err, "Failed to parse log file") + return logEntries +} + +func parseAndValidateSingleBufferedReadLog(t *testing.T) *read_logs.BufferedReadLogEntry { + t.Helper() + logEntries := parseBufferedReadLogs(t) + // The test is expected to generate exactly one buffered read log entry because + // all reads are performed through a single file handle. + require.Len(t, logEntries, 1, "Expected one buffered read log entry for the single file handle.") + + for _, entry := range logEntries { + return entry + } + return nil // Unreachable. +} diff --git a/tools/integration_tests/buffered_read/sequential_read_test.go b/tools/integration_tests/buffered_read/sequential_read_test.go new file mode 100644 index 0000000000..85bf04c6c6 --- /dev/null +++ b/tools/integration_tests/buffered_read/sequential_read_test.go @@ -0,0 +1,122 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package buffered_read + +import ( + "fmt" + "os" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +//////////////////////////////////////////////////////////////////////// +// Test Suite Boilerplate +//////////////////////////////////////////////////////////////////////// + +type SequentialReadSuite struct { + suite.Suite + // Helper struct with test flags. + testFlags *gcsfuseTestFlags +} + +func (s *SequentialReadSuite) SetupSuite() { + // Create config file. + configFile := createConfigFile(s.testFlags) + // Create the final flags slice. + // The static mounting helper adds --log-file and --log-severity flags, so we only need to add the format. + flags := []string{"--config-file=" + configFile} + // Mount GCSFuse. + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) +} + +func (s *SequentialReadSuite) TearDownSuite() { + setup.UnmountGCSFuse(setup.MntDir()) + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) +} + +// ////////////////////////////////////////////////////////////////////// +// Test Cases +// ////////////////////////////////////////////////////////////////////// +func (s *SequentialReadSuite) TestSequentialRead() { + blockSizeInBytes := s.testFlags.blockSizeMB * util.MiB + fileSizeTests := []struct { + name string + fileSize int64 + }{ + { + name: "SmallFile", + fileSize: blockSizeInBytes / 2, + }, + { + name: "LargeFile", + fileSize: blockSizeInBytes * 2, + }, + } + + chunkSizesToRead := []int64{128 * util.KiB, 512 * util.KiB, 1 * util.MiB} + + for _, fsTest := range fileSizeTests { + for _, chunkSize := range chunkSizesToRead { + testName := fmt.Sprintf("%s_%dKiB_Chunk", fsTest.name, chunkSize/util.KiB) + fsTest := fsTest // Capture range variable. + chunkSize := chunkSize // Capture range variable. + + s.T().Run(testName, func(t *testing.T) { + err := os.Truncate(setup.LogFile(), 0) + require.NoError(t, err, "Failed to truncate log file") + testDir := setup.SetupTestDirectory(testDirName) + fileName := setupFileInTestDir(ctx, storageClient, testDir, fsTest.fileSize, t) + + expected := readFileAndValidate(ctx, storageClient, testDir, fileName, true, 0, chunkSize, t) + + bufferedReadLogEntry := parseAndValidateSingleBufferedReadLog(t) + validate(expected, bufferedReadLogEntry, false, t) + assert.Equal(t, int64(0), bufferedReadLogEntry.RandomSeekCount, "RandomSeekCount should be 0 for sequential reads.") + }) + } + } +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestSequentialReadSuite(t *testing.T) { + // Define the different flag configurations to test against. + baseTestFlags := gcsfuseTestFlags{ + enableBufferedRead: true, + blockSizeMB: 8, + maxBlocksPerHandle: 20, + startBlocksPerHandle: 1, + minBlocksPerHandle: 2, + } + + protocols := []string{clientProtocolHTTP1, clientProtocolGRPC} + + for _, protocol := range protocols { + // Create a new suite for each protocol. + t.Run(protocol, func(t *testing.T) { + testFlags := baseTestFlags + testFlags.clientProtocol = protocol + + suite.Run(t, &SequentialReadSuite{testFlags: &testFlags}) + }) + } +} diff --git a/tools/integration_tests/buffered_read/setup_test.go b/tools/integration_tests/buffered_read/setup_test.go new file mode 100644 index 0000000000..d1dacfac16 --- /dev/null +++ b/tools/integration_tests/buffered_read/setup_test.go @@ -0,0 +1,128 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package buffered_read + +import ( + "context" + "log" + "os" + "path" + "testing" + + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" +) + +const ( + testDirName = "BufferedReadTest" + testFileName = "foo" + clientProtocolHTTP1 = "http1" + clientProtocolGRPC = "grpc" +) + +var ( + mountFunc func([]string) error + storageClient *storage.Client + ctx context.Context +) + +type gcsfuseTestFlags struct { + clientProtocol string + enableBufferedRead bool + blockSizeMB int64 + maxBlocksPerHandle int64 + startBlocksPerHandle int64 + minBlocksPerHandle int64 +} + +//////////////////////////////////////////////////////////////////////// +// Helpers +//////////////////////////////////////////////////////////////////////// + +func createConfigFile(flags *gcsfuseTestFlags) string { + mountConfig := make(map[string]interface{}) + readConfig := make(map[string]interface{}) + + // Only add flags to the config if they have a non-zero/non-default value. + // This prevents writing zero values that would override GCSFuse's built-in defaults. + if flags.enableBufferedRead { + readConfig["enable-buffered-read"] = flags.enableBufferedRead + } + if flags.blockSizeMB != 0 { + readConfig["block-size-mb"] = flags.blockSizeMB + } + if flags.maxBlocksPerHandle != 0 { + readConfig["max-blocks-per-handle"] = flags.maxBlocksPerHandle + } + if flags.startBlocksPerHandle != 0 { + readConfig["start-blocks-per-handle"] = flags.startBlocksPerHandle + } + if flags.minBlocksPerHandle != 0 { + readConfig["min-blocks-per-handle"] = flags.minBlocksPerHandle + } + if len(readConfig) > 0 { + mountConfig["read"] = readConfig + } + if flags.clientProtocol != "" { + mountConfig["gcs-connection"] = map[string]interface{}{"client-protocol": flags.clientProtocol} + } + filePath := setup.YAMLConfigFile(mountConfig, "config.yaml") + return filePath +} + +//////////////////////////////////////////////////////////////////////// +// TestMain +//////////////////////////////////////////////////////////////////////// + +func TestMain(m *testing.M) { + setup.ParseSetUpFlags() + + ctx = context.Background() + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() + + setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + + if setup.MountedDirectory() != "" { + os.Exit(setup.RunTestsForMountedDirectory(setup.MountedDirectory(), m)) + } + + // Else run tests for testBucket. + setup.SetUpTestDirForTestBucket(setup.TestBucket()) + + // Set up the static mounting function. + mountFunc = func(flags []string) error { + config := &test_suite.TestConfig{ + TestBucket: setup.TestBucket(), + MountedDirectory: setup.MountedDirectory(), + LogFile: setup.LogFile(), + } + return static_mounting.MountGcsfuseWithStaticMountingWithConfigFile(config, flags) + } + + // Run the tests. + successCode := m.Run() + + setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) + os.Exit(successCode) +} diff --git a/tools/integration_tests/improved_run_e2e_tests.sh b/tools/integration_tests/improved_run_e2e_tests.sh index a37944d086..ed6c36a5cc 100755 --- a/tools/integration_tests/improved_run_e2e_tests.sh +++ b/tools/integration_tests/improved_run_e2e_tests.sh @@ -218,6 +218,7 @@ TEST_PACKAGES_COMMON=( "release_version" "readdirplus" "dentry_cache" + "buffered_read" ) # Test packages for regional buckets. diff --git a/tools/integration_tests/run_e2e_tests.sh b/tools/integration_tests/run_e2e_tests.sh index e3005de2f5..99d8fa132a 100755 --- a/tools/integration_tests/run_e2e_tests.sh +++ b/tools/integration_tests/run_e2e_tests.sh @@ -121,6 +121,7 @@ TEST_DIR_PARALLEL=( "release_version" "readdirplus" "dentry_cache" + "buffered_read" ) # These tests never become parallel as it is changing bucket permissions. diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go index 30a40f0d6c..b014653563 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go @@ -18,7 +18,6 @@ import ( "encoding/json" "fmt" "io" - "log" "regexp" "strings" ) @@ -26,6 +25,7 @@ import ( var readFileRegex = regexp.MustCompile(`fuse_debug: Op (0x[0-9a-fA-F]+)\s+connection\.go:\d+\] <- ReadFile \(inode (\d+), PID (\d+), handle (\d+), offset (\d+), (\d+) bytes\)`) var readAtReqRegex = regexp.MustCompile(`([a-f0-9-]+) <- ReadAt\(([^:]+):/([^,]+), (\d+), (\d+), (\d+), (\d+)\)`) var readAtSimpleRespRegex = regexp.MustCompile(`([a-f0-9-]+) -> ReadAt\(\): Ok\(([0-9.]+(?:s|ms|µs))\)`) +var fallbackFromHandleRegex = regexp.MustCompile(`Fallback to another reader for object "[^"]+", handle (\d+)\.(?: Random seek count (\d+) exceeded threshold \d+\.)?`) // ParseBufferedReadLogsFromLogReader parses buffered read logs from an io.Reader and // returns a map of BufferedReadLogEntry keyed by file handle. @@ -88,8 +88,7 @@ func filterAndParseLogLineForBufferedRead( jsonLog := make(map[string]interface{}) if err := json.Unmarshal([]byte(logLine), &jsonLog); err != nil { - log.Printf("filterAndParseLogLineForBufferedRead: failed to unmarshal log line: %s, error: %v", logLine, err) - return nil // Silently ignore the bufferedReadLogsMap which are not in JSON format. + return nil // Silently ignore the logs which are not in JSON format. } if _, ok := jsonLog["timestamp"]; !ok { @@ -118,6 +117,41 @@ func filterAndParseLogLineForBufferedRead( if err := parseReadAtResponseLog(logMessage, bufferedReadLogsMap, opReverseMap); err != nil { return fmt.Errorf("parseReadAtResponseLog failed: %v", err) } + case strings.Contains(logMessage, "Fallback to another reader for object"): + if err := parseFallbackLogFromHandle(logMessage, bufferedReadLogsMap); err != nil { + return fmt.Errorf("parseFallbackLogFromHandle failed: %v", err) + } + } + return nil +} + +func parseFallbackLogFromHandle( + logMessage string, + bufferedReadLogsMap map[int64]*BufferedReadLogEntry) error { + + matches := fallbackFromHandleRegex.FindStringSubmatch(logMessage) + if len(matches) < 2 { + // Not a fallback log we are interested in, might be from a different reader. + return nil + } + + handleID, err := parseToInt64(matches[1]) + if err != nil { + return fmt.Errorf("invalid handle ID in fallback log: %v", err) + } + + logEntry, ok := bufferedReadLogsMap[handleID] + if !ok { + return fmt.Errorf("log entry for handle %d not found for fallback log", handleID) + } + + logEntry.Fallback = true + if len(matches) > 2 && matches[2] != "" { + randomSeekCount, err := parseToInt64(matches[2]) + if err != nil { + return fmt.Errorf("invalid random seek count in fallback log: %v", err) + } + logEntry.RandomSeekCount = randomSeekCount } return nil } diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser_test.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser_test.go index 36da3a4ac1..cbfb09a69c 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser_test.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser_test.go @@ -124,6 +124,40 @@ func TestParseBufferedReadLogsFromLogReaderSuccessful(t *testing.T) { }, }, }, + { + name: "Test buffered read logs with no fallback", + reader: bytes.NewReader([]byte(`{"timestamp":{"seconds":1754207548,"nanos":733110719},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:453] <- ReadFile (inode 2, PID 564246, handle 0, offset 34603008, 1048576 bytes)"} +{"timestamp":{"seconds":1754207548,"nanos":733199657},"severity":"TRACE","message":"2e4645d9-19a8 <- ReadAt(princer-working-dirs:/10G_file, 0, 34603008, 1048576, 2)"} +{"timestamp":{"seconds":1754207548,"nanos":733417812},"severity":"TRACE","message":"2e4645d9-19a8 -> ReadAt(): Ok(223.643µs)"} +{"timestamp":{"seconds":1754207548,"nanos":733444394},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:548] -> ReadFile ()"}`), + ), + expected: map[int64]*read_logs.BufferedReadLogEntry{ + 0: { + CommonReadLog: read_logs.CommonReadLog{ + Handle: 0, + StartTimeSeconds: 1754207548, + StartTimeNanos: 733110719, + ProcessID: 564246, + InodeID: 2, + BucketName: "princer-working-dirs", + ObjectName: "10G_file", + }, + Chunks: []read_logs.BufferedReadChunkData{ + { + StartTimeSeconds: 1754207548, + StartTimeNanos: 733199657, + RequestID: "2e4645d9-19a8", + Offset: 34603008, + Size: 1048576, + BlockIndex: 2, + ExecutionTime: "223.643µs", + }, + }, + Fallback: false, + RandomSeekCount: 0, + }, + }, + }, { name: "Test buffered read logs with no parsable logs", reader: bytes.NewReader([]byte(`{"timestamp":{"seconds":1754207548,"nanos":742190759},"severity":"TRACE","message":"Scheduling block: (10G_file, 9, false)."} @@ -134,6 +168,76 @@ func TestParseBufferedReadLogsFromLogReaderSuccessful(t *testing.T) { ), expected: make(map[int64]*read_logs.BufferedReadLogEntry), }, + { + name: "Test buffered read logs with fallback", + reader: bytes.NewReader([]byte(`{"timestamp":{"seconds":1754207548,"nanos":733110719},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:453] <- ReadFile (inode 2, PID 564246, handle 0, offset 34603008, 1048576 bytes)"} +{"timestamp":{"seconds":1754207548,"nanos":733199657},"severity":"TRACE","message":"2e4645d9-19a8 <- ReadAt(princer-working-dirs:/10G_file, 0, 34603008, 1048576, 2)"} +{"timestamp":{"seconds":1754207548,"nanos":733200000},"severity":"WARN","message":"Fallback to another reader for object \"10G_file\", handle 0. Random seek count 4 exceeded threshold 3."} +{"timestamp":{"seconds":1754207548,"nanos":733417812},"severity":"TRACE","message":"2e4645d9-19a8 -> ReadAt(): Ok(223.643µs)"} +{"timestamp":{"seconds":1754207548,"nanos":733444394},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:548] -> ReadFile ()"}`), + ), + expected: map[int64]*read_logs.BufferedReadLogEntry{ + 0: { + CommonReadLog: read_logs.CommonReadLog{ + Handle: 0, + StartTimeSeconds: 1754207548, + StartTimeNanos: 733110719, + ProcessID: 564246, + InodeID: 2, + BucketName: "princer-working-dirs", + ObjectName: "10G_file", + }, + Chunks: []read_logs.BufferedReadChunkData{ + { + StartTimeSeconds: 1754207548, + StartTimeNanos: 733199657, + RequestID: "2e4645d9-19a8", + Offset: 34603008, + Size: 1048576, + BlockIndex: 2, + ExecutionTime: "223.643µs", + }, + }, + Fallback: true, + RandomSeekCount: 4, + }, + }, + }, + { + name: "Test buffered read logs with generic fallback", + reader: bytes.NewReader([]byte(`{"timestamp":{"seconds":1754207548,"nanos":733110719},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:453] <- ReadFile (inode 2, PID 564246, handle 0, offset 34603008, 1048576 bytes)"} +{"timestamp":{"seconds":1754207548,"nanos":733199657},"severity":"TRACE","message":"2e4645d9-19a8 <- ReadAt(princer-working-dirs:/10G_file, 0, 34603008, 1048576, 2)"} +{"timestamp":{"seconds":1754207548,"nanos":733200000},"severity":"WARN","message":"Fallback to another reader for object \"10G_file\", handle 0. Due to freshStart failure: some error"} +{"timestamp":{"seconds":1754207548,"nanos":733417812},"severity":"TRACE","message":"2e4645d9-19a8 -> ReadAt(): Ok(223.643µs)"} +{"timestamp":{"seconds":1754207548,"nanos":733444394},"severity":"TRACE","message":"fuse_debug: Op 0x0000004e connection.go:548] -> ReadFile ()"}`), + ), + expected: map[int64]*read_logs.BufferedReadLogEntry{ + 0: { + CommonReadLog: read_logs.CommonReadLog{ + Handle: 0, + StartTimeSeconds: 1754207548, + StartTimeNanos: 733110719, + ProcessID: 564246, + InodeID: 2, + BucketName: "princer-working-dirs", + ObjectName: "10G_file", + }, + Chunks: []read_logs.BufferedReadChunkData{ + { + StartTimeSeconds: 1754207548, + StartTimeNanos: 733199657, + RequestID: "2e4645d9-19a8", + Offset: 34603008, + Size: 1048576, + BlockIndex: 2, + ExecutionTime: "223.643µs", + }, + }, + Fallback: true, + RandomSeekCount: 0, + }, + }, + }, { name: "Test buffered read logs with no JSON logs", reader: bytes.NewReader([]byte(`hello 123`)), @@ -190,6 +294,11 @@ func TestBufferedReadLogsFromLogReaderUnsuccessful(t *testing.T) { reader: bytes.NewReader([]byte(`{"timestamp": {"seconds": 1704458059, "nanos": 975956234}, "severity": "TRACE", "message": "fuse_debug: Op 0x00000182 connection.go:415] <- ReadFile (inode 6, PID 2382526, handle abc, offset 0, 4096 bytes)"}`)), errorString: "parseReadFileLog failed: invalid ReadFile log format", }, + { + name: "Test fallback log for unknown handle", + reader: bytes.NewReader([]byte(`{"timestamp":{"seconds":1754207548,"nanos":733200000},"severity":"WARN","message":"Fallback to another reader for object \"10G_file\", handle 99. Random seek count 4 exceeded threshold 3."}`)), + errorString: "log entry for handle 99 not found for fallback log", + }, } for _, tc := range tests { diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go index 6726887c9d..25e7f275a4 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/structure.go @@ -83,7 +83,9 @@ type LogEntry struct { type BufferedReadLogEntry struct { CommonReadLog - Chunks []BufferedReadChunkData + Chunks []BufferedReadChunkData + Fallback bool + RandomSeekCount int64 } type BufferedReadChunkData struct { diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 444057f706..09526b97fd 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -200,10 +200,8 @@ func ReadFileSequentially(filePath string, chunkSize int64) (content []byte, err file, err := os.OpenFile(filePath, os.O_RDONLY|syscall.O_DIRECT, FilePermission_0600) if err != nil { - log.Printf("Error in opening file: %v", err) + return nil, fmt.Errorf("error in opening file %q: %w", filePath, err) } - - // Closing the file at the end. defer CloseFile(file) for err != io.EOF { From 3799f396dfb1c2c5b9ab4dd0da2ecdac44d3578f Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 14 Aug 2025 14:52:09 +0530 Subject: [PATCH 0684/1298] test: Stabilize mount_timeout test by waiting for unmount (#3664) * stabilize test by waiting for unmount * Update tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> * resolved comments --------- Co-authored-by: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> --- .../gcsfuse_mount_timeout_test.go | 93 +++++++++++++------ 1 file changed, 64 insertions(+), 29 deletions(-) diff --git a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go index 0621c10630..e2861f94cc 100644 --- a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go +++ b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go @@ -15,11 +15,13 @@ package mount_timeout import ( + "bufio" "fmt" "math" "os" "path" "path/filepath" + "strings" "testing" "time" @@ -146,22 +148,9 @@ func (testSuite *MountTimeoutTest) TearDownTest() { assert.NoError(testSuite.T(), err) } -func (testSuite *NonZBMountTimeoutTest) SetupTest() { - testSuite.MountTimeoutTest.SetupTest() -} -func (testSuite *NonZBMountTimeoutTest) TearDownTest() { - testSuite.MountTimeoutTest.TearDownTest() -} -func (testSuite *ZBMountTimeoutTest) SetupTest() { - testSuite.MountTimeoutTest.SetupTest() -} -func (testSuite *ZBMountTimeoutTest) TearDownTest() { - testSuite.MountTimeoutTest.TearDownTest() -} - // mountOrTimeout mounts the bucket with the given client protocol. If the time taken // exceeds the expected for the particular test case , an error is thrown and test will fail. -func (testSuite *MountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientProtocol string, expectedMountTime time.Duration) (err error) { +func (testSuite *MountTimeoutTest) mountOrTimeout(bucketName, clientProtocol string, expectedMountTime time.Duration) (err error) { minMountTime := time.Duration(math.MaxInt64) logFile := setup.LogFile() defer func() { @@ -182,8 +171,8 @@ func (testSuite *MountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientPr minMountTime = time.Duration(math.Min(float64(minMountTime), float64(mountTime))) - if err = util.Unmount(mountDir); err != nil { - err = fmt.Errorf("unmount failed for bucket %q on attempt#%v: %w", bucketName, i, err) + if err = unmountAndWait(testSuite.dir); err != nil { + err = fmt.Errorf("unmountAndWait failed for bucket %q on attempt#%v: %w", bucketName, i, err) return err } } @@ -195,11 +184,57 @@ func (testSuite *MountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientPr return nil } -func (testSuite *NonZBMountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientProtocol string, expectedMountTime time.Duration) error { - return testSuite.MountTimeoutTest.mountOrTimeout(bucketName, mountDir, clientProtocol, expectedMountTime) -} -func (testSuite *ZBMountTimeoutTest) mountOrTimeout(bucketName, mountDir, clientProtocol string, expectedMountTime time.Duration) error { - return testSuite.MountTimeoutTest.mountOrTimeout(bucketName, mountDir, clientProtocol, expectedMountTime) +// unmountAndWait unmounts the given directory and waits for the associated +// resources to be released by polling, with a timeout. +func unmountAndWait(mountDir string) error { + // isMounted checks if a directory is currently a mount point by reading /proc/mounts. + isMounted := func(mountPoint string) (bool, error) { + file, err := os.Open("/proc/mounts") + if err != nil { + // On non-Linux systems, /proc/mounts doesn't exist, so we can't poll. + if os.IsNotExist(err) { + return false, nil + } + return false, fmt.Errorf("could not open /proc/mounts: %w", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + // The second field in /proc/mounts is the mount point. + if len(fields) >= 2 && fields[1] == mountPoint { + return true, nil // Found the mount point + } + } + return false, scanner.Err() + } + + if err := util.Unmount(mountDir); err != nil { + // It might already be unmounted or in a weird state. If the error + // indicates it's not mounted, we can proceed to verify. + if !strings.Contains(err.Error(), "not mounted") && !strings.Contains(err.Error(), "no such file or directory") { + return fmt.Errorf("unmount call failed: %w", err) + } + } + + // Poll for up to 5 seconds for the unmount to complete. + ticker := time.NewTicker(10 * time.Millisecond) + defer ticker.Stop() + timeout := time.After(5 * time.Second) + + for { + select { + case <-timeout: + return fmt.Errorf("timed out waiting for %q to unmount", mountDir) + case <-ticker.C: + mounted, err := isMounted(mountDir) + if err != nil || !mounted { + return err // Success or error checking mount status. + } + } + } } func (testSuite *NonZBMountTimeoutTest) TestMountMultiRegionUSBucketWithTimeout() { @@ -223,7 +258,7 @@ func (testSuite *NonZBMountTimeoutTest) TestMountMultiRegionUSBucketWithTimeout( for _, tc := range testCases { setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) - err := testSuite.mountOrTimeout(multiRegionUSBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.multiRegionUSTimeout) + err := testSuite.mountOrTimeout(multiRegionUSBucket, string(tc.clientProtocol), testSuite.timeouts.multiRegionUSTimeout) assert.NoError(testSuite.T(), err) } } @@ -249,7 +284,7 @@ func (testSuite *NonZBMountTimeoutTest) TestMountMultiRegionAsiaBucketWithTimeou for _, tc := range testCases { setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) - err := testSuite.mountOrTimeout(multiRegionAsiaBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.multiRegionAsiaTimeout) + err := testSuite.mountOrTimeout(multiRegionAsiaBucket, string(tc.clientProtocol), testSuite.timeouts.multiRegionAsiaTimeout) assert.NoError(testSuite.T(), err) } } @@ -275,7 +310,7 @@ func (testSuite *NonZBMountTimeoutTest) TestMountDualRegionUSBucketWithTimeout() for _, tc := range testCases { setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) - err := testSuite.mountOrTimeout(dualRegionUSBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.dualRegionUSTimeout) + err := testSuite.mountOrTimeout(dualRegionUSBucket, string(tc.clientProtocol), testSuite.timeouts.dualRegionUSTimeout) assert.NoError(testSuite.T(), err) } } @@ -301,7 +336,7 @@ func (testSuite *NonZBMountTimeoutTest) TestMountDualRegionAsiaBucketWithTimeout for _, tc := range testCases { setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) - err := testSuite.mountOrTimeout(dualRegionAsiaBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.dualRegionAsiaTimeout) + err := testSuite.mountOrTimeout(dualRegionAsiaBucket, string(tc.clientProtocol), testSuite.timeouts.dualRegionAsiaTimeout) assert.NoError(testSuite.T(), err) } } @@ -327,7 +362,7 @@ func (testSuite *NonZBMountTimeoutTest) TestMountSingleRegionUSBucketWithTimeout for _, tc := range testCases { setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) - err := testSuite.mountOrTimeout(singleRegionUSCentralBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.singleRegionUSCentralTimeout) + err := testSuite.mountOrTimeout(singleRegionUSCentralBucket, string(tc.clientProtocol), testSuite.timeouts.singleRegionUSCentralTimeout) assert.NoError(testSuite.T(), err) } } @@ -353,7 +388,7 @@ func (testSuite *NonZBMountTimeoutTest) TestMountSingleRegionAsiaBucketWithTimeo for _, tc := range testCases { setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, tc.name)) - err := testSuite.mountOrTimeout(singleRegionAsiaEastBucket, testSuite.dir, string(tc.clientProtocol), testSuite.timeouts.singleRegionAsiaEastTimeout) + err := testSuite.mountOrTimeout(singleRegionAsiaEastBucket, string(tc.clientProtocol), testSuite.timeouts.singleRegionAsiaEastTimeout) assert.NoError(testSuite.T(), err) } } @@ -361,13 +396,13 @@ func (testSuite *NonZBMountTimeoutTest) TestMountSingleRegionAsiaBucketWithTimeo func (testSuite *ZBMountTimeoutTest) TestMountSameZoneZonalBucketWithTimeout() { setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, "SameZoneZonalBucket")) - err := testSuite.mountOrTimeout(testSuite.config.sameZoneZonalBucket, testSuite.dir, cfg.GRPC, testSuite.config.sameZoneMountTimeout) + err := testSuite.mountOrTimeout(testSuite.config.sameZoneZonalBucket, string(cfg.GRPC), testSuite.config.sameZoneMountTimeout) assert.NoError(testSuite.T(), err) } func (testSuite *ZBMountTimeoutTest) TestMountCrossZoneZonalBucketWithTimeout() { setup.SetLogFile(fmt.Sprintf("%s%s.txt", logfilePathPrefix, "CrossZoneZonalBucket")) - err := testSuite.mountOrTimeout(testSuite.config.crossZoneZonalBucket, testSuite.dir, cfg.GRPC, testSuite.config.crossZoneMountTimeout) + err := testSuite.mountOrTimeout(testSuite.config.crossZoneZonalBucket, string(cfg.GRPC), testSuite.config.crossZoneMountTimeout) assert.NoError(testSuite.T(), err) } From 028357cf39b74edcbf48fc925d6adf95c755cda4 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 14 Aug 2025 15:53:53 +0530 Subject: [PATCH 0685/1298] fix(bash installation): use latest bash 5.3 and improve logging in case of error. (#3691) * fix bash installation race issue and logging * change bash version to 5.3 --- perfmetrics/scripts/install_bash.sh | 12 +++++++++--- .../scripts/presubmit_test/pr_perf_test/build.sh | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/perfmetrics/scripts/install_bash.sh b/perfmetrics/scripts/install_bash.sh index eefd7a8ec7..edd17cec68 100755 --- a/perfmetrics/scripts/install_bash.sh +++ b/perfmetrics/scripts/install_bash.sh @@ -22,7 +22,7 @@ set -euo pipefail if [[ $# -ne 1 ]]; then echo "This script requires exactly one argument." echo "Usage: $0 " - echo "Example: $0 5.1" + echo "Example: $0 5.3" exit 1 fi @@ -50,6 +50,8 @@ install_dependencies() { # Function to download, compile, and install Bash install_bash() { + ( + set -euo pipefail local temp_dir temp_dir=$(mktemp -d /tmp/bash_install_src.XXXXXX) pushd "$temp_dir" @@ -63,6 +65,7 @@ install_bash() { popd rm -rf "$temp_dir" + ) } echo "Installing bash version ${BASH_VERSION} to ${INSTALL_DIR}bin/bash" @@ -70,8 +73,11 @@ INSTALLATION_LOG=$(mktemp /tmp/bash_install_log.XXXXXX) # Installing dependencies before installing Bash install_dependencies - -if ! install_bash >"$INSTALLATION_LOG" 2>&1; then +set +e +install_bash >"$INSTALLATION_LOG" 2>&1 +installation_status=$? +set -e +if [[ $installation_status -ne 0 ]]; then echo "Error: Bash version ${BASH_VERSION} installation failed." cat "$INSTALLATION_LOG" rm -f "$INSTALLATION_LOG" diff --git a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh index 43d34339ca..8862a9ff60 100755 --- a/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh +++ b/perfmetrics/scripts/presubmit_test/pr_perf_test/build.sh @@ -22,7 +22,7 @@ readonly EXECUTE_PACKAGE_BUILD_TEST_LABEL="execute-package-build-tests" readonly EXECUTE_CHECKPOINT_TEST_LABEL="execute-checkpoint-test" readonly BUCKET_LOCATION=us-west4 readonly GO_VERSION="1.24.5" -readonly REQUIRED_BASH_VERSION_FOR_E2E_SCRIPT="5.1" +readonly REQUIRED_BASH_VERSION_FOR_E2E_SCRIPT="5.3" curl https://api.github.com/repos/GoogleCloudPlatform/gcsfuse/pulls/$KOKORO_GITHUB_PULL_REQUEST_NUMBER >> pr.json perfTest=$(grep "$EXECUTE_PERF_TEST_LABEL" pr.json) From e806577a6d0328c7a0d00f81878ff3d0455f4468 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Sun, 17 Aug 2025 19:03:36 +0530 Subject: [PATCH 0686/1298] fix(buffered read): Intermittent nil pointer crash in downloadTask during Reads (#3694) * adding fix * update the test * remove unncessary changes * review comment --- internal/bufferedread/buffered_reader.go | 21 +++++++++++-------- internal/bufferedread/buffered_reader_test.go | 7 ++++++- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index df537131d0..ed1823f169 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -474,23 +474,26 @@ func (p *BufferedReader) Destroy() { p.mu.Lock() defer p.mu.Unlock() - if p.cancelFunc != nil { - p.cancelFunc() - p.cancelFunc = nil - } - for !p.blockQueue.IsEmpty() { bqe := p.blockQueue.Pop() bqe.cancel() - // We expect a context.Canceled error here, but we wait to ensure the - // block's worker goroutine has finished before releasing the block. - if _, err := bqe.block.AwaitReady(p.ctx); err != nil && err != context.Canceled { - logger.Warnf("Destroy: waiting for block on destroy: %v", err) + // We wait for the block's worker goroutine to finish. We expect its + // status to contain a context.Canceled error because we just called cancel. + status, err := bqe.block.AwaitReady(context.Background()) + if err != nil { + logger.Warnf("Destroy: AwaitReady for block failed: %v", err) + } else if status.Err != nil && !errors.Is(status.Err, context.Canceled) { + logger.Warnf("Destroy: waiting for block on destroy: %v", status.Err) } p.blockPool.Release(bqe.block) } + if p.cancelFunc != nil { + p.cancelFunc() + p.cancelFunc = nil + } + err := p.blockPool.ClearFreeBlockChannel(true) if err != nil { logger.Warnf("Destroy: clearing free block channel: %v", err) diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 735f0620d4..9b4c7d4c07 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -225,9 +225,14 @@ func (t *BufferedReaderTest) TestDestroySuccess() { require.NoError(t.T(), err, "NewBufferedReader should not return error") b, err := reader.blockPool.Get() require.NoError(t.T(), err, "Failed to get block from pool") + ctx, cancel := context.WithCancel(context.Background()) + go func() { + <-ctx.Done() + b.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadFailed, Err: context.Canceled}) + }() reader.blockQueue.Push(&blockQueueEntry{ block: b, - cancel: func() {}, + cancel: cancel, }) reader.Destroy() From 08fed30a11e164e4b0fd42a7d50133b22fc4d503 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Sun, 17 Aug 2025 22:57:27 +0530 Subject: [PATCH 0687/1298] feat(bufferedread): Add integration tests for fallback mechanism in buffered reader (#3693) * initial changes * Add tests for fallback mechanism --- internal/bufferedread/buffered_reader_test.go | 27 +++ .../buffered_read/fallback_test.go | 172 ++++++++++++++++++ .../buffered_read/helpers_test.go | 27 +++ .../buffered_read/setup_test.go | 4 + .../read_logs/buffered_read_log_parser.go | 12 +- 5 files changed, 240 insertions(+), 2 deletions(-) create mode 100644 tools/integration_tests/buffered_read/fallback_test.go diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 9b4c7d4c07..7b454dc134 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -1297,6 +1297,33 @@ func (t *BufferedReaderTest) TestReadAtSucceedsWhenBackgroundPrefetchFailsOnGCSE assert.ErrorContains(t.T(), err, "download failed") } +func (t *BufferedReaderTest) TestReadAtSubsequentReadAfterFallbackAlsoFallsBack() { + t.config.InitialPrefetchBlockCnt = 1 + reader, err := NewBufferedReader(t.object, t.bucket, t.config, t.globalMaxBlocksSem, t.workerPool, t.metricHandle) + reader.randomReadsThreshold = 1 // Set a low threshold for the test. + require.NoError(t.T(), err) + buf := make([]byte, 10) + t.bucket.On("Name").Return("test-bucket").Maybe() + // First random read (offset != 0): should succeed and count as 1st random seek. + // This will trigger a freshStart, downloading block 2 and prefetching block 3. + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 2*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 2*testPrefetchBlockSizeBytes), nil).Once() + t.bucket.On("NewReaderWithReadHandle", mock.Anything, mock.MatchedBy(func(r *gcs.ReadObjectRequest) bool { return r.Range.Start == 3*uint64(testPrefetchBlockSizeBytes) })).Return(createFakeReaderWithOffset(t.T(), int(testPrefetchBlockSizeBytes), 3*testPrefetchBlockSizeBytes), nil).Once() + _, err = reader.ReadAt(t.ctx, buf, 2*testPrefetchBlockSizeBytes) + require.NoError(t.T(), err, "Random read #1 should succeed") + assert.Equal(t.T(), int64(1), reader.randomSeekCount) + // Second random read: should exceed threshold and trigger fallback. + _, err = reader.ReadAt(t.ctx, buf, 5*testPrefetchBlockSizeBytes) + assert.ErrorIs(t.T(), err, gcsx.FallbackToAnotherReader, "Random read #2 should trigger fallback") + assert.Equal(t.T(), int64(2), reader.randomSeekCount) + + // Third read (at any offset): should also fall back immediately because the reader is already in a fallback state. + _, err = reader.ReadAt(t.ctx, buf, 0) + + assert.ErrorIs(t.T(), err, gcsx.FallbackToAnotherReader, "Subsequent read should also fallback") + assert.Equal(t.T(), int64(2), reader.randomSeekCount, "Random seek count should not change") + t.bucket.AssertExpectations(t.T()) +} + func (t *BufferedReaderTest) TestReadAtConcurrentReads() { const ( fileSize = 10 * util.MiB diff --git a/tools/integration_tests/buffered_read/fallback_test.go b/tools/integration_tests/buffered_read/fallback_test.go new file mode 100644 index 0000000000..38f6d21f36 --- /dev/null +++ b/tools/integration_tests/buffered_read/fallback_test.go @@ -0,0 +1,172 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package buffered_read + +import ( + "os" + "path" + "syscall" + "testing" + + "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" +) + +//////////////////////////////////////////////////////////////////////// +// Test Suite Boilerplate +//////////////////////////////////////////////////////////////////////// + +// fallbackSuiteBase provides shared setup and teardown logic for fallback-related test suites. +type fallbackSuiteBase struct { + suite.Suite + testFlags *gcsfuseTestFlags +} + +func (s *fallbackSuiteBase) SetupSuite() { + configFile := createConfigFile(s.testFlags) + flags := []string{"--config-file=" + configFile} + setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) +} + +func (s *fallbackSuiteBase) SetupTest() { + err := os.Truncate(setup.LogFile(), 0) + require.NoError(s.T(), err, "Failed to truncate log file") +} + +func (s *fallbackSuiteBase) TearDownSuite() { + setup.UnmountGCSFuse(setup.MntDir()) + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) +} + +// InsufficientPoolCreationSuite tests scenarios where the buffered reader is not +// created due to an insufficient global block pool at the time of file opening. +type InsufficientPoolCreationSuite struct { + fallbackSuiteBase +} + +// RandomReadFallbackSuite tests fallback scenarios related to random reads. +type RandomReadFallbackSuite struct { + fallbackSuiteBase +} + +//////////////////////////////////////////////////////////////////////// +// Test Cases +//////////////////////////////////////////////////////////////////////// + +// TestNewBufferedReader_InsufficientGlobalPool_NoReaderAdded tests that when +// there are not enough blocks in the global pool to satisfy `min-blocks-per-handle`, +// the BufferedReader is not created, and reads fall back to the next reader +// without any buffered reading. +func (s *InsufficientPoolCreationSuite) TestNewBufferedReader_InsufficientGlobalPool_NoReaderAdded() { + fileSize := 3 * s.testFlags.blockSizeMB * util.MiB + chunkSize := int64(1 * util.MiB) + testDir := setup.SetupTestDirectory(testDirName) + fileName := setupFileInTestDir(ctx, storageClient, testDir, fileSize, s.T()) + filePath := path.Join(testDir, fileName) + + // Open and read the file. Since BufferedReader creation should fail, the read + // will be served by the GCSReader. + content, err := operations.ReadChunkFromFile(filePath, chunkSize, 0, os.O_RDONLY|syscall.O_DIRECT) + + require.NoError(s.T(), err, "Failed to read file") + client.ValidateObjectChunkFromGCS(ctx, storageClient, path.Base(testDir), fileName, 0, chunkSize, string(content), s.T()) + warningMsg := "Failed to create bufferedReader" + found := operations.CheckLogFileForMessage(s.T(), warningMsg, setup.LogFile()) + assert.True(s.T(), found, "Expected warning message not found in log file") + logEntries := parseBufferedReadLogs(s.T()) + assert.Empty(s.T(), logEntries, "Expected no buffered read log entries") +} + +func (s *RandomReadFallbackSuite) TestRandomRead_LargeFile_Fallback() { + const randomReadsThreshold = 3 + blockSizeInBytes := s.testFlags.blockSizeMB * util.MiB + // The distant block to read is just outside the initial prefetch window. + // Initial prefetch window size = (1 + initial_prefetch_blocks) blocks. + // So, we read the block at index (initial_prefetch_blocks + 1). + distantBlockIndex := s.testFlags.startBlocksPerHandle + 1 + fileSize := blockSizeInBytes * (distantBlockIndex + 1) + chunkSize := int64(1 * util.KiB) + testDir := setup.SetupTestDirectory(testDirName) + fileName := setupFileInTestDir(ctx, storageClient, testDir, fileSize, s.T()) + filePath := path.Join(testDir, fileName) + f, err := os.OpenFile(filePath, os.O_RDONLY|syscall.O_DIRECT, 0) + require.NoError(s.T(), err) + defer operations.CloseFileShouldNotThrowError(s.T(), f) + distantOffset := distantBlockIndex * blockSizeInBytes + + induceRandomReadFallback(s.T(), f, path.Base(testDir), fileName, chunkSize, distantOffset, randomReadsThreshold) + + bufferedReadLogEntry := parseAndValidateSingleBufferedReadLog(s.T()) + expected := &Expected{BucketName: setup.TestBucket(), ObjectName: path.Join(path.Base(testDir), fileName)} + validate(expected, bufferedReadLogEntry, true, s.T()) + assert.Equal(s.T(), int64(randomReadsThreshold+1), bufferedReadLogEntry.RandomSeekCount, "RandomSeekCount should be one greater than the threshold.") +} + +func (s *RandomReadFallbackSuite) TestRandomRead_SmallFile_NoFallback() { + blockSizeInBytes := s.testFlags.blockSizeMB * util.MiB + // File size is small, less than one block. + fileSize := blockSizeInBytes / 2 + chunkSize := int64(1 * util.KiB) + testDir := setup.SetupTestDirectory(testDirName) + fileName := setupFileInTestDir(ctx, storageClient, testDir, fileSize, s.T()) + filePath := path.Join(testDir, fileName) + f, err := os.OpenFile(filePath, os.O_RDONLY|syscall.O_DIRECT, 0) + require.NoError(s.T(), err) + defer operations.CloseFileShouldNotThrowError(s.T(), f) + // The first read (at offset 0) is sequential. + readAndValidateChunk(f, path.Base(testDir), fileName, 0, chunkSize, s.T()) + + // The second read should be served from the prefetched block and not be a random seek. + readAndValidateChunk(f, path.Base(testDir), fileName, fileSize/2, chunkSize, s.T()) + + bufferedReadLogEntry := parseAndValidateSingleBufferedReadLog(s.T()) + expected := &Expected{BucketName: setup.TestBucket(), ObjectName: path.Join(path.Base(testDir), fileName)} + validate(expected, bufferedReadLogEntry, false, s.T()) + assert.Equal(s.T(), int64(0), bufferedReadLogEntry.RandomSeekCount, "RandomSeekCount should be 0 for small file reads.") +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestFallbackSuites(t *testing.T) { + // Run the suite for insufficient pool at creation time. + insufficientPoolFlags := gcsfuseTestFlags{ + enableBufferedRead: true, + blockSizeMB: 8, + minBlocksPerHandle: 2, + globalMaxBlocks: 1, // Less than min-blocks-per-handle + maxBlocksPerHandle: 10, + startBlocksPerHandle: 2, + clientProtocol: clientProtocolHTTP1, + } + suite.Run(t, &InsufficientPoolCreationSuite{fallbackSuiteBase{testFlags: &insufficientPoolFlags}}) + + // Run the suite for random read fallback scenarios. + randomReadFlags := gcsfuseTestFlags{ + enableBufferedRead: true, + blockSizeMB: 8, + maxBlocksPerHandle: 20, + startBlocksPerHandle: 2, + minBlocksPerHandle: 2, + clientProtocol: clientProtocolHTTP1, + } + suite.Run(t, &RandomReadFallbackSuite{fallbackSuiteBase{testFlags: &randomReadFlags}}) +} diff --git a/tools/integration_tests/buffered_read/helpers_test.go b/tools/integration_tests/buffered_read/helpers_test.go index a6c68569d9..f441f8f14e 100644 --- a/tools/integration_tests/buffered_read/helpers_test.go +++ b/tools/integration_tests/buffered_read/helpers_test.go @@ -108,3 +108,30 @@ func parseAndValidateSingleBufferedReadLog(t *testing.T) *read_logs.BufferedRead } return nil // Unreachable. } + +func readAndValidateChunk(f *os.File, testDir, fileName string, offset, chunkSize int64, t *testing.T) { + t.Helper() + readBuffer := make([]byte, chunkSize) + + _, err := f.ReadAt(readBuffer, offset) + + require.NoError(t, err, "ReadAt failed at offset %d", offset) + client.ValidateObjectChunkFromGCS(ctx, storageClient, testDir, fileName, offset, chunkSize, string(readBuffer), t) +} + +// induceRandomReadFallback performs a sequence of reads designed to trigger the +// random read fallback mechanism in the buffered reader. It starts with a +// sequential read and then alternates between a distant offset and offset 0. +func induceRandomReadFallback(t *testing.T, f *os.File, testDir, fileName string, chunkSize, distantOffset int64, randomReadsThreshold int) { + t.Helper() + // We need 1 sequential read + randomReadsThreshold successful random reads before + // the final one that triggers fallback. + offset := int64(0) + for i := 0; i < randomReadsThreshold+1; i++ { + readAndValidateChunk(f, testDir, fileName, offset, chunkSize, t) + offset = distantOffset ^ offset + } + + // The next read should trigger the fallback but still succeed from the user's perspective. + readAndValidateChunk(f, testDir, fileName, offset, chunkSize, t) +} diff --git a/tools/integration_tests/buffered_read/setup_test.go b/tools/integration_tests/buffered_read/setup_test.go index d1dacfac16..b3583fbe67 100644 --- a/tools/integration_tests/buffered_read/setup_test.go +++ b/tools/integration_tests/buffered_read/setup_test.go @@ -48,6 +48,7 @@ type gcsfuseTestFlags struct { maxBlocksPerHandle int64 startBlocksPerHandle int64 minBlocksPerHandle int64 + globalMaxBlocks int64 } //////////////////////////////////////////////////////////////////////// @@ -75,6 +76,9 @@ func createConfigFile(flags *gcsfuseTestFlags) string { if flags.minBlocksPerHandle != 0 { readConfig["min-blocks-per-handle"] = flags.minBlocksPerHandle } + if flags.globalMaxBlocks != 0 { + readConfig["global-max-blocks"] = flags.globalMaxBlocks + } if len(readConfig) > 0 { mountConfig["read"] = readConfig } diff --git a/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go b/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go index b014653563..f58b908ee6 100644 --- a/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go +++ b/tools/integration_tests/util/log_parser/json_parser/read_logs/buffered_read_log_parser.go @@ -77,7 +77,15 @@ func ParseBufferedReadLogsFromLogReader(reader io.Reader) (map[int64]*BufferedRe } } - return bufferedReadLogsMap, nil + // Filter out entries that have no chunks, as they represent file handles + // that were opened but never read from using the buffered reader. + filteredLogsMap := make(map[int64]*BufferedReadLogEntry) + for handle, entry := range bufferedReadLogsMap { + if len(entry.Chunks) > 0 { + filteredLogsMap[handle] = entry + } + } + return filteredLogsMap, nil } // filterAndParseLogLineForBufferedRead filters and parses a log line for buffered read logs. @@ -137,7 +145,7 @@ func parseFallbackLogFromHandle( handleID, err := parseToInt64(matches[1]) if err != nil { - return fmt.Errorf("invalid handle ID in fallback log: %v", err) + return fmt.Errorf("invalid handle ID in fallback log: %w", err) } logEntry, ok := bufferedReadLogsMap[handleID] From 29e0bb9b31a8a0cf2b45df5ba63eeb6ac3375f1d Mon Sep 17 00:00:00 2001 From: Charith Chowdary Date: Mon, 18 Aug 2025 09:12:09 +0530 Subject: [PATCH 0688/1298] Add note on gcs-fuse-gke-sidecar service on troubleshooting Add Troubleshooting notes on gcs-fuse-gke-sidecar service --- docs/troubleshooting.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index f19cae8786..e8ee56d151 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -217,3 +217,6 @@ If you observe that GCSFuse is still utilizing staged writes despite streaming w - Truncating a file downwards while writes are in progress (this action also finalizes the object and subsequent writes will fall back). An informational log message will be emitted by GCSFuse whenever a fallback to staged writes occurs, providing details on the reason. + +### Issues related to the gcs-fuse-csi-driver +The `gcs-fuse-csi-driver` serves as the orchestration layer that manages the gke-gcsfuse-sidecar which hosts GCSFuse in Google Kubernetes Engine (GKE) environments. This driver is maintained in a separate repository. Consequently, any issues regarding the gcs-fuse-csi-driver should be reported in its dedicated GitHub repository: https://github.com/GoogleCloudPlatform/gcs-fuse-csi-driver From 58b37f39775a125723aab456d7cac19926c7c6f4 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Mon, 18 Aug 2025 19:21:41 +0530 Subject: [PATCH 0689/1298] test(Buffered Read): Add e2e test for Header, Footer and Body Read (#3692) * add e2e test for parquet file * update mod file * fix linux tests * rebase * rebase * review comment * lint fix * remove parquet file fomat * review comments --- .../buffered_read/sequential_read_test.go | 55 +++++++++++++++++++ .../util/operations/file_operations.go | 1 - 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/tools/integration_tests/buffered_read/sequential_read_test.go b/tools/integration_tests/buffered_read/sequential_read_test.go index 85bf04c6c6..8ba795d824 100644 --- a/tools/integration_tests/buffered_read/sequential_read_test.go +++ b/tools/integration_tests/buffered_read/sequential_read_test.go @@ -17,9 +17,13 @@ package buffered_read import ( "fmt" "os" + "path" + "syscall" "testing" + "time" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -94,6 +98,57 @@ func (s *SequentialReadSuite) TestSequentialRead() { } } +// TestReadHeaderFooterAndBody verifies that a single file handle can correctly handle +// a mix of random reads (header and footer) followed by a large sequential read. +// The key validation is that all these operations should be served from a single +// buffered read log entry, indicating efficient handling. +func (s *SequentialReadSuite) TestReadHeaderFooterAndBody() { + // Constants for block and file sizes + blockSizeInBytes := s.testFlags.blockSizeMB * util.MiB + // Header and footer sizes (10KB each) + headerSize := 10 * util.KiB + footerSize := 10 * util.KiB + s.T().Run("Read header, footer, then body from one file handle", func(t *testing.T) { + err := os.Truncate(setup.LogFile(), 0) + require.NoError(t, err, "Failed to truncate log file") + testDir := setup.SetupTestDirectory(testDirName) + fileSize := blockSizeInBytes * 2 + // Create a file of a given size in the test directory. + fileName := setupFileInTestDir(ctx, storageClient, testDir, fileSize, t) + filePath := path.Join(testDir, fileName) + // Get the actual file size. + fi, err := os.Stat(filePath) + require.NoError(t, err) + actualFileSize := fi.Size() + // The size of the main content to read sequentially + bodySize := actualFileSize - int64(headerSize) - int64(footerSize) + f, err := os.OpenFile(filePath, os.O_RDONLY|syscall.O_DIRECT, 0) + require.NoError(t, err) + expected := &Expected{ + StartTimeStampSeconds: time.Now().Unix(), + BucketName: setup.TestBucket(), + ObjectName: path.Join(path.Base(testDir), fileName), + } + if setup.DynamicBucketMounted() != "" { + expected.BucketName = setup.DynamicBucketMounted() + } + + // (a) Read first 10KB (header) + readAndValidateChunk(f, path.Base(testDir), fileName, 0, int64(headerSize), t) + // (b) Read last 10KB (footer) + readAndValidateChunk(f, path.Base(testDir), fileName, actualFileSize-int64(footerSize), int64(footerSize), t) + // (c) Read the remaining content sequentially + readAndValidateChunk(f, path.Base(testDir), fileName, int64(headerSize), bodySize, t) + + // Close the file handle to trigger log generation. + operations.CloseFileShouldNotThrowError(t, f) + expected.EndTimeStampSeconds = time.Now().Unix() + // Since all reads were on the same handle, there should be one log entry. + bufferedReadLogEntry := parseAndValidateSingleBufferedReadLog(t) + validate(expected, bufferedReadLogEntry, false, t) + }) +} + //////////////////////////////////////////////////////////////////////// // Test Function (Runs once before all tests) //////////////////////////////////////////////////////////////////////// diff --git a/tools/integration_tests/util/operations/file_operations.go b/tools/integration_tests/util/operations/file_operations.go index 09526b97fd..b4c8085de7 100644 --- a/tools/integration_tests/util/operations/file_operations.go +++ b/tools/integration_tests/util/operations/file_operations.go @@ -276,7 +276,6 @@ func WriteFilesSequentially(t *testing.T, filePaths []string, fileSize int64, ch assert.NoError(t, err) offset = offset + chunkSize } - return } func ReadChunkFromFile(filePath string, chunkSize int64, offset int64, flag int) (chunk []byte, err error) { From 0774b333bdce730ef44df28544bcd337321e3012 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 18 Aug 2025 19:35:39 +0530 Subject: [PATCH 0690/1298] feat(bufferedread): Limit the total workers in buffered reader by readGlobalMaxBlocks (#3697) * cap total workers by readGlobalMaxBlocks * Add unit tests for different values of numCPU * Update comment --- internal/fs/fs.go | 2 +- internal/fs/handle/file_test.go | 4 +- internal/workerpool/static_worker_pool.go | 19 ++++++- .../workerpool/static_worker_pool_test.go | 56 +++++++++++++++++-- 4 files changed, 71 insertions(+), 10 deletions(-) diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 023f21d201..c2735c3dc1 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -214,7 +214,7 @@ func NewFileSystem(ctx context.Context, serverCfg *ServerConfig) (fuseutil.FileS if serverCfg.NewConfig.Read.EnableBufferedRead { var err error - fs.bufferedReadWorkerPool, err = workerpool.NewStaticWorkerPoolForCurrentCPU() + fs.bufferedReadWorkerPool, err = workerpool.NewStaticWorkerPoolForCurrentCPU(serverCfg.NewConfig.Read.GlobalMaxBlocks) if err != nil { return nil, fmt.Errorf("failed to create worker pool for buffered read: %w", err) } diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 5f4b52b122..52d3ce444f 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -435,7 +435,7 @@ func (t *fileTest) Test_ReadWithReadManager_FullReadSuccessWithBufferedRead() { StartBlocksPerHandle: 2, }, } - workerPool, err := workerpool.NewStaticWorkerPoolForCurrentCPU() + workerPool, err := workerpool.NewStaticWorkerPoolForCurrentCPU(20) require.NoError(t.T(), err) defer workerPool.Stop() globalSemaphore := semaphore.NewWeighted(20) // Sufficient blocks for the test @@ -472,7 +472,7 @@ func (t *fileTest) Test_ReadWithReadManager_ConcurrentReadsWithBufferedReader() BlockSizeMb: 1, }, } - workerPool, err := workerpool.NewStaticWorkerPoolForCurrentCPU() + workerPool, err := workerpool.NewStaticWorkerPoolForCurrentCPU(20) require.NoError(t.T(), err) defer workerPool.Stop() globalSemaphore := semaphore.NewWeighted(20) diff --git a/internal/workerpool/static_worker_pool.go b/internal/workerpool/static_worker_pool.go index deb7af8c25..abb085b67d 100644 --- a/internal/workerpool/static_worker_pool.go +++ b/internal/workerpool/static_worker_pool.go @@ -61,12 +61,25 @@ func NewStaticWorkerPool(priorityWorker uint32, normalWorker uint32) (*staticWor } // NewStaticWorkerPoolForCurrentCPU creates and starts a new worker pool. The -// number of workers is determined based on the number of available CPUs. -func NewStaticWorkerPoolForCurrentCPU() (WorkerPool, error) { +// number of workers is determined based on the number of available CPUs and +// the provided readGlobalMaxBlocks. +func NewStaticWorkerPoolForCurrentCPU(readGlobalMaxBlocks int64) (WorkerPool, error) { + return newStaticWorkerPoolForCurrentCPU(readGlobalMaxBlocks, runtime.NumCPU) +} + +// newStaticWorkerPoolForCurrentCPU is an unexported helper for testing. +func newStaticWorkerPoolForCurrentCPU(readGlobalMaxBlocks int64, numCPU func() int) (WorkerPool, error) { // It's a general heuristic to use 2-3 times the number of CPUs for I/O-bound tasks. // We use 3x here as a balance between parallelism and resource consumption. const workersPerCPU = 3 - totalWorkers := workersPerCPU * runtime.NumCPU() + totalWorkers := workersPerCPU * numCPU() + + // Since the number of concurrent download tasks is limited by readGlobalMaxBlocks, + // creating more workers beyond this limit offers no performance gain and wastes + // resources. Hence, we cap total workers to ceil(1.1 * readGlobalMaxBlocks). + if cappedWorkers := (11*readGlobalMaxBlocks + 9) / 10; int64(totalWorkers) > cappedWorkers { + totalWorkers = int(cappedWorkers) + } // 10% of total workers for priority, rounded up. priorityWorkers := (totalWorkers + 9) / 10 diff --git a/internal/workerpool/static_worker_pool_test.go b/internal/workerpool/static_worker_pool_test.go index 6ac1ae3a42..af8fbaba7d 100644 --- a/internal/workerpool/static_worker_pool_test.go +++ b/internal/workerpool/static_worker_pool_test.go @@ -157,20 +157,68 @@ func TestStaticWorkerPool_Stop(t *testing.T) { } func TestNewStaticWorkerPoolForCurrentCPU(t *testing.T) { - pool, err := NewStaticWorkerPoolForCurrentCPU() + readGlobalMaxBlocks := int64(100) + + pool, err := NewStaticWorkerPoolForCurrentCPU(readGlobalMaxBlocks) + require.NoError(t, err) require.NotNil(t, pool) defer pool.Stop() staticPool, ok := pool.(*staticWorkerPool) require.True(t, ok, "The returned pool should be of type *staticWorkerPool") + // Re-calculate the expected number of workers based on the real CPU count + // to verify the logic. totalWorkers := 3 * runtime.NumCPU() - expectedPriorityWorkers := (3*runtime.NumCPU() + 9) / 10 + if cappedWorkers := (11*readGlobalMaxBlocks + 9) / 10; int64(totalWorkers) > cappedWorkers { + totalWorkers = int(cappedWorkers) + } + expectedPriorityWorkers := (totalWorkers + 9) / 10 expectedNormalWorkers := totalWorkers - expectedPriorityWorkers dt := &dummyTask{} - pool.Schedule(true, dt) - assert.Equal(t, uint32(expectedPriorityWorkers), staticPool.priorityWorker) assert.Equal(t, uint32(expectedNormalWorkers), staticPool.normalWorker) + // Verify that the pool is functional. assert.Eventually(t, func() bool { return dt.executed }, 100*time.Millisecond, time.Millisecond, "Task was not executed in time.") } + +func Test_newStaticWorkerPoolForCurrentCPU(t *testing.T) { + testCases := []struct { + name string + readGlobalMaxBlocks int64 + mockNumCPU func() int + expectedPriorityWorkers uint32 + expectedNormalWorkers uint32 + }{ + { + name: "low CPU count, workers not capped", + readGlobalMaxBlocks: 100, + mockNumCPU: func() int { return 2 }, + // totalWorkers = 3*2=6. priority=ceil(0.1*6)=1, normal=5. + expectedPriorityWorkers: 1, + expectedNormalWorkers: 5, + }, + { + name: "high CPU count, workers capped by max blocks", + readGlobalMaxBlocks: 50, + mockNumCPU: func() int { return 100 }, + // totalWorkers = 3*100=300, capped to ceil(1.1*50)=55. priority=ceil(0.1*55)=6, normal=49. + expectedPriorityWorkers: 6, + expectedNormalWorkers: 49, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + pool, err := newStaticWorkerPoolForCurrentCPU(tc.readGlobalMaxBlocks, tc.mockNumCPU) + + require.NoError(t, err) + require.NotNil(t, pool) + defer pool.Stop() + staticPool, ok := pool.(*staticWorkerPool) + require.True(t, ok, "The returned pool should be of type *staticWorkerPool") + assert.Equal(t, tc.expectedPriorityWorkers, staticPool.priorityWorker) + assert.Equal(t, tc.expectedNormalWorkers, staticPool.normalWorker) + }) + } +} From dac0f36706ec0017914521621c546c79e30c9e3e Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Mon, 18 Aug 2025 22:43:59 +0530 Subject: [PATCH 0691/1298] migrate implicit dir package (#3701) --- .../explicit_dir/explicit_dir_test.go | 18 ++----- .../implicit_dir/implicit_dir_test.go | 51 ++++++++++++++----- tools/integration_tests/test_config.yaml | 34 ++++++++++--- .../implicit_and_explicit_dir_setup.go | 31 +++-------- tools/integration_tests/util/setup/setup.go | 49 ++++++++++++++++++ .../util/test_suite/config.go | 9 +++- 6 files changed, 132 insertions(+), 60 deletions(-) diff --git a/tools/integration_tests/explicit_dir/explicit_dir_test.go b/tools/integration_tests/explicit_dir/explicit_dir_test.go index f1c0271d35..450a7052db 100644 --- a/tools/integration_tests/explicit_dir/explicit_dir_test.go +++ b/tools/integration_tests/explicit_dir/explicit_dir_test.go @@ -19,7 +19,6 @@ import ( "context" "log" "os" - "strings" "testing" "cloud.google.com/go/storage" @@ -68,7 +67,6 @@ func TestMain(m *testing.M) { // Populate the config manually. cfg.ExplicitDir = make([]test_suite.TestConfig, 1) cfg.ExplicitDir[0].TestBucket = setup.TestBucket() - cfg.ExplicitDir[0].Flags = []string{"--implicit-dirs=false", "--implicit-dirs=false --client-protocol=grpc"} cfg.ExplicitDir[0].MountedDirectory = setup.MountedDirectory() } @@ -82,20 +80,14 @@ func TestMain(m *testing.M) { } }() - // These tests will not run on HNS buckets because the "--implicit-dirs=false" flag does not function similarly to how it does on FLAT buckets. - // Note that HNS buckets do not have the concept of implicit directories. - if setup.ResolveIsHierarchicalBucket(testEnv.ctx, cfg.ExplicitDir[0].TestBucket, testEnv.storageClient) { - log.Println("These tests will not run on HNS buckets.") - return - } - // 4. Build the flag sets dynamically from the config. - var flags [][]string - for _, flagString := range cfg.ExplicitDir[0].Flags { - flags = append(flags, strings.Fields(flagString)) + bucketType, err := setup.BucketType(testEnv.ctx, cfg.ExplicitDir[0].TestBucket) + if err != nil { + log.Fatalf("BucketType failed: %v", err) } + flags := setup.BuildFlagSets(cfg.ExplicitDir[0], bucketType) // 5. Run tests with the dynamically generated flags. - successCode := implicit_and_explicit_dir_setup.RunTestsForExplicitDir(&cfg.ExplicitDir[0], flags, m) + successCode := implicit_and_explicit_dir_setup.RunTestsForExplicitAndImplicitDir(&cfg.ExplicitDir[0], flags, m) os.Exit(successCode) } diff --git a/tools/integration_tests/implicit_dir/implicit_dir_test.go b/tools/integration_tests/implicit_dir/implicit_dir_test.go index 7b303f88f9..675a6941ac 100644 --- a/tools/integration_tests/implicit_dir/implicit_dir_test.go +++ b/tools/integration_tests/implicit_dir/implicit_dir_test.go @@ -26,6 +26,8 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" + "gopkg.in/yaml.v3" ) const ExplicitDirInImplicitDir = "explicitDirInImplicitDir" @@ -58,10 +60,37 @@ func setupTestDir(dirName string) string { return dirPath } + +// Config holds all test configurations parsed from the YAML file. +type Config struct { + ImplicitDir []test_suite.TestConfig `yaml:"implicit_dir"` +} + func TestMain(m *testing.M) { setup.ParseSetUpFlags() - // Create storage client before running tests. + // 1. Load and parse the common configuration. + var cfg Config + if setup.ConfigFile() != "" { + configData, err := os.ReadFile(setup.ConfigFile()) + if err != nil { + log.Fatalf("could not read test_config.yaml: %v", err) + } + expandedYaml := os.ExpandEnv(string(configData)) + if err := yaml.Unmarshal([]byte(expandedYaml), &cfg); err != nil { + log.Fatalf("Failed to parse config YAML: %v", err) + } + } + if len(cfg.ImplicitDir) == 0 { + log.Println("No configuration found for explicit_dir tests in config. Using flags instead.") + // Populate the config manually. + cfg.ImplicitDir = make([]test_suite.TestConfig, 1) + cfg.ImplicitDir[0].TestBucket = setup.TestBucket() + cfg.ImplicitDir[0].MountedDirectory = setup.MountedDirectory() + } + + // 2. Create storage client before running tests. + setup.SetBucketFromConfigFile(cfg.ImplicitDir[0].TestBucket) testEnv.ctx = context.Background() closeStorageClient := client.CreateStorageClientWithCancel(&testEnv.ctx, &testEnv.storageClient) defer func() { @@ -71,21 +100,15 @@ func TestMain(m *testing.M) { } }() - flagsSet := [][]string{{"--implicit-dirs"}} - - // No need to run enable-hns and client-protocol GRPC configuration for ZB, - // as those are both by default enabled for ZB. - if !setup.IsZonalBucketRun() { - if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(testEnv.ctx, testEnv.storageClient); err == nil { - flagsSet = append(flagsSet, hnsFlagSet) - } - - if !testing.Short() { - flagsSet = append(flagsSet, []string{"--client-protocol=grpc", "--implicit-dirs"}) - } + // 4. Build the flag sets dynamically from the config. + bucketType, err := setup.BucketType(testEnv.ctx, cfg.ImplicitDir[0].TestBucket) + if err != nil { + log.Fatalf("BucketType failed: %v", err) } + flags := setup.BuildFlagSets(cfg.ImplicitDir[0], bucketType) - successCode := implicit_and_explicit_dir_setup.RunTestsForImplicitDirAndExplicitDir(flagsSet, m) + // 5. Run tests with the dynamically generated flags. + successCode := implicit_and_explicit_dir_setup.RunTestsForExplicitAndImplicitDir(&cfg.ImplicitDir[0], flags, m) setup.SaveLogFileInCaseOfFailure(successCode) // Clean up test directory created. diff --git a/tools/integration_tests/test_config.yaml b/tools/integration_tests/test_config.yaml index 21f6c71e39..c84354afbe 100644 --- a/tools/integration_tests/test_config.yaml +++ b/tools/integration_tests/test_config.yaml @@ -16,11 +16,31 @@ explicit_dir: - mounted_directory: "${MOUNTED_DIR}" # To be passed by GKE after mounting test_bucket: "${BUCKET_NAME}" # To be passed by both gcsfuse and gke tests log_file: # Optional flag required by some tests where log parsing is done to validate end behavior - flags: - - "--implicit-dirs=false" - - "--implicit-dirs=false --client-protocol=grpc" - compatible: # Bucket type to run these tests with - - flat: true - - hns: false - - zonal: false run_on_gke: false + configs: + - flags: + - "--implicit-dirs=false" + - "--implicit-dirs=false --client-protocol=grpc" + compatible: # Bucket type to run these tests with + flat: true + hns: false + zonal: false + +implicit_dir: + - mounted_directory: "${MOUNTED_DIR}" + test_bucket: "${BUCKET_NAME}" + log_file: # Optional + run_on_gke: false + configs: + - flags: + - "--implicit-dirs" + compatible: + flat: true + hns: true + zonal: true + - flags: + - "--implicit-dirs --client-protocol=grpc" + compatible: + flat: true + hns: true + zonal: false diff --git a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go index 86da34f457..73aecc51ca 100644 --- a/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go +++ b/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup/implicit_and_explicit_dir_setup.go @@ -22,7 +22,7 @@ import ( "strings" "testing" - storage "cloud.google.com/go/storage" + "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/persistent_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" @@ -47,12 +47,17 @@ const SecondFileInExplicitDirectory = "fileInExplicitDir2" const FileInImplicitDirectory = "fileInImplicitDir1" const FileInImplicitSubDirectory = "fileInImplicitDir2" -func RunTestsForExplicitDir(config *test_suite.TestConfig, flags [][]string, m *testing.M) int { +func RunTestsForExplicitAndImplicitDir(config *test_suite.TestConfig, flags [][]string, m *testing.M) int { if config == nil { log.Println("config is nil") return 1 } + if len(flags) == 0 { + log.Println("flags empty: no tests to run") + return 0 + } + if config.MountedDirectory != "" && config.TestBucket != "" { successCode := setup.RunTestsForMountedDirectory(config.MountedDirectory, m) return successCode @@ -73,28 +78,6 @@ func RunTestsForExplicitDir(config *test_suite.TestConfig, flags [][]string, m * return successCode } -func RunTestsForImplicitDirAndExplicitDir(flags [][]string, m *testing.M) int { - setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() - - if setup.TestBucket() == "" && setup.MountedDirectory() != "" { - log.Print("Please pass the name of bucket mounted at mountedDirectory to --testBucket flag.") - os.Exit(1) - } - - // Run tests for mountedDirectory only if --mountedDirectory and --testbucket flag is set. - setup.RunTestsForMountedDirectoryFlag(m) - - // Run tests for testBucket only if --testbucket flag is set. - setup.SetUpTestDirForTestBucketFlag() - - successCode := static_mounting.RunTests(flags, m) - - if successCode == 0 { - successCode = persistent_mounting.RunTests(flags, m) - } - return successCode -} - func RemoveAndCheckIfDirIsDeleted(dirPath string, dirName string, t *testing.T) { err := os.RemoveAll(dirPath) require.Nil(t, err) diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index 823fa3ba21..f70d78902e 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -32,7 +32,9 @@ import ( "time" "cloud.google.com/go/storage" + "cloud.google.com/go/storage/experimental" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" "github.com/googlecloudplatform/gcsfuse/v3/tools/util" "go.opentelemetry.io/contrib/detectors/gcp" "go.opentelemetry.io/otel/sdk/resource" @@ -551,6 +553,53 @@ func ResolveIsHierarchicalBucket(ctx context.Context, testBucket string, storage return false } +const FlatBucket = "flat" +const HNSBucket = "hns" +const ZonalBucket = "zonal" + +func BucketType(ctx context.Context, testBucket string) (bucketType string, err error) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + storageClient, err := storage.NewGRPCClient(ctx, experimental.WithGRPCBidiReads()) + if err != nil { + return "", fmt.Errorf("failed to create storage client: %w", err) + } + attrs, err := storageClient.Bucket(testBucket).Attrs(ctx) + if err != nil { + return "", fmt.Errorf("failed to get bucket attributes: %w", err) + } + if attrs.LocationType == "zone" { + return ZonalBucket, nil + } + if attrs.HierarchicalNamespace != nil && attrs.HierarchicalNamespace.Enabled { + return HNSBucket, nil + } + return FlatBucket, nil +} + +// BuildFlagSets dynamically builds a list of flag sets based on bucket compatibility. +// bucketType should be "flat", "hns", or "zonal". +func BuildFlagSets(cfg test_suite.TestConfig, bucketType string) [][]string { + var dynamicFlags [][]string + + // 1. Iterate through each defined test configuration (e.g., HTTP, gRPC). + for _, testCase := range cfg.Configs { + // 2. Check if the current test case is compatible with the bucket type. + // This is a safe and concise way to check the map. + if isCompatible, ok := testCase.Compatible[bucketType]; ok && isCompatible { + // 3. If compatible, process its flags and add them to the result. + for _, flagString := range testCase.Flags { + dynamicFlags = append(dynamicFlags, strings.Fields(flagString)) + } + } + } + return dynamicFlags +} + +func SetBucketFromConfigFile(testBucketFromConfigFile string) { + testBucket = &testBucketFromConfigFile +} + // Explicitly set the enable-hns config flag to true when running tests on the HNS bucket. func AddHNSFlagForHierarchicalBucket(ctx context.Context, storageClient *storage.Client) ([]string, error) { if !IsHierarchicalBucket(ctx, storageClient) { diff --git a/tools/integration_tests/util/test_suite/config.go b/tools/integration_tests/util/test_suite/config.go index dd85f7f72c..17f9e256ac 100644 --- a/tools/integration_tests/util/test_suite/config.go +++ b/tools/integration_tests/util/test_suite/config.go @@ -26,7 +26,12 @@ type TestConfig struct { MountedDirectory string `yaml:"mounted_directory"` TestBucket string `yaml:"test_bucket"` LogFile string `yaml:"log_file,omitempty"` - Flags []string `yaml:"flags"` - Compatible []BucketType `yaml:"compatible"` RunOnGKE bool `yaml:"run_on_gke"` + Configs []ConfigItem `yaml:"configs"` +} + +// ConfigItem defines the variable parts of each test run. +type ConfigItem struct { + Flags []string `yaml:"flags"` + Compatible map[string]bool `yaml:"compatible"` } From a93ad9f1a3a079e592e9904ef3699865085c3d41 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:35:41 +0530 Subject: [PATCH 0692/1298] fix implicit and explicit dir tests (#3706) --- tools/integration_tests/explicit_dir/explicit_dir_test.go | 3 +++ tools/integration_tests/implicit_dir/implicit_dir_test.go | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/integration_tests/explicit_dir/explicit_dir_test.go b/tools/integration_tests/explicit_dir/explicit_dir_test.go index 450a7052db..bc6b0c7a64 100644 --- a/tools/integration_tests/explicit_dir/explicit_dir_test.go +++ b/tools/integration_tests/explicit_dir/explicit_dir_test.go @@ -68,6 +68,9 @@ func TestMain(m *testing.M) { cfg.ExplicitDir = make([]test_suite.TestConfig, 1) cfg.ExplicitDir[0].TestBucket = setup.TestBucket() cfg.ExplicitDir[0].MountedDirectory = setup.MountedDirectory() + cfg.ExplicitDir[0].Configs = make([]test_suite.ConfigItem, 1) + cfg.ExplicitDir[0].Configs[0].Flags = []string{"--implicit-dirs=false", "--implicit-dirs=false --client-protocol=grpc"} + cfg.ExplicitDir[0].Configs[0].Compatible = map[string]bool{"flat": true, "hns": false, "zonal": false} } // 2. Create storage client before running tests. diff --git a/tools/integration_tests/implicit_dir/implicit_dir_test.go b/tools/integration_tests/implicit_dir/implicit_dir_test.go index 675a6941ac..aa36def2b0 100644 --- a/tools/integration_tests/implicit_dir/implicit_dir_test.go +++ b/tools/integration_tests/implicit_dir/implicit_dir_test.go @@ -82,11 +82,16 @@ func TestMain(m *testing.M) { } } if len(cfg.ImplicitDir) == 0 { - log.Println("No configuration found for explicit_dir tests in config. Using flags instead.") + log.Println("No configuration found for implicit_dir tests in config. Using flags instead.") // Populate the config manually. cfg.ImplicitDir = make([]test_suite.TestConfig, 1) cfg.ImplicitDir[0].TestBucket = setup.TestBucket() cfg.ImplicitDir[0].MountedDirectory = setup.MountedDirectory() + cfg.ImplicitDir[0].Configs = make([]test_suite.ConfigItem, 2) + cfg.ImplicitDir[0].Configs[0].Flags = []string{"--implicit-dirs"} + cfg.ImplicitDir[0].Configs[0].Compatible = map[string]bool{"flat": true, "hns": true, "zonal": true} + cfg.ImplicitDir[0].Configs[1].Flags = []string{"--implicit-dirs --client-protocol=grpc"} + cfg.ImplicitDir[0].Configs[1].Compatible = map[string]bool{"flat": true, "hns": true, "zonal": false} } // 2. Create storage client before running tests. From ab514e7d2670641dc6da0665fe75439fc626b4d1 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 19 Aug 2025 17:50:10 +0530 Subject: [PATCH 0693/1298] fix: Disable e2 test for renaming unfinalized objects (#3707) * Disable test for renaming unfinalized objects The support for renaming unfinalized objects is there in some cloud regions, but not in others. So, this test (TestUnfinalizedObjectCanBeRenamedIfCreatedFromDifferentMount) fails in some regions and passes in others. To be consistent across all runs in all regions, I am disabling this test. Will re-enable it when rename for unfinalized objects is there consistently everywhere. * add todo comment for reverting --- .../unfinalized_object/unfinalized_object_operations_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go index 02599d979d..4dcf8c415c 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go @@ -121,6 +121,9 @@ func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCanBeRenamedIfCreated } func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCanBeRenamedIfCreatedFromDifferentMount() { + // TODO: Remove this skip when b/439785781 is resolved. + t.T().Skip("Skipping this test as rename for unfinalized objects is not yet supported.") + size := operations.MiB _ = client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), setup.GenerateRandomString(size)) From 2617eeca505258dbb324bb075b8bd18aa7600b4e Mon Sep 17 00:00:00 2001 From: Pranjal Chaturvedi Date: Wed, 20 Aug 2025 11:01:10 +0530 Subject: [PATCH 0694/1298] refactor: list large dir test package migration [GKE-GCSFuse Test migration] (#3708) * correct flags added bucketType * removing duplication of config that runs twice * updating config file --- .../list_large_dir/list_large_dir_test.go | 66 +++++++++++++++---- tools/integration_tests/test_config.yaml | 19 ++++++ tools/integration_tests/util/setup/setup.go | 4 ++ 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/tools/integration_tests/list_large_dir/list_large_dir_test.go b/tools/integration_tests/list_large_dir/list_large_dir_test.go index 53860cdda9..c908a88a3d 100644 --- a/tools/integration_tests/list_large_dir/list_large_dir_test.go +++ b/tools/integration_tests/list_large_dir/list_large_dir_test.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -25,6 +25,8 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" + "gopkg.in/yaml.v3" ) const prefixFileInDirectoryWithTwelveThousandFiles = "fileInDirectoryWithTwelveThousandFiles" @@ -40,10 +42,43 @@ var ( ctx context.Context ) +// Config holds all test configurations parsed from the YAML file. +type Config struct { + ListLargeDir []test_suite.TestConfig `yaml:"list_large_dir"` +} + func TestMain(m *testing.M) { - setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + setup.ParseSetUpFlags() - // Create common storage client to be used in test. + // 1. Load and parse the common configuration. + var cfg Config + if setup.ConfigFile() != "" { + configData, err := os.ReadFile(setup.ConfigFile()) + if err != nil { + log.Fatalf("could not read test_config.yaml: %v", err) + } + expandedYaml := os.ExpandEnv(string(configData)) + if err := yaml.Unmarshal([]byte(expandedYaml), &cfg); err != nil { + log.Fatalf("Failed to parse config YAML: %v", err) + } + } + if len(cfg.ListLargeDir) == 0 { + log.Println("No configuration found for list large dir tests in config. Using flags instead.") + // Populate the config manually. + cfg.ListLargeDir = make([]test_suite.TestConfig, 1) + cfg.ListLargeDir[0].TestBucket = setup.TestBucket() + cfg.ListLargeDir[0].MountedDirectory = setup.MountedDirectory() + cfg.ListLargeDir[0].Configs = make([]test_suite.ConfigItem, 2) + cfg.ListLargeDir[0].Configs[0].Flags = []string{"--implicit-dirs=true --stat-cache-ttl=0 --kernel-list-cache-ttl-secs=-1"} + cfg.ListLargeDir[0].Configs[0].Compatible = map[string]bool{"flat": true, "hns": true, "zonal": true} + cfg.ListLargeDir[0].Configs[1].Flags = []string{ + "--client-protocol=grpc --implicit-dirs=true --stat-cache-ttl=0 --kernel-list-cache-ttl-secs=-1", + } + cfg.ListLargeDir[0].Configs[1].Compatible = map[string]bool{"flat": true, "hns": true, "zonal": false} + } + + // 2. Create storage client before running tests. + setup.SetBucketFromConfigFile(cfg.ListLargeDir[0].TestBucket) ctx = context.Background() closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) defer func() { @@ -53,19 +88,26 @@ func TestMain(m *testing.M) { } }() - flags := [][]string{{"--implicit-dirs", "--stat-cache-ttl=0", "--kernel-list-cache-ttl-secs=-1"}} - // Don't run for grpc if -short flat is passed. - // Don't run for grpc for zonal bucket as zonal buckets by default use grpc. - if !testing.Short() && !setup.IsZonalBucketRun() { - flags = append(flags, []string{"--client-protocol=grpc", "--implicit-dirs=true", "--stat-cache-ttl=0", "--kernel-list-cache-ttl-secs=-1"}) + // 3. To run mountedDirectory tests, we need both testBucket and mountedDirectory + // flags to be set, as ListLargeDir tests validates content from the bucket. + if cfg.ListLargeDir[0].MountedDirectory != "" && cfg.ListLargeDir[0].TestBucket != "" { + os.Exit(setup.RunTestsForMountedDirectory(cfg.ListLargeDir[0].MountedDirectory, m)) } - // Run tests for mountedDirectory only if --mountedDirectory flag is set. - setup.RunTestsForMountedDirectoryFlag(m) + // Run tests for testBucket + // 4. Build the flag sets dynamically from the config. + bucketType, err := setup.BucketType(ctx, cfg.ListLargeDir[0].TestBucket) + if err != nil { + log.Fatalf("BucketType failed: %v", err) + } + if bucketType == setup.ZonalBucket { + setup.SetIsZonalBucketRun(true) + } + flags := setup.BuildFlagSets(cfg.ListLargeDir[0], bucketType) - setup.SetUpTestDirForTestBucketFlag() + setup.SetUpTestDirForTestBucket(cfg.ListLargeDir[0].TestBucket) - successCode := static_mounting.RunTests(flags, m) + successCode := static_mounting.RunTestsWithConfigFile(&cfg.ListLargeDir[0], flags, m) os.Exit(successCode) } diff --git a/tools/integration_tests/test_config.yaml b/tools/integration_tests/test_config.yaml index c84354afbe..ca40a8b056 100644 --- a/tools/integration_tests/test_config.yaml +++ b/tools/integration_tests/test_config.yaml @@ -44,3 +44,22 @@ implicit_dir: flat: true hns: true zonal: false + +list_large_dir: + - mounted_directory: "${MOUNTED_DIR}" + test_bucket: "${BUCKET_NAME}" + log_file: # Optional flag required by some tests where log parsing is done to validate end behavior + run_on_gke: false + configs: + - flags: + - "--implicit-dirs=true --stat-cache-ttl=0 --kernel-list-cache-ttl-secs=-1" + compatible: + flat: true + hns: true + zonal: true + - flags: + - "--client-protocol=grpc --implicit-dirs=true --stat-cache-ttl=0 --kernel-list-cache-ttl-secs=-1" + compatible: + flat: true + hns: true + zonal: false diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index f70d78902e..b61dc54dfd 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -93,6 +93,10 @@ func IsZonalBucketRun() bool { return *isZonalBucketRun } +func SetIsZonalBucketRun(val bool) { + *isZonalBucketRun = val +} + func IsIntegrationTest() bool { return *integrationTest } From 69f7d72da8abe376139d1ef462d923b759af4133 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Wed, 20 Aug 2025 12:40:28 +0530 Subject: [PATCH 0695/1298] upgrade go sdk dependency (#3710) --- go.mod | 26 +++++++++++++------------- go.sum | 52 ++++++++++++++++++++++++++-------------------------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/go.mod b/go.mod index f455826fce..2f96a547d9 100644 --- a/go.mod +++ b/go.mod @@ -3,19 +3,19 @@ module github.com/googlecloudplatform/gcsfuse/v3 go 1.24.5 require ( - cloud.google.com/go/auth v0.16.3 + cloud.google.com/go/auth v0.16.5 cloud.google.com/go/auth/oauth2adapt v0.2.8 - cloud.google.com/go/compute/metadata v0.7.0 + cloud.google.com/go/compute/metadata v0.8.0 cloud.google.com/go/iam v1.5.2 cloud.google.com/go/profiler v0.4.3 cloud.google.com/go/secretmanager v1.15.0 - cloud.google.com/go/storage v1.55.0 + cloud.google.com/go/storage v1.56.1 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.29.0 github.com/fsouza/fake-gcs-server v1.52.2 github.com/go-viper/mapstructure/v2 v2.4.0 github.com/google/uuid v1.6.0 - github.com/googleapis/gax-go/v2 v2.14.2 + github.com/googleapis/gax-go/v2 v2.15.0 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec github.com/jacobsa/fuse v0.0.0-20250726160139-b8f47b05858b github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd @@ -41,22 +41,22 @@ require ( go.opentelemetry.io/otel/sdk v1.37.0 go.opentelemetry.io/otel/sdk/metric v1.37.0 go.opentelemetry.io/otel/trace v1.37.0 - golang.org/x/net v0.42.0 + golang.org/x/net v0.43.0 golang.org/x/oauth2 v0.30.0 golang.org/x/sync v0.16.0 - golang.org/x/sys v0.34.0 - golang.org/x/text v0.27.0 + golang.org/x/sys v0.35.0 + golang.org/x/text v0.28.0 golang.org/x/time v0.12.0 - google.golang.org/api v0.239.0 + google.golang.org/api v0.247.0 google.golang.org/grpc v1.74.2 - google.golang.org/protobuf v1.36.6 + google.golang.org/protobuf v1.36.7 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 ) require ( cel.dev/expr v0.24.0 // indirect - cloud.google.com/go v0.121.4 // indirect + cloud.google.com/go v0.121.6 // indirect cloud.google.com/go/longrunning v0.6.7 // indirect cloud.google.com/go/monitoring v1.24.2 // indirect cloud.google.com/go/pubsub v1.49.0 // indirect @@ -102,8 +102,8 @@ require ( go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.40.0 // indirect + golang.org/x/crypto v0.41.0 // indirect google.golang.org/genproto v0.0.0-20250721164621-a45f3dfb1074 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect ) diff --git a/go.sum b/go.sum index 70bdd56414..f90f21f91d 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,14 @@ cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.121.4 h1:cVvUiY0sX0xwyxPwdSU2KsF9knOVmtRyAMt8xou0iTs= -cloud.google.com/go v0.121.4/go.mod h1:XEBchUiHFJbz4lKBZwYBDHV/rSyfFktk737TLDU089s= -cloud.google.com/go/auth v0.16.3 h1:kabzoQ9/bobUmnseYnBO6qQG7q4a/CffFRlJSxv2wCc= -cloud.google.com/go/auth v0.16.3/go.mod h1:NucRGjaXfzP1ltpcQ7On/VTZ0H4kWB5Jy+Y9Dnm76fA= +cloud.google.com/go v0.121.6 h1:waZiuajrI28iAf40cWgycWNgaXPO06dupuS+sgibK6c= +cloud.google.com/go v0.121.6/go.mod h1:coChdst4Ea5vUpiALcYKXEpR1S9ZgXbhEzzMcMR66vI= +cloud.google.com/go/auth v0.16.5 h1:mFWNQ2FEVWAliEQWpAdH80omXFokmrnbDhUS9cBywsI= +cloud.google.com/go/auth v0.16.5/go.mod h1:utzRfHMP+Vv0mpOkTRQoWD2q3BatTOoWbA7gCc2dUhQ= cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc= cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c= -cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= -cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/compute/metadata v0.8.0 h1:HxMRIbao8w17ZX6wBnjhcDkW6lTFpgcaobyVfZWqRLA= +cloud.google.com/go/compute/metadata v0.8.0/go.mod h1:sYOGTp851OV9bOFJ9CH7elVvyzopvWQFNNghtDQ/Biw= cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8= cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE= cloud.google.com/go/kms v1.22.0 h1:dBRIj7+GDeeEvatJeTB19oYZNV0aj6wEqSIT/7gLqtk= @@ -25,8 +25,8 @@ cloud.google.com/go/pubsub v1.49.0 h1:5054IkbslnrMCgA2MAEPcsN3Ky+AyMpEZcii/DoySP cloud.google.com/go/pubsub v1.49.0/go.mod h1:K1FswTWP+C1tI/nfi3HQecoVeFvL4HUOB1tdaNXKhUY= cloud.google.com/go/secretmanager v1.15.0 h1:RtkCMgTpaBMbzozcRUGfZe46jb9a3qh5EdEtVRUATF8= cloud.google.com/go/secretmanager v1.15.0/go.mod h1:1hQSAhKK7FldiYw//wbR/XPfPc08eQ81oBsnRUHEvUc= -cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0= -cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY= +cloud.google.com/go/storage v1.56.1 h1:n6gy+yLnHn0hTwBFzNn8zJ1kqWfR91wzdM8hjRF4wP0= +cloud.google.com/go/storage v1.56.1/go.mod h1:C9xuCZgFl3buo2HZU/1FncgvvOgTAs/rnh4gF4lMg0s= cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= @@ -128,8 +128,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4= github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA= -github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0= -github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w= +github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo= +github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc= github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= @@ -264,8 +264,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= -golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -276,8 +276,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= -golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= @@ -291,12 +291,12 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220408201424-a24fb2fb8a0f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= -golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= -golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -305,8 +305,8 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.239.0 h1:2hZKUnFZEy81eugPs4e2XzIJ5SOwQg0G82bpXD65Puo= -google.golang.org/api v0.239.0/go.mod h1:cOVEm2TpdAGHL2z+UwyS+kmlGr3bVWQQ6sYEqkKje50= +google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc= +google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -314,10 +314,10 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20250721164621-a45f3dfb1074 h1:OC4JjCnGdf5dQ5lMsq3KOGmd0xFXTeeo4h8QFoiLQhA= google.golang.org/genproto v0.0.0-20250721164621-a45f3dfb1074/go.mod h1:ZIjaIRmV0lzMh6VMUdtRvj3TTfpe0uA3cHt3skrCdSQ= -google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79 h1:iOye66xuaAK0WnkPuhQPUFy8eJcmwUXqGGP3om6IxX8= -google.golang.org/genproto/googleapis/api v0.0.0-20250715232539-7130f93afb79/go.mod h1:HKJDgKsFUnv5VAGeQjz8kxcgDP0HoE0iZNp0OdZNlhE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79 h1:1ZwqphdOdWYXsUHgMpU/101nCtf/kSp9hOrcvFsnl10= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250715232539-7130f93afb79/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -334,8 +334,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= +google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 13bc4f6d74169cf2933c1da73ff78d3e8b42fdbe Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 20 Aug 2025 14:42:58 +0530 Subject: [PATCH 0696/1298] Handle histogram without attributes (#3713) --- metrics/otel_metrics.go | 6 +++++- tools/metrics-gen/otel_metrics.tpl | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/metrics/otel_metrics.go b/metrics/otel_metrics.go index d4ff06fce7..a00fde1809 100644 --- a/metrics/otel_metrics.go +++ b/metrics/otel_metrics.go @@ -2723,7 +2723,11 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr go func() { defer wg.Done() for record := range ch { - record.instrument.Record(record.ctx, record.value, record.attributes) + if record.attributes != nil { + record.instrument.Record(record.ctx, record.value, record.attributes) + } else { + record.instrument.Record(record.ctx, record.value) + } } }() } diff --git a/tools/metrics-gen/otel_metrics.tpl b/tools/metrics-gen/otel_metrics.tpl index d9e8fde8fb..c823994e8e 100644 --- a/tools/metrics-gen/otel_metrics.tpl +++ b/tools/metrics-gen/otel_metrics.tpl @@ -103,7 +103,11 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr go func() { defer wg.Done() for record := range ch { - record.instrument.Record(record.ctx, record.value, record.attributes) + if record.attributes != nil { + record.instrument.Record(record.ctx, record.value, record.attributes) + } else { + record.instrument.Record(record.ctx, record.value) + } } }() } From 9304584601798966eac4cd05bb284432ed183c29 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Wed, 20 Aug 2025 15:28:37 +0530 Subject: [PATCH 0697/1298] enable write stall in streaming writes (#3712) --- internal/storage/bucket_handle.go | 3 +-- .../emulator_tests/write_stall/writes_stall_on_sync_test.go | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 0a19d37827..6c2e5b1b60 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -258,8 +258,7 @@ func (bh *bucketHandle) CreateObjectChunkWriter(ctx context.Context, req *gcs.Cr wc := &ObjectWriter{obj.NewWriter(ctx)} wc.ChunkSize = chunkSize wc.Writer = storageutil.SetAttrsInWriter(wc.Writer, req) - // TODO(b/424091803): Uncomment once chunk transfer timeout issue in resumable uploads is fixed in dependencies. - // wc.ChunkTransferTimeout = time.Duration(req.ChunkTransferTimeoutSecs) * time.Second + wc.ChunkTransferTimeout = time.Duration(req.ChunkTransferTimeoutSecs) * time.Second wc.ProgressFunc = callBack // All objects in zonal buckets must be appendable. wc.Append = bh.BucketType().Zonal diff --git a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go index 9373ad084c..a60484b974 100644 --- a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go @@ -101,10 +101,9 @@ func TestChunkTransferTimeoutInfinity(t *testing.T) { } func TestChunkTransferTimeout(t *testing.T) { - // TODO(b/424091803): Enable streaming writes once chunk transfer timeout issue in resumable uploads is fixed in dependencies. flagSets := [][]string{ - {"--enable-streaming-writes=false"}, - {"--enable-streaming-writes=false", "--chunk-transfer-timeout-secs=5"}, + {}, + {"--chunk-transfer-timeout-secs=5"}, } stallScenarios := []struct { From b91dfc7c0cc2614b321b4a485fea23f5e7cc0b27 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Wed, 20 Aug 2025 20:36:29 +0530 Subject: [PATCH 0698/1298] Add require metrics for buffered reader (#3715) --- metrics/metric_handle.go | 12 +++ metrics/metrics.yaml | 88 ++++++++++++----- metrics/noop_metrics.go | 9 ++ metrics/otel_metrics.go | 143 ++++++++++++++++++++++++--- metrics/otel_metrics_test.go | 187 +++++++++++++++++++++++++++++++++++ 5 files changed, 398 insertions(+), 41 deletions(-) diff --git a/metrics/metric_handle.go b/metrics/metric_handle.go index c21e6aa96d..82cae15366 100644 --- a/metrics/metric_handle.go +++ b/metrics/metric_handle.go @@ -24,6 +24,18 @@ import ( // The methods of this interface are auto-generated from metrics.yaml. // Each method corresponds to a metric defined in metrics.yaml. type MetricHandle interface { + // BufferedReadDownloadBlockLatency - The cumulative distribution of block download latencies, along with status: successful or cancelled. + BufferedReadDownloadBlockLatency(ctx context.Context, duration time.Duration, status string) + + // BufferedReadFallbackTriggerCount - The cumulative number of times the BufferedReader falls back to a different reader, along with the reason: random_read_detected or insufficient_memory. + BufferedReadFallbackTriggerCount(inc int64, reason string) + + // BufferedReadReadLatency - The cumulative distribution of latencies for ReadAt calls served by the buffered reader. + BufferedReadReadLatency(ctx context.Context, duration time.Duration) + + // BufferedReadScheduledBlockCount - The cumulative number of scheduled download blocks, along with their final status: successful or cancelled. + BufferedReadScheduledBlockCount(inc int64, status string) + // FileCacheReadBytesCount - The cumulative number of bytes read from file cache along with read type - Sequential/Random FileCacheReadBytesCount(inc int64, readType string) diff --git a/metrics/metrics.yaml b/metrics/metrics.yaml index 31861c2caa..ac9a5800b7 100644 --- a/metrics/metrics.yaml +++ b/metrics/metrics.yaml @@ -1,29 +1,7 @@ -- metric-name: "file_cache/read_bytes_count" - description: "The cumulative number of bytes read from file cache along with read type - Sequential/Random" - unit: "By" - type: "int_counter" - attributes: - - attribute-name: read_type - attribute-type: string - values: &read_types_list - - "Parallel" - - "Random" - - "Sequential" - -- metric-name: "file_cache/read_count" - description: "Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false" - type: "int_counter" - attributes: - - attribute-name: cache_hit - attribute-type: bool - - attribute-name: read_type - attribute-type: string - values: *read_types_list - -- metric-name: "file_cache/read_latencies" - description: "The cumulative distribution of the file cache read latencies along with cache hit - true/false." - type: "int_histogram" +- metric-name: "buffered_read/download_block_latency" + description: "The cumulative distribution of block download latencies, along with status: successful or cancelled." unit: "us" + type: "int_histogram" boundaries: &common_boundaries - 1 - 2 @@ -60,6 +38,66 @@ - 50000 - 100000 attributes: + - attribute-name: status + attribute-type: string + values: + - "successful" + - "cancelled" + +- metric-name: "buffered_read/fallback_trigger_count" + description: "The cumulative number of times the BufferedReader falls back to a different reader, along with the reason: random_read_detected or insufficient_memory." + type: "int_counter" + attributes: + - attribute-name: reason + attribute-type: string + values: + - "random_read_detected" + - "insufficient_memory" + +- metric-name: "buffered_read/read_latency" + description: "The cumulative distribution of latencies for ReadAt calls served by the buffered reader." + unit: "us" + type: "int_histogram" + boundaries: *common_boundaries + +- metric-name: "buffered_read/scheduled_block_count" + description: "The cumulative number of scheduled download blocks, along with their final status: successful or cancelled." + type: "int_counter" + attributes: + - attribute-name: status + attribute-type: string + values: + - "successful" + - "cancelled" + +- metric-name: "file_cache/read_bytes_count" + description: "The cumulative number of bytes read from file cache along with read type - Sequential/Random" + unit: "By" + type: "int_counter" + attributes: + - attribute-name: read_type + attribute-type: string + values: &read_types_list + - "Parallel" + - "Random" + - "Sequential" + +- metric-name: "file_cache/read_count" + description: "Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false" + type: "int_counter" + attributes: + - attribute-name: cache_hit + attribute-type: bool + - attribute-name: read_type + attribute-type: string + values: *read_types_list + +- metric-name: "file_cache/read_latencies" + description: "The cumulative distribution of the file cache read latencies along with cache hit - true/false." + type: "int_histogram" + unit: "us" + boundaries: *common_boundaries + attributes: - attribute-name: cache_hit attribute-type: bool diff --git a/metrics/noop_metrics.go b/metrics/noop_metrics.go index 4336a6b064..bb03281cc9 100644 --- a/metrics/noop_metrics.go +++ b/metrics/noop_metrics.go @@ -22,6 +22,15 @@ import ( type noopMetrics struct{} +func (*noopMetrics) BufferedReadDownloadBlockLatency(ctx context.Context, duration time.Duration, status string) { +} + +func (*noopMetrics) BufferedReadFallbackTriggerCount(inc int64, reason string) {} + +func (*noopMetrics) BufferedReadReadLatency(ctx context.Context, duration time.Duration) {} + +func (*noopMetrics) BufferedReadScheduledBlockCount(inc int64, status string) {} + func (*noopMetrics) FileCacheReadBytesCount(inc int64, readType string) {} func (*noopMetrics) FileCacheReadCount(inc int64, cacheHit bool, readType string) {} diff --git a/metrics/otel_metrics.go b/metrics/otel_metrics.go index a00fde1809..ff987bbf30 100644 --- a/metrics/otel_metrics.go +++ b/metrics/otel_metrics.go @@ -33,6 +33,12 @@ const logInterval = 5 * time.Minute var ( unrecognizedAttr atomic.Value + bufferedReadDownloadBlockLatencyStatusCancelledAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("status", "cancelled"))) + bufferedReadDownloadBlockLatencyStatusSuccessfulAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("status", "successful"))) + bufferedReadFallbackTriggerCountReasonInsufficientMemoryAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("reason", "insufficient_memory"))) + bufferedReadFallbackTriggerCountReasonRandomReadDetectedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("reason", "random_read_detected"))) + bufferedReadScheduledBlockCountStatusCancelledAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("status", "cancelled"))) + bufferedReadScheduledBlockCountStatusSuccessfulAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("status", "successful"))) fileCacheReadBytesCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) fileCacheReadBytesCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) fileCacheReadBytesCountReadTypeSequentialAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Sequential"))) @@ -663,6 +669,10 @@ type histogramRecord struct { type otelMetrics struct { ch chan histogramRecord wg *sync.WaitGroup + bufferedReadFallbackTriggerCountReasonInsufficientMemoryAtomic *atomic.Int64 + bufferedReadFallbackTriggerCountReasonRandomReadDetectedAtomic *atomic.Int64 + bufferedReadScheduledBlockCountStatusCancelledAtomic *atomic.Int64 + bufferedReadScheduledBlockCountStatusSuccessfulAtomic *atomic.Int64 fileCacheReadBytesCountReadTypeParallelAtomic *atomic.Int64 fileCacheReadBytesCountReadTypeRandomAtomic *atomic.Int64 fileCacheReadBytesCountReadTypeSequentialAtomic *atomic.Int64 @@ -1230,11 +1240,71 @@ type otelMetrics struct { gcsRequestCountGcsMethodUpdateObjectAtomic *atomic.Int64 gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic *atomic.Int64 gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic *atomic.Int64 + bufferedReadDownloadBlockLatency metric.Int64Histogram + bufferedReadReadLatency metric.Int64Histogram fileCacheReadLatencies metric.Int64Histogram fsOpsLatency metric.Int64Histogram gcsRequestLatencies metric.Int64Histogram } +func (o *otelMetrics) BufferedReadDownloadBlockLatency( + ctx context.Context, latency time.Duration, status string) { + var record histogramRecord + switch status { + case "cancelled": + record = histogramRecord{ctx: ctx, instrument: o.bufferedReadDownloadBlockLatency, value: latency.Microseconds(), attributes: bufferedReadDownloadBlockLatencyStatusCancelledAttrSet} + case "successful": + record = histogramRecord{ctx: ctx, instrument: o.bufferedReadDownloadBlockLatency, value: latency.Microseconds(), attributes: bufferedReadDownloadBlockLatencyStatusSuccessfulAttrSet} + default: + updateUnrecognizedAttribute(status) + return + } + + select { + case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + default: // Unblock writes to channel if it's full. + } +} + +func (o *otelMetrics) BufferedReadFallbackTriggerCount( + inc int64, reason string) { + switch reason { + case "insufficient_memory": + o.bufferedReadFallbackTriggerCountReasonInsufficientMemoryAtomic.Add(inc) + case "random_read_detected": + o.bufferedReadFallbackTriggerCountReasonRandomReadDetectedAtomic.Add(inc) + default: + updateUnrecognizedAttribute(reason) + return + } + +} + +func (o *otelMetrics) BufferedReadReadLatency( + ctx context.Context, latency time.Duration) { + var record histogramRecord + record = histogramRecord{instrument: o.bufferedReadReadLatency, value: latency.Microseconds()} + + select { + case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + default: // Unblock writes to channel if it's full. + } +} + +func (o *otelMetrics) BufferedReadScheduledBlockCount( + inc int64, status string) { + switch status { + case "cancelled": + o.bufferedReadScheduledBlockCountStatusCancelledAtomic.Add(inc) + case "successful": + o.bufferedReadScheduledBlockCountStatusSuccessfulAtomic.Add(inc) + default: + updateUnrecognizedAttribute(status) + return + } + +} + func (o *otelMetrics) FileCacheReadBytesCount( inc int64, readType string) { switch readType { @@ -2732,6 +2802,13 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr }() } meter := otel.Meter("gcsfuse") + + var bufferedReadFallbackTriggerCountReasonInsufficientMemoryAtomic, + bufferedReadFallbackTriggerCountReasonRandomReadDetectedAtomic atomic.Int64 + + var bufferedReadScheduledBlockCountStatusCancelledAtomic, + bufferedReadScheduledBlockCountStatusSuccessfulAtomic atomic.Int64 + var fileCacheReadBytesCountReadTypeParallelAtomic, fileCacheReadBytesCountReadTypeRandomAtomic, fileCacheReadBytesCountReadTypeSequentialAtomic atomic.Int64 @@ -3309,7 +3386,35 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr var gcsRetryCountRetryErrorCategoryOTHERERRORSAtomic, gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic atomic.Int64 - _, err0 := meter.Int64ObservableCounter("file_cache/read_bytes_count", + bufferedReadDownloadBlockLatency, err0 := meter.Int64Histogram("buffered_read/download_block_latency", + metric.WithDescription("The cumulative distribution of block download latencies, along with status: successful or cancelled."), + metric.WithUnit("us"), + metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) + + _, err1 := meter.Int64ObservableCounter("buffered_read/fallback_trigger_count", + metric.WithDescription("The cumulative number of times the BufferedReader falls back to a different reader, along with the reason: random_read_detected or insufficient_memory."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &bufferedReadFallbackTriggerCountReasonInsufficientMemoryAtomic, bufferedReadFallbackTriggerCountReasonInsufficientMemoryAttrSet) + conditionallyObserve(obsrv, &bufferedReadFallbackTriggerCountReasonRandomReadDetectedAtomic, bufferedReadFallbackTriggerCountReasonRandomReadDetectedAttrSet) + return nil + })) + + bufferedReadReadLatency, err2 := meter.Int64Histogram("buffered_read/read_latency", + metric.WithDescription("The cumulative distribution of latencies for ReadAt calls served by the buffered reader."), + metric.WithUnit("us"), + metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) + + _, err3 := meter.Int64ObservableCounter("buffered_read/scheduled_block_count", + metric.WithDescription("The cumulative number of scheduled download blocks, along with their final status: successful or cancelled."), + metric.WithUnit(""), + metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { + conditionallyObserve(obsrv, &bufferedReadScheduledBlockCountStatusCancelledAtomic, bufferedReadScheduledBlockCountStatusCancelledAttrSet) + conditionallyObserve(obsrv, &bufferedReadScheduledBlockCountStatusSuccessfulAtomic, bufferedReadScheduledBlockCountStatusSuccessfulAttrSet) + return nil + })) + + _, err4 := meter.Int64ObservableCounter("file_cache/read_bytes_count", metric.WithDescription("The cumulative number of bytes read from file cache along with read type - Sequential/Random"), metric.WithUnit("By"), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { @@ -3319,7 +3424,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return nil })) - _, err1 := meter.Int64ObservableCounter("file_cache/read_count", + _, err5 := meter.Int64ObservableCounter("file_cache/read_count", metric.WithDescription("Specifies the number of read requests made via file cache along with type - Sequential/Random and cache hit - true/false"), metric.WithUnit(""), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { @@ -3332,12 +3437,12 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return nil })) - fileCacheReadLatencies, err2 := meter.Int64Histogram("file_cache/read_latencies", + fileCacheReadLatencies, err6 := meter.Int64Histogram("file_cache/read_latencies", metric.WithDescription("The cumulative distribution of the file cache read latencies along with cache hit - true/false."), metric.WithUnit("us"), metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) - _, err3 := meter.Int64ObservableCounter("fs/ops_count", + _, err7 := meter.Int64ObservableCounter("fs/ops_count", metric.WithDescription("The cumulative number of ops processed by the file system."), metric.WithUnit(""), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { @@ -3375,7 +3480,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return nil })) - _, err4 := meter.Int64ObservableCounter("fs/ops_error_count", + _, err8 := meter.Int64ObservableCounter("fs/ops_error_count", metric.WithDescription("The cumulative number of errors generated by file system operations."), metric.WithUnit(""), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { @@ -3878,12 +3983,12 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return nil })) - fsOpsLatency, err5 := meter.Int64Histogram("fs/ops_latency", + fsOpsLatency, err9 := meter.Int64Histogram("fs/ops_latency", metric.WithDescription("The cumulative distribution of file system operation latencies"), metric.WithUnit("us"), metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) - _, err6 := meter.Int64ObservableCounter("gcs/download_bytes_count", + _, err10 := meter.Int64ObservableCounter("gcs/download_bytes_count", metric.WithDescription("The cumulative number of bytes downloaded from GCS along with type - Sequential/Random"), metric.WithUnit("By"), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { @@ -3893,7 +3998,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return nil })) - _, err7 := meter.Int64ObservableCounter("gcs/read_bytes_count", + _, err11 := meter.Int64ObservableCounter("gcs/read_bytes_count", metric.WithDescription("The cumulative number of bytes read from GCS objects."), metric.WithUnit("By"), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { @@ -3901,7 +4006,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return nil })) - _, err8 := meter.Int64ObservableCounter("gcs/read_count", + _, err12 := meter.Int64ObservableCounter("gcs/read_count", metric.WithDescription("Specifies the number of gcs reads made along with type - Sequential/Random"), metric.WithUnit(""), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { @@ -3911,7 +4016,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return nil })) - _, err9 := meter.Int64ObservableCounter("gcs/reader_count", + _, err13 := meter.Int64ObservableCounter("gcs/reader_count", metric.WithDescription("The cumulative number of GCS object readers opened or closed."), metric.WithUnit(""), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { @@ -3921,7 +4026,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return nil })) - _, err10 := meter.Int64ObservableCounter("gcs/request_count", + _, err14 := meter.Int64ObservableCounter("gcs/request_count", metric.WithDescription("The cumulative number of GCS requests processed along with the GCS method."), metric.WithUnit(""), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { @@ -3947,12 +4052,12 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return nil })) - gcsRequestLatencies, err11 := meter.Int64Histogram("gcs/request_latencies", + gcsRequestLatencies, err15 := meter.Int64Histogram("gcs/request_latencies", metric.WithDescription("The cumulative distribution of the GCS request latencies."), metric.WithUnit("ms"), metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) - _, err12 := meter.Int64ObservableCounter("gcs/retry_count", + _, err16 := meter.Int64ObservableCounter("gcs/retry_count", metric.WithDescription("The cumulative number of retry requests made to GCS."), metric.WithUnit(""), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { @@ -3961,14 +4066,20 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr return nil })) - errs := []error{err0, err1, err2, err3, err4, err5, err6, err7, err8, err9, err10, err11, err12} + errs := []error{err0, err1, err2, err3, err4, err5, err6, err7, err8, err9, err10, err11, err12, err13, err14, err15, err16} if err := errors.Join(errs...); err != nil { return nil, err } return &otelMetrics{ - ch: ch, - wg: &wg, + ch: ch, + wg: &wg, + bufferedReadDownloadBlockLatency: bufferedReadDownloadBlockLatency, + bufferedReadFallbackTriggerCountReasonInsufficientMemoryAtomic: &bufferedReadFallbackTriggerCountReasonInsufficientMemoryAtomic, + bufferedReadFallbackTriggerCountReasonRandomReadDetectedAtomic: &bufferedReadFallbackTriggerCountReasonRandomReadDetectedAtomic, + bufferedReadReadLatency: bufferedReadReadLatency, + bufferedReadScheduledBlockCountStatusCancelledAtomic: &bufferedReadScheduledBlockCountStatusCancelledAtomic, + bufferedReadScheduledBlockCountStatusSuccessfulAtomic: &bufferedReadScheduledBlockCountStatusSuccessfulAtomic, fileCacheReadBytesCountReadTypeParallelAtomic: &fileCacheReadBytesCountReadTypeParallelAtomic, fileCacheReadBytesCountReadTypeRandomAtomic: &fileCacheReadBytesCountReadTypeRandomAtomic, fileCacheReadBytesCountReadTypeSequentialAtomic: &fileCacheReadBytesCountReadTypeSequentialAtomic, diff --git a/metrics/otel_metrics_test.go b/metrics/otel_metrics_test.go index 66952354d7..3dfc22311e 100644 --- a/metrics/otel_metrics_test.go +++ b/metrics/otel_metrics_test.go @@ -128,6 +128,193 @@ func gatherNonZeroCounterMetrics(ctx context.Context, t *testing.T, rd *metric.M return results } +func TestBufferedReadDownloadBlockLatency(t *testing.T) { + tests := []struct { + name string + latencies []time.Duration + status string + }{ + { + name: "status_cancelled", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + status: "cancelled", + }, + { + name: "status_successful", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + status: "successful", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + var totalLatency time.Duration + + for _, latency := range tc.latencies { + m.BufferedReadDownloadBlockLatency(ctx, latency, tc.status) + totalLatency += latency + } + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + metric, ok := metrics["buffered_read/download_block_latency"] + require.True(t, ok, "buffered_read/download_block_latency metric not found") + + attrs := []attribute.KeyValue{ + attribute.String("status", tc.status), + } + s := attribute.NewSet(attrs...) + expectedKey := s.Encoded(encoder) + dp, ok := metric[expectedKey] + require.True(t, ok, "DataPoint not found for key: %s", expectedKey) + assert.Equal(t, uint64(len(tc.latencies)), dp.Count) + assert.Equal(t, totalLatency.Microseconds(), dp.Sum) + }) + } +} + +func TestBufferedReadFallbackTriggerCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "reason_insufficient_memory", + f: func(m *otelMetrics) { + m.BufferedReadFallbackTriggerCount(5, "insufficient_memory") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("reason", "insufficient_memory")): 5, + }, + }, + { + name: "reason_random_read_detected", + f: func(m *otelMetrics) { + m.BufferedReadFallbackTriggerCount(5, "random_read_detected") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("reason", "random_read_detected")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.BufferedReadFallbackTriggerCount(5, "insufficient_memory") + m.BufferedReadFallbackTriggerCount(2, "random_read_detected") + m.BufferedReadFallbackTriggerCount(3, "insufficient_memory") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("reason", "insufficient_memory")): 8, + attribute.NewSet(attribute.String("reason", "random_read_detected")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["buffered_read/fallback_trigger_count"] + assert.True(t, ok, "buffered_read/fallback_trigger_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + +func TestBufferedReadReadLatency(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + var totalLatency time.Duration + latencies := []time.Duration{100 * time.Microsecond, 200 * time.Microsecond} + + for _, latency := range latencies { + m.BufferedReadReadLatency(ctx, latency) + totalLatency += latency + } + waitForMetricsProcessing() + + metrics := gatherHistogramMetrics(ctx, t, rd) + metric, ok := metrics["buffered_read/read_latency"] + require.True(t, ok, "buffered_read/read_latency metric not found") + + s := attribute.NewSet() + expectedKey := s.Encoded(encoder) + dp, ok := metric[expectedKey] + require.True(t, ok, "DataPoint not found for key: %s", expectedKey) + assert.Equal(t, uint64(len(latencies)), dp.Count) + assert.Equal(t, totalLatency.Microseconds(), dp.Sum) +} + +func TestBufferedReadScheduledBlockCount(t *testing.T) { + tests := []struct { + name string + f func(m *otelMetrics) + expected map[attribute.Set]int64 + }{ + { + name: "status_cancelled", + f: func(m *otelMetrics) { + m.BufferedReadScheduledBlockCount(5, "cancelled") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("status", "cancelled")): 5, + }, + }, + { + name: "status_successful", + f: func(m *otelMetrics) { + m.BufferedReadScheduledBlockCount(5, "successful") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("status", "successful")): 5, + }, + }, { + name: "multiple_attributes_summed", + f: func(m *otelMetrics) { + m.BufferedReadScheduledBlockCount(5, "cancelled") + m.BufferedReadScheduledBlockCount(2, "successful") + m.BufferedReadScheduledBlockCount(3, "cancelled") + }, + expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("status", "cancelled")): 8, + attribute.NewSet(attribute.String("status", "successful")): 2, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + encoder := attribute.DefaultEncoder() + m, rd := setupOTel(ctx, t) + + tc.f(m) + waitForMetricsProcessing() + + metrics := gatherNonZeroCounterMetrics(ctx, t, rd) + metric, ok := metrics["buffered_read/scheduled_block_count"] + assert.True(t, ok, "buffered_read/scheduled_block_count metric not found") + expectedMap := make(map[string]int64) + for k, v := range tc.expected { + expectedMap[k.Encoded(encoder)] = v + } + assert.Equal(t, expectedMap, metric) + }) + } +} + func TestFileCacheReadBytesCount(t *testing.T) { tests := []struct { name string From 09de513480c90488730f6adcfbc34f436f6a0fcc Mon Sep 17 00:00:00 2001 From: Pranjal Chaturvedi Date: Wed, 20 Aug 2025 20:48:28 +0530 Subject: [PATCH 0699/1298] refactor: write large files test package migration [GKE-GCSFuse Test migration] (#3711) * added configs and update test * lint correction * new config struct and common function to remove code duplicacy * changed write global max blocks to 5 * identify bucket type before creating client, prevent failing of ZB --- tools/integration_tests/test_config.yaml | 17 +++++ .../util/test_suite/config.go | 51 +++++++++++++- .../write_large_files_test.go | 69 ++++++++++++++----- 3 files changed, 120 insertions(+), 17 deletions(-) diff --git a/tools/integration_tests/test_config.yaml b/tools/integration_tests/test_config.yaml index ca40a8b056..18e8f9fd7b 100644 --- a/tools/integration_tests/test_config.yaml +++ b/tools/integration_tests/test_config.yaml @@ -63,3 +63,20 @@ list_large_dir: flat: true hns: true zonal: false + +write_large_files: + - mounted_directory: "${MOUNTED_DIR}" + test_bucket: "${BUCKET_NAME}" + log_file: # Optional + run_on_gke: false + configs: + - flags: + - "--enable-streaming-writes=false" + # write-global-max-blocks=5 is for checking multiple file writes in parallel. + # concurrent_write_files_test.go- we are writing 3 files in parallel. + # with this config, we are giving 2 blocks to 2 files and 1 block to other file. + - "--write-max-blocks-per-file=2 --write-global-max-blocks=5" + compatible: + flat: true + hns: true + zonal: true diff --git a/tools/integration_tests/util/test_suite/config.go b/tools/integration_tests/util/test_suite/config.go index 17f9e256ac..a7bc6dfa65 100644 --- a/tools/integration_tests/util/test_suite/config.go +++ b/tools/integration_tests/util/test_suite/config.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -14,6 +14,13 @@ package test_suite +import ( + "log" + "os" + + "gopkg.in/yaml.v3" +) + // BucketType represents the 'compatible' field. type BucketType struct { Flat bool `yaml:"flat"` @@ -35,3 +42,45 @@ type ConfigItem struct { Flags []string `yaml:"flags"` Compatible map[string]bool `yaml:"compatible"` } + +// Config holds all test configurations parsed from the YAML file. +type Config struct { + ImplicitDir []TestConfig `yaml:"implicit_dir"` + ExplicitDir []TestConfig `yaml:"explicit_dir"` + ListLargeDir []TestConfig `yaml:"list_large_dir"` + WriteLargeFiles []TestConfig `yaml:"write_large_files"` + Operations []TestConfig `yaml:"operations"` + ReadLargeFiles []TestConfig `yaml:"read_large_files"` + ReadOnly []TestConfig `yaml:"readonly"` + ReadCache []TestConfig `yaml:"read_cache"` + RenameDirLimit []TestConfig `yaml:"rename_dir_limit"` + Gzip []TestConfig `yaml:"gzip"` + LocalFile []TestConfig `yaml:"local_file"` + LogRotation []TestConfig `yaml:"log_rotation"` + ManagedFolders []TestConfig `yaml:"managed_folders"` + ConcurrentOperations []TestConfig `yaml:"concurrent_operations"` + Benchmarking []TestConfig `yaml:"benchmarking"` + StaleHandles []TestConfig `yaml:"stale_handles"` + StreamingWrites []TestConfig `yaml:"streaming_writes"` + InactiveStreamTimeout []TestConfig `yaml:"inactive_stream_timeout"` + CloudProfiler []TestConfig `yaml:"cloud_profiler"` + KernelListCache []TestConfig `yaml:"kernel_list_cache"` + ReadDirPlus []TestConfig `yaml:"readdirplus"` + DentryCache []TestConfig `yaml:"dentry_cache"` +} + +// ReadConfigFile returns a Config struct from the YAML file. +func ReadConfigFile(configFilePath string) Config { + var cfg Config + if configFilePath != "" { + configData, err := os.ReadFile(configFilePath) + if err != nil { + log.Fatalf("could not read config file %q: %v", configFilePath, err) + } + expandedYaml := os.ExpandEnv(string(configData)) + if err := yaml.Unmarshal([]byte(expandedYaml), &cfg); err != nil { + log.Fatalf("Failed to parse config YAML: %v", err) + } + } + return cfg +} diff --git a/tools/integration_tests/write_large_files/write_large_files_test.go b/tools/integration_tests/write_large_files/write_large_files_test.go index 80359711c7..f51f8f0d7a 100644 --- a/tools/integration_tests/write_large_files/write_large_files_test.go +++ b/tools/integration_tests/write_large_files/write_large_files_test.go @@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -16,12 +16,16 @@ package write_large_files import ( + "context" "log" "os" "testing" + "cloud.google.com/go/storage" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" ) const ( @@ -30,29 +34,62 @@ const ( WritePermission_0200 = 0200 ) +var ( + storageClient *storage.Client + ctx context.Context +) + func TestMain(m *testing.M) { setup.ParseSetUpFlags() - // write-global-max-blocks=2 is for checking multiple file writes in parallel. - // concurrent_write_files_test.go- we are writing 3 files in parallel. - // with this config, we are giving 2 blocks to 2 files and 1 block to other file. - flags := [][]string{ - {"--enable-streaming-writes=false"}, - {"--write-max-blocks-per-file=2", "--write-global-max-blocks=2"}} + // 1. Load and parse the common configuration. + cfg := test_suite.ReadConfigFile(setup.ConfigFile()) + if len(cfg.WriteLargeFiles) == 0 { + log.Println("No configuration found for write large files tests in config. Using flags instead.") + // Populate the config manually. + cfg.WriteLargeFiles = make([]test_suite.TestConfig, 1) + cfg.WriteLargeFiles[0].TestBucket = setup.TestBucket() + cfg.WriteLargeFiles[0].MountedDirectory = setup.MountedDirectory() + cfg.WriteLargeFiles[0].Configs = make([]test_suite.ConfigItem, 1) + cfg.WriteLargeFiles[0].Configs[0].Flags = []string{ + "--enable-streaming-writes=false", + "--write-max-blocks-per-file=2 --write-global-max-blocks=5", + } + cfg.WriteLargeFiles[0].Configs[0].Compatible = map[string]bool{"flat": true, "hns": true, "zonal": true} + } - setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + setup.SetBucketFromConfigFile(cfg.WriteLargeFiles[0].TestBucket) + ctx = context.Background() + bucketType, err := setup.BucketType(ctx, cfg.WriteLargeFiles[0].TestBucket) + if err != nil { + log.Fatalf("BucketType failed: %v", err) + } + if bucketType == setup.ZonalBucket { + setup.SetIsZonalBucketRun(true) + } + + // 2. Create storage client before running tests. + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() - if setup.TestBucket() == "" && setup.MountedDirectory() != "" { - log.Print("Please pass the name of bucket mounted at mountedDirectory to --testBucket flag.") - os.Exit(1) + // 3. To run mountedDirectory tests, we need both testBucket and mountedDirectory + // flags to be set, as WriteLargeFiles tests validates content from the bucket. + if cfg.WriteLargeFiles[0].MountedDirectory != "" && cfg.WriteLargeFiles[0].TestBucket != "" { + os.Exit(setup.RunTestsForMountedDirectory(cfg.WriteLargeFiles[0].MountedDirectory, m)) } - // Run tests for mountedDirectory only if --mountedDirectory flag is set. - setup.RunTestsForMountedDirectoryFlag(m) + // Run tests for testBucket// Run tests for testBucket + // 4. Build the flag sets dynamically from the config. + flags := setup.BuildFlagSets(cfg.WriteLargeFiles[0], bucketType) + + setup.SetUpTestDirForTestBucket(cfg.WriteLargeFiles[0].TestBucket) - // Run tests for testBucket - setup.SetUpTestDirForTestBucketFlag() + successCode := static_mounting.RunTestsWithConfigFile(&cfg.WriteLargeFiles[0], flags, m) - successCode := static_mounting.RunTests(flags, m) os.Exit(successCode) } From 3d24bc9e6a3b69b3cf7300d3bbe531c65b117f06 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 21 Aug 2025 10:09:03 +0530 Subject: [PATCH 0700/1298] test(write_large_files): Add slow writer integration tests. (#3699) * add slow writer test * rebase to latest master. * fix assert order * Write only 2 blocks and reduce delay to 40s with a comment * fix typo --- .../write_large_files/slow_file_write_test.go | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tools/integration_tests/write_large_files/slow_file_write_test.go diff --git a/tools/integration_tests/write_large_files/slow_file_write_test.go b/tools/integration_tests/write_large_files/slow_file_write_test.go new file mode 100644 index 0000000000..a8a34d4e85 --- /dev/null +++ b/tools/integration_tests/write_large_files/slow_file_write_test.go @@ -0,0 +1,50 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package write_large_files + +import ( + "path" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/stretchr/testify/assert" +) + +const ( + DirForSlowWrite = "dirForSlowWrite" +) + +func TestSlowWriteToFile(t *testing.T) { + slowWriteDir := setup.SetupTestDirectory(DirForSlowWrite) + slowWriteFileName := "slowWriteFile" + setup.GenerateRandomString(5) + ".txt" + mountedDirFilePath := path.Join(slowWriteDir, slowWriteFileName) + fh := operations.OpenFileWithODirect(t, mountedDirFilePath) + defer operations.CloseFileShouldNotThrowError(t, fh) + data := setup.GenerateRandomString(33 * operations.MiB) + + // Write 2 blocks of 33MiB to file + for range 2 { + n, err := fh.Write([]byte(data)) + + assert.NoError(t, err) + assert.Equal(t, len(data), n) + operations.SyncFile(fh, t) + // Add a delay of 40 sec between each block to ensure 32 sec + // ChunkRetryDeadline is completely used while reading the second Chunk. + time.Sleep(40 * time.Second) + } +} From 2c1feaaf41f8349ee98560148bf480be65e652b7 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 21 Aug 2025 10:49:18 +0530 Subject: [PATCH 0701/1298] perf(metrics): Don't create an unnecessary histogramRecord (#3716) * Get rid of creation of histogramRecord This will make the histogram metrics updates more succinct and efficient. --- metrics/otel_metrics.go | 12 ++++++------ tools/metrics-gen/main.go | 2 +- tools/metrics-gen/otel_metrics.tpl | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/metrics/otel_metrics.go b/metrics/otel_metrics.go index ff987bbf30..203961c982 100644 --- a/metrics/otel_metrics.go +++ b/metrics/otel_metrics.go @@ -1261,7 +1261,7 @@ func (o *otelMetrics) BufferedReadDownloadBlockLatency( } select { - case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + case o.ch <- record: // Do nothing default: // Unblock writes to channel if it's full. } } @@ -1283,10 +1283,10 @@ func (o *otelMetrics) BufferedReadFallbackTriggerCount( func (o *otelMetrics) BufferedReadReadLatency( ctx context.Context, latency time.Duration) { var record histogramRecord - record = histogramRecord{instrument: o.bufferedReadReadLatency, value: latency.Microseconds()} + record = histogramRecord{ctx: ctx, instrument: o.bufferedReadReadLatency, value: latency.Microseconds()} select { - case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + case o.ch <- record: // Do nothing default: // Unblock writes to channel if it's full. } } @@ -1363,7 +1363,7 @@ func (o *otelMetrics) FileCacheReadLatencies( } select { - case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + case o.ch <- record: // Do nothing default: // Unblock writes to channel if it's full. } } @@ -2610,7 +2610,7 @@ func (o *otelMetrics) FsOpsLatency( } select { - case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + case o.ch <- record: // Do nothing default: // Unblock writes to channel if it's full. } } @@ -2765,7 +2765,7 @@ func (o *otelMetrics) GcsRequestLatencies( } select { - case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + case o.ch <- record: // Do nothing default: // Unblock writes to channel if it's full. } } diff --git a/tools/metrics-gen/main.go b/tools/metrics-gen/main.go index 1565fa0b12..dc229b1a4b 100644 --- a/tools/metrics-gen/main.go +++ b/tools/metrics-gen/main.go @@ -396,7 +396,7 @@ func buildSwitches(metric Metric) string { if len(metric.Attributes) == 0 { if metric.Type == "int_histogram" { unitMethod := getUnitMethod(metric.Unit) - builder.WriteString(fmt.Sprintf("\trecord = histogramRecord{instrument: o.%s, value: latency%s}\n", toCamel(metric.Name), unitMethod)) + builder.WriteString(fmt.Sprintf("\trecord = histogramRecord{ctx: ctx, instrument: o.%s, value: latency%s}\n", toCamel(metric.Name), unitMethod)) } else if metric.Type == "int_counter" { atomicName := getAtomicName(metric.Name, AttrCombination{}) builder.WriteString(fmt.Sprintf("\to.%s.Add(inc)\n", atomicName)) diff --git a/tools/metrics-gen/otel_metrics.tpl b/tools/metrics-gen/otel_metrics.tpl index c823994e8e..6de1d58ea9 100644 --- a/tools/metrics-gen/otel_metrics.tpl +++ b/tools/metrics-gen/otel_metrics.tpl @@ -87,7 +87,7 @@ func (o *otelMetrics) {{toPascal .Name}}( var record histogramRecord {{buildSwitches .}} select { - case o.ch <- histogramRecord{instrument: record.instrument, value: record.value, attributes: record.attributes, ctx: ctx}: // Do nothing + case o.ch <- record: // Do nothing default: // Unblock writes to channel if it's full. } {{- end}} From 761387cd87affbe1df623cfa12faedac9975311c Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Thu, 21 Aug 2025 12:52:48 +0530 Subject: [PATCH 0702/1298] fix auto reminder issue when PR is raised from forked gcsfuse. (#3719) --- .github/workflows/auto-pr-reminder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/auto-pr-reminder.yml b/.github/workflows/auto-pr-reminder.yml index 593a1c21fe..74cecd59b1 100644 --- a/.github/workflows/auto-pr-reminder.yml +++ b/.github/workflows/auto-pr-reminder.yml @@ -17,6 +17,7 @@ jobs: runs-on: ubuntu-latest permissions: pull-requests: write + issues: write steps: - name: Add 'remind-reviewers' label uses: actions/github-script@v6 From f720ef40f2cbed2365d4c12860f9417d8b136106 Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Thu, 21 Aug 2025 14:48:34 +0530 Subject: [PATCH 0703/1298] Adding concurrent read test (#3695) * Adding concurrent read test * Fix lint issue * review comments * review comments * review comments. * minor update --- .../concurrent_listing_test.go | 26 +- .../concurrent_read_test.go | 282 ++++++++++++++++++ .../concurrent_operations/setup_test.go | 49 ++- .../infinite_kernel_list_cache_test.go | 1 + 4 files changed, 341 insertions(+), 17 deletions(-) create mode 100644 tools/integration_tests/concurrent_operations/concurrent_read_test.go diff --git a/tools/integration_tests/concurrent_operations/concurrent_listing_test.go b/tools/integration_tests/concurrent_operations/concurrent_listing_test.go index 98f6d1a590..20a831908a 100644 --- a/tools/integration_tests/concurrent_operations/concurrent_listing_test.go +++ b/tools/integration_tests/concurrent_operations/concurrent_listing_test.go @@ -677,5 +677,29 @@ func (s *concurrentListingTest) Test_StatWithNewFileWrite(t *testing.T) { func TestConcurrentListing(t *testing.T) { ts := &concurrentListingTest{} - test_setup.RunTests(t, ts) + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + flagsSet := [][]string{ + {"--kernel-list-cache-ttl-secs=0"}, {"--kernel-list-cache-ttl-secs=-1"}, + } + + if !testing.Short() { + setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "", "--client-protocol=grpc") + } + + for _, flags := range flagsSet { + mountGCSFuseAndSetupTestDir(flags, testDirName, t) + // Parallel subtest execution is suspended until its calling test function has + // returned. Hence invoking RunTest inside another test, otherwise unmount will + // happen before the subtest execution starts. + t.Run(fmt.Sprintf("Flags_%v", flags), func(t *testing.T) { + test_setup.RunTests(t, ts) + }) + setup.UnmountGCSFuse(setup.MntDir()) + } } diff --git a/tools/integration_tests/concurrent_operations/concurrent_read_test.go b/tools/integration_tests/concurrent_operations/concurrent_read_test.go new file mode 100644 index 0000000000..2935b73dbd --- /dev/null +++ b/tools/integration_tests/concurrent_operations/concurrent_read_test.go @@ -0,0 +1,282 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package concurrent_operations + +import ( + "bytes" + "fmt" + "math/rand" + "os" + "path" + "sync" + "syscall" + "testing" + "time" + + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + testDirNameForRead = "concurrent_read_test" +) + +var testDirPathForRead string + +//////////////////////////////////////////////////////////////////////// +// Boilerplate +//////////////////////////////////////////////////////////////////////// + +type concurrentReadTest struct { + flags []string +} + +func (s *concurrentReadTest) Setup(t *testing.T) { + mountGCSFuseAndSetupTestDir(s.flags, testDirNameForRead, t) +} + +func (s *concurrentReadTest) Teardown(t *testing.T) { + setup.UnmountGCSFuse(setup.MntDir()) +} + +//////////////////////////////////////////////////////////////////////// +// Test scenarios +//////////////////////////////////////////////////////////////////////// + +// Test_ConcurrentSequentialAndRandomReads tests concurrent read operations where +// 5 goroutines read a 500MiB file sequentially and 5 goroutines read randomly. +// This test validates that concurrent sequential and random read patterns work +// correctly without deadlocks or race conditions. It also validates data integrity +// using CRC32 checksums for sequential reads and chunk validation for random reads. +func (s *concurrentReadTest) Test_ConcurrentSequentialAndRandomReads(t *testing.T) { + const ( + fileSize = 500 * operations.OneMiB // 500 MiB file + chunkSize = 64 * operations.OneKiB // 64 KiB chunks for reads + sequentialReads = 5 // Number of sequential readers + randomReads = 5 // Number of random readers + ) + // Create a 500MiB test file + testFilePath := path.Join(testDirPathForRead, "large_test_file.bin") + operations.CreateFileOfSize(fileSize, testFilePath, t) + var wg sync.WaitGroup + timeout := 300 * time.Second // 5 minutes timeout for 500MiB operations + + // Launch 5 sequential readers + for i := 0; i < sequentialReads; i++ { + wg.Add(1) + go func(readerID int) { + defer wg.Done() + // Use operations.ReadFileSequentially to read the entire file + content, err := operations.ReadFileSequentially(testFilePath, chunkSize) + require.NoError(t, err, "Sequential reader %d: read failed.", readerID) + require.Equal(t, fileSize, len(content), "Sequential reader %d: expected to read entire file", readerID) + obj := storageClient.Bucket(setup.TestBucket()).Object(path.Join(path.Base(testDirPathForRead), "large_test_file.bin")) + attrs, err := obj.Attrs(ctx) + require.NoError(t, err, "obj.Attrs") + localCRC32C, err := operations.CalculateCRC32(bytes.NewReader(content)) + require.NoError(t, err, "Sequential reader %d: failed to calculate local CRC32C", readerID) + assert.Equal(t, attrs.CRC32C, localCRC32C, "Sequential reader %d: CRC32C mismatch. GCS: %d, Local: %d", readerID, attrs.CRC32C, localCRC32C) + }(i) + } + // Launch 5 random readers + for i := 0; i < randomReads; i++ { + wg.Add(1) + go func(readerID int) { + defer wg.Done() + numRandomReads := 200 // Number of random read operations per goroutine + rand.New(rand.NewSource(time.Now().UnixNano() + int64(readerID))) + for j := 0; j < numRandomReads; j++ { + // Generate random offset within bound. + randomOffset := int64(rand.Intn(fileSize/chunkSize)) * chunkSize + // Use operations.ReadChunkFromFile for reading chunks + chunk, err := operations.ReadChunkFromFile(testFilePath, chunkSize, randomOffset, os.O_RDONLY) + require.NoError(t, err, "Random reader %d: ReadChunkFromFile failed at offset %d", readerID, randomOffset) + client.ValidateObjectChunkFromGCS(ctx, storageClient, path.Base(testDirPathForRead), "large_test_file.bin", randomOffset, int64(len(chunk)), string(chunk), t) + } + }(i) + } + // Wait for all goroutines or timeout + done := make(chan bool, 1) + go func() { + wg.Wait() + done <- true + }() + + select { + case <-done: + t.Log("All concurrent read operations completed successfully") + case <-time.After(timeout): + assert.FailNow(t, "Concurrent read operations timed out - possible deadlock or performance issue") + } +} + +// Test_ConcurrentSegmentReadsSharedHandle tests concurrent read operations where +// 5 goroutines read different segments of a file using the same shared file handle. +// This test validates that multiple goroutines can safely read from different +// parts of the same file using a single shared file handle without race conditions, +// with each reader handling a distinct segment of the file for comprehensive coverage. +func (s *concurrentReadTest) Test_ConcurrentSegmentReadsSharedHandle(t *testing.T) { + const ( + fileSize = 500 * operations.OneMiB // 500 MiB file + numReaders = 5 // Number of concurrent readers + segmentSize = fileSize / numReaders // Each reader reads 100 MiB segment + ) + // Create a 500MiB test file + testFilePath := path.Join(testDirPathForRead, "segment_test_file.bin") + operations.CreateFileOfSize(fileSize, testFilePath, t) + // Open shared file handle that will be used by all goroutines + sharedFile, err := os.Open(testFilePath) + require.NoError(t, err, "Failed to open shared file handle") + defer func() { + err := sharedFile.Close() + require.NoError(t, err, "Failed to close shared file handle") + }() + var wg sync.WaitGroup + segmentData := make([][]byte, numReaders) + timeout := 300 * time.Second // 5 minutes timeout + + // Launch 5 readers, each reading a different segment using the shared file handle + for i := 0; i < numReaders; i++ { + wg.Add(1) + go func(readerID int) { + defer wg.Done() + // Calculate segment boundaries + segmentStart := int64(readerID) * segmentSize + segmentEnd := segmentStart + segmentSize + if readerID == numReaders-1 { + // Last reader takes any remaining bytes + segmentEnd = fileSize + } + actualSegmentSize := segmentEnd - segmentStart + // Read segment using shared file handle with ReadAt + buffer := make([]byte, actualSegmentSize) + n, err := sharedFile.ReadAt(buffer, segmentStart) + require.NoError(t, err, "Reader %d: ReadAt failed for segment %d-%d", readerID, segmentStart, segmentEnd-1) + require.Equal(t, int(actualSegmentSize), n, "Reader %d: expected to read %d bytes, got %d", readerID, actualSegmentSize, n) + // Store segment data for later validation + segmentData[readerID] = buffer + }(i) + } + // Wait for all goroutines or timeout + done := make(chan bool, 1) + go func() { + wg.Wait() + done <- true + }() + + select { + case <-done: + t.Log("All concurrent segment read operations completed successfully") + // Reconstruct the full file from segments and validate checksum + var fullContent bytes.Buffer + for i, segment := range segmentData { + n, err := fullContent.Write(segment) + require.NoError(t, err, "Failed to write segment %d to buffer", i) + require.Equal(t, len(segment), n, "Segment %d: wrote different number of bytes than expected", i) + } + // Validate total size + require.Equal(t, fileSize, fullContent.Len(), "Reconstructed file size mismatch") + // Validate checksum of reconstructed content + reconstructedChecksum, err := operations.CalculateCRC32(bytes.NewReader(fullContent.Bytes())) + require.NoError(t, err, "Failed to calculate reconstructed checksum") + obj := storageClient.Bucket(setup.TestBucket()).Object(path.Join(path.Base(testDirPathForRead), "segment_test_file.bin")) + attrs, err := obj.Attrs(ctx) + require.NoError(t, err, "obj.Attrs") + assert.Equal(t, attrs.CRC32C, reconstructedChecksum, "CRC32C mismatch. GCS: %d, Local: %d", attrs.CRC32C, reconstructedChecksum) + case <-time.After(timeout): + assert.FailNow(t, "Concurrent segment read operations timed out - possible deadlock or performance issue") + } +} + +func (s *concurrentReadTest) Test_ConcurrentReadPlusWrite(t *testing.T) { + const ( + fileSize = 32 * operations.OneMiB // 32 MiB file + numGoRoutines = 10 // Number of concurrent readers + chunkSize = 128 * operations.OneKiB // 128 KiB chunks for reads + ) + var wg sync.WaitGroup + wg.Add(numGoRoutines) + timeout := 300 * time.Second + + for i := 0; i < numGoRoutines; i++ { + go func(workerId int) { + defer wg.Done() + + fileName := fmt.Sprintf("test_%d.bin", workerId) + filePath := path.Join(testDirPathForRead, fileName) + f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC|syscall.O_DIRECT, setup.FilePermission_0600) + require.NoError(t, err) + randomData, err := operations.GenerateRandomData(fileSize) + require.NoError(t, err) + n, err := f.Write(randomData) + require.NoError(t, err) + require.Equal(t, int(fileSize), n) + operations.CloseFileShouldNotThrowError(t, f) + + content, err := operations.ReadFileSequentially(filePath, chunkSize) + require.NoError(t, err, "Sequential reader %d: read failed.", workerId) + require.Equal(t, fileSize, len(content), "Sequential reader %d: expected to read entire file", workerId) + obj := storageClient.Bucket(setup.TestBucket()).Object(path.Join(path.Base(testDirPathForRead), fileName)) + attrs, err := obj.Attrs(ctx) + require.NoError(t, err, "obj.Attrs") + localCRC32C, err := operations.CalculateCRC32(bytes.NewReader(content)) + require.NoError(t, err, "Sequential reader %d: failed to calculate local CRC32C", workerId) + assert.Equal(t, attrs.CRC32C, localCRC32C, "Sequential reader %d: CRC32C mismatch. GCS: %d, Local: %d", workerId, attrs.CRC32C, localCRC32C) + }(i) + } + // Wait for all goroutines or timeout + done := make(chan bool, 1) + go func() { + wg.Wait() + done <- true + }() + + select { + case <-done: + t.Log("All concurrent goroutines completed successfully") + case <-time.After(timeout): + assert.FailNow(t, "Concurrent go routines timedout.") + } +} + +//////////////////////////////////////////////////////////////////////// +// Test Function (Runs once before all tests) +//////////////////////////////////////////////////////////////////////// + +func TestConcurrentRead(t *testing.T) { + ts := &concurrentReadTest{} + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + test_setup.RunTests(t, ts) + return + } + + // Define flag sets specific for concurrent read tests + flagsSet := [][]string{ + {}, // For default read path. + {"--enable-buffered-read"}, // For Buffered read enabled. + } + + // Run tests with each flag set + for _, flags := range flagsSet { + ts.flags = flags + test_setup.RunTests(t, ts) + } +} diff --git a/tools/integration_tests/concurrent_operations/setup_test.go b/tools/integration_tests/concurrent_operations/setup_test.go index 13d2dde58a..ce11845a23 100644 --- a/tools/integration_tests/concurrent_operations/setup_test.go +++ b/tools/integration_tests/concurrent_operations/setup_test.go @@ -23,10 +23,9 @@ import ( "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/dynamic_mounting" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/only_dir_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" ) const ( @@ -38,8 +37,36 @@ var ( storageClient *storage.Client ctx context.Context testDirPath string + + // root directory is the directory to be unmounted. + rootDir string ) +//////////////////////////////////////////////////////////////////////// +// Helper functions +//////////////////////////////////////////////////////////////////////// + +func mountGCSFuseAndSetupTestDir(flags []string, testDirName string, t *testing.T) { + // When tests are running in GKE environment, use the mounted directory provided as test flag. + if setup.MountedDirectory() != "" { + testDirPathForRead = setup.MountedDirectory() + } else { + config := &test_suite.TestConfig{ + TestBucket: setup.TestBucket(), + MountedDirectory: setup.MountedDirectory(), + LogFile: setup.LogFile(), + } + if err := static_mounting.MountGcsfuseWithStaticMountingWithConfigFile(config, flags); err != nil { + t.Fatalf("Failed to mount GCS FUSE: %v", err) + return + } + testDirPathForRead = setup.MntDir() + } + setup.SetMntDir(testDirPathForRead) + testDirPath := setup.SetupTestDirectory(testDirName) + testDirPathForRead = testDirPath +} + func TestMain(m *testing.M) { setup.ParseSetUpFlags() setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() @@ -60,21 +87,11 @@ func TestMain(m *testing.M) { // Set up test directory. setup.SetUpTestDirForTestBucketFlag() - flagsSet := [][]string{ - {"--kernel-list-cache-ttl-secs=-1"}, {"--kernel-list-cache-ttl-secs=0"}, - } - if !testing.Short() { - setup.AppendFlagsToAllFlagsInTheFlagsSet(&flagsSet, "", "--client-protocol=grpc") - } - successCode := static_mounting.RunTests(flagsSet, m) - - if successCode == 0 { - successCode = only_dir_mounting.RunTests(flagsSet, onlyDirMounted, m) - } + // Save root directory variables. + rootDir = setup.MntDir() - if successCode == 0 { - successCode = dynamic_mounting.RunTests(ctx, storageClient, flagsSet, m) - } + log.Println("Running static mounting tests...") + successCode := m.Run() // Clean up test directory created. setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go index 015ef2029f..d359ea892f 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go @@ -474,6 +474,7 @@ func TestInfiniteKernelListCacheTest(t *testing.T) { for _, flags := range flagsSet { ts.flags = flags log.Printf("Running tests with flags: %s", ts.flags) + test_setup.RunTests(t, ts) } } From 0019524542cbb3740156aeb59c01ee4c387ed756 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Thu, 21 Aug 2025 15:36:39 +0530 Subject: [PATCH 0704/1298] feat(tracing): Add tracing for HTTP/1 and gRPC protocols (#3722) * Add tracing for HTTP/1 and gRPC protocols --- cmd/legacy_main.go | 1 + go.mod | 5 +- go.sum | 6 ++- internal/storage/storage_handle.go | 5 ++ internal/storage/storage_handle_test.go | 25 ++++++++++ internal/storage/storageutil/client.go | 12 +++++ internal/storage/storageutil/client_test.go | 51 +++++++++++++++++++++ 7 files changed, 101 insertions(+), 4 deletions(-) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index 6611c87500..dcdc5a2557 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -144,6 +144,7 @@ func createStorageHandle(newConfig *cfg.Config, userAgent string, metricHandle m EnableGoogleLibAuth: newConfig.EnableGoogleLibAuth, ReadStallRetryConfig: newConfig.GcsRetries.ReadStall, MetricHandle: metricHandle, + TracingEnabled: cfg.IsTracingEnabled(newConfig), } logger.Infof("UserAgent = %s\n", storageClientConfig.UserAgent) storageHandle, err = storage.NewStorageHandle(context.Background(), storageClientConfig, newConfig.GcsConnection.BillingProject) diff --git a/go.mod b/go.mod index 2f96a547d9..bde0da5277 100644 --- a/go.mod +++ b/go.mod @@ -34,6 +34,9 @@ require ( github.com/stretchr/testify v1.10.0 go.opencensus.io v0.24.0 go.opentelemetry.io/contrib/detectors/gcp v1.37.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 go.opentelemetry.io/otel v1.37.0 go.opentelemetry.io/otel/exporters/prometheus v0.59.1 go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.37.0 @@ -99,8 +102,6 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/zeebo/errs v1.4.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.41.0 // indirect google.golang.org/genproto v0.0.0-20250721164621-a45f3dfb1074 // indirect diff --git a/go.sum b/go.sum index f90f21f91d..7b29e1df76 100644 --- a/go.sum +++ b/go.sum @@ -240,8 +240,10 @@ go.opentelemetry.io/contrib/detectors/gcp v1.37.0 h1:B+WbN9RPsvobe6q4vP6KgM8/9pl go.opentelemetry.io/contrib/detectors/gcp v1.37.0/go.mod h1:K5zQ3TT7p2ru9Qkzk0bKtCql0RGkPj9pRjpXgZJZ+rU= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0 h1:wCeciVlAfb5DC8MQl/DlmAv/FVPNpQgFvI/71+hatuc= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0/go.mod h1:WfEApdZDMlLUAev/0QQpr8EJ/z0VWDKYZ5tF5RH5T1U= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= go.opentelemetry.io/otel/exporters/prometheus v0.59.1 h1:HcpSkTkJbggT8bjYP+BjyqPWlD17BH9C5CYNKeDzmcA= diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index 6be4c5ab09..1134254eed 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -30,6 +30,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "golang.org/x/net/context" "golang.org/x/oauth2" option "google.golang.org/api/option" @@ -115,6 +116,10 @@ func createClientOptionForGRPCClient(ctx context.Context, clientConfig *storageu clientOpts = append(clientOpts, experimental.WithGRPCBidiReads()) } + if clientConfig.TracingEnabled { + clientOpts = append(clientOpts, option.WithGRPCDialOption(grpc.WithStatsHandler(otelgrpc.NewClientHandler()))) + } + clientOpts = append(clientOpts, option.WithGRPCConnectionPool(clientConfig.GrpcConnPoolSize)) clientOpts = append(clientOpts, option.WithUserAgent(clientConfig.UserAgent)) // Turning off the go-sdk metrics exporter to prevent any problems. diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index b778016281..004e759b76 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -612,6 +612,31 @@ func (testSuite *StorageHandleTest) Test_CreateHTTPClientHandle_WithoutGoogleLib assert.NotNil(testSuite.T(), httpClient) } +func (testSuite *StorageHandleTest) Test_CreateClientOptionForGRPCClient_WithTracing() { + sc := storageutil.GetDefaultStorageClientConfig(keyFile) + sc.TracingEnabled = true + + clientOption, err := createClientOptionForGRPCClient(context.TODO(), &sc, false) + + assert.Nil(testSuite.T(), err) + assert.NotNil(testSuite.T(), clientOption) +} + +func (testSuite *StorageHandleTest) Test_CreateClientOptionForGRPCClient_WithTracingAddsOneOption() { + scWithoutTracing := storageutil.GetDefaultStorageClientConfig(keyFile) + scWithoutTracing.TracingEnabled = false + optsWithoutTracing, err := createClientOptionForGRPCClient(context.TODO(), &scWithoutTracing, false) + assert.Nil(testSuite.T(), err) + scWithTracing := storageutil.GetDefaultStorageClientConfig(keyFile) + scWithTracing.TracingEnabled = true + + optsWithTracing, err := createClientOptionForGRPCClient(context.TODO(), &scWithTracing, false) + + assert.Nil(testSuite.T(), err) + assert.NotNil(testSuite.T(), optsWithTracing) + assert.Len(testSuite.T(), optsWithTracing, len(optsWithoutTracing)+1, "Enabling tracing should add exactly one client option.") +} + func (testSuite *StorageHandleTest) Test_CreateClientOptionForGRPCClient_AuthFailures() { tests := []struct { name string diff --git a/internal/storage/storageutil/client.go b/internal/storage/storageutil/client.go index 6fb6eb8a77..0347ceb7f4 100644 --- a/internal/storage/storageutil/client.go +++ b/internal/storage/storageutil/client.go @@ -18,12 +18,16 @@ import ( "crypto/tls" "fmt" "net/http" + "net/http/httptrace" "strings" "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/internal/auth" "github.com/googlecloudplatform/gcsfuse/v3/metrics" + "go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace" + "go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp" + "go.opentelemetry.io/otel" "golang.org/x/net/context" "golang.org/x/oauth2" ) @@ -62,6 +66,8 @@ type StorageClientConfig struct { ReadStallRetryConfig cfg.ReadStallGcsRetriesConfig MetricHandle metrics.MetricHandle + + TracingEnabled bool } func CreateHttpClient(storageClientConfig *StorageClientConfig, tokenSrc oauth2.TokenSource) (httpClient *http.Client, err error) { @@ -122,6 +128,12 @@ func CreateHttpClient(storageClientConfig *StorageClientConfig, tokenSrc oauth2. wrapped: httpClient.Transport, UserAgent: storageClientConfig.UserAgent, } + + if storageClientConfig.TracingEnabled { + httpClient.Transport = otelhttp.NewTransport(httpClient.Transport, otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace { + return otelhttptrace.NewClientTrace(ctx) + }), otelhttp.WithTracerProvider(otel.GetTracerProvider())) + } } return httpClient, err } diff --git a/internal/storage/storageutil/client_test.go b/internal/storage/storageutil/client_test.go index 78aac1574c..eb3cafe947 100644 --- a/internal/storage/storageutil/client_test.go +++ b/internal/storage/storageutil/client_test.go @@ -15,9 +15,18 @@ package storageutil import ( + "net/http" + "net/http/httptest" + "slices" "testing" + "go.opentelemetry.io/otel" + sdktrace "go.opentelemetry.io/otel/sdk/trace" + "go.opentelemetry.io/otel/sdk/trace/tracetest" + "golang.org/x/oauth2" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -31,6 +40,16 @@ type clientTest struct { suite.Suite } +func newInMemoryExporter(t *testing.T) *tracetest.InMemoryExporter { + t.Helper() + ex := tracetest.NewInMemoryExporter() + t.Cleanup(func() { + ex.Reset() + }) + otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithSyncer(ex))) + return ex +} + // Tests func (t *clientTest) TestCreateHttpClientWithHttp1() { @@ -126,3 +145,35 @@ func (t *clientTest) TestStripScheme() { assert.Equal(t.T(), tc.expectedOutput, output) } } + +func (t *clientTest) TestCreateHttpClientWithHttpTracing() { + ex := newInMemoryExporter(t.T()) + sc := GetDefaultStorageClientConfig(keyFile) + sc.TracingEnabled = true + sc.UserAgent = "test-agent" + var tokenSrc = oauth2.StaticTokenSource(&oauth2.Token{AccessToken: "test-token"}) + + var userAgent, authHeader string + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userAgent = r.Header.Get("User-Agent") + authHeader = r.Header.Get("Authorization") + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + httpClient, err := CreateHttpClient(&sc, tokenSrc) + require.NoError(t.T(), err) + require.NotNil(t.T(), httpClient) + + _, err = httpClient.Get(server.URL) + + assert.NoError(t.T(), err) + assert.Equal(t.T(), "test-agent", userAgent) + assert.Equal(t.T(), "Bearer test-token", authHeader) + ss := ex.GetSpans() + assert.Condition(t.T(), func() bool { + return slices.ContainsFunc(ss, func(s tracetest.SpanStub) bool { return s.Name == "http.connect" }) + }) + assert.Condition(t.T(), func() bool { + return slices.ContainsFunc(ss, func(s tracetest.SpanStub) bool { return s.Name == "http.send" }) + }) +} From 01db8769979d55c46920c0ae862ba528e391d906 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 21 Aug 2025 18:08:22 +0530 Subject: [PATCH 0705/1298] Modify buffered rerad e2e tests to run for mounted dir (#3725) --- .../buffered_read/fallback_test.go | 45 +++++++++++++++---- .../buffered_read/sequential_read_test.go | 18 +++++++- .../buffered_read/setup_test.go | 23 +++++++--- 3 files changed, 70 insertions(+), 16 deletions(-) diff --git a/tools/integration_tests/buffered_read/fallback_test.go b/tools/integration_tests/buffered_read/fallback_test.go index 38f6d21f36..0c62fb26ef 100644 --- a/tools/integration_tests/buffered_read/fallback_test.go +++ b/tools/integration_tests/buffered_read/fallback_test.go @@ -40,6 +40,10 @@ type fallbackSuiteBase struct { } func (s *fallbackSuiteBase) SetupSuite() { + if setup.MountedDirectory() != "" { + setupForMountedDirectoryTests() + return + } configFile := createConfigFile(s.testFlags) flags := []string{"--config-file=" + configFile} setup.MountGCSFuseWithGivenMountFunc(flags, mountFunc) @@ -51,7 +55,11 @@ func (s *fallbackSuiteBase) SetupTest() { } func (s *fallbackSuiteBase) TearDownSuite() { - setup.UnmountGCSFuse(setup.MntDir()) + if setup.MountedDirectory() != "" { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) + return + } + setup.UnmountGCSFuse(rootDir) setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) } @@ -147,26 +155,45 @@ func (s *RandomReadFallbackSuite) TestRandomRead_SmallFile_NoFallback() { //////////////////////////////////////////////////////////////////////// func TestFallbackSuites(t *testing.T) { - // Run the suite for insufficient pool at creation time. - insufficientPoolFlags := gcsfuseTestFlags{ + // Define base flags for insufficient pool creation tests. + baseInsufficientPoolFlags := gcsfuseTestFlags{ enableBufferedRead: true, blockSizeMB: 8, minBlocksPerHandle: 2, globalMaxBlocks: 1, // Less than min-blocks-per-handle maxBlocksPerHandle: 10, startBlocksPerHandle: 2, - clientProtocol: clientProtocolHTTP1, } - suite.Run(t, &InsufficientPoolCreationSuite{fallbackSuiteBase{testFlags: &insufficientPoolFlags}}) - // Run the suite for random read fallback scenarios. - randomReadFlags := gcsfuseTestFlags{ + // Define base flags for random read fallback tests. + baseRandomReadFlags := gcsfuseTestFlags{ enableBufferedRead: true, blockSizeMB: 8, maxBlocksPerHandle: 20, startBlocksPerHandle: 2, minBlocksPerHandle: 2, - clientProtocol: clientProtocolHTTP1, } - suite.Run(t, &RandomReadFallbackSuite{fallbackSuiteBase{testFlags: &randomReadFlags}}) + + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + suite.Run(t, &InsufficientPoolCreationSuite{fallbackSuiteBase{testFlags: &baseInsufficientPoolFlags}}) + suite.Run(t, &RandomReadFallbackSuite{fallbackSuiteBase{testFlags: &baseRandomReadFlags}}) + return + } + + protocols := []string{clientProtocolHTTP1, clientProtocolGRPC} + + for _, protocol := range protocols { + t.Run(protocol, func(t *testing.T) { + // Run the suite for insufficient pool at creation time. + insufficientPoolFlags := baseInsufficientPoolFlags + insufficientPoolFlags.clientProtocol = protocol + suite.Run(t, &InsufficientPoolCreationSuite{fallbackSuiteBase{testFlags: &insufficientPoolFlags}}) + + // Run the suite for random read fallback scenarios. + randomReadFlags := baseRandomReadFlags + randomReadFlags.clientProtocol = protocol + suite.Run(t, &RandomReadFallbackSuite{fallbackSuiteBase{testFlags: &randomReadFlags}}) + }) + } } diff --git a/tools/integration_tests/buffered_read/sequential_read_test.go b/tools/integration_tests/buffered_read/sequential_read_test.go index 8ba795d824..59f5924d7d 100644 --- a/tools/integration_tests/buffered_read/sequential_read_test.go +++ b/tools/integration_tests/buffered_read/sequential_read_test.go @@ -41,6 +41,10 @@ type SequentialReadSuite struct { } func (s *SequentialReadSuite) SetupSuite() { + if setup.MountedDirectory() != "" { + setupForMountedDirectoryTests() + return + } // Create config file. configFile := createConfigFile(s.testFlags) // Create the final flags slice. @@ -51,7 +55,11 @@ func (s *SequentialReadSuite) SetupSuite() { } func (s *SequentialReadSuite) TearDownSuite() { - setup.UnmountGCSFuse(setup.MntDir()) + if setup.MountedDirectory() != "" { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) + return + } + setup.UnmountGCSFuse(rootDir) setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) } @@ -108,7 +116,7 @@ func (s *SequentialReadSuite) TestReadHeaderFooterAndBody() { // Header and footer sizes (10KB each) headerSize := 10 * util.KiB footerSize := 10 * util.KiB - s.T().Run("Read header, footer, then body from one file handle", func(t *testing.T) { + s.T().Run("Read header footer then body from one file handle", func(t *testing.T) { err := os.Truncate(setup.LogFile(), 0) require.NoError(t, err, "Failed to truncate log file") testDir := setup.SetupTestDirectory(testDirName) @@ -163,6 +171,12 @@ func TestSequentialReadSuite(t *testing.T) { minBlocksPerHandle: 2, } + // Run tests for mounted directory if the flag is set. + if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { + suite.Run(t, &SequentialReadSuite{testFlags: &baseTestFlags}) + return + } + protocols := []string{clientProtocolHTTP1, clientProtocolGRPC} for _, protocol := range protocols { diff --git a/tools/integration_tests/buffered_read/setup_test.go b/tools/integration_tests/buffered_read/setup_test.go index b3583fbe67..f4cd20b0b1 100644 --- a/tools/integration_tests/buffered_read/setup_test.go +++ b/tools/integration_tests/buffered_read/setup_test.go @@ -29,16 +29,18 @@ import ( ) const ( - testDirName = "BufferedReadTest" - testFileName = "foo" - clientProtocolHTTP1 = "http1" - clientProtocolGRPC = "grpc" + testDirName = "BufferedReadTest" + testFileName = "foo" + clientProtocolHTTP1 = "http1" + clientProtocolGRPC = "grpc" + logFileNameForMountedDirectoryTests = "/tmp/gcsfuse_buffered_read_test_logs/log.json" ) var ( mountFunc func([]string) error storageClient *storage.Client ctx context.Context + rootDir string ) type gcsfuseTestFlags struct { @@ -55,6 +57,12 @@ type gcsfuseTestFlags struct { // Helpers //////////////////////////////////////////////////////////////////////// +func setupForMountedDirectoryTests() { + if setup.MountedDirectory() != "" { + setup.SetLogFile(logFileNameForMountedDirectoryTests) + } +} + func createConfigFile(flags *gcsfuseTestFlags) string { mountConfig := make(map[string]interface{}) readConfig := make(map[string]interface{}) @@ -105,14 +113,19 @@ func TestMain(m *testing.M) { } }() - setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() + // A test bucket must be provided for all tests. + if setup.TestBucket() == "" { + log.Fatal("The --testbucket flag is required for this test.") + } if setup.MountedDirectory() != "" { + rootDir = setup.MountedDirectory() os.Exit(setup.RunTestsForMountedDirectory(setup.MountedDirectory(), m)) } // Else run tests for testBucket. setup.SetUpTestDirForTestBucket(setup.TestBucket()) + rootDir = setup.MntDir() // Set up the static mounting function. mountFunc = func(flags []string) error { From 305000298429e64bb9df329ca649ba40dc2662d8 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 21 Aug 2025 18:16:27 +0530 Subject: [PATCH 0706/1298] re-use read handle from previous read request (#3714) --- internal/storage/bucket_handle.go | 3 +-- internal/storage/debug_bucket.go | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 6c2e5b1b60..4a00608946 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -93,8 +93,7 @@ func (bh *bucketHandle) NewReaderWithReadHandle( // This produces the exact same object and generation and does not check if // the generation is still the newest one. if req.ReadHandle != nil { - // TODO: b/432639555 fix code to use read handle from previous read. - obj = obj.ReadHandle([]byte("opaque-handle")) + obj = obj.ReadHandle(req.ReadHandle) } // NewRangeReader creates a "storage.Reader" object which is also io.ReadCloser since it contains both Read() and Close() methods present in io.ReadCloser interface. diff --git a/internal/storage/debug_bucket.go b/internal/storage/debug_bucket.go index 8590aab44a..ab11eff743 100644 --- a/internal/storage/debug_bucket.go +++ b/internal/storage/debug_bucket.go @@ -93,7 +93,7 @@ type debugReader struct { requestID uint64 desc string startTime time.Time - wrapped io.ReadCloser + wrapped gcs.StorageReader } func (dr *debugReader) Read(p []byte) (n int, err error) { @@ -119,9 +119,7 @@ func (dr *debugReader) Close() (err error) { } func (dr *debugReader) ReadHandle() storagev2.ReadHandle { - // TODO: b/432639555 fix code to use read handle from previous read. - hd := "opaque-handle" - return []byte(hd) + return dr.wrapped.ReadHandle() } //////////////////////////////////////////////////////////////////////// From c90fb5e4a4eda3cf4a1ba1b22805915c7f8ba60d Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 22 Aug 2025 10:49:54 +0530 Subject: [PATCH 0707/1298] feat: Retry stalled GetStorageLayout API call for zonal buckets (#3561) * add retry-stall timeout for storagelayout api and its tests * fix lint error * adress some review comments * stop handling improper inputs in withRetryOnStorageLayoutStall * add exponential jittery backoff in-between retries in contrl-client retrier * minor fix in interface of control-client wrapper creations * rename minRetryDeadline to initialRetryDeadline * remove stall from retry in name everywhere * split out backoff as a separate struct and its own unit tests * remove gax retry for GetStorageLayout API call * address some self and gemini review comments * remove exponential scaling from retry-deadline * add min backoff duration * enhance comment on a function * create a new backoff object every api call * introduce jitter in first retry backoff The algorithm for defining the jitter in the next backoff duration now more closely resembles the algorithm in gax retries. Refer https://github.com/googleapis/gax-go/blob/d68660c4840e4d5aa3f78eee0705e0391082140d/v2/call_option.go#L202 for jittery backoff in gax retries. * rename min backoff to initial backoff * address review comments --- internal/storage/control_client_wrapper.go | 195 +++++++++++++ .../storage/control_client_wrapper_test.go | 260 ++++++++++++++++++ internal/storage/storage_handle.go | 3 + internal/storage/storage_handle_test.go | 14 +- .../storage/storageutil/control_client.go | 1 - 5 files changed, 467 insertions(+), 6 deletions(-) create mode 100644 internal/storage/control_client_wrapper_test.go diff --git a/internal/storage/control_client_wrapper.go b/internal/storage/control_client_wrapper.go index a655189875..049bf39258 100644 --- a/internal/storage/control_client_wrapper.go +++ b/internal/storage/control_client_wrapper.go @@ -16,13 +16,27 @@ package storage import ( "context" + "fmt" + "math/rand" + "time" control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googleapis/gax-go/v2" + "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "google.golang.org/grpc/metadata" ) +const ( + // Default retry parameters for control client calls. + defaultControlClientRetryDeadline = 30 * time.Second + defaultControlClientTotalRetryBudget = 5 * time.Minute + defaultInitialBackoff = 1 * time.Second + defaultMaxBackoff = 1 * time.Minute + defaultBackoffMultiplier = 2.0 +) + type StorageControlClient interface { GetStorageLayout(ctx context.Context, req *controlpb.GetStorageLayoutRequest, @@ -82,3 +96,184 @@ func withBillingProject(controlClient StorageControlClient, billingProject strin } return controlClient } + +// exponentialBackoffConfig is config parameters +// needed to create an exponentialBackoff. +type exponentialBackoffConfig struct { + //Initial duration for next backoff. + initial time.Duration + // Max duration for next back backoff. + max time.Duration + // The rate at which the backoff duration should grow + // over subsequent calls to next(). + multiplier float64 +} + +// exponentialBackoff holds the duration parameters for exponential backoff. +type exponentialBackoff struct { + // config used to create this backoff object. + config exponentialBackoffConfig + // Duration for next backoff. Capped at max. Returned by next(). + next time.Duration +} + +// newExponentialBackoff returns a new exponentialBackoff given +// the config for it. +func newExponentialBackoff(config *exponentialBackoffConfig) *exponentialBackoff { + return &exponentialBackoff{ + config: *config, + next: config.initial, + } +} + +// nextDuration returns the next backoff duration. +func (b *exponentialBackoff) nextDuration() time.Duration { + next := b.next + b.next = min(b.config.max, time.Duration(float64(b.next)*b.config.multiplier)) + return next +} + +// waitWithJitter waits for the next backoff duration with added jitter. +// The jitter adds randomness to the backoff duration to prevent the thundering herd problem. +// This is similar to how gax-retries backoff after each failed retry. +func (b *exponentialBackoff) waitWithJitter(ctx context.Context) error { + nextDuration := b.nextDuration() + jitteryBackoffDuration := time.Duration(1 + rand.Int63n(int64(nextDuration))) + select { + case <-time.After(jitteryBackoffDuration): + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// storageControlClientWithRetry is a wrapper for an existing StorageControlClient object +// which implements gcsfuse-level retry logic if any of the control-client call gets stalled or returns a retryable error. +// It makes time-bound attempts to call the underlying StorageControlClient methods, +// retrying on errors that should be retried according to gcsfuse's retry logic. +type storageControlClientWithRetry struct { + raw StorageControlClient + + // Time-limit on every individual retry attempt. + retryDeadline time.Duration + // Total duration allowed across all the attempts. + totalRetryBudget time.Duration + + // Config for managing backoff durations in-between retry attempts. + backoffConfig exponentialBackoffConfig + + // Whether or not to enable retries for GetStorageLayout call. + enableRetriesOnStorageLayoutCall bool +} + +// executeWithRetry encapsulates the retry logic for control client operations. +// It performs time-bound, exponential backoff retries for a given API call. +// It is expected that the given apiCall returns a structure, and not an HTTP response, +// so that it does not leave behind any trace of a pending operation on server. +func executeWithRetry[T any]( + sccwros *storageControlClientWithRetry, + ctx context.Context, + operationName string, + reqDescription string, + apiCall func(attemptCtx context.Context) (T, error), +) (T, error) { + var zero T + + parentCtx, cancel := context.WithTimeout(ctx, sccwros.totalRetryBudget) + defer cancel() + + // Create a new backoff controller specific to this api call. + backoff := newExponentialBackoff(&sccwros.backoffConfig) + for { + attemptCtx, attemptCancel := context.WithTimeout(parentCtx, sccwros.retryDeadline) + + logger.Tracef("Calling %s for %q with deadline=%v ...", operationName, reqDescription, sccwros.retryDeadline) + result, err := apiCall(attemptCtx) + // Cancel attemptCtx after it is no longer needed to free up its resources. + attemptCancel() + + if err == nil { + return result, nil + } + + // If the error is not retryable, return it immediately. + if !storageutil.ShouldRetry(err) { + return zero, fmt.Errorf("%s for %q failed with a non-retryable error: %w", operationName, reqDescription, err) + } + + // If the parent context is cancelled/timed-out, we should stop retrying. + if parentCtx.Err() != nil { + return zero, fmt.Errorf("%s for %q failed after multiple retries (last server/client error = %v): %w", operationName, reqDescription, err, parentCtx.Err()) + } + + // Do a jittery backoff after each retry. + parentCtxErr := backoff.waitWithJitter(parentCtx) + if parentCtxErr != nil { + return zero, fmt.Errorf("%s for %q failed after multiple retries (last server/client error = %v): %w", operationName, reqDescription, err, parentCtxErr) + } + } +} + +func (sccwros *storageControlClientWithRetry) GetStorageLayout(ctx context.Context, + req *controlpb.GetStorageLayoutRequest, + opts ...gax.CallOption) (*controlpb.StorageLayout, error) { + if !sccwros.enableRetriesOnStorageLayoutCall { + return sccwros.raw.GetStorageLayout(ctx, req, opts...) + } + + apiCall := func(attemptCtx context.Context) (*controlpb.StorageLayout, error) { + return sccwros.raw.GetStorageLayout(attemptCtx, req, opts...) + } + + return executeWithRetry(sccwros, ctx, "GetStorageLayout", req.Name, apiCall) +} + +func (sccwros *storageControlClientWithRetry) DeleteFolder(ctx context.Context, + req *controlpb.DeleteFolderRequest, + opts ...gax.CallOption) error { + return sccwros.raw.DeleteFolder(ctx, req, opts...) +} + +func (sccwros *storageControlClientWithRetry) GetFolder(ctx context.Context, + req *controlpb.GetFolderRequest, + opts ...gax.CallOption) (*controlpb.Folder, error) { + return sccwros.raw.GetFolder(ctx, req, opts...) +} + +func (sccwros *storageControlClientWithRetry) RenameFolder(ctx context.Context, + req *controlpb.RenameFolderRequest, + opts ...gax.CallOption) (*control.RenameFolderOperation, error) { + return sccwros.raw.RenameFolder(ctx, req, opts...) +} + +func (sccwros *storageControlClientWithRetry) CreateFolder(ctx context.Context, + req *controlpb.CreateFolderRequest, + opts ...gax.CallOption) (*controlpb.Folder, error) { + return sccwros.raw.CreateFolder(ctx, req, opts...) +} + +func newRetryWrapper(controlClient StorageControlClient, + retryDeadline, + totalRetryBudget, + initialBackoff, + maxBackoff time.Duration, + backoffMultiplier float64) StorageControlClient { + // Avoid creating a nested wrapper. + raw := controlClient + if sccwros, ok := controlClient.(*storageControlClientWithRetry); ok { + raw = sccwros.raw + } + + return &storageControlClientWithRetry{ + raw: raw, + retryDeadline: retryDeadline, + totalRetryBudget: totalRetryBudget, + backoffConfig: exponentialBackoffConfig{initialBackoff, maxBackoff, backoffMultiplier}, + enableRetriesOnStorageLayoutCall: true, + } +} + +// withRetryOnStorageLayout wraps a StorageControlClient to do a time-bound retry approach for retryable errors for the GetStorageLayout call through it. +func withRetryOnStorageLayout(controlClient StorageControlClient, retryDeadline time.Duration, totalRetryBudget time.Duration) StorageControlClient { + return newRetryWrapper(controlClient, retryDeadline, totalRetryBudget, defaultInitialBackoff, defaultMaxBackoff, defaultBackoffMultiplier) +} diff --git a/internal/storage/control_client_wrapper_test.go b/internal/storage/control_client_wrapper_test.go new file mode 100644 index 0000000000..f64335f600 --- /dev/null +++ b/internal/storage/control_client_wrapper_test.go @@ -0,0 +1,260 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package storage + +import ( + "context" + "testing" + "time" + + "cloud.google.com/go/storage/control/apiv2/controlpb" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + control "cloud.google.com/go/storage/control/apiv2" + "github.com/googleapis/gax-go/v2" +) + +// stallingStorageControlClient is a wrapper that introduces a controllable delay +// to every call, to simulate network latency for testing timeout-based retries. +type stallingStorageControlClient struct { + wrapped StorageControlClient + stallDurationForGetStorageLayout *time.Duration +} + +func (s *stallingStorageControlClient) GetStorageLayout(ctx context.Context, req *controlpb.GetStorageLayoutRequest, opts ...gax.CallOption) (*controlpb.StorageLayout, error) { + if s.stallDurationForGetStorageLayout != nil { + select { + case <-time.After(*s.stallDurationForGetStorageLayout): + case <-ctx.Done(): + return nil, ctx.Err() + } + } + return s.wrapped.GetStorageLayout(ctx, req, opts...) +} + +func (s *stallingStorageControlClient) DeleteFolder(ctx context.Context, req *controlpb.DeleteFolderRequest, opts ...gax.CallOption) error { + return s.wrapped.DeleteFolder(ctx, req, opts...) +} + +func (s *stallingStorageControlClient) GetFolder(ctx context.Context, req *controlpb.GetFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) { + return s.wrapped.GetFolder(ctx, req, opts...) +} + +func (s *stallingStorageControlClient) RenameFolder(ctx context.Context, req *controlpb.RenameFolderRequest, opts ...gax.CallOption) (*control.RenameFolderOperation, error) { + return s.wrapped.RenameFolder(ctx, req, opts...) +} + +func (s *stallingStorageControlClient) CreateFolder(ctx context.Context, req *controlpb.CreateFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) { + return s.wrapped.CreateFolder(ctx, req, opts...) +} + +type ControlClientRetryWrapperTest struct { + suite.Suite + // The raw mock client for setting expectations on return values. + mockRawClient *MockStorageControlClient + ctx context.Context +} + +type StorageLayoutRetryWrapperTest struct { + ControlClientRetryWrapperTest + stallingClient *stallingStorageControlClient + // The simulated execution time for each GetStorageLayout call made through stallingClient. + stallDurationForGetStorageLayout time.Duration +} + +func TestControlClientWrapperTestSuite(t *testing.T) { + t.Run("StorageLayoutRetryWrapperTest", func(t *testing.T) { + suite.Run(t, new(StorageLayoutRetryWrapperTest)) + }) +} + +func (t *ControlClientRetryWrapperTest) SetupTest() { + t.mockRawClient = new(MockStorageControlClient) + t.ctx = context.Background() +} + +func (t *StorageLayoutRetryWrapperTest) SetupTest() { + t.ControlClientRetryWrapperTest.SetupTest() + t.stallDurationForGetStorageLayout = 0 + t.stallingClient = &stallingStorageControlClient{ + wrapped: t.mockRawClient, + stallDurationForGetStorageLayout: &t.stallDurationForGetStorageLayout, + } +} + +func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_SuccessOnFirstAttempt() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2) + req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} + expectedLayout := &controlpb.StorageLayout{Location: "some-location"} + t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(expectedLayout, nil).Once() + + // Act + layout, err := client.GetStorageLayout(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + assert.Equal(t.T(), expectedLayout, layout) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_RetryableErrorThenSuccess() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2) + req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} + expectedLayout := &controlpb.StorageLayout{Location: "some-location"} + retryableErr := status.Error(codes.Unavailable, "try again") + + // First call fails, second succeeds. + t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(nil, retryableErr).Once() + t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(expectedLayout, nil).Once() + + // Act + layout, err := client.GetStorageLayout(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + assert.Equal(t.T(), expectedLayout, layout) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_NonRetryableError() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2) + req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} + nonRetryableErr := status.Error(codes.NotFound, "does not exist") + t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(nil, nonRetryableErr).Once() + + // Act + layout, err := client.GetStorageLayout(t.ctx, req) + + // Assert + assert.Error(t.T(), err) + assert.Nil(t.T(), layout) + assert.Contains(t.T(), err.Error(), "failed with a non-retryable error") + assert.Contains(t.T(), err.Error(), nonRetryableErr.Error()) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_AllAttemptsTimeOut() { + // Arrange + // This test requires different retry parameters, so we create a new client. + client := newRetryWrapper(t.stallingClient, 1000*time.Microsecond, 10000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2) + req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} + // Set stall time to be longer than the attempt timeout. + t.stallDurationForGetStorageLayout = 6000 * time.Microsecond + + // Act + _, err := client.GetStorageLayout(t.ctx, req) + + // The mock should never be called because every attempt will time out. + assert.ErrorIs(t.T(), err, context.DeadlineExceeded) + t.mockRawClient.AssertExpectations(t.T()) +} + +type ExponentialBackoffTest struct { + suite.Suite +} + +func TestExponentialBackoffTestSuite(t *testing.T) { + suite.Run(t, new(ExponentialBackoffTest)) +} + +func (t *ExponentialBackoffTest) TestNewBackoff() { + initial := 1 * time.Second + max := 10 * time.Second + multiplier := 2.0 + + b := newExponentialBackoff(&exponentialBackoffConfig{ + initial: initial, + max: max, + multiplier: multiplier, + }) + + assert.NotNil(t.T(), b) + assert.Equal(t.T(), initial, b.next) + assert.Equal(t.T(), initial, b.config.initial) + assert.Equal(t.T(), max, b.config.max) + assert.Equal(t.T(), multiplier, b.config.multiplier) +} + +func (t *ExponentialBackoffTest) TestNext() { + initial := 1 * time.Second + max := 3 * time.Second + multiplier := 2.0 + b := newExponentialBackoff(&exponentialBackoffConfig{ + initial: initial, + max: max, + multiplier: multiplier, + }) + + // First call to next() should return initial, and update current. + assert.Equal(t.T(), 1*time.Second, b.nextDuration()) + + // Second call. + assert.Equal(t.T(), 2*time.Second, b.nextDuration()) + + // Third call - capped at max. + assert.Equal(t.T(), 3*time.Second, b.nextDuration()) + + // Should stay capped at max. + assert.Equal(t.T(), 3*time.Second, b.nextDuration()) +} + +func (t *ExponentialBackoffTest) TestWaitWithJitter_ContextCancelled() { + initial := 100 * time.Microsecond // A long duration to ensure cancellation happens first. + max := 5 * initial + b := newExponentialBackoff(&exponentialBackoffConfig{ + initial: initial, + max: max, + multiplier: 2.0, + }) + ctx, cancel := context.WithCancel(context.Background()) + // Cancel the context immediately. + cancel() + + start := time.Now() + err := b.waitWithJitter(ctx) + elapsed := time.Since(start) + + assert.ErrorIs(t.T(), err, context.Canceled) + // The function should return almost immediately. + assert.Less(t.T(), elapsed, initial, "waitWithJitter should return quickly when context is cancelled") +} + +func (t *ExponentialBackoffTest) TestWaitWithJitter_NoContextCancelled() { + initial := 10 * time.Millisecond // A short duration to ensure it waits. + max := 5 * initial + b := newExponentialBackoff(&exponentialBackoffConfig{ + initial: initial, + max: max, + multiplier: 2.0, + }) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + start := time.Now() + err := b.waitWithJitter(ctx) + elapsed := time.Since(start) + + assert.NoError(t.T(), err) + // The function should wait for a duration close to initial. + assert.GreaterOrEqual(t.T(), elapsed, 1*time.Millisecond, "waitWithJitter should wait for at least 1ms") + assert.LessOrEqual(t.T(), elapsed, initial*2, "waitWithJitter should not wait excessively long") +} diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index 1134254eed..be889d76e1 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -310,6 +310,9 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien } // special handling for requester-pays buckets and for mounts created with custom billing projects. controlClient = withBillingProject(controlClient, billingProject) + // Wrap the control client with retry-on-stall logic. + // This will retry on only on GetStorageLayout call for all buckets. + controlClient = withRetryOnStorageLayout(controlClient, defaultControlClientRetryDeadline, defaultControlClientTotalRetryBudget) } else { logger.Infof("Skipping storage control client creation because custom-endpoint %q was passed, which is assumed to be a storage testbench server because of 'localhost' in it.", clientConfig.CustomEndpoint) } diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index 004e759b76..fb111b4cf1 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -244,11 +244,15 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithBillingProject() { // Confirm that the returned storage-handle's control-client is of type storageControlClientWithBillingProject // and its billing-project is same as the one passed while // creating the storage-handle. - storageControlClient, ok := storageClient.storageControlClient.(*storageControlClientWithBillingProject) - assert.NotNil(testSuite.T(), storageControlClient) - assert.True(testSuite.T(), ok) - assert.Equal(testSuite.T(), storageControlClient.billingProject, projectID) - assert.NotNil(testSuite.T(), storageControlClient.raw) + // Check that storageControlClient is wrapped correctly and billing project is set. + retrierControlClient, ok := storageClient.storageControlClient.(*storageControlClientWithRetry) + assert.True(testSuite.T(), ok, "retrierControlClient should be of type *storageControlClientWithRetry") + assert.NotNil(testSuite.T(), retrierControlClient, "retrierControlClient should not be nil") + billingProjectControlClient, ok := retrierControlClient.raw.(*storageControlClientWithBillingProject) + assert.True(testSuite.T(), ok, "raw should be of type *storageControlClientWithBillingProject") + assert.NotNil(testSuite.T(), billingProjectControlClient, "storageControlClientWithBillingProject should not be nil") + assert.Equal(testSuite.T(), projectID, billingProjectControlClient.billingProject, "billingProject should match the provided projectID") + assert.NotNil(testSuite.T(), billingProjectControlClient.raw, "raw client inside storageControlClientWithBillingProject should not be nil") } func (testSuite *StorageHandleTest) TestNewStorageHandleWithInvalidClientProtocol() { diff --git a/internal/storage/storageutil/control_client.go b/internal/storage/storageutil/control_client.go index c3961d1f30..6ae42f1029 100644 --- a/internal/storage/storageutil/control_client.go +++ b/internal/storage/storageutil/control_client.go @@ -48,7 +48,6 @@ func storageControlClientRetryOptions(clientConfig *StorageClientConfig) []gax.C func setRetryConfigForFolderAPIs(sc *control.StorageControlClient, clientConfig *StorageClientConfig) { sc.CallOptions.RenameFolder = storageControlClientRetryOptions(clientConfig) sc.CallOptions.GetFolder = storageControlClientRetryOptions(clientConfig) - sc.CallOptions.GetStorageLayout = storageControlClientRetryOptions(clientConfig) sc.CallOptions.CreateFolder = storageControlClientRetryOptions(clientConfig) sc.CallOptions.DeleteFolder = storageControlClientRetryOptions(clientConfig) } From acdf037408d7969cd398e1481055fae0cdad19c5 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 22 Aug 2025 15:25:46 +0530 Subject: [PATCH 0708/1298] Add buffered read package to run_tests (#3726) --- .../run_tests_mounted_directory.sh | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tools/integration_tests/run_tests_mounted_directory.sh b/tools/integration_tests/run_tests_mounted_directory.sh index 4ea62c88f1..c199f9c55d 100755 --- a/tools/integration_tests/run_tests_mounted_directory.sh +++ b/tools/integration_tests/run_tests_mounted_directory.sh @@ -709,3 +709,39 @@ test_case="TestDeleteOperationTest/TestDeleteFileWhenFileIsClobbered" gcsfuse --implicit-dirs --experimental-enable-dentry-cache --metadata-cache-ttl-secs=1000 "$TEST_BUCKET_NAME" "$MOUNT_DIR" GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/dentry_cache/... -p 1 --integrationTest -v --mountedDirectory="$MOUNT_DIR" --testbucket="$TEST_BUCKET_NAME" -run $test_case sudo umount "$MOUNT_DIR" + +# package buffered_read +log_dir="/tmp/gcsfuse_buffered_read_test_logs" +mkdir -p $log_dir +log_file="$log_dir/log.json" + +# Run TestSequentialReadSuite +sequential_read_test_case="TestSequentialReadSuite" +gcsfuse --log-severity=trace --enable-buffered-read=true --read-block-size-mb=8 --read-max-blocks-per-handle=20 --read-start-blocks-per-handle=1 \ +--read-min-blocks-per-handle=2 --log-file=$log_file $TEST_BUCKET_NAME $MOUNT_DIR +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/buffered_read/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR \ +--testbucket=$TEST_BUCKET_NAME -run ${sequential_read_test_case} ${ZONAL_BUCKET_ARG} +sudo umount $MOUNT_DIR + +# Run tests for fallback to another reader on random reads. +random_read_fallback_test_cases=( + "TestFallbackSuites/TestRandomRead_LargeFile_Fallback" + "TestFallbackSuites/TestRandomRead_SmallFile_NoFallback" +) +gcsfuse --log-severity=trace --enable-buffered-read=true --read-block-size-mb=8 --read-max-blocks-per-handle=20 --read-start-blocks-per-handle=2 \ +--read-min-blocks-per-handle=2 --log-file=$log_file $TEST_BUCKET_NAME $MOUNT_DIR +for test_case in "${random_read_fallback_test_cases[@]}"; do + GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/buffered_read/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR \ + --testbucket=$TEST_BUCKET_NAME -run ${test_case} ${ZONAL_BUCKET_ARG} +done +sudo umount $MOUNT_DIR + +# Run test for fallback when the global block pool is insufficient for buffered reader creation. +insufficient_pool_test_case="TestFallbackSuites/TestNewBufferedReader_InsufficientGlobalPool_NoReaderAdded" +gcsfuse --log-severity=trace --enable-buffered-read=true --read-block-size-mb=8 --read-max-blocks-per-handle=10 --read-start-blocks-per-handle=2 \ +--read-min-blocks-per-handle=2 --read-global-max-blocks=1 --log-file=$log_file $TEST_BUCKET_NAME $MOUNT_DIR +GODEBUG=asyncpreemptoff=1 go test ./tools/integration_tests/buffered_read/... -p 1 --integrationTest -v --mountedDirectory=$MOUNT_DIR \ +--testbucket=$TEST_BUCKET_NAME -run ${insufficient_pool_test_case} ${ZONAL_BUCKET_ARG} +sudo umount $MOUNT_DIR + +rm -rf $log_dir From d40e978f8b82bc5859f65015ed15d3ddb93aad8d Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 25 Aug 2025 11:28:07 +0530 Subject: [PATCH 0709/1298] feat(bufferedread): Implement the logic for newly added metrics for buffered read (#3731) * Implement buffered read metrics * Remove metric from read manager and add to buffered reader constructor --- internal/bufferedread/buffered_reader.go | 8 +++++- internal/bufferedread/download_task.go | 28 ++++++++++++++------- internal/bufferedread/download_task_test.go | 19 ++++++++------ internal/monitor/otelexporters.go | 2 +- metrics/metric_handle.go | 4 +-- metrics/metrics.yaml | 6 +++-- metrics/otel_metrics.go | 14 +++++++++-- metrics/otel_metrics_test.go | 18 +++++++++++-- 8 files changed, 72 insertions(+), 27 deletions(-) diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index ed1823f169..123f049e4a 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -122,6 +122,9 @@ func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *Buffere numBlocksToReserve := min(blocksInFile, config.MinBlocksPerHandle) blockpool, err := block.NewPrefetchBlockPool(config.PrefetchBlockSizeBytes, config.MaxPrefetchBlockCnt, numBlocksToReserve, globalMaxBlocksSem) if err != nil { + if errors.Is(err, block.CantAllocateAnyBlockError) { + metricHandle.BufferedReadFallbackTriggerCount(1, "insufficient_memory") + } return nil, fmt.Errorf("NewBufferedReader: creating block-pool: %w", err) } @@ -178,6 +181,7 @@ func (p *BufferedReader) handleRandomRead(offset int64, handleID int64) error { if p.randomSeekCount > p.randomReadsThreshold { logger.Warnf("Fallback to another reader for object %q, handle %d. Random seek count %d exceeded threshold %d.", p.object.Name, handleID, p.randomSeekCount, p.randomReadsThreshold) + p.metricHandle.BufferedReadFallbackTriggerCount(1, "random_read_detected") return gcsx.FallbackToAnotherReader } @@ -280,6 +284,7 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) defer func() { dur := time.Since(start) + p.metricHandle.BufferedReadReadLatency(ctx, dur) if err == nil || errors.Is(err, io.EOF) { logger.Tracef("%.13v -> ReadAt(): Ok(%v)", reqID, dur) } @@ -298,6 +303,7 @@ func (p *BufferedReader) ReadAt(ctx context.Context, inputBuf []byte, off int64) if p.blockQueue.IsEmpty() { if err = p.freshStart(off); err != nil { logger.Warnf("Fallback to another reader for object %q, handle %d, due to freshStart failure: %v", p.object.Name, handleID, err) + p.metricHandle.BufferedReadFallbackTriggerCount(1, "insufficient_memory") return resp, gcsx.FallbackToAnotherReader } prefetchTriggered = true @@ -458,7 +464,7 @@ func (p *BufferedReader) scheduleBlockWithIndex(b block.PrefetchBlock, blockInde } ctx, cancel := context.WithCancel(p.ctx) - task := NewDownloadTask(ctx, p.object, p.bucket, b, p.readHandle) + task := NewDownloadTask(ctx, p.object, p.bucket, b, p.readHandle, p.metricHandle) logger.Tracef("Scheduling block: (%s, %d, %t).", p.object.Name, blockIndex, urgent) p.blockQueue.Push(&blockQueueEntry{ diff --git a/internal/bufferedread/download_task.go b/internal/bufferedread/download_task.go index 9d12c9d5b2..017f2bcf27 100644 --- a/internal/bufferedread/download_task.go +++ b/internal/bufferedread/download_task.go @@ -25,12 +25,14 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" ) type DownloadTask struct { workerpool.Task - object *gcs.MinObject - bucket gcs.Bucket + object *gcs.MinObject + bucket gcs.Bucket + metricHandle metrics.MetricHandle // block is the block to which the data will be downloaded. block block.PrefetchBlock @@ -42,13 +44,14 @@ type DownloadTask struct { readHandle []byte } -func NewDownloadTask(ctx context.Context, object *gcs.MinObject, bucket gcs.Bucket, block block.PrefetchBlock, readHandle []byte) *DownloadTask { +func NewDownloadTask(ctx context.Context, object *gcs.MinObject, bucket gcs.Bucket, block block.PrefetchBlock, readHandle []byte, metricHandle metrics.MetricHandle) *DownloadTask { return &DownloadTask{ - ctx: ctx, - object: object, - bucket: bucket, - block: block, - readHandle: readHandle, + ctx: ctx, + object: object, + bucket: bucket, + block: block, + readHandle: readHandle, + metricHandle: metricHandle, } } @@ -65,16 +68,23 @@ func (p *DownloadTask) Execute() { stime := time.Now() var err error defer func() { + var status string + dur := time.Since(stime) if err == nil { - logger.Tracef("Download: -> block (%s, %v) Ok(%v).", p.object.Name, blockId, time.Since(stime)) + status = "successful" + logger.Tracef("Download: -> block (%s, %v) Ok(%v).", p.object.Name, blockId, dur) p.block.NotifyReady(block.BlockStatus{State: block.BlockStateDownloaded}) } else if errors.Is(err, context.Canceled) && p.ctx.Err() == context.Canceled { + status = "cancelled" logger.Tracef("Download: -> block (%s, %v) cancelled: %v.", p.object.Name, blockId, err) p.block.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadFailed, Err: err}) } else { + status = "failed" logger.Errorf("Download: -> block (%s, %v) failed: %v.", p.object.Name, blockId, err) p.block.NotifyReady(block.BlockStatus{State: block.BlockStateDownloadFailed, Err: err}) } + p.metricHandle.BufferedReadDownloadBlockLatency(p.ctx, dur, status) + p.metricHandle.BufferedReadScheduledBlockCount(1, status) }() start := uint64(startOff) diff --git a/internal/bufferedread/download_task_test.go b/internal/bufferedread/download_task_test.go index 5f9df5539c..6977da0949 100644 --- a/internal/bufferedread/download_task_test.go +++ b/internal/bufferedread/download_task_test.go @@ -28,6 +28,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" testutil "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" + "github.com/googlecloudplatform/gcsfuse/v3/metrics" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -42,9 +43,10 @@ const ( type DownloadTaskTestSuite struct { workerpool.Task suite.Suite - object *gcs.MinObject - mockBucket *storage.TestifyMockBucket - blockPool *block.GenBlockPool[block.PrefetchBlock] + object *gcs.MinObject + mockBucket *storage.TestifyMockBucket + blockPool *block.GenBlockPool[block.PrefetchBlock] + metricHandle metrics.MetricHandle } func TestDownloadTaskTestSuite(t *testing.T) { @@ -60,6 +62,7 @@ func (dts *DownloadTaskTestSuite) SetupTest() { dts.mockBucket = new(storage.TestifyMockBucket) var err error dts.blockPool, err = block.NewPrefetchBlockPool(testBlockSize, 10, 1, semaphore.NewWeighted(100)) + dts.metricHandle = metrics.NewNoopMetrics() require.NoError(dts.T(), err, "Failed to create block pool") } @@ -74,7 +77,7 @@ func (dts *DownloadTaskTestSuite) TestExecuteSuccess() { require.Nil(dts.T(), err) err = downloadBlock.SetAbsStartOff(0) require.Nil(dts.T(), err) - task := NewDownloadTask(context.Background(), dts.object, dts.mockBucket, downloadBlock, nil) + task := NewDownloadTask(context.Background(), dts.object, dts.mockBucket, downloadBlock, nil, dts.metricHandle) testContent := testutil.GenerateRandomBytes(testBlockSize) rc := &fake.FakeReader{ReadCloser: getReadCloser(testContent)} readObjectRequest := &gcs.ReadObjectRequest{ @@ -105,7 +108,7 @@ func (dts *DownloadTaskTestSuite) TestExecuteError() { require.Nil(dts.T(), err) err = downloadBlock.SetAbsStartOff(0) require.Nil(dts.T(), err) - task := NewDownloadTask(context.Background(), dts.object, dts.mockBucket, downloadBlock, nil) + task := NewDownloadTask(context.Background(), dts.object, dts.mockBucket, downloadBlock, nil, dts.metricHandle) readObjectRequest := &gcs.ReadObjectRequest{ Name: dts.object.Name, Generation: dts.object.Generation, @@ -134,7 +137,7 @@ func (dts *DownloadTaskTestSuite) TestExecuteContextDeadlineExceededByServerTrea require.Nil(dts.T(), err) taskCtx, taskCancelFunc := context.WithTimeout(context.Background(), 1*time.Millisecond) defer taskCancelFunc() // Ensure the context is cancelled after the test. - task := NewDownloadTask(taskCtx, dts.object, dts.mockBucket, downloadBlock, nil) + task := NewDownloadTask(taskCtx, dts.object, dts.mockBucket, downloadBlock, nil, dts.metricHandle) readObjectRequest := &gcs.ReadObjectRequest{ Name: dts.object.Name, Generation: dts.object.Generation, @@ -163,7 +166,7 @@ func (dts *DownloadTaskTestSuite) TestExecuteContextCancelledWhileReaderCreation err = downloadBlock.SetAbsStartOff(0) require.Nil(dts.T(), err) taskCtx, taskCancelFunc := context.WithCancel(context.TODO()) - task := NewDownloadTask(taskCtx, dts.object, dts.mockBucket, downloadBlock, nil) + task := NewDownloadTask(taskCtx, dts.object, dts.mockBucket, downloadBlock, nil, dts.metricHandle) rc := &fake.FakeReader{ReadCloser: getReadCloser(nil)} // No content since context is cancelled readObjectRequest := &gcs.ReadObjectRequest{ Name: dts.object.Name, @@ -208,7 +211,7 @@ func (dts *DownloadTaskTestSuite) TestExecuteContextCancelledWhileReadingFromRea err = downloadBlock.SetAbsStartOff(0) require.Nil(dts.T(), err) taskCtx, taskCancelFunc := context.WithCancel(context.TODO()) - task := NewDownloadTask(taskCtx, dts.object, dts.mockBucket, downloadBlock, nil) + task := NewDownloadTask(taskCtx, dts.object, dts.mockBucket, downloadBlock, nil, dts.metricHandle) rc := &fake.FakeReader{ReadCloser: new(ctxCancelledReader)} readObjectRequest := &gcs.ReadObjectRequest{ Name: dts.object.Name, diff --git a/internal/monitor/otelexporters.go b/internal/monitor/otelexporters.go index 1d637049ab..f8887bd93b 100644 --- a/internal/monitor/otelexporters.go +++ b/internal/monitor/otelexporters.go @@ -40,7 +40,7 @@ import ( const serviceName = "gcsfuse" const cloudMonitoringMetricPrefix = "custom.googleapis.com/gcsfuse/" -var allowedMetricPrefixes = []string{"fs/", "gcs/", "file_cache/"} +var allowedMetricPrefixes = []string{"fs/", "gcs/", "file_cache/", "buffered_read/"} // SetupOTelMetricExporters sets up the metrics exporters func SetupOTelMetricExporters(ctx context.Context, c *cfg.Config) (shutdownFn common.ShutdownFn) { diff --git a/metrics/metric_handle.go b/metrics/metric_handle.go index 82cae15366..2420f8550a 100644 --- a/metrics/metric_handle.go +++ b/metrics/metric_handle.go @@ -24,7 +24,7 @@ import ( // The methods of this interface are auto-generated from metrics.yaml. // Each method corresponds to a metric defined in metrics.yaml. type MetricHandle interface { - // BufferedReadDownloadBlockLatency - The cumulative distribution of block download latencies, along with status: successful or cancelled. + // BufferedReadDownloadBlockLatency - The cumulative distribution of block download latencies, along with status: successful, cancelled, or failed. BufferedReadDownloadBlockLatency(ctx context.Context, duration time.Duration, status string) // BufferedReadFallbackTriggerCount - The cumulative number of times the BufferedReader falls back to a different reader, along with the reason: random_read_detected or insufficient_memory. @@ -33,7 +33,7 @@ type MetricHandle interface { // BufferedReadReadLatency - The cumulative distribution of latencies for ReadAt calls served by the buffered reader. BufferedReadReadLatency(ctx context.Context, duration time.Duration) - // BufferedReadScheduledBlockCount - The cumulative number of scheduled download blocks, along with their final status: successful or cancelled. + // BufferedReadScheduledBlockCount - The cumulative number of scheduled download blocks, along with their final status: successful, cancelled, or failed. BufferedReadScheduledBlockCount(inc int64, status string) // FileCacheReadBytesCount - The cumulative number of bytes read from file cache along with read type - Sequential/Random diff --git a/metrics/metrics.yaml b/metrics/metrics.yaml index ac9a5800b7..1a4686cc59 100644 --- a/metrics/metrics.yaml +++ b/metrics/metrics.yaml @@ -1,5 +1,5 @@ - metric-name: "buffered_read/download_block_latency" - description: "The cumulative distribution of block download latencies, along with status: successful or cancelled." + description: "The cumulative distribution of block download latencies, along with status: successful, cancelled, or failed." unit: "us" type: "int_histogram" boundaries: &common_boundaries @@ -43,6 +43,7 @@ values: - "successful" - "cancelled" + - "failed" - metric-name: "buffered_read/fallback_trigger_count" description: "The cumulative number of times the BufferedReader falls back to a different reader, along with the reason: random_read_detected or insufficient_memory." @@ -61,7 +62,7 @@ boundaries: *common_boundaries - metric-name: "buffered_read/scheduled_block_count" - description: "The cumulative number of scheduled download blocks, along with their final status: successful or cancelled." + description: "The cumulative number of scheduled download blocks, along with their final status: successful, cancelled, or failed." type: "int_counter" attributes: - attribute-name: status @@ -69,6 +70,7 @@ values: - "successful" - "cancelled" + - "failed" - metric-name: "file_cache/read_bytes_count" description: "The cumulative number of bytes read from file cache along with read type - Sequential/Random" diff --git a/metrics/otel_metrics.go b/metrics/otel_metrics.go index 203961c982..3fd5ffcdca 100644 --- a/metrics/otel_metrics.go +++ b/metrics/otel_metrics.go @@ -34,10 +34,12 @@ const logInterval = 5 * time.Minute var ( unrecognizedAttr atomic.Value bufferedReadDownloadBlockLatencyStatusCancelledAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("status", "cancelled"))) + bufferedReadDownloadBlockLatencyStatusFailedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("status", "failed"))) bufferedReadDownloadBlockLatencyStatusSuccessfulAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("status", "successful"))) bufferedReadFallbackTriggerCountReasonInsufficientMemoryAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("reason", "insufficient_memory"))) bufferedReadFallbackTriggerCountReasonRandomReadDetectedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("reason", "random_read_detected"))) bufferedReadScheduledBlockCountStatusCancelledAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("status", "cancelled"))) + bufferedReadScheduledBlockCountStatusFailedAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("status", "failed"))) bufferedReadScheduledBlockCountStatusSuccessfulAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("status", "successful"))) fileCacheReadBytesCountReadTypeParallelAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Parallel"))) fileCacheReadBytesCountReadTypeRandomAttrSet = metric.WithAttributeSet(attribute.NewSet(attribute.String("read_type", "Random"))) @@ -672,6 +674,7 @@ type otelMetrics struct { bufferedReadFallbackTriggerCountReasonInsufficientMemoryAtomic *atomic.Int64 bufferedReadFallbackTriggerCountReasonRandomReadDetectedAtomic *atomic.Int64 bufferedReadScheduledBlockCountStatusCancelledAtomic *atomic.Int64 + bufferedReadScheduledBlockCountStatusFailedAtomic *atomic.Int64 bufferedReadScheduledBlockCountStatusSuccessfulAtomic *atomic.Int64 fileCacheReadBytesCountReadTypeParallelAtomic *atomic.Int64 fileCacheReadBytesCountReadTypeRandomAtomic *atomic.Int64 @@ -1253,6 +1256,8 @@ func (o *otelMetrics) BufferedReadDownloadBlockLatency( switch status { case "cancelled": record = histogramRecord{ctx: ctx, instrument: o.bufferedReadDownloadBlockLatency, value: latency.Microseconds(), attributes: bufferedReadDownloadBlockLatencyStatusCancelledAttrSet} + case "failed": + record = histogramRecord{ctx: ctx, instrument: o.bufferedReadDownloadBlockLatency, value: latency.Microseconds(), attributes: bufferedReadDownloadBlockLatencyStatusFailedAttrSet} case "successful": record = histogramRecord{ctx: ctx, instrument: o.bufferedReadDownloadBlockLatency, value: latency.Microseconds(), attributes: bufferedReadDownloadBlockLatencyStatusSuccessfulAttrSet} default: @@ -1296,6 +1301,8 @@ func (o *otelMetrics) BufferedReadScheduledBlockCount( switch status { case "cancelled": o.bufferedReadScheduledBlockCountStatusCancelledAtomic.Add(inc) + case "failed": + o.bufferedReadScheduledBlockCountStatusFailedAtomic.Add(inc) case "successful": o.bufferedReadScheduledBlockCountStatusSuccessfulAtomic.Add(inc) default: @@ -2807,6 +2814,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr bufferedReadFallbackTriggerCountReasonRandomReadDetectedAtomic atomic.Int64 var bufferedReadScheduledBlockCountStatusCancelledAtomic, + bufferedReadScheduledBlockCountStatusFailedAtomic, bufferedReadScheduledBlockCountStatusSuccessfulAtomic atomic.Int64 var fileCacheReadBytesCountReadTypeParallelAtomic, @@ -3387,7 +3395,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr gcsRetryCountRetryErrorCategorySTALLEDREADREQUESTAtomic atomic.Int64 bufferedReadDownloadBlockLatency, err0 := meter.Int64Histogram("buffered_read/download_block_latency", - metric.WithDescription("The cumulative distribution of block download latencies, along with status: successful or cancelled."), + metric.WithDescription("The cumulative distribution of block download latencies, along with status: successful, cancelled, or failed."), metric.WithUnit("us"), metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) @@ -3406,10 +3414,11 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr metric.WithExplicitBucketBoundaries(1, 2, 3, 4, 5, 6, 8, 10, 13, 16, 20, 25, 30, 40, 50, 65, 80, 100, 130, 160, 200, 250, 300, 400, 500, 650, 800, 1000, 2000, 5000, 10000, 20000, 50000, 100000)) _, err3 := meter.Int64ObservableCounter("buffered_read/scheduled_block_count", - metric.WithDescription("The cumulative number of scheduled download blocks, along with their final status: successful or cancelled."), + metric.WithDescription("The cumulative number of scheduled download blocks, along with their final status: successful, cancelled, or failed."), metric.WithUnit(""), metric.WithInt64Callback(func(_ context.Context, obsrv metric.Int64Observer) error { conditionallyObserve(obsrv, &bufferedReadScheduledBlockCountStatusCancelledAtomic, bufferedReadScheduledBlockCountStatusCancelledAttrSet) + conditionallyObserve(obsrv, &bufferedReadScheduledBlockCountStatusFailedAtomic, bufferedReadScheduledBlockCountStatusFailedAttrSet) conditionallyObserve(obsrv, &bufferedReadScheduledBlockCountStatusSuccessfulAtomic, bufferedReadScheduledBlockCountStatusSuccessfulAttrSet) return nil })) @@ -4079,6 +4088,7 @@ func NewOTelMetrics(ctx context.Context, workers int, bufferSize int) (*otelMetr bufferedReadFallbackTriggerCountReasonRandomReadDetectedAtomic: &bufferedReadFallbackTriggerCountReasonRandomReadDetectedAtomic, bufferedReadReadLatency: bufferedReadReadLatency, bufferedReadScheduledBlockCountStatusCancelledAtomic: &bufferedReadScheduledBlockCountStatusCancelledAtomic, + bufferedReadScheduledBlockCountStatusFailedAtomic: &bufferedReadScheduledBlockCountStatusFailedAtomic, bufferedReadScheduledBlockCountStatusSuccessfulAtomic: &bufferedReadScheduledBlockCountStatusSuccessfulAtomic, fileCacheReadBytesCountReadTypeParallelAtomic: &fileCacheReadBytesCountReadTypeParallelAtomic, fileCacheReadBytesCountReadTypeRandomAtomic: &fileCacheReadBytesCountReadTypeRandomAtomic, diff --git a/metrics/otel_metrics_test.go b/metrics/otel_metrics_test.go index 3dfc22311e..11c2202456 100644 --- a/metrics/otel_metrics_test.go +++ b/metrics/otel_metrics_test.go @@ -139,6 +139,11 @@ func TestBufferedReadDownloadBlockLatency(t *testing.T) { latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, status: "cancelled", }, + { + name: "status_failed", + latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, + status: "failed", + }, { name: "status_successful", latencies: []time.Duration{100 * time.Microsecond, 200 * time.Microsecond}, @@ -273,6 +278,15 @@ func TestBufferedReadScheduledBlockCount(t *testing.T) { attribute.NewSet(attribute.String("status", "cancelled")): 5, }, }, + { + name: "status_failed", + f: func(m *otelMetrics) { + m.BufferedReadScheduledBlockCount(5, "failed") + }, + expected: map[attribute.Set]int64{ + attribute.NewSet(attribute.String("status", "failed")): 5, + }, + }, { name: "status_successful", f: func(m *otelMetrics) { @@ -285,11 +299,11 @@ func TestBufferedReadScheduledBlockCount(t *testing.T) { name: "multiple_attributes_summed", f: func(m *otelMetrics) { m.BufferedReadScheduledBlockCount(5, "cancelled") - m.BufferedReadScheduledBlockCount(2, "successful") + m.BufferedReadScheduledBlockCount(2, "failed") m.BufferedReadScheduledBlockCount(3, "cancelled") }, expected: map[attribute.Set]int64{attribute.NewSet(attribute.String("status", "cancelled")): 8, - attribute.NewSet(attribute.String("status", "successful")): 2, + attribute.NewSet(attribute.String("status", "failed")): 2, }, }, } From a49233fa2b1f1b3ea115342e1cb5c5b3edc4c0d3 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Mon, 25 Aug 2025 13:55:49 +0530 Subject: [PATCH 0710/1298] feat: Retry stalled Folder API calls for zonal buckets (#3684) * add retry-stall timeout for folder APIs and their tests * call -> API for consistency * remove stall from stallRetry everywhere * address gemini comment * remove gax-based retries from folder API calls from ZB add back gax-retries for non-zb for folder APIs empty commit temp wip wip wip final code change and debug logs empty commit * fix rebase issues * rename storageControlClientRetryOptions -> storageControlClientGaxRetryOptions * remove debug logs and add useful comments * address some self-comments * address more self-comments * address some self-review comments * add more unit tests * enhance existing unit tests * refactor BucketHandle() and add more unit tests to it * empty commit --- internal/storage/control_client_wrapper.go | 133 +++- .../storage/control_client_wrapper_test.go | 644 +++++++++++++++++- internal/storage/storage_handle.go | 53 +- internal/storage/storage_handle_test.go | 304 ++++++++- .../storage/storageutil/control_client.go | 31 - .../storageutil/control_client_test.go | 8 - 6 files changed, 1087 insertions(+), 86 deletions(-) diff --git a/internal/storage/control_client_wrapper.go b/internal/storage/control_client_wrapper.go index 049bf39258..9cf9c4a69e 100644 --- a/internal/storage/control_client_wrapper.go +++ b/internal/storage/control_client_wrapper.go @@ -25,6 +25,7 @@ import ( "github.com/googleapis/gax-go/v2" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" ) @@ -32,9 +33,7 @@ const ( // Default retry parameters for control client calls. defaultControlClientRetryDeadline = 30 * time.Second defaultControlClientTotalRetryBudget = 5 * time.Minute - defaultInitialBackoff = 1 * time.Second - defaultMaxBackoff = 1 * time.Minute - defaultBackoffMultiplier = 2.0 + defaultInitialBackoff = 1 * time.Millisecond ) type StorageControlClient interface { @@ -102,7 +101,7 @@ func withBillingProject(controlClient StorageControlClient, billingProject strin type exponentialBackoffConfig struct { //Initial duration for next backoff. initial time.Duration - // Max duration for next back backoff. + // Max duration for next backoff. max time.Duration // The rate at which the backoff duration should grow // over subsequent calls to next(). @@ -163,7 +162,9 @@ type storageControlClientWithRetry struct { backoffConfig exponentialBackoffConfig // Whether or not to enable retries for GetStorageLayout call. - enableRetriesOnStorageLayoutCall bool + enableRetriesOnStorageLayoutAPI bool + // Whether or not to enable retries for folder APIs. + enableRetriesOnFolderAPIs bool } // executeWithRetry encapsulates the retry logic for control client operations. @@ -217,7 +218,7 @@ func executeWithRetry[T any]( func (sccwros *storageControlClientWithRetry) GetStorageLayout(ctx context.Context, req *controlpb.GetStorageLayoutRequest, opts ...gax.CallOption) (*controlpb.StorageLayout, error) { - if !sccwros.enableRetriesOnStorageLayoutCall { + if !sccwros.enableRetriesOnStorageLayoutAPI { return sccwros.raw.GetStorageLayout(ctx, req, opts...) } @@ -231,33 +232,74 @@ func (sccwros *storageControlClientWithRetry) GetStorageLayout(ctx context.Conte func (sccwros *storageControlClientWithRetry) DeleteFolder(ctx context.Context, req *controlpb.DeleteFolderRequest, opts ...gax.CallOption) error { - return sccwros.raw.DeleteFolder(ctx, req, opts...) + if !sccwros.enableRetriesOnFolderAPIs { + return sccwros.raw.DeleteFolder(ctx, req, opts...) + } + + apiCall := func(attemptCtx context.Context) (any, error) { + err := sccwros.raw.DeleteFolder(attemptCtx, req, opts...) + return struct{}{}, err + } + + _, err := executeWithRetry(sccwros, ctx, "DeleteFolder", req.Name, apiCall) + return err } func (sccwros *storageControlClientWithRetry) GetFolder(ctx context.Context, req *controlpb.GetFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) { - return sccwros.raw.GetFolder(ctx, req, opts...) + if !sccwros.enableRetriesOnFolderAPIs { + return sccwros.raw.GetFolder(ctx, req, opts...) + } + + apiCall := func(attemptCtx context.Context) (*controlpb.Folder, error) { + return sccwros.raw.GetFolder(attemptCtx, req, opts...) + } + + return executeWithRetry(sccwros, ctx, "GetFolder", req.Name, apiCall) } func (sccwros *storageControlClientWithRetry) RenameFolder(ctx context.Context, req *controlpb.RenameFolderRequest, opts ...gax.CallOption) (*control.RenameFolderOperation, error) { - return sccwros.raw.RenameFolder(ctx, req, opts...) + if !sccwros.enableRetriesOnFolderAPIs { + return sccwros.raw.RenameFolder(ctx, req, opts...) + } + + apiCall := func(attemptCtx context.Context) (*control.RenameFolderOperation, error) { + return sccwros.raw.RenameFolder(attemptCtx, req, opts...) + } + + reqDescription := fmt.Sprintf("%q to %q", req.Name, req.DestinationFolderId) + return executeWithRetry(sccwros, ctx, "RenameFolder", reqDescription, apiCall) } func (sccwros *storageControlClientWithRetry) CreateFolder(ctx context.Context, req *controlpb.CreateFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) { - return sccwros.raw.CreateFolder(ctx, req, opts...) + if !sccwros.enableRetriesOnFolderAPIs { + return sccwros.raw.CreateFolder(ctx, req, opts...) + } + + apiCall := func(attemptCtx context.Context) (*controlpb.Folder, error) { + return sccwros.raw.CreateFolder(attemptCtx, req, opts...) + } + + reqDescription := fmt.Sprintf("%q in %q", req.FolderId, req.Parent) + return executeWithRetry(sccwros, ctx, "CreateFolder", reqDescription, apiCall) } +// newRetryWrapper creates a new StorageControlClient with retry capabilities. +// It accepts various parameters to configure the retry behavior. +// The returned control client retries storage-layout. +// It also retries folder-related APIs if specified. func newRetryWrapper(controlClient StorageControlClient, retryDeadline, totalRetryBudget, initialBackoff, maxBackoff time.Duration, - backoffMultiplier float64) StorageControlClient { + backoffMultiplier float64, + retryFolderAPIs bool) StorageControlClient { // Avoid creating a nested wrapper. raw := controlClient if sccwros, ok := controlClient.(*storageControlClientWithRetry); ok { @@ -265,15 +307,70 @@ func newRetryWrapper(controlClient StorageControlClient, } return &storageControlClientWithRetry{ - raw: raw, - retryDeadline: retryDeadline, - totalRetryBudget: totalRetryBudget, - backoffConfig: exponentialBackoffConfig{initialBackoff, maxBackoff, backoffMultiplier}, - enableRetriesOnStorageLayoutCall: true, + raw: raw, + retryDeadline: retryDeadline, + totalRetryBudget: totalRetryBudget, + backoffConfig: exponentialBackoffConfig{initialBackoff, maxBackoff, backoffMultiplier}, + enableRetriesOnStorageLayoutAPI: true, + enableRetriesOnFolderAPIs: retryFolderAPIs, } } +// withRetryOnAllAPIs wraps a StorageControlClient to do a time-bound retry approach for retryable errors for all API calls through it. +func withRetryOnAllAPIs(controlClient StorageControlClient, + clientConfig *storageutil.StorageClientConfig) StorageControlClient { + return newRetryWrapper(controlClient, + defaultControlClientRetryDeadline, + defaultControlClientTotalRetryBudget, + defaultInitialBackoff, + clientConfig.MaxRetrySleep, + clientConfig.RetryMultiplier, + true) +} + // withRetryOnStorageLayout wraps a StorageControlClient to do a time-bound retry approach for retryable errors for the GetStorageLayout call through it. -func withRetryOnStorageLayout(controlClient StorageControlClient, retryDeadline time.Duration, totalRetryBudget time.Duration) StorageControlClient { - return newRetryWrapper(controlClient, retryDeadline, totalRetryBudget, defaultInitialBackoff, defaultMaxBackoff, defaultBackoffMultiplier) +func withRetryOnStorageLayout(controlClient StorageControlClient, + clientConfig *storageutil.StorageClientConfig) StorageControlClient { + return newRetryWrapper(controlClient, + defaultControlClientRetryDeadline, + defaultControlClientTotalRetryBudget, + defaultInitialBackoff, + clientConfig.MaxRetrySleep, + clientConfig.RetryMultiplier, false) +} + +func storageControlClientGaxRetryOptions(clientConfig *storageutil.StorageClientConfig) []gax.CallOption { + return []gax.CallOption{ + gax.WithTimeout(defaultControlClientTotalRetryBudget), + gax.WithRetry(func() gax.Retryer { + return gax.OnCodes([]codes.Code{ + codes.ResourceExhausted, + codes.Unavailable, + codes.DeadlineExceeded, + codes.Internal, + codes.Unknown, + }, gax.Backoff{ + Max: clientConfig.MaxRetrySleep, + Multiplier: clientConfig.RetryMultiplier, + }) + }), + } +} + +func withGaxRetriesForFolderAPIs(rawControlClientWithoutGaxRetries *control.StorageControlClient, + clientConfig *storageutil.StorageClientConfig) *control.StorageControlClient { + if rawControlClientWithoutGaxRetries == nil || clientConfig == nil { + return rawControlClientWithoutGaxRetries + } + + // Create a copy of passed rawControlClientWithoutGaxRetries. + rawControlClientWithGaxRetries := *rawControlClientWithoutGaxRetries + rawControlClientWithGaxRetries.CallOptions = &control.StorageControlCallOptions{} + + gaxRetryOptions := storageControlClientGaxRetryOptions(clientConfig) + rawControlClientWithGaxRetries.CallOptions.RenameFolder = gaxRetryOptions + rawControlClientWithGaxRetries.CallOptions.GetFolder = gaxRetryOptions + rawControlClientWithGaxRetries.CallOptions.CreateFolder = gaxRetryOptions + rawControlClientWithGaxRetries.CallOptions.DeleteFolder = gaxRetryOptions + return &rawControlClientWithGaxRetries } diff --git a/internal/storage/control_client_wrapper_test.go b/internal/storage/control_client_wrapper_test.go index f64335f600..4e71352398 100644 --- a/internal/storage/control_client_wrapper_test.go +++ b/internal/storage/control_client_wrapper_test.go @@ -22,12 +22,14 @@ import ( "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" control "cloud.google.com/go/storage/control/apiv2" "github.com/googleapis/gax-go/v2" + "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" ) // stallingStorageControlClient is a wrapper that introduces a controllable delay @@ -35,6 +37,7 @@ import ( type stallingStorageControlClient struct { wrapped StorageControlClient stallDurationForGetStorageLayout *time.Duration + stallDurationForFolderAPIs *time.Duration } func (s *stallingStorageControlClient) GetStorageLayout(ctx context.Context, req *controlpb.GetStorageLayoutRequest, opts ...gax.CallOption) (*controlpb.StorageLayout, error) { @@ -49,58 +52,106 @@ func (s *stallingStorageControlClient) GetStorageLayout(ctx context.Context, req } func (s *stallingStorageControlClient) DeleteFolder(ctx context.Context, req *controlpb.DeleteFolderRequest, opts ...gax.CallOption) error { + if s.stallDurationForFolderAPIs != nil { + select { + case <-time.After(*s.stallDurationForFolderAPIs): + case <-ctx.Done(): + return ctx.Err() + } + } return s.wrapped.DeleteFolder(ctx, req, opts...) } func (s *stallingStorageControlClient) GetFolder(ctx context.Context, req *controlpb.GetFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) { + if s.stallDurationForFolderAPIs != nil { + select { + case <-time.After(*s.stallDurationForFolderAPIs): + case <-ctx.Done(): + return nil, ctx.Err() + } + } return s.wrapped.GetFolder(ctx, req, opts...) } func (s *stallingStorageControlClient) RenameFolder(ctx context.Context, req *controlpb.RenameFolderRequest, opts ...gax.CallOption) (*control.RenameFolderOperation, error) { + if s.stallDurationForFolderAPIs != nil { + select { + case <-time.After(*s.stallDurationForFolderAPIs): + case <-ctx.Done(): + return nil, ctx.Err() + } + } return s.wrapped.RenameFolder(ctx, req, opts...) } func (s *stallingStorageControlClient) CreateFolder(ctx context.Context, req *controlpb.CreateFolderRequest, opts ...gax.CallOption) (*controlpb.Folder, error) { + if s.stallDurationForFolderAPIs != nil { + select { + case <-time.After(*s.stallDurationForFolderAPIs): + case <-ctx.Done(): + return nil, ctx.Err() + } + } return s.wrapped.CreateFolder(ctx, req, opts...) } type ControlClientRetryWrapperTest struct { suite.Suite // The raw mock client for setting expectations on return values. - mockRawClient *MockStorageControlClient - ctx context.Context + mockRawClient *MockStorageControlClient + ctx context.Context + stallingClient *stallingStorageControlClient + // The simulated execution time for each GetStorageLayout call made through stallingClient. + stallDurationForGetStorageLayout time.Duration } type StorageLayoutRetryWrapperTest struct { ControlClientRetryWrapperTest - stallingClient *stallingStorageControlClient - // The simulated execution time for each GetStorageLayout call made through stallingClient. - stallDurationForGetStorageLayout time.Duration +} + +type AllApiRetryWrapperTest struct { + ControlClientRetryWrapperTest + // The execution time for each folder API call made through stallingClient. Can be adjusted + // per test. + stallDurationForFolderAPIs time.Duration } func TestControlClientWrapperTestSuite(t *testing.T) { t.Run("StorageLayoutRetryWrapperTest", func(t *testing.T) { suite.Run(t, new(StorageLayoutRetryWrapperTest)) }) + t.Run("AllApiRetryWrapperTest", func(t *testing.T) { + suite.Run(t, new(AllApiRetryWrapperTest)) + }) } func (t *ControlClientRetryWrapperTest) SetupTest() { t.mockRawClient = new(MockStorageControlClient) t.ctx = context.Background() + t.stallDurationForGetStorageLayout = 0 } func (t *StorageLayoutRetryWrapperTest) SetupTest() { t.ControlClientRetryWrapperTest.SetupTest() - t.stallDurationForGetStorageLayout = 0 t.stallingClient = &stallingStorageControlClient{ wrapped: t.mockRawClient, stallDurationForGetStorageLayout: &t.stallDurationForGetStorageLayout, } } +func (t *AllApiRetryWrapperTest) SetupTest() { + t.ControlClientRetryWrapperTest.SetupTest() + t.stallDurationForFolderAPIs = 0 + t.stallingClient = &stallingStorageControlClient{ + wrapped: t.mockRawClient, + stallDurationForGetStorageLayout: &t.stallDurationForGetStorageLayout, + stallDurationForFolderAPIs: &t.stallDurationForFolderAPIs, + } +} + func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_SuccessOnFirstAttempt() { // Arrange - client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2) + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2, false) req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} expectedLayout := &controlpb.StorageLayout{Location: "some-location"} t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(expectedLayout, nil).Once() @@ -116,11 +167,10 @@ func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_SuccessOnFirstAttem func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_RetryableErrorThenSuccess() { // Arrange - client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2) + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2, false) req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} expectedLayout := &controlpb.StorageLayout{Location: "some-location"} retryableErr := status.Error(codes.Unavailable, "try again") - // First call fails, second succeeds. t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(nil, retryableErr).Once() t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(expectedLayout, nil).Once() @@ -136,7 +186,7 @@ func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_RetryableErrorThenS func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_NonRetryableError() { // Arrange - client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2) + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2, false) req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} nonRetryableErr := status.Error(codes.NotFound, "does not exist") t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(nil, nonRetryableErr).Once() @@ -155,7 +205,7 @@ func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_NonRetryableError() func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_AllAttemptsTimeOut() { // Arrange // This test requires different retry parameters, so we create a new client. - client := newRetryWrapper(t.stallingClient, 1000*time.Microsecond, 10000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2) + client := newRetryWrapper(t.stallingClient, 1000*time.Microsecond, 10000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2, false) req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} // Set stall time to be longer than the attempt timeout. t.stallDurationForGetStorageLayout = 6000 * time.Microsecond @@ -168,6 +218,492 @@ func (t *StorageLayoutRetryWrapperTest) TestGetStorageLayout_AllAttemptsTimeOut( t.mockRawClient.AssertExpectations(t.T()) } +func (t *StorageLayoutRetryWrapperTest) TestGetFolder_IsNotRetried() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2, false) + req := &controlpb.GetFolderRequest{Name: "some/folder"} + retryableErr := status.Error(codes.Unavailable, "try again") + // Mock the raw client to return a retryable error once. + t.mockRawClient.On("GetFolder", mock.Anything, req, mock.Anything).Return(nil, retryableErr).Once() + + // Act + folder, err := client.GetFolder(t.ctx, req) + + // Assert + assert.Error(t.T(), err) + assert.Nil(t.T(), folder) + assert.Equal(t.T(), retryableErr, err) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *StorageLayoutRetryWrapperTest) TestDeleteFolder_IsNotRetried() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2, false) + req := &controlpb.DeleteFolderRequest{Name: "some/folder"} + retryableErr := status.Error(codes.Unavailable, "try again") + // Mock the raw client to return a retryable error once. + t.mockRawClient.On("DeleteFolder", mock.Anything, req, mock.Anything).Return(retryableErr).Once() + + // Act + err := client.DeleteFolder(t.ctx, req) + + // Assert + assert.Error(t.T(), err) + assert.Equal(t.T(), retryableErr, err) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *StorageLayoutRetryWrapperTest) TestCreateFolder_IsNotRetried() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2, false) + req := &controlpb.CreateFolderRequest{Parent: "some/", FolderId: "folder"} + retryableErr := status.Error(codes.Unavailable, "try again") + // Mock the raw client to return a retryable error once. + t.mockRawClient.On("CreateFolder", mock.Anything, req, mock.Anything).Return(nil, retryableErr).Once() + + // Act + folder, err := client.CreateFolder(t.ctx, req) + + // Assert + assert.Error(t.T(), err) + assert.Nil(t.T(), folder) + assert.Equal(t.T(), retryableErr, err) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *StorageLayoutRetryWrapperTest) TestRenameFolder_IsNotRetried() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, 1*time.Microsecond, 10*time.Microsecond, 2, false) + req := &controlpb.RenameFolderRequest{Name: "some/folder", DestinationFolderId: "new/folder"} + retryableErr := status.Error(codes.Unavailable, "try again") + // Mock the raw client to return a retryable error once. + t.mockRawClient.On("RenameFolder", mock.Anything, req, mock.Anything).Return(nil, retryableErr).Once() + + // Act + op, err := client.RenameFolder(t.ctx, req) + + // Assert + assert.Error(t.T(), err) + assert.Nil(t.T(), op) + assert.Equal(t.T(), retryableErr, err) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestGetStorageLayout_SuccessOnFirstAttempt() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} + expectedLayout := &controlpb.StorageLayout{Location: "some-location"} + t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(expectedLayout, nil).Once() + + // Act + layout, err := client.GetStorageLayout(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + assert.Equal(t.T(), expectedLayout, layout) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestGetStorageLayout_RetryableErrorThenSuccess() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} + expectedLayout := &controlpb.StorageLayout{Location: "some-location"} + retryableErr := status.Error(codes.Unavailable, "try again") + // First call fails, second succeeds. + t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(nil, retryableErr).Once() + t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(expectedLayout, nil).Once() + + // Act + layout, err := client.GetStorageLayout(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + assert.Equal(t.T(), expectedLayout, layout) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestGetStorageLayout_NonRetryableError() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} + nonRetryableErr := status.Error(codes.NotFound, "does not exist") + t.mockRawClient.On("GetStorageLayout", mock.Anything, req, mock.Anything).Return(nil, nonRetryableErr).Once() + + // Act + layout, err := client.GetStorageLayout(t.ctx, req) + + // Assert + assert.Error(t.T(), err) + assert.Nil(t.T(), layout) + assert.Contains(t.T(), err.Error(), "failed with a non-retryable error") + assert.Contains(t.T(), err.Error(), nonRetryableErr.Error()) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestGetStorageLayout_AllAttemptsTimeOut() { + // Arrange + client := newRetryWrapper(t.stallingClient, 1000*time.Microsecond, 10000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.GetStorageLayoutRequest{Name: "some/bucket"} + // Set stall time to be longer than the attempt timeout. + t.stallDurationForGetStorageLayout = 6000 * time.Microsecond + + // Act + _, err := client.GetStorageLayout(t.ctx, req) + + // The mock should never be called because every attempt will time out. + assert.ErrorIs(t.T(), err, context.DeadlineExceeded) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestDeleteFolder_SuccessOnFirstAttempt() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.DeleteFolderRequest{Name: "some/folder"} + t.mockRawClient.On("DeleteFolder", mock.Anything, req, mock.Anything).Return(nil).Once() + + // Act + err := client.DeleteFolder(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestDeleteFolder_RetryableErrorThenSuccess() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.DeleteFolderRequest{Name: "some/folder"} + retryableErr := status.Error(codes.Unavailable, "try again") + // First call fails, second succeeds. + t.mockRawClient.On("DeleteFolder", mock.Anything, req, mock.Anything).Return(retryableErr).Once() + t.mockRawClient.On("DeleteFolder", mock.Anything, req, mock.Anything).Return(nil).Once() + + // Act + err := client.DeleteFolder(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestDeleteFolder_NonRetryableError() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.DeleteFolderRequest{Name: "some/folder"} + nonRetryableErr := status.Error(codes.NotFound, "does not exist") + t.mockRawClient.On("DeleteFolder", mock.Anything, req, mock.Anything).Return(nonRetryableErr).Once() + + // Act + err := client.DeleteFolder(t.ctx, req) + + // Assert + assert.Error(t.T(), err) + assert.Contains(t.T(), err.Error(), "failed with a non-retryable error") + assert.Contains(t.T(), err.Error(), nonRetryableErr.Error()) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestDeleteFolder_AllAttemptsTimeOut() { + // Arrange + client := newRetryWrapper(t.stallingClient, 1000*time.Microsecond, 10000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.DeleteFolderRequest{Name: "some/folder"} + // Set stall time to be longer than the attempt timeout. + t.stallDurationForFolderAPIs = 6000 * time.Microsecond + + // Act + err := client.DeleteFolder(t.ctx, req) + + // The mock should never be called because every attempt will time out. + assert.Error(t.T(), err) + assert.ErrorIs(t.T(), err, context.DeadlineExceeded) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestGetFolder_SuccessOnFirstAttempt() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.GetFolderRequest{Name: "some/folder"} + expectedFolder := &controlpb.Folder{Name: "some/folder"} + t.mockRawClient.On("GetFolder", mock.Anything, req, mock.Anything).Return(expectedFolder, nil).Once() + + // Act + folder, err := client.GetFolder(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + assert.Equal(t.T(), expectedFolder, folder) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestGetFolder_RetryableErrorThenSuccess() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.GetFolderRequest{Name: "some/folder"} + expectedFolder := &controlpb.Folder{Name: "some/folder"} + retryableErr := status.Error(codes.Unavailable, "try again") + // First call fails, second succeeds. + t.mockRawClient.On("GetFolder", mock.Anything, req, mock.Anything).Return(nil, retryableErr).Once() + t.mockRawClient.On("GetFolder", mock.Anything, req, mock.Anything).Return(expectedFolder, nil).Once() + + // Act + folder, err := client.GetFolder(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + assert.Equal(t.T(), expectedFolder, folder) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestGetFolder_NonRetryableError() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.GetFolderRequest{Name: "some/folder"} + nonRetryableErr := status.Error(codes.NotFound, "does not exist") + t.mockRawClient.On("GetFolder", mock.Anything, req, mock.Anything).Return(nil, nonRetryableErr).Once() + + // Act + folder, err := client.GetFolder(t.ctx, req) + + // Assert + assert.Error(t.T(), err) + assert.Nil(t.T(), folder) + assert.Contains(t.T(), err.Error(), "failed with a non-retryable error") + assert.Contains(t.T(), err.Error(), nonRetryableErr.Error()) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestGetFolder_AllAttemptsTimeOut() { + // Arrange + client := newRetryWrapper(t.stallingClient, 1000*time.Microsecond, 10000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.GetFolderRequest{Name: "some/folder"} + // Set execution time to be longer than the attempt timeout. + t.stallDurationForFolderAPIs = 6000 * time.Microsecond + + // Act + _, err := client.GetFolder(t.ctx, req) + + // Assert: The mock should never be called because every attempt will time out. + assert.Error(t.T(), err) + assert.ErrorIs(t.T(), err, context.DeadlineExceeded) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestRenameFolder_SuccessOnFirstAttempt() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.RenameFolderRequest{Name: "some/folder", DestinationFolderId: "new/folder"} + expectedOp := &control.RenameFolderOperation{} + t.mockRawClient.On("RenameFolder", mock.Anything, req, mock.Anything).Return(expectedOp, nil).Once() + + // Act + op, err := client.RenameFolder(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + assert.Equal(t.T(), expectedOp, op) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestRenameFolder_RetryableErrorThenSuccess() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.RenameFolderRequest{Name: "some/folder", DestinationFolderId: "new/folder"} + expectedOp := &control.RenameFolderOperation{} + retryableErr := status.Error(codes.Unavailable, "try again") + // First call fails, second succeeds. + t.mockRawClient.On("RenameFolder", mock.Anything, req, mock.Anything).Return(nil, retryableErr).Once() + t.mockRawClient.On("RenameFolder", mock.Anything, req, mock.Anything).Return(expectedOp, nil).Once() + + // Act + op, err := client.RenameFolder(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + assert.Equal(t.T(), expectedOp, op) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestRenameFolder_NonRetryableError() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.RenameFolderRequest{Name: "some/folder", DestinationFolderId: "new/folder"} + nonRetryableErr := status.Error(codes.NotFound, "does not exist") + t.mockRawClient.On("RenameFolder", mock.Anything, req, mock.Anything).Return(nil, nonRetryableErr).Once() + + // Act + op, err := client.RenameFolder(t.ctx, req) + + // Assert + assert.Error(t.T(), err) + assert.Nil(t.T(), op) + assert.Contains(t.T(), err.Error(), "failed with a non-retryable error") + assert.Contains(t.T(), err.Error(), nonRetryableErr.Error()) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestRenameFolder_AllAttemptsTimeOut() { + // Arrange + client := newRetryWrapper(t.stallingClient, 1000*time.Microsecond, 10000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.RenameFolderRequest{Name: "some/folder", DestinationFolderId: "new/folder"} + // Set execution time to be longer than the attempt timeout. + t.stallDurationForFolderAPIs = 6000 * time.Microsecond + + // Act + _, err := client.RenameFolder(t.ctx, req) + + // Assert: The mock should never be called because every attempt will time out. + assert.Error(t.T(), err) + assert.ErrorIs(t.T(), err, context.DeadlineExceeded) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestCreateFolder_SuccessOnFirstAttempt() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.CreateFolderRequest{Parent: "some/", FolderId: "folder"} + expectedFolder := &controlpb.Folder{Name: "some/folder"} + t.mockRawClient.On("CreateFolder", mock.Anything, req, mock.Anything).Return(expectedFolder, nil).Once() + + // Act + folder, err := client.CreateFolder(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + assert.Equal(t.T(), expectedFolder, folder) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestCreateFolder_RetryableErrorThenSuccess() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.CreateFolderRequest{Parent: "some/", FolderId: "folder"} + expectedFolder := &controlpb.Folder{Name: "some/folder"} + retryableErr := status.Error(codes.Unavailable, "try again") + // First call fails, second succeeds. + t.mockRawClient.On("CreateFolder", mock.Anything, req, mock.Anything).Return(nil, retryableErr).Once() + t.mockRawClient.On("CreateFolder", mock.Anything, req, mock.Anything).Return(expectedFolder, nil).Once() + + // Act + folder, err := client.CreateFolder(t.ctx, req) + + // Assert + assert.NoError(t.T(), err) + assert.Equal(t.T(), expectedFolder, folder) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestCreateFolder_NonRetryableError() { + // Arrange + client := newRetryWrapper(t.stallingClient, 100*time.Microsecond, 1000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.CreateFolderRequest{Parent: "some/", FolderId: "folder"} + nonRetryableErr := status.Error(codes.NotFound, "does not exist") + t.mockRawClient.On("CreateFolder", mock.Anything, req, mock.Anything).Return(nil, nonRetryableErr).Once() + + // Act + folder, err := client.CreateFolder(t.ctx, req) + + // Assert + assert.Error(t.T(), err) + assert.Nil(t.T(), folder) + assert.Contains(t.T(), err.Error(), "failed with a non-retryable error") + assert.Contains(t.T(), err.Error(), nonRetryableErr.Error()) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (t *AllApiRetryWrapperTest) TestCreateFolder_AllAttemptsTimeOut() { + // Arrange + client := newRetryWrapper(t.stallingClient, 1000*time.Microsecond, 10000*time.Microsecond, time.Microsecond, 10*time.Microsecond, 2, true) + req := &controlpb.CreateFolderRequest{Parent: "some/", FolderId: "folder"} + // Set execution time to be longer than the attempt timeout. + t.stallDurationForFolderAPIs = 6000 * time.Microsecond + + // Act + _, err := client.CreateFolder(t.ctx, req) + + // Assert: The mock should never be called because every attempt will time out. + assert.Error(t.T(), err) + assert.ErrorIs(t.T(), err, context.DeadlineExceeded) + t.mockRawClient.AssertExpectations(t.T()) +} + +func (testSuite *StorageLayoutRetryWrapperTest) TestWithRetryOnStorageLayout_WrapsClient() { + // Arrange + mockClient := new(MockStorageControlClient) + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + + // Act + wrappedClient := withRetryOnStorageLayout(mockClient, &clientConfig) + + // Assert + require.NotNil(testSuite.T(), wrappedClient) + retryWrapper, ok := wrappedClient.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok, "The returned client should be of type *storageControlClientWithRetry") + assert.Same(testSuite.T(), mockClient, retryWrapper.raw) + assert.True(testSuite.T(), retryWrapper.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout APIs") + assert.False(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should not be enabled for folder APIs") +} + +func (testSuite *StorageLayoutRetryWrapperTest) TestWithRetryOnStorageLayout_UnwrapsNestedRetryClient() { + // Arrange + mockClient := new(MockStorageControlClient) + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + // Create a client that is already wrapped. + alreadyWrappedClient := withRetryOnStorageLayout(mockClient, &clientConfig) + + // Act + // Wrap it again. + doubleWrappedClient := withRetryOnStorageLayout(alreadyWrappedClient, &clientConfig) + + // Assert + require.NotNil(testSuite.T(), doubleWrappedClient) + retryWrapper, ok := doubleWrappedClient.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok, "The returned client should be of type *storageControlClientWithRetry") + assert.Same(testSuite.T(), mockClient, retryWrapper.raw, "Should unwrap the nested retry client") + assert.NotSame(testSuite.T(), alreadyWrappedClient, retryWrapper.raw) + assert.True(testSuite.T(), retryWrapper.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout APIs") + assert.False(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should not be enabled for folder APIs") +} + +func (testSuite *AllApiRetryWrapperTest) TestWithRetryOnAllAPIs_WrapsClient() { + // Arrange + mockClient := new(MockStorageControlClient) + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + + // Act + wrappedClient := withRetryOnAllAPIs(mockClient, &clientConfig) + + // Assert + require.NotNil(testSuite.T(), wrappedClient) + retryWrapper, ok := wrappedClient.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok, "The returned client should be of type *storageControlClientWithRetry") + assert.Same(testSuite.T(), mockClient, retryWrapper.raw) + assert.True(testSuite.T(), retryWrapper.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout APIs") + assert.True(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should be enabled for folder APIs") +} + +func (testSuite *AllApiRetryWrapperTest) TestWithRetryOnAllAPIs_UnwrapsNestedRetryClient() { + // Arrange + mockClient := new(MockStorageControlClient) + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + // Create a client that is already wrapped. + alreadyWrappedClient := withRetryOnAllAPIs(mockClient, &clientConfig) + + // Act + // Wrap it again. + doubleWrappedClient := withRetryOnAllAPIs(alreadyWrappedClient, &clientConfig) + + // Assert + require.NotNil(testSuite.T(), doubleWrappedClient) + retryWrapper, ok := doubleWrappedClient.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok, "The returned client should be of type *storageControlClientWithRetry") + assert.Same(testSuite.T(), mockClient, retryWrapper.raw, "Should unwrap the nested retry client") + assert.NotSame(testSuite.T(), alreadyWrappedClient, retryWrapper.raw) + assert.True(testSuite.T(), retryWrapper.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout APIs") + assert.True(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should be enabled for folder APIs") +} + type ExponentialBackoffTest struct { suite.Suite } @@ -239,7 +775,8 @@ func (t *ExponentialBackoffTest) TestWaitWithJitter_ContextCancelled() { } func (t *ExponentialBackoffTest) TestWaitWithJitter_NoContextCancelled() { - initial := 10 * time.Millisecond // A short duration to ensure it waits. + initial := 1000 * time.Microsecond // A short duration to ensure it waits. Making it any shorter can cause random failures + // because context cancel itself takes about a millisecond. max := 5 * initial b := newExponentialBackoff(&exponentialBackoffConfig{ initial: initial, @@ -255,6 +792,87 @@ func (t *ExponentialBackoffTest) TestWaitWithJitter_NoContextCancelled() { assert.NoError(t.T(), err) // The function should wait for a duration close to initial. - assert.GreaterOrEqual(t.T(), elapsed, 1*time.Millisecond, "waitWithJitter should wait for at least 1ms") assert.LessOrEqual(t.T(), elapsed, initial*2, "waitWithJitter should not wait excessively long") } + +type ControlClientGaxRetryWrapperTest struct { + suite.Suite +} + +func TestControlClientGaxRetryWrapperTestSuite(t *testing.T) { + suite.Run(t, new(ControlClientGaxRetryWrapperTest)) +} + +func (testSuite *ControlClientGaxRetryWrapperTest) TestStorageControlClientGaxRetryOptions() { + // Arrange + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + + // Act + gaxOpts := storageControlClientGaxRetryOptions(&clientConfig) + + // Assert + require.NotEmpty(testSuite.T(), gaxOpts) + require.Len(testSuite.T(), gaxOpts, 2) +} + +func (testSuite *ControlClientGaxRetryWrapperTest) TestWithGaxRetriesForFolderAPIs_NilRawControlClient() { + // Arrange + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + + // Act + result := withGaxRetriesForFolderAPIs(nil, &clientConfig) + + // Assert + require.Nil(testSuite.T(), result) +} + +func (testSuite *ControlClientGaxRetryWrapperTest) TestWithGaxRetriesForFolderAPIs_NilClientConfig() { + // Arrange + rawClient := &control.StorageControlClient{} + + // Act + result := withGaxRetriesForFolderAPIs(rawClient, nil) + + // Assert + require.Equal(testSuite.T(), rawClient, result) +} + +func (testSuite *ControlClientGaxRetryWrapperTest) TestWithGaxRetriesForFolderAPIs_ReturnsNewClient() { + // Arrange + rawClient := &control.StorageControlClient{} + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + + // Act + result := withGaxRetriesForFolderAPIs(rawClient, &clientConfig) + + // Assert + require.NotNil(testSuite.T(), result) + assert.NotSame(testSuite.T(), rawClient, result) + require.NotNil(testSuite.T(), result.CallOptions) + assert.NotSame(testSuite.T(), rawClient.CallOptions, result.CallOptions) // Should be a new CallOptions object + assert.Nil(testSuite.T(), result.CallOptions.GetStorageLayout) // GetStorageLayout should not have GAX retries applied + assert.NotNil(testSuite.T(), result.CallOptions.DeleteFolder) // DeleteFolder should have GAX retries applied + assert.NotNil(testSuite.T(), result.CallOptions.GetFolder) // GetFolder should have GAX retries applied + assert.NotNil(testSuite.T(), result.CallOptions.CreateFolder) // CreateFolder should have GAX retries applied + assert.NotNil(testSuite.T(), result.CallOptions.RenameFolder) // RenameFolder should have GAX retries applied +} + +func (testSuite *ControlClientGaxRetryWrapperTest) TestWithGaxRetriesForFolderAPIs_AppliesGaxOptions() { + // Arrange + rawClient := &control.StorageControlClient{} + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + + // Act + result := withGaxRetriesForFolderAPIs(rawClient, &clientConfig) + + // Assert + require.NotNil(testSuite.T(), result.CallOptions.RenameFolder) + assert.NotNil(testSuite.T(), result.CallOptions.GetFolder) + require.NotNil(testSuite.T(), result.CallOptions.CreateFolder) + assert.NotNil(testSuite.T(), result.CallOptions.DeleteFolder) + assert.Nil(testSuite.T(), result.CallOptions.GetStorageLayout) // GetStorageLayout should not have GAX retries applied + assert.NotNil(testSuite.T(), result.CallOptions.DeleteFolder) // DeleteFolder should have GAX retries applied + assert.NotNil(testSuite.T(), result.CallOptions.GetFolder) // GetFolder should have GAX retries applied + assert.NotNil(testSuite.T(), result.CallOptions.CreateFolder) // CreateFolder should have GAX retries applied + assert.NotNil(testSuite.T(), result.CallOptions.RenameFolder) // RenameFolder should have GAX retries applied +} diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index be889d76e1..6c0ef3413d 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -23,6 +23,7 @@ import ( "time" "cloud.google.com/go/storage" + control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" "cloud.google.com/go/storage/experimental" "github.com/googleapis/gax-go/v2" @@ -65,8 +66,11 @@ type storageClient struct { grpcClient *storage.Client grpcClientWithBidiConfig *storage.Client clientConfig storageutil.StorageClientConfig - storageControlClient StorageControlClient - directPathDetector *gRPCDirectPathDetector + // rawStorageControlClient is without any retries and without any handling for billing project. + rawStorageControlClient *control.StorageControlClient + // storageControlClient is with retry for GetStorageLayout and with handling for billing project. + storageControlClient StorageControlClient + directPathDetector *gRPCDirectPathDetector } type gRPCDirectPathDetector struct { @@ -295,6 +299,7 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien // The default protocol for the Go Storage control client's folders API is gRPC. // gcsfuse will initially mirror this behavior due to the client's lack of HTTP support. var controlClient StorageControlClient + var rawStorageControlClient *control.StorageControlClient var clientOpts []option.ClientOption // Control-client is needed for folder APIs and for getting storage-layout of the bucket. @@ -304,23 +309,24 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien if err != nil { return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) } - controlClient, err = storageutil.CreateGRPCControlClient(ctx, clientOpts, &clientConfig) + rawStorageControlClient, err = storageutil.CreateGRPCControlClient(ctx, clientOpts, &clientConfig) if err != nil { return nil, fmt.Errorf("could not create StorageControl Client: %w", err) } - // special handling for requester-pays buckets and for mounts created with custom billing projects. - controlClient = withBillingProject(controlClient, billingProject) + // special handling for mounts created with custom billing projects. + controlClientWithBillingProject := withBillingProject(rawStorageControlClient, billingProject) // Wrap the control client with retry-on-stall logic. // This will retry on only on GetStorageLayout call for all buckets. - controlClient = withRetryOnStorageLayout(controlClient, defaultControlClientRetryDeadline, defaultControlClientTotalRetryBudget) + controlClient = withRetryOnStorageLayout(controlClientWithBillingProject, &clientConfig) } else { logger.Infof("Skipping storage control client creation because custom-endpoint %q was passed, which is assumed to be a storage testbench server because of 'localhost' in it.", clientConfig.CustomEndpoint) } sh = &storageClient{ - storageControlClient: controlClient, - clientConfig: clientConfig, - directPathDetector: &gRPCDirectPathDetector{clientOptions: clientOpts}, + rawStorageControlClient: rawStorageControlClient, + storageControlClient: controlClient, + clientConfig: clientConfig, + directPathDetector: &gRPCDirectPathDetector{clientOptions: clientOpts}, } return } @@ -351,6 +357,31 @@ func (sh *storageClient) getClient(ctx context.Context, isbucketZonal bool) (*st return nil, fmt.Errorf("invalid client-protocol requested: %s", sh.clientConfig.ClientProtocol) } +// controlClientForBucketHandle returns a storage control client for the given bucket handle, +// which takes care of properly adding support for retries and for billing project. +func (sh *storageClient) controlClientForBucketHandle(bucketType *gcs.BucketType, billingProject string) StorageControlClient { + if sh.storageControlClient == nil { + return nil + } + + if bucketType.Zonal { + // sh.storageControlClient already contains handling for billing project, + // and enhanced retries for GetStorageLayout API call. Extending it here for + // retries for folder APIs. + // For zonal buckets, wrap the control client with retry-on-all-APIs. + return withRetryOnAllAPIs(sh.storageControlClient, &sh.clientConfig) + } else { + // Apply GAX retries to the raw storage control client and returns a copy of it, + // as it is important to avoid overwriting it, + // as it is used with enhanced retries used by zonal buckets. + rawControlClientWithGaxFolderAPIRetries := withGaxRetriesForFolderAPIs(sh.rawStorageControlClient, &sh.clientConfig) + rawControlClientWithAllAPIRetries := withRetryOnStorageLayout(rawControlClientWithGaxFolderAPIRetries, &sh.clientConfig) + // Special handling for mounts created with custom billing projects. + // Wrap it with billing-project, if there is any. + return withBillingProject(rawControlClientWithAllAPIRetries, billingProject) + } +} + func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, billingProject string, enableRapidAppends bool) (bh *bucketHandle, err error) { var client *storage.Client bucketType, err := sh.lookupBucketType(bucketName) @@ -374,15 +405,15 @@ func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, bi } storageBucketHandle := client.Bucket(bucketName) - if billingProject != "" { storageBucketHandle = storageBucketHandle.UserProject(billingProject) } + controlClient := sh.controlClientForBucketHandle(bucketType, billingProject) bh = &bucketHandle{ bucket: storageBucketHandle, bucketName: bucketName, - controlClient: sh.storageControlClient, + controlClient: controlClient, bucketType: bucketType, enableRapidAppends: enableRapidAppends, } diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index fb111b4cf1..e7111879c2 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -21,6 +21,7 @@ import ( "testing" "time" + control "cloud.google.com/go/storage/control/apiv2" "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googlecloudplatform/gcsfuse/v3/cfg" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" @@ -230,27 +231,53 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWhenJsonReadEnabled() { assert.NotNil(testSuite.T(), handleCreated) } +func (testSuite *StorageHandleTest) TestNewStorageHandleWithoutBillingProject() { + // Arrange. + sc := storageutil.GetDefaultStorageClientConfig(keyFile) + sc.EnableHNS = true + + // Act. + handleCreated, err := NewStorageHandle(testSuite.ctx, sc, "") + + // Assert. + assert.Nil(testSuite.T(), err) + assert.NotNil(testSuite.T(), handleCreated) + storageClient, ok := handleCreated.(*storageClient) + assert.NotNil(testSuite.T(), storageClient) + assert.True(testSuite.T(), ok) + // Confirm that the returned storage-handle's control-client is of type storageControlClientWithRetry + retrierControlClient, ok := storageClient.storageControlClient.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok, "retrierControlClient should be of type *storageControlClientWithRetry") + require.NotNil(testSuite.T(), retrierControlClient, "retrierControlClient should not be nil") + assert.True(testSuite.T(), retrierControlClient.enableRetriesOnStorageLayoutAPI, "enableRetriesOnStorageLayoutAPI should be true") + assert.False(testSuite.T(), retrierControlClient.enableRetriesOnFolderAPIs, "enableRetriesOnFolderAPIs should be false") + // Confirm that it has no underlying storageControlClientWithBillingProject in it. + _, ok = retrierControlClient.raw.(*storageControlClientWithBillingProject) + assert.False(testSuite.T(), ok, "raw should be of type *storageControlClientWithBillingProject") +} + func (testSuite *StorageHandleTest) TestNewStorageHandleWithBillingProject() { + // Arrange. sc := storageutil.GetDefaultStorageClientConfig(keyFile) sc.EnableHNS = true + // Act. handleCreated, err := NewStorageHandle(testSuite.ctx, sc, projectID) + // Assert. assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), handleCreated) storageClient, ok := handleCreated.(*storageClient) assert.NotNil(testSuite.T(), storageClient) assert.True(testSuite.T(), ok) + retrierControlClient := storageClient.storageControlClient.(*storageControlClientWithRetry) // Confirm that the returned storage-handle's control-client is of type storageControlClientWithBillingProject // and its billing-project is same as the one passed while // creating the storage-handle. // Check that storageControlClient is wrapped correctly and billing project is set. - retrierControlClient, ok := storageClient.storageControlClient.(*storageControlClientWithRetry) - assert.True(testSuite.T(), ok, "retrierControlClient should be of type *storageControlClientWithRetry") - assert.NotNil(testSuite.T(), retrierControlClient, "retrierControlClient should not be nil") billingProjectControlClient, ok := retrierControlClient.raw.(*storageControlClientWithBillingProject) - assert.True(testSuite.T(), ok, "raw should be of type *storageControlClientWithBillingProject") - assert.NotNil(testSuite.T(), billingProjectControlClient, "storageControlClientWithBillingProject should not be nil") + require.True(testSuite.T(), ok, "raw should be of type *storageControlClientWithBillingProject") + require.NotNil(testSuite.T(), billingProjectControlClient, "storageControlClientWithBillingProject should not be nil") assert.Equal(testSuite.T(), projectID, billingProjectControlClient.billingProject, "billingProject should match the provided projectID") assert.NotNil(testSuite.T(), billingProjectControlClient.raw, "raw client inside storageControlClientWithBillingProject should not be nil") } @@ -756,3 +783,270 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithMaxRetryAttemptsNotZ assert.NotNil(testSuite.T(), handleCreated) } } + +func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NilControlClient() { + // Arrange + sh := &storageClient{} // storageControlClient is nil by default + + // Act + controlClient := sh.controlClientForBucketHandle(&gcs.BucketType{}, "") + + // Assert + assert.Nil(testSuite.T(), controlClient) +} + +func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_ZonalBucket_NoBillingProject() { + // Arrange + mockRawControlClient := &control.StorageControlClient{} + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + mockControlClient := withRetryOnStorageLayout(mockRawControlClient, &clientConfig) + sh := &storageClient{ + storageControlClient: mockControlClient, + rawStorageControlClient: mockRawControlClient, + clientConfig: clientConfig, + } + bucketType := &gcs.BucketType{Zonal: true} + + // Act + controlClient := sh.controlClientForBucketHandle(bucketType, "") + + // Assert + require.NotNil(testSuite.T(), controlClient) + retryWrapper, ok := controlClient.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok, "Expected a retry wrapper for zonal bucket") + assert.True(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should be enabled for all APIs on zonal buckets") + assert.Same(testSuite.T(), mockRawControlClient, retryWrapper.raw, "Expected raw client to be the same in the wrapped client.") + assert.NotSame(testSuite.T(), mockControlClient, retryWrapper) + assert.True(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should be enabled for folder APIs on zonal buckets") + assert.True(testSuite.T(), retryWrapper.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout APIs on zonal buckets") +} + +func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_ZonalBucket_WithBillingProject() { + // Arrange + billingProject := "test-project" + mockRawControlClient := &control.StorageControlClient{} + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + mockControlClient := withBillingProject(mockRawControlClient, billingProject) + mockControlClient = withRetryOnStorageLayout(mockControlClient, &clientConfig) + sh := &storageClient{ + storageControlClient: mockControlClient, + rawStorageControlClient: mockRawControlClient, + clientConfig: clientConfig, + } + bucketType := &gcs.BucketType{Zonal: true} + + // Act + controlClient := sh.controlClientForBucketHandle(bucketType, billingProject) + + // Assert + require.NotNil(testSuite.T(), controlClient) + retryWrapper, ok := controlClient.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok, "Expected a retry wrapper for zonal bucket") + assert.True(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should be enabled for folder APIs on zonal buckets") + assert.True(testSuite.T(), retryWrapper.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout APIs on zonal buckets") + assert.True(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should be enabled for all APIs on zonal buckets") + assert.NotSame(testSuite.T(), mockRawControlClient, retryWrapper.raw) + assert.NotSame(testSuite.T(), mockControlClient, retryWrapper) + billingProjectWrapper, ok := retryWrapper.raw.(*storageControlClientWithBillingProject) + require.True(testSuite.T(), ok, "Expected a billing project wrapper") + assert.Equal(testSuite.T(), billingProject, billingProjectWrapper.billingProject) + assert.Same(testSuite.T(), mockRawControlClient, billingProjectWrapper.raw) +} + +func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBucket_WithoutBillingProject() { + // Arrange + mockRawControlClient := &control.StorageControlClient{} + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + mockControlClient := withBillingProject(mockRawControlClient, "") + mockControlClient = withRetryOnStorageLayout(mockControlClient, &clientConfig) + sh := &storageClient{ + storageControlClient: mockControlClient, + rawStorageControlClient: mockRawControlClient, + clientConfig: clientConfig, + } + bucketType := &gcs.BucketType{Zonal: false} + + // Act + controlClient := sh.controlClientForBucketHandle(bucketType, "") + + // Assert + require.NotNil(testSuite.T(), controlClient) + // It should be the raw client with GAX retries, not the billing project wrapper. + _, isBillingWrapper := controlClient.(*storageControlClientWithBillingProject) + assert.False(testSuite.T(), isBillingWrapper) + // It should be an enhanced storage control client with retries for GetStorageLayout. + controlClientWithStorageLayoutRetries, ok := controlClient.(*storageControlClientWithRetry) + assert.True(testSuite.T(), ok, "Expected a control client with retry") + assert.NotNil(testSuite.T(), controlClientWithStorageLayoutRetries) + assert.True(testSuite.T(), controlClientWithStorageLayoutRetries.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout API on non-zonal buckets") + assert.False(testSuite.T(), controlClientWithStorageLayoutRetries.enableRetriesOnFolderAPIs, "Retries should not be enabled for folder APIs on non-zonal buckets") + // Check if it's the GAX-retries-added client + gaxClient, ok := controlClientWithStorageLayoutRetries.raw.(*control.StorageControlClient) + require.True(testSuite.T(), ok) + assert.NotNil(testSuite.T(), gaxClient.CallOptions) + assert.NotNil(testSuite.T(), gaxClient.CallOptions.CreateFolder) + assert.NotNil(testSuite.T(), gaxClient.CallOptions.GetFolder) + assert.NotNil(testSuite.T(), gaxClient.CallOptions.DeleteFolder) + assert.NotNil(testSuite.T(), gaxClient.CallOptions.RenameFolder) + assert.Nil(testSuite.T(), gaxClient.CallOptions.GetStorageLayout) +} + +func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBucket_WithBillingProject() { + // Arrange + billingProject := "test-project" + mockRawControlClient := &control.StorageControlClient{} + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + mockControlClient := withBillingProject(mockRawControlClient, billingProject) + mockControlClient = withRetryOnStorageLayout(mockControlClient, &clientConfig) + sh := &storageClient{ + storageControlClient: mockControlClient, + rawStorageControlClient: mockRawControlClient, + clientConfig: clientConfig, + } + bucketType := &gcs.BucketType{Zonal: false} + + // Act + controlClient := sh.controlClientForBucketHandle(bucketType, billingProject) + + // Assert + require.NotNil(testSuite.T(), controlClient) + billingWrapper, ok := controlClient.(*storageControlClientWithBillingProject) + require.True(testSuite.T(), ok, "Expected a billing project wrapper") + assert.Equal(testSuite.T(), billingProject, billingWrapper.billingProject) + // Check that the underlying control client is a storageControlClientWithRetry and also uses GAX retries. + controlClientWithAllRetriesNonZB, ok := billingWrapper.raw.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok) + require.NotNil(testSuite.T(), controlClientWithAllRetriesNonZB) + assert.True(testSuite.T(), controlClientWithAllRetriesNonZB.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout API on non-zonal buckets") + assert.False(testSuite.T(), controlClientWithAllRetriesNonZB.enableRetriesOnFolderAPIs, "Retries should not be enabled for folder APIs on non-zonal buckets") + // Check that the inner client has GAX retries + gaxClient, ok := controlClientWithAllRetriesNonZB.raw.(*control.StorageControlClient) + require.True(testSuite.T(), ok) + assert.NotNil(testSuite.T(), gaxClient.CallOptions) + assert.NotNil(testSuite.T(), gaxClient.CallOptions.CreateFolder) + assert.NotNil(testSuite.T(), gaxClient.CallOptions.GetFolder) + assert.NotNil(testSuite.T(), gaxClient.CallOptions.DeleteFolder) + assert.NotNil(testSuite.T(), gaxClient.CallOptions.RenameFolder) + assert.Nil(testSuite.T(), gaxClient.CallOptions.GetStorageLayout) +} + +func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBucket_ThenZonalBucket_WithoutBillingProject() { + // Arrange + mockRawControlClient := &control.StorageControlClient{} + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + mockControlClient := withRetryOnStorageLayout(mockRawControlClient, &clientConfig) + sh := &storageClient{ + storageControlClient: mockControlClient, + rawStorageControlClient: mockRawControlClient, + clientConfig: clientConfig, + } + + // Act + // create control-client for non-ZB, which should create a control-client with gax retries. + bucketType := gcs.BucketType{Zonal: false} + controlClientForNonZB := sh.controlClientForBucketHandle(&bucketType, "") + + // Assert + require.NotNil(testSuite.T(), controlClientForNonZB) + // Check that the raw control client is a storageControlClientWithRetry and also uses GAX retries. + controlClientWithAllRetriesNonZB, ok := controlClientForNonZB.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok) + require.NotNil(testSuite.T(), controlClientWithAllRetriesNonZB) + assert.True(testSuite.T(), controlClientWithAllRetriesNonZB.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout API on non-zonal buckets") + assert.False(testSuite.T(), controlClientWithAllRetriesNonZB.enableRetriesOnFolderAPIs, "Retries should not be enabled for folder APIs on non-zonal buckets") + // Check that the underlying control client is not a storageControlClientWithRetry and uses GAX retries for all folder APIs. + rawControlClientForNonZB, ok := controlClientWithAllRetriesNonZB.raw.(*control.StorageControlClient) + require.True(testSuite.T(), ok) + require.NotNil(testSuite.T(), rawControlClientForNonZB, "Expected a control client with GAX retries") + // Check that the inner client has GAX retries. + assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions) + assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.CreateFolder) + assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.GetFolder) + assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.DeleteFolder) + assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.RenameFolder) + assert.Nil(testSuite.T(), rawControlClientForNonZB.CallOptions.GetStorageLayout) + + // Act + // create control-client for ZB afterwards, which should create a storageControlClientWithRetry a raw control.StorageControlClient without gax retries. + bucketType = gcs.BucketType{Zonal: true} + controlClientForZB := sh.controlClientForBucketHandle(&bucketType, "") + + // Assert + require.NotNil(testSuite.T(), controlClientForZB) + // Check that the control client is a storageControlClientWithRetry with all APIs retried. + controlClientWithRetry, ok := controlClientForZB.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok, "Expected a control client with retry") + assert.Same(testSuite.T(), mockRawControlClient, controlClientWithRetry.raw) + assert.True(testSuite.T(), controlClientWithRetry.enableRetriesOnFolderAPIs, "Retries should be enabled for folder APIs on zonal buckets") + assert.True(testSuite.T(), controlClientWithRetry.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout API on zonal buckets") + // Confirm that the inner client has no GAX retries. + rawControlClientWithoutGaxRetry, ok := controlClientWithRetry.raw.(*control.StorageControlClient) + require.True(testSuite.T(), ok) + require.NotNil(testSuite.T(), rawControlClientWithoutGaxRetry) + assert.Nil(testSuite.T(), rawControlClientWithoutGaxRetry.CallOptions, "Expected no GAX retries for zonal bucket") +} + +func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBucket_ThenZonalBucket_WithBillingProject() { + // Arrange + billingProject := "test-project" + mockRawControlClient := &control.StorageControlClient{} + clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) + mockControlClient := withBillingProject(mockRawControlClient, billingProject) + mockControlClient = withRetryOnStorageLayout(mockControlClient, &clientConfig) + sh := &storageClient{ + storageControlClient: mockControlClient, + rawStorageControlClient: mockRawControlClient, + clientConfig: clientConfig, + } + + // Act + // create control-client for non-ZB, which should create a control-client with gax retries. + bucketType := gcs.BucketType{Zonal: false} + controlClientForNonZB := sh.controlClientForBucketHandle(&bucketType, billingProject) + + // Assert + require.NotNil(testSuite.T(), controlClientForNonZB) + // Check that the control client is not a storageControlClientWithRetry and uses GAX retries. + controlClientWithBillingProjectAndAllRetriesForNonZB, ok := controlClientForNonZB.(*storageControlClientWithBillingProject) + require.True(testSuite.T(), ok) + require.NotNil(testSuite.T(), controlClientWithBillingProjectAndAllRetriesForNonZB) + assert.Equal(testSuite.T(), billingProject, controlClientWithBillingProjectAndAllRetriesForNonZB.billingProject) + // Check that the underlying control client is a storageControlClientWithRetry and also uses GAX retries. + controlClientWithAllRetriesNonZB, ok := controlClientWithBillingProjectAndAllRetriesForNonZB.raw.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok) + require.NotNil(testSuite.T(), controlClientWithAllRetriesNonZB) + assert.True(testSuite.T(), controlClientWithAllRetriesNonZB.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout API on non-zonal buckets") + assert.False(testSuite.T(), controlClientWithAllRetriesNonZB.enableRetriesOnFolderAPIs, "Retries should not be enabled for folder APIs on non-zonal buckets") + // Check that the inner client has GAX retries for all folder APIs. + rawControlClientForNonZB, ok := controlClientWithAllRetriesNonZB.raw.(*control.StorageControlClient) + require.True(testSuite.T(), ok, "Expected a raw control client with GAX retries") + assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions) + assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.CreateFolder) + assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.GetFolder) + assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.DeleteFolder) + assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.RenameFolder) + assert.Nil(testSuite.T(), rawControlClientForNonZB.CallOptions.GetStorageLayout) + + // Act + // create control-client for ZB afterwards, which should create a storageControlClientWithRetry a raw control.StorageControlClient without gax retries. + bucketType = gcs.BucketType{Zonal: true} + controlClientForZB := sh.controlClientForBucketHandle(&bucketType, billingProject) + + // Assert + require.NotNil(testSuite.T(), controlClientForZB) + // Check that the control client is a storageControlClientWithRetry with all APIs retried. + controlClientWithRetry, ok := controlClientForZB.(*storageControlClientWithRetry) + require.True(testSuite.T(), ok, "Expected a control client with retry") + assert.True(testSuite.T(), controlClientWithRetry.enableRetriesOnFolderAPIs, "Retries should be enabled for folder APIs on zonal buckets") + assert.True(testSuite.T(), controlClientWithRetry.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout API on zonal buckets") + // Check that the control client contains a storageControlClientWithBillingProject. + controlClientWithBillingProjectForZB, ok := controlClientWithRetry.raw.(*storageControlClientWithBillingProject) + require.True(testSuite.T(), ok) + require.NotNil(testSuite.T(), controlClientWithBillingProjectForZB) + assert.Same(testSuite.T(), mockRawControlClient, controlClientWithBillingProjectForZB.raw) + // Check that the inner client does not have GAX retries. + rawControlClientWithoutGaxRetry, ok := controlClientWithBillingProjectForZB.raw.(*control.StorageControlClient) + require.True(testSuite.T(), ok) + require.NotNil(testSuite.T(), rawControlClientWithoutGaxRetry) + assert.Nil(testSuite.T(), rawControlClientWithoutGaxRetry.CallOptions, "Expected no GAX retries for zonal bucket") +} diff --git a/internal/storage/storageutil/control_client.go b/internal/storage/storageutil/control_client.go index 6ae42f1029..6872f578c3 100644 --- a/internal/storage/storageutil/control_client.go +++ b/internal/storage/storageutil/control_client.go @@ -18,40 +18,12 @@ import ( "context" "fmt" "os" - "time" control "cloud.google.com/go/storage/control/apiv2" - "github.com/googleapis/gax-go/v2" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "google.golang.org/api/option" - "google.golang.org/grpc/codes" ) -func storageControlClientRetryOptions(clientConfig *StorageClientConfig) []gax.CallOption { - return []gax.CallOption{ - gax.WithTimeout(300000 * time.Millisecond), - gax.WithRetry(func() gax.Retryer { - return gax.OnCodes([]codes.Code{ - codes.ResourceExhausted, - codes.Unavailable, - codes.DeadlineExceeded, - codes.Internal, - codes.Unknown, - }, gax.Backoff{ - Max: clientConfig.MaxRetrySleep, - Multiplier: clientConfig.RetryMultiplier, - }) - }), - } -} - -func setRetryConfigForFolderAPIs(sc *control.StorageControlClient, clientConfig *StorageClientConfig) { - sc.CallOptions.RenameFolder = storageControlClientRetryOptions(clientConfig) - sc.CallOptions.GetFolder = storageControlClientRetryOptions(clientConfig) - sc.CallOptions.CreateFolder = storageControlClientRetryOptions(clientConfig) - sc.CallOptions.DeleteFolder = storageControlClientRetryOptions(clientConfig) -} - func CreateGRPCControlClient(ctx context.Context, clientOpts []option.ClientOption, clientConfig *StorageClientConfig) (controlClient *control.StorageControlClient, err error) { if err := os.Setenv("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS", "true"); err != nil { logger.Fatal("error setting direct path env var: %v", err) @@ -62,9 +34,6 @@ func CreateGRPCControlClient(ctx context.Context, clientOpts []option.ClientOpti return nil, fmt.Errorf("NewStorageControlClient: %w", err) } - // Set retries for control client. - setRetryConfigForFolderAPIs(controlClient, clientConfig) - // Unset the environment variable, since it's used only while creation of grpc client. if err := os.Unsetenv("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS"); err != nil { logger.Fatal("error while unsetting direct path env var: %v", err) diff --git a/internal/storage/storageutil/control_client_test.go b/internal/storage/storageutil/control_client_test.go index cb9bdffa50..898695a25f 100644 --- a/internal/storage/storageutil/control_client_test.go +++ b/internal/storage/storageutil/control_client_test.go @@ -37,14 +37,6 @@ func (testSuite *ControlClientTest) SetupTest() { func (testSuite *ControlClientTest) TearDownTest() { } -func (testSuite *ControlClientTest) TestStorageControlClientRetryOptions() { - clientConfig := GetDefaultStorageClientConfig(keyFile) - - gaxOpts := storageControlClientRetryOptions(&clientConfig) - - assert.NotNil(testSuite.T(), gaxOpts) -} - func (testSuite *ControlClientTest) TestStorageControlClient() { var clientOpts []option.ClientOption clientOpts = append(clientOpts, option.WithoutAuthentication()) From f26336146d573c2c448b4f59b1f48c1f12971f96 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 25 Aug 2025 21:31:21 +0530 Subject: [PATCH 0711/1298] Handle clobbered error for buffered reader (#3738) --- internal/bufferedread/download_task.go | 7 +++++ internal/bufferedread/download_task_test.go | 32 +++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/internal/bufferedread/download_task.go b/internal/bufferedread/download_task.go index 017f2bcf27..5b18cdad4d 100644 --- a/internal/bufferedread/download_task.go +++ b/internal/bufferedread/download_task.go @@ -22,6 +22,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/workerpool" @@ -105,9 +106,15 @@ func (p *DownloadTask) Execute() { ReadHandle: p.readHandle, }) if err != nil { + var notFoundError *gcs.NotFoundError + if errors.As(err, ¬FoundError) { + err = &gcsfuse_errors.FileClobberedError{Err: err, ObjectName: p.object.Name} + return + } err = fmt.Errorf("DownloadTask.Execute: while reader-creations: %w", err) return } + defer newReader.Close() _, err = io.CopyN(p.block, newReader, int64(end-start)) if err != nil { diff --git a/internal/bufferedread/download_task_test.go b/internal/bufferedread/download_task_test.go index 6977da0949..f655aef711 100644 --- a/internal/bufferedread/download_task_test.go +++ b/internal/bufferedread/download_task_test.go @@ -23,6 +23,7 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/internal/block" + "github.com/googlecloudplatform/gcsfuse/v3/internal/fs/gcsfuse_errors" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/fake" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" @@ -231,6 +232,37 @@ func (dts *DownloadTaskTestSuite) TestExecuteContextCancelledWhileReadingFromRea ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) defer cancelFunc() status, err := downloadBlock.AwaitReady(ctx) + assert.NoError(dts.T(), err) assert.Equal(dts.T(), block.BlockStateDownloadFailed, status.State) assert.ErrorIs(dts.T(), status.Err, context.Canceled) } + +func (dts *DownloadTaskTestSuite) TestExecuteClobbered() { + downloadBlock, err := dts.blockPool.Get() + require.Nil(dts.T(), err) + err = downloadBlock.SetAbsStartOff(0) + require.Nil(dts.T(), err) + task := NewDownloadTask(context.Background(), dts.object, dts.mockBucket, downloadBlock, nil, dts.metricHandle) + // Simulate NewReaderWithReadHandle returning a NotFoundError. + notFoundErr := &gcs.NotFoundError{Err: errors.New("object not found")} + readObjectRequest := &gcs.ReadObjectRequest{ + Name: dts.object.Name, + Generation: dts.object.Generation, + Range: &gcs.ByteRange{ + Start: uint64(0), + Limit: uint64(testBlockSize), + }, + } + dts.mockBucket.On("NewReaderWithReadHandle", mock.Anything, readObjectRequest).Return(nil, notFoundErr).Times(1) + + task.Execute() + + dts.mockBucket.AssertExpectations(dts.T()) + ctx, cancelFunc := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second)) + defer cancelFunc() + status, err := downloadBlock.AwaitReady(ctx) + assert.NoError(dts.T(), err) + assert.Equal(dts.T(), block.BlockStateDownloadFailed, status.State) + var fileClobberedError *gcsfuse_errors.FileClobberedError + assert.True(dts.T(), errors.As(status.Err, &fileClobberedError)) +} From bc97dcdc100d117beeda9f8cbededcc274096ce5 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Mon, 25 Aug 2025 22:13:07 +0530 Subject: [PATCH 0712/1298] Update default values of buffered read flags (#3733) --- cfg/config.go | 10 +--------- cfg/params.yaml | 4 +--- cmd/config_validation_test.go | 4 ++-- cmd/root_test.go | 14 +++++++------- cmd/testdata/valid_config.yaml | 2 +- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 493f36e15f..65fe5b3e71 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -385,10 +385,6 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("enable-buffered-read", "", false, "When enabled, read starts using buffer to prefetch (asynchronous and in parallel) data from GCS. This improves performance for large file sequential reads. Note: Enabling this flag can increase the memory usage significantly.") - if err := flagSet.MarkHidden("enable-buffered-read"); err != nil { - return err - } - flagSet.BoolP("enable-cloud-profiling", "", false, "Enables cloud profiling, by default disabled.") if err := flagSet.MarkHidden("enable-cloud-profiling"); err != nil { @@ -643,11 +639,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.IntP("read-global-max-blocks", "", 20, "Specifies the maximum number of blocks available for buffered reads across all file-handles. The value should be >= 0 or -1 (for infinite blocks). A value of 0 disables buffered reads.") - - if err := flagSet.MarkHidden("read-global-max-blocks"); err != nil { - return err - } + flagSet.IntP("read-global-max-blocks", "", 80, "Specifies the maximum number of blocks available for buffered reads across all file-handles. The value should be >= 0 or -1 (for infinite blocks). A value of 0 disables buffered reads.") flagSet.DurationP("read-inactive-stream-timeout", "", 10000000000*time.Nanosecond, "Duration of inactivity after which an open GCS read stream is automatically closed. This helps conserve resources when a file handle remains open without active Read calls. A value of '0s' disables this timeout.") diff --git a/cfg/params.yaml b/cfg/params.yaml index 1d3103f028..db80138907 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -767,7 +767,6 @@ data from GCS. This improves performance for large file sequential reads. Note: Enabling this flag can increase the memory usage significantly. default: false - hide-flag: true - config-path: "read.global-max-blocks" flag-name: "read-global-max-blocks" @@ -776,8 +775,7 @@ Specifies the maximum number of blocks available for buffered reads across all file-handles. The value should be >= 0 or -1 (for infinite blocks). A value of 0 disables buffered reads. - default: 20 - hide-flag: true + default: 80 - config-path: "read.inactive-stream-timeout" flag-name: "read-inactive-stream-timeout" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index adda4dbc83..b8288e7cd7 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -239,7 +239,7 @@ func TestValidateConfigFile_ReadConfig(t *testing.T) { InactiveStreamTimeout: 10 * time.Second, BlockSizeMb: 16, EnableBufferedRead: false, - GlobalMaxBlocks: 20, + GlobalMaxBlocks: 80, MaxBlocksPerHandle: 20, StartBlocksPerHandle: 1, MinBlocksPerHandle: 4, @@ -252,7 +252,7 @@ func TestValidateConfigFile_ReadConfig(t *testing.T) { expectedConfig: &cfg.Config{ Read: cfg.ReadConfig{ InactiveStreamTimeout: 10 * time.Second, - BlockSizeMb: 16, + BlockSizeMb: 8, EnableBufferedRead: true, MaxBlocksPerHandle: 20, GlobalMaxBlocks: 40, diff --git a/cmd/root_test.go b/cmd/root_test.go index 75650a7640..d1f371a979 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -407,7 +407,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test default flags.", args: []string{"gcsfuse", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 20, + expectedReadGlobalMaxBlocks: 80, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 4, @@ -416,7 +416,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test enable buffered read flag true.", args: []string{"gcsfuse", "--enable-buffered-read", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 20, + expectedReadGlobalMaxBlocks: 80, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 4, @@ -425,7 +425,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test enable buffered read flag false.", args: []string{"gcsfuse", "--enable-buffered-read=false", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 20, + expectedReadGlobalMaxBlocks: 80, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 4, @@ -434,7 +434,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test positive read-block-size-mb flag.", args: []string{"gcsfuse", "--read-block-size-mb=10", "abc", "pqr"}, expectedReadBlockSizeMB: 10, - expectedReadGlobalMaxBlocks: 20, + expectedReadGlobalMaxBlocks: 80, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 4, @@ -452,7 +452,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test positive read-max-blocks-per-handle flag.", args: []string{"gcsfuse", "--read-max-blocks-per-handle=10", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 20, + expectedReadGlobalMaxBlocks: 80, expectedReadMaxBlocksPerHandle: 10, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 4, @@ -461,7 +461,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test positive read-start-blocks-per-handle flag.", args: []string{"gcsfuse", "--read-start-blocks-per-handle=10", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 20, + expectedReadGlobalMaxBlocks: 80, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 10, expectedReadMinBlocksPerHandle: 4, @@ -470,7 +470,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test positive read-min-blocks-per-handle flag.", args: []string{"gcsfuse", "--read-min-blocks-per-handle=10", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 20, + expectedReadGlobalMaxBlocks: 80, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 10, diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index ac90de2b49..16651c8f57 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -3,7 +3,7 @@ read: inactive-stream-timeout: 10s enable-buffered-read: true global-max-blocks: 40 - block-size-mb: 16 + block-size-mb: 8 start-blocks-per-handle: 4 max-blocks-per-handle: 20 min-blocks-per-handle: 2 From 9a76c4bfe56c3f26cf42b4a1f2f5e2e2fae794e3 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 26 Aug 2025 10:24:39 +0530 Subject: [PATCH 0713/1298] Remove unused method parameter (#3744) --- internal/storage/storage_handle.go | 2 +- internal/storage/storageutil/control_client.go | 2 +- internal/storage/storageutil/control_client_test.go | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index 6c0ef3413d..db79642c39 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -309,7 +309,7 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien if err != nil { return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) } - rawStorageControlClient, err = storageutil.CreateGRPCControlClient(ctx, clientOpts, &clientConfig) + rawStorageControlClient, err = storageutil.CreateGRPCControlClient(ctx, clientOpts) if err != nil { return nil, fmt.Errorf("could not create StorageControl Client: %w", err) } diff --git a/internal/storage/storageutil/control_client.go b/internal/storage/storageutil/control_client.go index 6872f578c3..93f96a0ce6 100644 --- a/internal/storage/storageutil/control_client.go +++ b/internal/storage/storageutil/control_client.go @@ -24,7 +24,7 @@ import ( "google.golang.org/api/option" ) -func CreateGRPCControlClient(ctx context.Context, clientOpts []option.ClientOption, clientConfig *StorageClientConfig) (controlClient *control.StorageControlClient, err error) { +func CreateGRPCControlClient(ctx context.Context, clientOpts []option.ClientOption) (controlClient *control.StorageControlClient, err error) { if err := os.Setenv("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS", "true"); err != nil { logger.Fatal("error setting direct path env var: %v", err) } diff --git a/internal/storage/storageutil/control_client_test.go b/internal/storage/storageutil/control_client_test.go index 898695a25f..3fad1aa6c2 100644 --- a/internal/storage/storageutil/control_client_test.go +++ b/internal/storage/storageutil/control_client_test.go @@ -40,9 +40,8 @@ func (testSuite *ControlClientTest) TearDownTest() { func (testSuite *ControlClientTest) TestStorageControlClient() { var clientOpts []option.ClientOption clientOpts = append(clientOpts, option.WithoutAuthentication()) - clientConfig := GetDefaultStorageClientConfig(keyFile) - controlClient, err := CreateGRPCControlClient(context.Background(), clientOpts, &clientConfig) + controlClient, err := CreateGRPCControlClient(context.Background(), clientOpts) assert.Nil(testSuite.T(), err) assert.NotNil(testSuite.T(), controlClient) From 980194f078497ef45c7b4a3b2922070ed653c648 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 26 Aug 2025 10:28:51 +0530 Subject: [PATCH 0714/1298] feat(bufferedread): Enabling both file cache and buffered read should disable buffered read (#3737) * Fail Mount if both file cache and buffered read are enabled * intiital * final cahnges * Update error message --- cfg/rationalize.go | 14 +++++++ cfg/rationalize_test.go | 85 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 84e6743692..79a8faee03 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -15,6 +15,7 @@ package cfg import ( + "log" "math" "net/url" @@ -121,6 +122,18 @@ func resolveParallelDownloadsValue(v isSet, fc *FileCacheConfig, c *Config) { } } +func resolveFileCacheAndBufferedReadConflict(v isSet, c *Config) { + if IsFileCacheEnabled(c) && c.Read.EnableBufferedRead { + // Log a warning only if the user has explicitly enabled buffered-read. + // The default value for enable-buffered-read is true, so we don't want to + // log a warning for the default case. + if v.IsSet("read.enable-buffered-read") { + log.Printf("Warning: File Cache and Buffered Read features are mutually exclusive. Disabling Buffered Read in favor of File Cache.") + } + c.Read.EnableBufferedRead = false + } +} + // Rationalize updates the config fields based on the values of other fields. func Rationalize(v isSet, c *Config, optimizedFlags []string) error { var err error @@ -141,6 +154,7 @@ func Rationalize(v isSet, c *Config, optimizedFlags []string) error { resolveStatCacheMaxSizeMB(v, &c.MetadataCache, optimizedFlags) resolveCloudMetricsUploadIntervalSecs(&c.Metrics) resolveParallelDownloadsValue(v, &c.FileCache, c) + resolveFileCacheAndBufferedReadConflict(v, c) return nil } diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index e142ee1a82..5ae5a7f465 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -15,7 +15,11 @@ package cfg import ( + "bytes" + "log" "math" + "os" + "strings" "testing" "time" @@ -579,3 +583,84 @@ func TestRationalize_ParallelDownloadsConfig(t *testing.T) { }) } } + +func TestRationalize_FileCacheAndBufferedReadConflict(t *testing.T) { + testCases := []struct { + name string + flags flagSet + config *Config + expectedEnableBufferedRead bool + expectWarning bool + }{ + { + name: "file cache and buffered read enabled (user set)", + flags: flagSet{"read.enable-buffered-read": true}, + config: &Config{ + CacheDir: "/some/path", + FileCache: FileCacheConfig{ + MaxSizeMb: -1, + }, + Read: ReadConfig{ + EnableBufferedRead: true, + }, + }, + expectedEnableBufferedRead: false, + expectWarning: true, + }, + { + name: "file cache enabled, buffered read enabled (default)", + flags: flagSet{}, + config: &Config{ + CacheDir: "/some/path", + FileCache: FileCacheConfig{ + MaxSizeMb: -1, + }, + Read: ReadConfig{ + EnableBufferedRead: true, + }, + }, + expectedEnableBufferedRead: false, + expectWarning: false, + }, + { + name: "file cache disabled, buffered read enabled", + flags: flagSet{"read.enable-buffered-read": true}, + config: &Config{ + Read: ReadConfig{ + EnableBufferedRead: true, + }, + }, + expectedEnableBufferedRead: true, + expectWarning: false, + }, + { + name: "both disabled", + flags: flagSet{}, + config: &Config{}, + expectedEnableBufferedRead: false, + expectWarning: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Capture log output. + var buf bytes.Buffer + log.SetOutput(&buf) + // Restore original logger output after test. + defer log.SetOutput(os.Stderr) + + err := Rationalize(tc.flags, tc.config, []string{}) + + if assert.NoError(t, err) { + assert.Equal(t, tc.expectedEnableBufferedRead, tc.config.Read.EnableBufferedRead) + logOutput := buf.String() + if tc.expectWarning { + assert.True(t, strings.Contains(logOutput, "Warning: File Cache and Buffered Read features are mutually exclusive. Disabling Buffered Read in favor of File Cache.")) + } else { + assert.False(t, strings.Contains(logOutput, "Warning: File Cache and Buffered Read features are mutually exclusive. Disabling Buffered Read in favor of File Cache.")) + } + } + }) + } +} From a2d8ce3b0819909ec69bad5559558343994df984 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Tue, 26 Aug 2025 09:30:38 +0000 Subject: [PATCH 0715/1298] chore(logging): Add log for unsupported log formats When an unsupported log format is provided in the configuration, the system --- cfg/constants.go | 7 +++++ cfg/rationalize.go | 16 ++++++++--- cfg/rationalize_test.go | 61 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/cfg/constants.go b/cfg/constants.go index 00cec6da0d..828eb12ec6 100644 --- a/cfg/constants.go +++ b/cfg/constants.go @@ -32,6 +32,13 @@ const ( OFF string = "OFF" ) +const ( + // Logging-format constants + LogFormatText = "text" + LogFormatJSON = "json" + DefaultLogFormat = LogFormatJSON +) + const ( // ExperimentalMetadataPrefetchOnMountDisabled is the mode without metadata-prefetch. ExperimentalMetadataPrefetchOnMountDisabled = "disabled" diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 79a8faee03..2a2dfa1577 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -134,6 +134,17 @@ func resolveFileCacheAndBufferedReadConflict(v isSet, c *Config) { } } +func resolveLoggingConfig(config *Config) { + if config.Debug.Fuse || config.Debug.Gcs || config.Debug.LogMutex { + config.Logging.Severity = "TRACE" + } + + if config.Logging.Format != LogFormatText && config.Logging.Format != LogFormatJSON { + log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", config.Logging.Format, DefaultLogFormat) + config.Logging.Format = DefaultLogFormat // defaulting to json format + } +} + // Rationalize updates the config fields based on the values of other fields. func Rationalize(v isSet, c *Config, optimizedFlags []string) error { var err error @@ -145,10 +156,7 @@ func Rationalize(v isSet, c *Config, optimizedFlags []string) error { return err } - if c.Debug.Fuse || c.Debug.Gcs || c.Debug.LogMutex { - c.Logging.Severity = "TRACE" - } - + resolveLoggingConfig(c) resolveStreamingWriteConfig(&c.Write) resolveMetadataCacheTTL(v, &c.MetadataCache, optimizedFlags) resolveStatCacheMaxSizeMB(v, &c.MetadataCache, optimizedFlags) diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 5ae5a7f465..d062422c3b 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -16,6 +16,7 @@ package cfg import ( "bytes" + "fmt" "log" "math" "os" @@ -664,3 +665,63 @@ func TestRationalize_FileCacheAndBufferedReadConflict(t *testing.T) { }) } } + +func TestResolveLoggingConfig(t *testing.T) { + testCases := []struct { + name string + config *Config + expectedLogFormat string + expectWarning bool + }{ + { + name: "valid log format (json)", + config: &Config{ + Logging: LoggingConfig{ + Format: "json", + }, + }, + expectedLogFormat: "json", + expectWarning: false, + }, + { + name: "valid log format (text)", + config: &Config{ + Logging: LoggingConfig{ + Format: "text", + }, + }, + expectedLogFormat: "text", + expectWarning: false, + }, + { + name: "invalid log format, default to json", + config: &Config{ + Logging: LoggingConfig{ + Format: "INVALID", + }, + }, + expectedLogFormat: DefaultLogFormat, // Should default to JSON + expectWarning: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Capture log output. + var buf bytes.Buffer + log.SetOutput(&buf) + // Restore original logger output after test. + defer log.SetOutput(os.Stderr) + + resolveLoggingConfig(tc.config) + + assert.Equal(t, tc.expectedLogFormat, tc.config.Logging.Format) + logOutput := buf.String() + if tc.expectWarning { + assert.True(t, strings.Contains(logOutput, fmt.Sprintf("Unsupported log format provided: INVALID. Defaulting to %s log format.", DefaultLogFormat))) + } else { + assert.False(t, strings.Contains(logOutput, "Unsupported log format provided:")) + } + }) + } +} From 077dd207c6d267c18d3dd88ec438361918d00de9 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Tue, 26 Aug 2025 17:07:37 +0530 Subject: [PATCH 0716/1298] Update log level from warning to trace (#3749) --- internal/bufferedread/buffered_reader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index 123f049e4a..55909b0d8e 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -443,7 +443,7 @@ func (p *BufferedReader) scheduleNextBlock(urgent bool) error { // can't get a block. For the buffered reader, this is a recoverable // condition that should either trigger a fallback to another reader (for // urgent reads) or be ignored (for background prefetches). - logger.Warnf("scheduleNextBlock: could not get block from pool (urgent=%t): %v", urgent, err) + logger.Tracef("scheduleNextBlock: could not get block from pool (urgent=%t): %v", urgent, err) return ErrPrefetchBlockNotAvailable } From 45ecdaad0bf29c417305d5952360078334a1f655 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Tue, 26 Aug 2025 13:42:13 +0000 Subject: [PATCH 0717/1298] Update constant naming and remove log output check in tests --- cfg/constants.go | 6 +++--- cfg/rationalize.go | 6 +++--- cfg/rationalize_test.go | 18 ++++-------------- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/cfg/constants.go b/cfg/constants.go index 828eb12ec6..7897de8bc8 100644 --- a/cfg/constants.go +++ b/cfg/constants.go @@ -34,9 +34,9 @@ const ( const ( // Logging-format constants - LogFormatText = "text" - LogFormatJSON = "json" - DefaultLogFormat = LogFormatJSON + logFormatText = "text" + logFormatJSON = "json" + defaultLogFormat = logFormatJSON ) const ( diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 2a2dfa1577..bd80ef2f0e 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -139,9 +139,9 @@ func resolveLoggingConfig(config *Config) { config.Logging.Severity = "TRACE" } - if config.Logging.Format != LogFormatText && config.Logging.Format != LogFormatJSON { - log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", config.Logging.Format, DefaultLogFormat) - config.Logging.Format = DefaultLogFormat // defaulting to json format + if config.Logging.Format != logFormatText && config.Logging.Format != logFormatJSON { + log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", config.Logging.Format, defaultLogFormat) + config.Logging.Format = defaultLogFormat // defaulting to json format } } diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index d062422c3b..815e61c71c 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -16,7 +16,6 @@ package cfg import ( "bytes" - "fmt" "log" "math" "os" @@ -674,34 +673,31 @@ func TestResolveLoggingConfig(t *testing.T) { expectWarning bool }{ { - name: "valid log format (json)", + name: "valid_log_format_json", config: &Config{ Logging: LoggingConfig{ Format: "json", }, }, expectedLogFormat: "json", - expectWarning: false, }, { - name: "valid log format (text)", + name: "valid_log_format_text", config: &Config{ Logging: LoggingConfig{ Format: "text", }, }, expectedLogFormat: "text", - expectWarning: false, }, { - name: "invalid log format, default to json", + name: "invalid_log_format", config: &Config{ Logging: LoggingConfig{ Format: "INVALID", }, }, - expectedLogFormat: DefaultLogFormat, // Should default to JSON - expectWarning: true, + expectedLogFormat: defaultLogFormat, // Should default to JSON }, } @@ -716,12 +712,6 @@ func TestResolveLoggingConfig(t *testing.T) { resolveLoggingConfig(tc.config) assert.Equal(t, tc.expectedLogFormat, tc.config.Logging.Format) - logOutput := buf.String() - if tc.expectWarning { - assert.True(t, strings.Contains(logOutput, fmt.Sprintf("Unsupported log format provided: INVALID. Defaulting to %s log format.", DefaultLogFormat))) - } else { - assert.False(t, strings.Contains(logOutput, "Unsupported log format provided:")) - } }) } } From 309b2e6f85a2b496b0ed37efdc45cbce4983fe07 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Tue, 26 Aug 2025 13:48:44 +0000 Subject: [PATCH 0718/1298] Remove unwanted test buffer collection in unit test. --- cfg/rationalize_test.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 815e61c71c..a6acd2182e 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -703,12 +703,6 @@ func TestResolveLoggingConfig(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - // Capture log output. - var buf bytes.Buffer - log.SetOutput(&buf) - // Restore original logger output after test. - defer log.SetOutput(os.Stderr) - resolveLoggingConfig(tc.config) assert.Equal(t, tc.expectedLogFormat, tc.config.Logging.Format) From 1d0b7df281b6f0a9903034abc22c1ad00be02178 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Tue, 26 Aug 2025 14:23:09 +0000 Subject: [PATCH 0719/1298] Remove residual code from test configuration. --- cfg/rationalize_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index a6acd2182e..0ee8f4181d 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -670,7 +670,6 @@ func TestResolveLoggingConfig(t *testing.T) { name string config *Config expectedLogFormat string - expectWarning bool }{ { name: "valid_log_format_json", From 73a74a676d85f5ed76b5bc43bd767daf3c715183 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 26 Aug 2025 21:52:01 +0530 Subject: [PATCH 0720/1298] fix(log level): Downgrade info log to trace. (#3746) * Downgrade info log to trace. * Update inactive_timeout_reader.go --- internal/gcsx/inactive_timeout_reader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/gcsx/inactive_timeout_reader.go b/internal/gcsx/inactive_timeout_reader.go index f992da4ea6..5b8df947b8 100644 --- a/internal/gcsx/inactive_timeout_reader.go +++ b/internal/gcsx/inactive_timeout_reader.go @@ -236,7 +236,7 @@ func (itr *InactiveTimeoutReader) closeGCSReader() { } // Not printing the timeout explicitly, as can be refer from the code/config. - logger.Infof("Closing reader for object %q due to inactivity.\n", itr.object.Name) + logger.Tracef("Closing reader for object %q due to inactivity.\n", itr.object.Name) itr.readHandle = itr.gcsReader.ReadHandle() if err := itr.gcsReader.Close(); err != nil { logger.Warnf("Error closing inactive reader for object %q: %v", itr.object.Name, err) From e95c5baedb77ab133cb76a41d4d43d59e86a2781 Mon Sep 17 00:00:00 2001 From: Tulsi Shah <46474643+Tulsishah@users.noreply.github.com> Date: Wed, 27 Aug 2025 08:42:41 +0530 Subject: [PATCH 0721/1298] feat(Buffered Read): Make flag for Random seek count (#3750) * flag to change value * change the flag name * rebase * update unit test for config * review comment --- cfg/config.go | 12 ++++++++++++ cfg/params.yaml | 8 ++++++++ cmd/config_validation_test.go | 2 ++ cmd/testdata/valid_config.yaml | 1 + internal/bufferedread/buffered_reader.go | 8 ++++---- internal/bufferedread/buffered_reader_test.go | 2 ++ internal/gcsx/read_manager/read_manager.go | 1 + 7 files changed, 30 insertions(+), 4 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 65fe5b3e71..29f9a4b62b 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -274,6 +274,8 @@ type ReadConfig struct { MinBlocksPerHandle int64 `yaml:"min-blocks-per-handle"` + RandomSeekThreshold int64 `yaml:"random-seek-threshold"` + StartBlocksPerHandle int64 `yaml:"start-blocks-per-handle"` } @@ -659,6 +661,12 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } + flagSet.IntP("read-random-seek-threshold", "", 3, "Specifies the random seek threshold to switch to another reader when random reads are detected.") + + if err := flagSet.MarkHidden("read-random-seek-threshold"); err != nil { + return err + } + flagSet.DurationP("read-stall-initial-req-timeout", "", 20000000000*time.Nanosecond, "Initial value of the read-request dynamic timeout.") if err := flagSet.MarkHidden("read-stall-initial-req-timeout"); err != nil { @@ -1100,6 +1108,10 @@ func BindFlags(v *viper.Viper, flagSet *pflag.FlagSet) error { return err } + if err := v.BindPFlag("read.random-seek-threshold", flagSet.Lookup("read-random-seek-threshold")); err != nil { + return err + } + if err := v.BindPFlag("gcs-retries.read-stall.initial-req-timeout", flagSet.Lookup("read-stall-initial-req-timeout")); err != nil { return err } diff --git a/cfg/params.yaml b/cfg/params.yaml index db80138907..3db73c12bc 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -806,6 +806,14 @@ default: 4 hide-flag: true +- config-path: "read.random-seek-threshold" + flag-name: "read-random-seek-threshold" + type: "int" + usage: >- + Specifies the random seek threshold to switch to another reader when random reads are detected. + default: 3 + hide-flag: true + - config-path: "read.start-blocks-per-handle" flag-name: "read-start-blocks-per-handle" type: "int" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index b8288e7cd7..3b4ee9bddc 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -243,6 +243,7 @@ func TestValidateConfigFile_ReadConfig(t *testing.T) { MaxBlocksPerHandle: 20, StartBlocksPerHandle: 1, MinBlocksPerHandle: 4, + RandomSeekThreshold: 3, }, }, }, @@ -258,6 +259,7 @@ func TestValidateConfigFile_ReadConfig(t *testing.T) { GlobalMaxBlocks: 40, StartBlocksPerHandle: 4, MinBlocksPerHandle: 2, + RandomSeekThreshold: 10, }, }, }, diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 16651c8f57..6e3ec0346f 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -7,6 +7,7 @@ read: start-blocks-per-handle: 4 max-blocks-per-handle: 20 min-blocks-per-handle: 2 + random-seek-threshold: 10 write: create-empty-file: true enable-streaming-writes: true diff --git a/internal/bufferedread/buffered_reader.go b/internal/bufferedread/buffered_reader.go index 55909b0d8e..075feff163 100644 --- a/internal/bufferedread/buffered_reader.go +++ b/internal/bufferedread/buffered_reader.go @@ -45,12 +45,12 @@ type BufferedReadConfig struct { PrefetchBlockSizeBytes int64 // Size of each block to be prefetched. InitialPrefetchBlockCnt int64 // Number of blocks to prefetch initially. MinBlocksPerHandle int64 // Minimum number of blocks available in block-pool to start buffered-read. + RandomSeekThreshold int64 // Seek count threshold to switch another reader } const ( - defaultRandomReadsThreshold = 3 - defaultPrefetchMultiplier = 2 - ReadOp = "readOp" + defaultPrefetchMultiplier = 2 + ReadOp = "readOp" ) // blockQueueEntry holds a data block with a function @@ -140,7 +140,7 @@ func NewBufferedReader(object *gcs.MinObject, bucket gcs.Bucket, config *Buffere workerPool: workerPool, metricHandle: metricHandle, prefetchMultiplier: defaultPrefetchMultiplier, - randomReadsThreshold: defaultRandomReadsThreshold, + randomReadsThreshold: config.RandomSeekThreshold, } reader.ctx, reader.cancelFunc = context.WithCancel(context.Background()) diff --git a/internal/bufferedread/buffered_reader_test.go b/internal/bufferedread/buffered_reader_test.go index 7b454dc134..fd2123f010 100644 --- a/internal/bufferedread/buffered_reader_test.go +++ b/internal/bufferedread/buffered_reader_test.go @@ -41,6 +41,7 @@ import ( const ( testMaxPrefetchBlockCnt int64 = 10 testMinBlocksPerHandle int64 = 2 + testRandomSeekThreshold int64 = 3 testGlobalMaxBlocks int64 = 20 testPrefetchBlockSizeBytes int64 = 1024 testInitialPrefetchBlockCnt int64 = 2 @@ -116,6 +117,7 @@ func (t *BufferedReaderTest) SetupTest() { PrefetchBlockSizeBytes: testPrefetchBlockSizeBytes, InitialPrefetchBlockCnt: testInitialPrefetchBlockCnt, MinBlocksPerHandle: testMinBlocksPerHandle, + RandomSeekThreshold: testRandomSeekThreshold, } var err error t.workerPool, err = workerpool.NewStaticWorkerPool(5, 10) diff --git a/internal/gcsx/read_manager/read_manager.go b/internal/gcsx/read_manager/read_manager.go index 97522f91af..c88b4eda1b 100644 --- a/internal/gcsx/read_manager/read_manager.go +++ b/internal/gcsx/read_manager/read_manager.go @@ -81,6 +81,7 @@ func NewReadManager(object *gcs.MinObject, bucket gcs.Bucket, config *ReadManage PrefetchBlockSizeBytes: readConfig.BlockSizeMb * util.MiB, InitialPrefetchBlockCnt: readConfig.StartBlocksPerHandle, MinBlocksPerHandle: readConfig.MinBlocksPerHandle, + RandomSeekThreshold: readConfig.RandomSeekThreshold, } bufferedReader, err := bufferedread.NewBufferedReader( object, From 187e20011ffa32966809967e915d2d9581aae9e7 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 05:09:58 +0000 Subject: [PATCH 0722/1298] Normalize log format to lowercase for consistency --- cfg/rationalize.go | 5 ++++- cfg/rationalize_test.go | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index bd80ef2f0e..cddcf73ef7 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -18,6 +18,7 @@ import ( "log" "math" "net/url" + "strings" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" ) @@ -139,8 +140,10 @@ func resolveLoggingConfig(config *Config) { config.Logging.Severity = "TRACE" } + var configLogFormat = config.Logging.Format // capture initial value for error reporting + config.Logging.Format = strings.ToLower(config.Logging.Format) if config.Logging.Format != logFormatText && config.Logging.Format != logFormatJSON { - log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", config.Logging.Format, defaultLogFormat) + log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", configLogFormat, defaultLogFormat) config.Logging.Format = defaultLogFormat // defaulting to json format } } diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 0ee8f4181d..c0d62f9aa9 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -689,6 +689,15 @@ func TestResolveLoggingConfig(t *testing.T) { }, expectedLogFormat: "text", }, + { + name: "valid_case_insensitive_log_format", + config: &Config{ + Logging: LoggingConfig{ + Format: "TEXT", + }, + }, + expectedLogFormat: "text", // Should default to JSON + }, { name: "invalid_log_format", config: &Config{ From 61adba3ac471446dcd90f96b017b299ae0042c53 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 05:12:25 +0000 Subject: [PATCH 0723/1298] Change validation logic to contains. --- cfg/rationalize.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index cddcf73ef7..30481a286d 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -18,6 +18,7 @@ import ( "log" "math" "net/url" + "slices" "strings" "github.com/googlecloudplatform/gcsfuse/v3/internal/util" @@ -142,7 +143,7 @@ func resolveLoggingConfig(config *Config) { var configLogFormat = config.Logging.Format // capture initial value for error reporting config.Logging.Format = strings.ToLower(config.Logging.Format) - if config.Logging.Format != logFormatText && config.Logging.Format != logFormatJSON { + if slices.Contains([]string{logFormatText, logFormatJSON}, config.Logging.Format) { log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", configLogFormat, defaultLogFormat) config.Logging.Format = defaultLogFormat // defaulting to json format } From 95fac96fe20650c859c4a5ffa935cce3fefeac49 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 05:16:02 +0000 Subject: [PATCH 0724/1298] Reduce code --- cfg/rationalize.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 30481a286d..c7e1076b44 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -141,10 +141,8 @@ func resolveLoggingConfig(config *Config) { config.Logging.Severity = "TRACE" } - var configLogFormat = config.Logging.Format // capture initial value for error reporting - config.Logging.Format = strings.ToLower(config.Logging.Format) - if slices.Contains([]string{logFormatText, logFormatJSON}, config.Logging.Format) { - log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", configLogFormat, defaultLogFormat) + if slices.Contains([]string{logFormatText, logFormatJSON}, strings.ToLower(config.Logging.Format)) { + log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", config.Logging.Format, defaultLogFormat) config.Logging.Format = defaultLogFormat // defaulting to json format } } From ed314d0c24571b172b4c8fabecbf60112d243358 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 05:16:59 +0000 Subject: [PATCH 0725/1298] Lower case --- cfg/rationalize.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index c7e1076b44..3d0120680f 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -145,6 +145,7 @@ func resolveLoggingConfig(config *Config) { log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", config.Logging.Format, defaultLogFormat) config.Logging.Format = defaultLogFormat // defaulting to json format } + config.Logging.Format = strings.ToLower(config.Logging.Format) } // Rationalize updates the config fields based on the values of other fields. From 580916ff4c359095f1b98f1146668e61f6adf349 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 05:21:14 +0000 Subject: [PATCH 0726/1298] Fix test case --- cfg/rationalize.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 3d0120680f..561e650845 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -144,8 +144,9 @@ func resolveLoggingConfig(config *Config) { if slices.Contains([]string{logFormatText, logFormatJSON}, strings.ToLower(config.Logging.Format)) { log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", config.Logging.Format, defaultLogFormat) config.Logging.Format = defaultLogFormat // defaulting to json format + } else { + config.Logging.Format = strings.ToLower(config.Logging.Format) } - config.Logging.Format = strings.ToLower(config.Logging.Format) } // Rationalize updates the config fields based on the values of other fields. From f6708f3b797570a6e276b709ac4e662623028fcc Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 05:23:14 +0000 Subject: [PATCH 0727/1298] Fix condition check --- cfg/rationalize.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 561e650845..ded75237e6 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -141,11 +141,11 @@ func resolveLoggingConfig(config *Config) { config.Logging.Severity = "TRACE" } - if slices.Contains([]string{logFormatText, logFormatJSON}, strings.ToLower(config.Logging.Format)) { + if slices.Contains([]string{"", logFormatText, logFormatJSON}, strings.ToLower(config.Logging.Format)) { + config.Logging.Format = strings.ToLower(config.Logging.Format) + } else { log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", config.Logging.Format, defaultLogFormat) config.Logging.Format = defaultLogFormat // defaulting to json format - } else { - config.Logging.Format = strings.ToLower(config.Logging.Format) } } From fd35b97ebbffbc2b9c2ae6316bc95b0041688ccc Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 05:27:11 +0000 Subject: [PATCH 0728/1298] Code cleanup --- cfg/rationalize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index ded75237e6..4016a3b872 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -141,7 +141,7 @@ func resolveLoggingConfig(config *Config) { config.Logging.Severity = "TRACE" } - if slices.Contains([]string{"", logFormatText, logFormatJSON}, strings.ToLower(config.Logging.Format)) { + if slices.Contains([]string{logFormatText, logFormatJSON}, strings.ToLower(config.Logging.Format)) { config.Logging.Format = strings.ToLower(config.Logging.Format) } else { log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", config.Logging.Format, defaultLogFormat) From 7812347f27c18420926071f9444c66330e7330b9 Mon Sep 17 00:00:00 2001 From: Pranjal Chaturvedi Date: Wed, 27 Aug 2025 11:22:55 +0530 Subject: [PATCH 0729/1298] refactor: update parsing the yaml file & Zonal bucket case handling [GKE-GCSFuse Test migration] (#3753) * update implicit & explicit readConfigFile function, client before buckettype * buckettype before client for explicit&implicit dirs * list-large-dir buckettype before client * remove unnecessary struct --- .../explicit_dir/explicit_dir_test.go | 28 ++++----------- .../implicit_dir/implicit_dir_test.go | 28 ++++----------- .../list_large_dir/list_large_dir_test.go | 34 ++++++------------- 3 files changed, 24 insertions(+), 66 deletions(-) diff --git a/tools/integration_tests/explicit_dir/explicit_dir_test.go b/tools/integration_tests/explicit_dir/explicit_dir_test.go index bc6b0c7a64..c4cb4148b7 100644 --- a/tools/integration_tests/explicit_dir/explicit_dir_test.go +++ b/tools/integration_tests/explicit_dir/explicit_dir_test.go @@ -26,16 +26,10 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" - "gopkg.in/yaml.v3" ) const DirForExplicitDirTests = "dirForExplicitDirTests" -// Config holds all test configurations parsed from the YAML file. -type Config struct { - ExplicitDir []test_suite.TestConfig `yaml:"explicit_dir"` -} - // IMPORTANT: To prevent global variable pollution, enhance code clarity, // and avoid inadvertent errors. We strongly suggest that, all new package-level // variables (which would otherwise be declared with `var` at the package root) should @@ -51,17 +45,7 @@ func TestMain(m *testing.M) { setup.ParseSetUpFlags() // 1. Load and parse the common configuration. - var cfg Config - if setup.ConfigFile() != "" { - configData, err := os.ReadFile(setup.ConfigFile()) - if err != nil { - log.Fatalf("could not read test_config.yaml: %v", err) - } - expandedYaml := os.ExpandEnv(string(configData)) - if err := yaml.Unmarshal([]byte(expandedYaml), &cfg); err != nil { - log.Fatalf("Failed to parse config YAML: %v", err) - } - } + cfg := test_suite.ReadConfigFile(setup.ConfigFile()) if len(cfg.ExplicitDir) == 0 { log.Println("No configuration found for explicit_dir tests in config. Using flags instead.") // Populate the config manually. @@ -75,6 +59,12 @@ func TestMain(m *testing.M) { // 2. Create storage client before running tests. testEnv.ctx = context.Background() + + bucketType, err := setup.BucketType(testEnv.ctx, cfg.ExplicitDir[0].TestBucket) + if err != nil { + log.Fatalf("BucketType failed: %v", err) + } + closeStorageClient := client.CreateStorageClientWithCancel(&testEnv.ctx, &testEnv.storageClient) defer func() { err := closeStorageClient() @@ -84,10 +74,6 @@ func TestMain(m *testing.M) { }() // 4. Build the flag sets dynamically from the config. - bucketType, err := setup.BucketType(testEnv.ctx, cfg.ExplicitDir[0].TestBucket) - if err != nil { - log.Fatalf("BucketType failed: %v", err) - } flags := setup.BuildFlagSets(cfg.ExplicitDir[0], bucketType) // 5. Run tests with the dynamically generated flags. diff --git a/tools/integration_tests/implicit_dir/implicit_dir_test.go b/tools/integration_tests/implicit_dir/implicit_dir_test.go index aa36def2b0..d454146fd5 100644 --- a/tools/integration_tests/implicit_dir/implicit_dir_test.go +++ b/tools/integration_tests/implicit_dir/implicit_dir_test.go @@ -27,7 +27,6 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup/implicit_and_explicit_dir_setup" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" - "gopkg.in/yaml.v3" ) const ExplicitDirInImplicitDir = "explicitDirInImplicitDir" @@ -61,26 +60,11 @@ func setupTestDir(dirName string) string { return dirPath } -// Config holds all test configurations parsed from the YAML file. -type Config struct { - ImplicitDir []test_suite.TestConfig `yaml:"implicit_dir"` -} - func TestMain(m *testing.M) { setup.ParseSetUpFlags() // 1. Load and parse the common configuration. - var cfg Config - if setup.ConfigFile() != "" { - configData, err := os.ReadFile(setup.ConfigFile()) - if err != nil { - log.Fatalf("could not read test_config.yaml: %v", err) - } - expandedYaml := os.ExpandEnv(string(configData)) - if err := yaml.Unmarshal([]byte(expandedYaml), &cfg); err != nil { - log.Fatalf("Failed to parse config YAML: %v", err) - } - } + cfg := test_suite.ReadConfigFile(setup.ConfigFile()) if len(cfg.ImplicitDir) == 0 { log.Println("No configuration found for implicit_dir tests in config. Using flags instead.") // Populate the config manually. @@ -97,6 +81,12 @@ func TestMain(m *testing.M) { // 2. Create storage client before running tests. setup.SetBucketFromConfigFile(cfg.ImplicitDir[0].TestBucket) testEnv.ctx = context.Background() + + bucketType, err := setup.BucketType(testEnv.ctx, cfg.ImplicitDir[0].TestBucket) + if err != nil { + log.Fatalf("BucketType failed: %v", err) + } + closeStorageClient := client.CreateStorageClientWithCancel(&testEnv.ctx, &testEnv.storageClient) defer func() { err := closeStorageClient() @@ -106,10 +96,6 @@ func TestMain(m *testing.M) { }() // 4. Build the flag sets dynamically from the config. - bucketType, err := setup.BucketType(testEnv.ctx, cfg.ImplicitDir[0].TestBucket) - if err != nil { - log.Fatalf("BucketType failed: %v", err) - } flags := setup.BuildFlagSets(cfg.ImplicitDir[0], bucketType) // 5. Run tests with the dynamically generated flags. diff --git a/tools/integration_tests/list_large_dir/list_large_dir_test.go b/tools/integration_tests/list_large_dir/list_large_dir_test.go index c908a88a3d..211050964b 100644 --- a/tools/integration_tests/list_large_dir/list_large_dir_test.go +++ b/tools/integration_tests/list_large_dir/list_large_dir_test.go @@ -26,7 +26,6 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" - "gopkg.in/yaml.v3" ) const prefixFileInDirectoryWithTwelveThousandFiles = "fileInDirectoryWithTwelveThousandFiles" @@ -42,26 +41,11 @@ var ( ctx context.Context ) -// Config holds all test configurations parsed from the YAML file. -type Config struct { - ListLargeDir []test_suite.TestConfig `yaml:"list_large_dir"` -} - func TestMain(m *testing.M) { setup.ParseSetUpFlags() // 1. Load and parse the common configuration. - var cfg Config - if setup.ConfigFile() != "" { - configData, err := os.ReadFile(setup.ConfigFile()) - if err != nil { - log.Fatalf("could not read test_config.yaml: %v", err) - } - expandedYaml := os.ExpandEnv(string(configData)) - if err := yaml.Unmarshal([]byte(expandedYaml), &cfg); err != nil { - log.Fatalf("Failed to parse config YAML: %v", err) - } - } + cfg := test_suite.ReadConfigFile(setup.ConfigFile()) if len(cfg.ListLargeDir) == 0 { log.Println("No configuration found for list large dir tests in config. Using flags instead.") // Populate the config manually. @@ -80,6 +64,15 @@ func TestMain(m *testing.M) { // 2. Create storage client before running tests. setup.SetBucketFromConfigFile(cfg.ListLargeDir[0].TestBucket) ctx = context.Background() + + bucketType, err := setup.BucketType(ctx, cfg.ListLargeDir[0].TestBucket) + if err != nil { + log.Fatalf("BucketType failed: %v", err) + } + if bucketType == setup.ZonalBucket { + setup.SetIsZonalBucketRun(true) + } + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) defer func() { err := closeStorageClient() @@ -96,13 +89,6 @@ func TestMain(m *testing.M) { // Run tests for testBucket // 4. Build the flag sets dynamically from the config. - bucketType, err := setup.BucketType(ctx, cfg.ListLargeDir[0].TestBucket) - if err != nil { - log.Fatalf("BucketType failed: %v", err) - } - if bucketType == setup.ZonalBucket { - setup.SetIsZonalBucketRun(true) - } flags := setup.BuildFlagSets(cfg.ListLargeDir[0], bucketType) setup.SetUpTestDirForTestBucket(cfg.ListLargeDir[0].TestBucket) From 36455c1e37cab44f15c3e163bc12bad23423dc6e Mon Sep 17 00:00:00 2001 From: Pranjal Chaturvedi Date: Wed, 27 Aug 2025 11:30:23 +0530 Subject: [PATCH 0730/1298] refactor: gzip test package migration [GKE-GCSFuse Test migration] (#3754) * test migration gzip * gzip update * resolve comments --- tools/integration_tests/gzip/gzip_test.go | 64 +++++++++++++++-------- tools/integration_tests/test_config.yaml | 16 +++++- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/tools/integration_tests/gzip/gzip_test.go b/tools/integration_tests/gzip/gzip_test.go index 0fa96f766e..6e3bdf1e3c 100644 --- a/tools/integration_tests/gzip/gzip_test.go +++ b/tools/integration_tests/gzip/gzip_test.go @@ -29,6 +29,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" ) const ( @@ -193,31 +194,42 @@ func createContentOfSize(contentSize int) (string, error) { func TestMain(m *testing.M) { setup.ParseSetUpFlags() + // 1. Load and parse the common configuration. + cfg := test_suite.ReadConfigFile(setup.ConfigFile()) + if len(cfg.Gzip) == 0 { + log.Println("No configuration found for gzip tests in config. Using flags instead.") + // Populate the config manually. + cfg.Gzip = make([]test_suite.TestConfig, 1) + cfg.Gzip[0].TestBucket = setup.TestBucket() + cfg.Gzip[0].MountedDirectory = setup.MountedDirectory() + cfg.Gzip[0].Configs = make([]test_suite.ConfigItem, 1) + cfg.Gzip[0].Configs[0].Flags = []string{ + "--sequential-read-size-mb=1 --implicit-dirs", + "--sequential-read-size-mb=1 --implicit-dirs --client-protocol=grpc", + } + cfg.Gzip[0].Configs[0].Compatible = map[string]bool{"flat": true, "hns": true, "zonal": true} + } var err error + + setup.SetBucketFromConfigFile(cfg.Gzip[0].TestBucket) ctx = context.Background() - if storageClient, err = client.CreateStorageClient(ctx); err != nil { - log.Fatalf("Error creating storage client: %v\n", err) + bucketType, err := setup.BucketType(ctx, cfg.Gzip[0].TestBucket) + if err != nil { + log.Fatalf("BucketType failed: %v", err) } + if bucketType == setup.ZonalBucket { + setup.SetIsZonalBucketRun(true) + } + + // 2. Create storage client before running tests. + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) defer func() { - if err := storageClient.Close(); err != nil { - log.Printf("failed to close storage client: %v", err) + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) } }() - commonFlags := []string{"--sequential-read-size-mb=" + fmt.Sprint(SeqReadSizeMb), "--implicit-dirs"} - flags := [][]string{commonFlags} - - if !testing.Short() { - gRPCFlags := append(commonFlags, "--client-protocol=grpc") - flags = append(flags, gRPCFlags) - } - - setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() - - if setup.TestBucket() == "" && setup.MountedDirectory() != "" { - log.Fatal("Please pass the name of bucket mounted at mountedDirectory to --testBucket flag.") - } - err = setup_testdata() if err != nil { log.Fatalf("Failed to setup test data: %v", err) @@ -230,13 +242,19 @@ func TestMain(m *testing.M) { } }() - // Run tests for mountedDirectory only if --mountedDirectory flag is set. - setup.RunTestsForMountedDirectoryFlag(m) + // 3. To run mountedDirectory tests, we need both testBucket and mountedDirectory + // flags to be set, as Gzip tests validates content from the bucket. + if cfg.Gzip[0].MountedDirectory != "" && cfg.Gzip[0].TestBucket != "" { + os.Exit(setup.RunTestsForMountedDirectory(cfg.Gzip[0].MountedDirectory, m)) + } + + // Run tests for testBucket// Run tests for testBucket + // 4. Build the flag sets dynamically from the config. + flags := setup.BuildFlagSets(cfg.Gzip[0], bucketType) - // Run tests for testBucket - setup.SetUpTestDirForTestBucketFlag() + setup.SetUpTestDirForTestBucket(cfg.Gzip[0].TestBucket) - successCode := static_mounting.RunTests(flags, m) + successCode := static_mounting.RunTestsWithConfigFile(&cfg.Gzip[0], flags, m) os.Exit(successCode) } diff --git a/tools/integration_tests/test_config.yaml b/tools/integration_tests/test_config.yaml index 18e8f9fd7b..f64505bb68 100644 --- a/tools/integration_tests/test_config.yaml +++ b/tools/integration_tests/test_config.yaml @@ -67,7 +67,7 @@ list_large_dir: write_large_files: - mounted_directory: "${MOUNTED_DIR}" test_bucket: "${BUCKET_NAME}" - log_file: # Optional + log_file: # Not Required by write large files tests run_on_gke: false configs: - flags: @@ -80,3 +80,17 @@ write_large_files: flat: true hns: true zonal: true + +gzip: + - mounted_directory: "${MOUNTED_DIR}" + test_bucket: "${BUCKET_NAME}" + log_file: # Not Required by gzip tests + run_on_gke: false + configs: + - flags: + - "--sequential-read-size-mb=1 --implicit-dirs" + - "--sequential-read-size-mb=1 --implicit-dirs --client-protocol=grpc" + compatible: + flat: true + hns: true + zonal: true From 113f3a04b9d60c6f06a6929a294c3b376bc324c8 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 06:43:08 +0000 Subject: [PATCH 0731/1298] Remove incorrect comment. --- cfg/rationalize_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index c0d62f9aa9..27109c5245 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -696,7 +696,7 @@ func TestResolveLoggingConfig(t *testing.T) { Format: "TEXT", }, }, - expectedLogFormat: "text", // Should default to JSON + expectedLogFormat: "text", }, { name: "invalid_log_format", From 842a7d3ee8dc16aba503f80f20c8912f1641ad51 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Wed, 27 Aug 2025 13:27:34 +0530 Subject: [PATCH 0732/1298] Hide flag create-empty-file on gcsfuse --help (#3755) Needed to fix b/438387832. This flag is no longer advised for end user to use. --- cfg/config.go | 4 ++++ cfg/params.yaml | 1 + 2 files changed, 5 insertions(+) diff --git a/cfg/config.go b/cfg/config.go index 29f9a4b62b..2a8700fb3d 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -329,6 +329,10 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { flagSet.BoolP("create-empty-file", "", false, "For a new file, it creates an empty file in Cloud Storage bucket as a hold.") + if err := flagSet.MarkHidden("create-empty-file"); err != nil { + return err + } + flagSet.StringP("custom-endpoint", "", "", "Specifies an alternative custom endpoint for fetching data. The custom endpoint must support the equivalent resources and operations as the GCS JSON endpoint, https://storage.googleapis.com/storage/v1. If a custom endpoint is not specified, GCSFuse uses the global GCS JSON API endpoint, https://storage.googleapis.com/storage/v1.") flagSet.BoolP("debug_fs", "", false, "This flag is unused.") diff --git a/cfg/params.yaml b/cfg/params.yaml index 3db73c12bc..be86fc0bbd 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -836,6 +836,7 @@ type: "bool" usage: "For a new file, it creates an empty file in Cloud Storage bucket as a hold." default: false + hide-flag: true - config-path: "write.enable-rapid-appends" flag-name: "enable-rapid-appends" From a6eecf6abf78efe3a0cd524ffcbed2b261b04439 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 09:10:37 +0000 Subject: [PATCH 0733/1298] Redo per code review comments. --- cfg/rationalize.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 4016a3b872..8b6227ed5a 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -141,9 +141,8 @@ func resolveLoggingConfig(config *Config) { config.Logging.Severity = "TRACE" } - if slices.Contains([]string{logFormatText, logFormatJSON}, strings.ToLower(config.Logging.Format)) { - config.Logging.Format = strings.ToLower(config.Logging.Format) - } else { + config.Logging.Format = strings.ToLower(config.Logging.Format) + if !slices.Contains([]string{logFormatText, logFormatJSON}, config.Logging.Format) { log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", config.Logging.Format, defaultLogFormat) config.Logging.Format = defaultLogFormat // defaulting to json format } From cd3eb5fba9348f7b8e9790c558fcf8ba30fe42ea Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 09:34:19 +0000 Subject: [PATCH 0734/1298] Print log message in the original format of user input --- cfg/rationalize.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 8b6227ed5a..42c3329e4f 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -141,9 +141,10 @@ func resolveLoggingConfig(config *Config) { config.Logging.Severity = "TRACE" } + var configLogFormat = config.Logging.Format // capture initial value for error reporting config.Logging.Format = strings.ToLower(config.Logging.Format) if !slices.Contains([]string{logFormatText, logFormatJSON}, config.Logging.Format) { - log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", config.Logging.Format, defaultLogFormat) + log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", configLogFormat, defaultLogFormat) config.Logging.Format = defaultLogFormat // defaulting to json format } } From c08e0dcf0bab3fcdf23a7e028c6ce5d710209bd0 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 09:37:54 +0000 Subject: [PATCH 0735/1298] Make test case more robust. --- cfg/rationalize_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 27109c5245..3ec3715214 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -705,7 +705,7 @@ func TestResolveLoggingConfig(t *testing.T) { Format: "INVALID", }, }, - expectedLogFormat: defaultLogFormat, // Should default to JSON + expectedLogFormat: "json", }, } From 44a87f7dd295d9bcd7d9c8d21516c2a248e1f3e0 Mon Sep 17 00:00:00 2001 From: Aditya Alle Date: Wed, 27 Aug 2025 09:46:33 +0000 Subject: [PATCH 0736/1298] Code cleanup --- cfg/rationalize.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 42c3329e4f..9854554a9f 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -141,7 +141,7 @@ func resolveLoggingConfig(config *Config) { config.Logging.Severity = "TRACE" } - var configLogFormat = config.Logging.Format // capture initial value for error reporting + configLogFormat := config.Logging.Format // capture initial value for error reporting config.Logging.Format = strings.ToLower(config.Logging.Format) if !slices.Contains([]string{logFormatText, logFormatJSON}, config.Logging.Format) { log.Printf("Unsupported log format provided: %s. Defaulting to %s log format.", configLogFormat, defaultLogFormat) From 09fac4983a2cbb6810424193010d8d9be52a0dc9 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Thu, 28 Aug 2025 10:49:38 +0530 Subject: [PATCH 0737/1298] Update default value of readGlobalMaxBlocks to 40 (#3760) --- cfg/config.go | 2 +- cfg/params.yaml | 2 +- cmd/config_validation_test.go | 4 ++-- cmd/root_test.go | 14 +++++++------- cmd/testdata/valid_config.yaml | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/cfg/config.go b/cfg/config.go index 2a8700fb3d..e7ca6c995e 100644 --- a/cfg/config.go +++ b/cfg/config.go @@ -645,7 +645,7 @@ func BuildFlagSet(flagSet *pflag.FlagSet) error { return err } - flagSet.IntP("read-global-max-blocks", "", 80, "Specifies the maximum number of blocks available for buffered reads across all file-handles. The value should be >= 0 or -1 (for infinite blocks). A value of 0 disables buffered reads.") + flagSet.IntP("read-global-max-blocks", "", 40, "Specifies the maximum number of blocks available for buffered reads across all file-handles. The value should be >= 0 or -1 (for infinite blocks). A value of 0 disables buffered reads.") flagSet.DurationP("read-inactive-stream-timeout", "", 10000000000*time.Nanosecond, "Duration of inactivity after which an open GCS read stream is automatically closed. This helps conserve resources when a file handle remains open without active Read calls. A value of '0s' disables this timeout.") diff --git a/cfg/params.yaml b/cfg/params.yaml index be86fc0bbd..80b6de6d04 100644 --- a/cfg/params.yaml +++ b/cfg/params.yaml @@ -775,7 +775,7 @@ Specifies the maximum number of blocks available for buffered reads across all file-handles. The value should be >= 0 or -1 (for infinite blocks). A value of 0 disables buffered reads. - default: 80 + default: 40 - config-path: "read.inactive-stream-timeout" flag-name: "read-inactive-stream-timeout" diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 3b4ee9bddc..9a2539620a 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -239,7 +239,7 @@ func TestValidateConfigFile_ReadConfig(t *testing.T) { InactiveStreamTimeout: 10 * time.Second, BlockSizeMb: 16, EnableBufferedRead: false, - GlobalMaxBlocks: 80, + GlobalMaxBlocks: 40, MaxBlocksPerHandle: 20, StartBlocksPerHandle: 1, MinBlocksPerHandle: 4, @@ -256,7 +256,7 @@ func TestValidateConfigFile_ReadConfig(t *testing.T) { BlockSizeMb: 8, EnableBufferedRead: true, MaxBlocksPerHandle: 20, - GlobalMaxBlocks: 40, + GlobalMaxBlocks: 20, StartBlocksPerHandle: 4, MinBlocksPerHandle: 2, RandomSeekThreshold: 10, diff --git a/cmd/root_test.go b/cmd/root_test.go index d1f371a979..ff99ebe371 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -407,7 +407,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test default flags.", args: []string{"gcsfuse", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 80, + expectedReadGlobalMaxBlocks: 40, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 4, @@ -416,7 +416,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test enable buffered read flag true.", args: []string{"gcsfuse", "--enable-buffered-read", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 80, + expectedReadGlobalMaxBlocks: 40, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 4, @@ -425,7 +425,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test enable buffered read flag false.", args: []string{"gcsfuse", "--enable-buffered-read=false", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 80, + expectedReadGlobalMaxBlocks: 40, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 4, @@ -434,7 +434,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test positive read-block-size-mb flag.", args: []string{"gcsfuse", "--read-block-size-mb=10", "abc", "pqr"}, expectedReadBlockSizeMB: 10, - expectedReadGlobalMaxBlocks: 80, + expectedReadGlobalMaxBlocks: 40, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 4, @@ -452,7 +452,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test positive read-max-blocks-per-handle flag.", args: []string{"gcsfuse", "--read-max-blocks-per-handle=10", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 80, + expectedReadGlobalMaxBlocks: 40, expectedReadMaxBlocksPerHandle: 10, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 4, @@ -461,7 +461,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test positive read-start-blocks-per-handle flag.", args: []string{"gcsfuse", "--read-start-blocks-per-handle=10", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 80, + expectedReadGlobalMaxBlocks: 40, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 10, expectedReadMinBlocksPerHandle: 4, @@ -470,7 +470,7 @@ func TestArgsParsing_ReadConfigFlags(t *testing.T) { name: "Test positive read-min-blocks-per-handle flag.", args: []string{"gcsfuse", "--read-min-blocks-per-handle=10", "abc", "pqr"}, expectedReadBlockSizeMB: 16, - expectedReadGlobalMaxBlocks: 80, + expectedReadGlobalMaxBlocks: 40, expectedReadMaxBlocksPerHandle: 20, expectedReadStartBlocksPerHandle: 1, expectedReadMinBlocksPerHandle: 10, diff --git a/cmd/testdata/valid_config.yaml b/cmd/testdata/valid_config.yaml index 6e3ec0346f..8891598add 100644 --- a/cmd/testdata/valid_config.yaml +++ b/cmd/testdata/valid_config.yaml @@ -2,7 +2,7 @@ app-name: hello read: inactive-stream-timeout: 10s enable-buffered-read: true - global-max-blocks: 40 + global-max-blocks: 20 block-size-mb: 8 start-blocks-per-handle: 4 max-blocks-per-handle: 20 From 21bfce33c73e9616707089096612d7484b0d1f99 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:25:05 +0530 Subject: [PATCH 0738/1298] refactor: readonly test package migration [GKE-GCSFuse Test migration] (#3759) * move read only package * review comments --- .../explicit_dir/explicit_dir_test.go | 11 +-- tools/integration_tests/gzip/gzip_test.go | 17 +--- .../implicit_dir/implicit_dir_test.go | 14 +-- .../list_large_dir/list_large_dir_test.go | 11 +-- .../readonly/readonly_test.go | 95 ++++++++----------- tools/integration_tests/test_config.yaml | 20 +++- .../util/creds_tests/creds.go | 22 ++++- tools/integration_tests/util/setup/setup.go | 38 ++++---- .../write_large_files_test.go | 14 +-- 9 files changed, 110 insertions(+), 132 deletions(-) diff --git a/tools/integration_tests/explicit_dir/explicit_dir_test.go b/tools/integration_tests/explicit_dir/explicit_dir_test.go index c4cb4148b7..1b22fb0ccc 100644 --- a/tools/integration_tests/explicit_dir/explicit_dir_test.go +++ b/tools/integration_tests/explicit_dir/explicit_dir_test.go @@ -59,12 +59,7 @@ func TestMain(m *testing.M) { // 2. Create storage client before running tests. testEnv.ctx = context.Background() - - bucketType, err := setup.BucketType(testEnv.ctx, cfg.ExplicitDir[0].TestBucket) - if err != nil { - log.Fatalf("BucketType failed: %v", err) - } - + bucketType := setup.BucketTestEnvironment(testEnv.ctx, cfg.ExplicitDir[0].TestBucket) closeStorageClient := client.CreateStorageClientWithCancel(&testEnv.ctx, &testEnv.storageClient) defer func() { err := closeStorageClient() @@ -73,10 +68,10 @@ func TestMain(m *testing.M) { } }() - // 4. Build the flag sets dynamically from the config. + // 3. Build the flag sets dynamically from the config. flags := setup.BuildFlagSets(cfg.ExplicitDir[0], bucketType) - // 5. Run tests with the dynamically generated flags. + // 4. Run tests with the dynamically generated flags. successCode := implicit_and_explicit_dir_setup.RunTestsForExplicitAndImplicitDir(&cfg.ExplicitDir[0], flags, m) os.Exit(successCode) } diff --git a/tools/integration_tests/gzip/gzip_test.go b/tools/integration_tests/gzip/gzip_test.go index 6e3bdf1e3c..8351093478 100644 --- a/tools/integration_tests/gzip/gzip_test.go +++ b/tools/integration_tests/gzip/gzip_test.go @@ -209,19 +209,10 @@ func TestMain(m *testing.M) { } cfg.Gzip[0].Configs[0].Compatible = map[string]bool{"flat": true, "hns": true, "zonal": true} } - var err error - - setup.SetBucketFromConfigFile(cfg.Gzip[0].TestBucket) - ctx = context.Background() - bucketType, err := setup.BucketType(ctx, cfg.Gzip[0].TestBucket) - if err != nil { - log.Fatalf("BucketType failed: %v", err) - } - if bucketType == setup.ZonalBucket { - setup.SetIsZonalBucketRun(true) - } // 2. Create storage client before running tests. + ctx = context.Background() + bucketType := setup.BucketTestEnvironment(ctx, cfg.Gzip[0].TestBucket) closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) defer func() { err := closeStorageClient() @@ -230,7 +221,7 @@ func TestMain(m *testing.M) { } }() - err = setup_testdata() + err := setup_testdata() if err != nil { log.Fatalf("Failed to setup test data: %v", err) } @@ -248,7 +239,7 @@ func TestMain(m *testing.M) { os.Exit(setup.RunTestsForMountedDirectory(cfg.Gzip[0].MountedDirectory, m)) } - // Run tests for testBucket// Run tests for testBucket + // Run tests for testBucket. // 4. Build the flag sets dynamically from the config. flags := setup.BuildFlagSets(cfg.Gzip[0], bucketType) diff --git a/tools/integration_tests/implicit_dir/implicit_dir_test.go b/tools/integration_tests/implicit_dir/implicit_dir_test.go index d454146fd5..6e3029fa33 100644 --- a/tools/integration_tests/implicit_dir/implicit_dir_test.go +++ b/tools/integration_tests/implicit_dir/implicit_dir_test.go @@ -79,14 +79,8 @@ func TestMain(m *testing.M) { } // 2. Create storage client before running tests. - setup.SetBucketFromConfigFile(cfg.ImplicitDir[0].TestBucket) testEnv.ctx = context.Background() - - bucketType, err := setup.BucketType(testEnv.ctx, cfg.ImplicitDir[0].TestBucket) - if err != nil { - log.Fatalf("BucketType failed: %v", err) - } - + bucketType := setup.BucketTestEnvironment(testEnv.ctx, cfg.ImplicitDir[0].TestBucket) closeStorageClient := client.CreateStorageClientWithCancel(&testEnv.ctx, &testEnv.storageClient) defer func() { err := closeStorageClient() @@ -95,14 +89,14 @@ func TestMain(m *testing.M) { } }() - // 4. Build the flag sets dynamically from the config. + // 3. Build the flag sets dynamically from the config. flags := setup.BuildFlagSets(cfg.ImplicitDir[0], bucketType) - // 5. Run tests with the dynamically generated flags. + // 4. Run tests with the dynamically generated flags. successCode := implicit_and_explicit_dir_setup.RunTestsForExplicitAndImplicitDir(&cfg.ImplicitDir[0], flags, m) setup.SaveLogFileInCaseOfFailure(successCode) - // Clean up test directory created. + // 5. Clean up test directory created. setup.CleanupDirectoryOnGCS(testEnv.ctx, testEnv.storageClient, path.Join(setup.TestBucket(), testDirName)) os.Exit(successCode) } diff --git a/tools/integration_tests/list_large_dir/list_large_dir_test.go b/tools/integration_tests/list_large_dir/list_large_dir_test.go index 211050964b..5c43cba1eb 100644 --- a/tools/integration_tests/list_large_dir/list_large_dir_test.go +++ b/tools/integration_tests/list_large_dir/list_large_dir_test.go @@ -62,17 +62,8 @@ func TestMain(m *testing.M) { } // 2. Create storage client before running tests. - setup.SetBucketFromConfigFile(cfg.ListLargeDir[0].TestBucket) ctx = context.Background() - - bucketType, err := setup.BucketType(ctx, cfg.ListLargeDir[0].TestBucket) - if err != nil { - log.Fatalf("BucketType failed: %v", err) - } - if bucketType == setup.ZonalBucket { - setup.SetIsZonalBucketRun(true) - } - + bucketType := setup.BucketTestEnvironment(ctx, cfg.ListLargeDir[0].TestBucket) closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) defer func() { err := closeStorageClient() diff --git a/tools/integration_tests/readonly/readonly_test.go b/tools/integration_tests/readonly/readonly_test.go index 2dcf2203c3..cbd304f2b9 100644 --- a/tools/integration_tests/readonly/readonly_test.go +++ b/tools/integration_tests/readonly/readonly_test.go @@ -21,7 +21,6 @@ import ( "log" "os" "path" - "strconv" "strings" "testing" @@ -31,6 +30,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/persistent_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" ) const TestDirForReadOnlyTest = "testDirForReadOnlyTest" @@ -53,7 +53,6 @@ const RenameDir = "rename" var ( storageClient *storage.Client ctx context.Context - cacheDir string ) func createTestDataForReadOnlyTests(ctx context.Context, storageClient *storage.Client) error { @@ -100,76 +99,66 @@ func checkErrorForObjectNotExist(err error, t *testing.T) { } } -func createMountConfigsAndEquivalentFlags() (flags [][]string) { - cacheDirPath := path.Join(os.TempDir(), cacheDir) - - // Set up config file for file cache. - mountConfig := map[string]interface{}{ - "file-cache": map[string]interface{}{ - // Keeping the size as small because the operations are performed on small - // files. - "max-size-mb": 3, - }, - "cache-dir": cacheDirPath, - } - filePath := setup.YAMLConfigFile(mountConfig, "config.yaml") - flags = append(flags, []string{"--o=ro", "--implicit-dirs=true", "--config-file=" + filePath}) - - return flags -} - func TestMain(m *testing.M) { setup.ParseSetUpFlags() - - var err error ctx = context.Background() - storageClient, err = client.CreateStorageClient(ctx) - if err != nil { - log.Printf("Error creating storage client: %v\n", err) - os.Exit(1) - } - defer storageClient.Close() - - cacheDir = "cache-dir-readonly-hns-" + strconv.FormatBool(setup.IsHierarchicalBucket(ctx, storageClient)) - flags := [][]string{{"--o=ro", "--implicit-dirs=true"}, {"--file-mode=544", "--dir-mode=544", "--implicit-dirs=true"}} - - if !testing.Short() { - flags = append(flags, []string{"--client-protocol=grpc", "--o=ro", "--implicit-dirs=true"}) + // 1. Load and parse the common configuration. + cfg := test_suite.ReadConfigFile(setup.ConfigFile()) + if len(cfg.ReadOnly) == 0 { + log.Println("No configuration found for readonly tests in config. Using flags instead.") + // Populate the config manually. + cfg.ReadOnly = make([]test_suite.TestConfig, 1) + cfg.ReadOnly[0].TestBucket = setup.TestBucket() + cfg.ReadOnly[0].MountedDirectory = setup.MountedDirectory() + cfg.ReadOnly[0].Configs = make([]test_suite.ConfigItem, 1) + cacheDirPath := path.Join(os.TempDir(), "cache-dir-readonly-"+setup.GenerateRandomString(4)) + cfg.ReadOnly[0].Configs[0].Flags = []string{ + "--o=ro --implicit-dirs=true", + "--file-mode=544 --dir-mode=544 --implicit-dirs=true", + "--client-protocol=grpc --o=ro --implicit-dirs=true", + fmt.Sprintf("--o=ro --implicit-dirs=true --cache-dir=%s --file-cache-max-size-mb=3", cacheDirPath), + } + cfg.ReadOnly[0].Configs[0].Compatible = map[string]bool{"flat": true, "hns": true, "zonal": true} } - setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() - - if setup.TestBucket() == "" && setup.MountedDirectory() != "" { - log.Print("Please pass the name of bucket mounted at mountedDirectory to --testBucket flag.") - os.Exit(1) - } + // 2. Create storage client before running tests. + bucketType := setup.BucketTestEnvironment(ctx, cfg.ReadOnly[0].TestBucket) + closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) + defer func() { + err := closeStorageClient() + if err != nil { + log.Fatalf("closeStorageClient failed: %v", err) + } + }() // Create test data. if err := createTestDataForReadOnlyTests(ctx, storageClient); err != nil { - log.Printf("Failed creating test data for readonly tests: %v", err) - os.Exit(1) + log.Fatalf("Failed creating test data for readonly tests: %v", err) } - // Run tests for mountedDirectory only if --mountedDirectory flag is set. - setup.RunTestsForMountedDirectoryFlag(m) + // 3. To run mountedDirectory tests, we need both testBucket and mountedDirectory + // flags to be set. + if cfg.ReadOnly[0].MountedDirectory != "" && cfg.ReadOnly[0].TestBucket != "" { + os.Exit(setup.RunTestsForMountedDirectory(cfg.ReadOnly[0].MountedDirectory, m)) + } - // Run tests for testBucket - setup.SetUpTestDirForTestBucketFlag() + // Run tests for testBucket// Run tests for testBucket + // 4. Build the flag sets dynamically from the config. + flags := setup.BuildFlagSets(cfg.ReadOnly[0], bucketType) - // Setup config file for tests when --testbucket flag is enabled. - mountConfigFlags := createMountConfigsAndEquivalentFlags() - flags = append(flags, mountConfigFlags...) + setup.SetUpTestDirForTestBucket(cfg.ReadOnly[0].TestBucket) - successCode := static_mounting.RunTests(flags, m) + successCode := static_mounting.RunTestsWithConfigFile(&cfg.ReadOnly[0], flags, m) if successCode == 0 { - successCode = persistent_mounting.RunTests(flags, m) + successCode = persistent_mounting.RunTestsWithConfigFile(&cfg.ReadOnly[0], flags, m) } if successCode == 0 { - // Test for viewer permission on test bucket. - successCode = creds_tests.RunTestsForKeyFileAndGoogleApplicationCredentialsEnvVarSet(ctx, storageClient, flags, "objectViewer", m) + // These tests don't apply to GCSFuse sidecar. + // Validate that tests work with viewer permission on test bucket. + successCode = creds_tests.RunTestsForDifferentAuthMethods(ctx, &cfg.ReadOnly[0], storageClient, flags, "objectViewer", m) } os.Exit(successCode) diff --git a/tools/integration_tests/test_config.yaml b/tools/integration_tests/test_config.yaml index f64505bb68..04fc16c82e 100644 --- a/tools/integration_tests/test_config.yaml +++ b/tools/integration_tests/test_config.yaml @@ -52,13 +52,13 @@ list_large_dir: run_on_gke: false configs: - flags: - - "--implicit-dirs=true --stat-cache-ttl=0 --kernel-list-cache-ttl-secs=-1" + - "--implicit-dirs=true --stat-cache-ttl=0 --kernel-list-cache-ttl-secs=-1" compatible: flat: true hns: true zonal: true - flags: - - "--client-protocol=grpc --implicit-dirs=true --stat-cache-ttl=0 --kernel-list-cache-ttl-secs=-1" + - "--client-protocol=grpc --implicit-dirs=true --stat-cache-ttl=0 --kernel-list-cache-ttl-secs=-1" compatible: flat: true hns: true @@ -94,3 +94,19 @@ gzip: flat: true hns: true zonal: true + +readonly: + - mounted_directory: "${MOUNTED_DIR}" # To be passed by GKE after mounting + test_bucket: "${BUCKET_NAME}" # To be passed by both gcsfuse and gke tests + log_file: # Not required by readonly tests. + run_on_gke: false + configs: + - flags: + - "--o=ro --implicit-dirs=true" + - "--file-mode=544 --dir-mode=544 --implicit-dirs=true" + - "--client-protocol=grpc --o=ro --implicit-dirs=true" + - "--o=ro --implicit-dirs=true --cache-dir=${CACHE_DIR_PATH} --file-cache-max-size-mb=3" + compatible: + flat: true + hns: true + zonal: true diff --git a/tools/integration_tests/util/creds_tests/creds.go b/tools/integration_tests/util/creds_tests/creds.go index 672774d86f..26da942a3d 100644 --- a/tools/integration_tests/util/creds_tests/creds.go +++ b/tools/integration_tests/util/creds_tests/creds.go @@ -36,6 +36,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" ) const NameOfServiceAccount = "creds-integration-tests" @@ -124,7 +125,7 @@ func RevokePermission(ctx context.Context, storageClient *storage.Client, servic } } -func RunTestsForKeyFileAndGoogleApplicationCredentialsEnvVarSet(ctx context.Context, storageClient *storage.Client, testFlagSet [][]string, permission string, m *testing.M) (successCode int) { +func RunTestsForDifferentAuthMethods(ctx context.Context, cfg *test_suite.TestConfig, storageClient *storage.Client, testFlagSet [][]string, permission string, m *testing.M) (successCode int) { serviceAccount, localKeyFilePath := CreateCredentials(ctx) ApplyPermissionToServiceAccount(ctx, storageClient, serviceAccount, permission, setup.TestBucket()) defer RevokePermission(ctx, storageClient, serviceAccount, permission, setup.TestBucket()) @@ -139,7 +140,7 @@ func RunTestsForKeyFileAndGoogleApplicationCredentialsEnvVarSet(ctx context.Cont setup.LogAndExit(fmt.Sprintf("Error in setting environment variable: %v", err)) } - successCode = static_mounting.RunTests(testFlagSet, m) + successCode = static_mounting.RunTestsWithConfigFile(cfg, testFlagSet, m) if successCode != 0 { return @@ -152,7 +153,7 @@ func RunTestsForKeyFileAndGoogleApplicationCredentialsEnvVarSet(ctx context.Cont testFlagSet[i] = append(testFlagSet[i], keyFileFlag) } - successCode = static_mounting.RunTests(testFlagSet, m) + successCode = static_mounting.RunTestsWithConfigFile(cfg, testFlagSet, m) if successCode != 0 { return @@ -163,8 +164,8 @@ func RunTestsForKeyFileAndGoogleApplicationCredentialsEnvVarSet(ctx context.Cont setup.LogAndExit(fmt.Sprintf("Error in unsetting environment variable: %v", err)) } - // Testing with --key-file flag only - successCode = static_mounting.RunTests(testFlagSet, m) + // Testing with --key-file flag only. + successCode = static_mounting.RunTestsWithConfigFile(cfg, testFlagSet, m) if successCode != 0 { return @@ -172,3 +173,14 @@ func RunTestsForKeyFileAndGoogleApplicationCredentialsEnvVarSet(ctx context.Cont return successCode } + +// Deprecated: Use RunTestsForDifferentAuthMethods instead. +// TODO(b/438068132): cleanup deprecated methods after migration is complete. +func RunTestsForKeyFileAndGoogleApplicationCredentialsEnvVarSet(ctx context.Context, storageClient *storage.Client, testFlagSet [][]string, permission string, m *testing.M) (successCode int) { + config := &test_suite.TestConfig{ + TestBucket: setup.TestBucket(), + MountedDirectory: setup.MountedDirectory(), + LogFile: setup.LogFile(), + } + return RunTestsForDifferentAuthMethods(ctx, config, storageClient, testFlagSet, permission, m) +} diff --git a/tools/integration_tests/util/setup/setup.go b/tools/integration_tests/util/setup/setup.go index b61dc54dfd..f18fd21275 100644 --- a/tools/integration_tests/util/setup/setup.go +++ b/tools/integration_tests/util/setup/setup.go @@ -97,10 +97,6 @@ func SetIsZonalBucketRun(val bool) { *isZonalBucketRun = val } -func IsIntegrationTest() bool { - return *integrationTest -} - func TestBucket() string { return *testBucket } @@ -386,20 +382,6 @@ func ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() { } } -func ExitWithFailureIfMountedDirectoryIsSetOrTestBucketIsNotSet() { - ParseSetUpFlags() - - if *testBucket == "" { - log.Print("Please pass the name of bucket to be mounted to --testBucket flag. It is required for this test.") - os.Exit(1) - } - - if *mountedDirectory != "" { - log.Print("Please do not pass the mountedDirectory at test runtime. It is not supported for this test.") - os.Exit(1) - } -} - // Deprecated: Use RunTestsForMountedDirectory instead. // TODO(b/438068132): cleanup deprecated methods after migration is complete. func RunTestsForMountedDirectoryFlag(m *testing.M) { @@ -557,6 +539,21 @@ func ResolveIsHierarchicalBucket(ctx context.Context, testBucket string, storage return false } +// BucketTestEnvironment sets the global testBucket and isZonalBucket variable +// based on the bucket type. +func BucketTestEnvironment(ctx context.Context, bucketName string) string { + SetTestBucket(bucketName) + bucketType, err := BucketType(ctx, bucketName) + if err != nil { + log.Fatalf("BucketType failed: %v", err) + } + if bucketType == ZonalBucket { + SetIsZonalBucketRun(true) + } + + return bucketType +} + const FlatBucket = "flat" const HNSBucket = "hns" const ZonalBucket = "zonal" @@ -600,8 +597,9 @@ func BuildFlagSets(cfg test_suite.TestConfig, bucketType string) [][]string { return dynamicFlags } -func SetBucketFromConfigFile(testBucketFromConfigFile string) { - testBucket = &testBucketFromConfigFile +// SetTestBucket sets the testBucket global variable. +func SetTestBucket(bucketName string) { + testBucket = &bucketName } // Explicitly set the enable-hns config flag to true when running tests on the HNS bucket. diff --git a/tools/integration_tests/write_large_files/write_large_files_test.go b/tools/integration_tests/write_large_files/write_large_files_test.go index f51f8f0d7a..1780325710 100644 --- a/tools/integration_tests/write_large_files/write_large_files_test.go +++ b/tools/integration_tests/write_large_files/write_large_files_test.go @@ -58,17 +58,9 @@ func TestMain(m *testing.M) { cfg.WriteLargeFiles[0].Configs[0].Compatible = map[string]bool{"flat": true, "hns": true, "zonal": true} } - setup.SetBucketFromConfigFile(cfg.WriteLargeFiles[0].TestBucket) - ctx = context.Background() - bucketType, err := setup.BucketType(ctx, cfg.WriteLargeFiles[0].TestBucket) - if err != nil { - log.Fatalf("BucketType failed: %v", err) - } - if bucketType == setup.ZonalBucket { - setup.SetIsZonalBucketRun(true) - } - // 2. Create storage client before running tests. + ctx = context.Background() + bucketType := setup.BucketTestEnvironment(ctx, cfg.WriteLargeFiles[0].TestBucket) closeStorageClient := client.CreateStorageClientWithCancel(&ctx, &storageClient) defer func() { err := closeStorageClient() @@ -83,7 +75,7 @@ func TestMain(m *testing.M) { os.Exit(setup.RunTestsForMountedDirectory(cfg.WriteLargeFiles[0].MountedDirectory, m)) } - // Run tests for testBucket// Run tests for testBucket + // Run tests for testBucket // 4. Build the flag sets dynamically from the config. flags := setup.BuildFlagSets(cfg.WriteLargeFiles[0], bucketType) From a073d5deb958fa2d9f242d66449c68128d5dbc31 Mon Sep 17 00:00:00 2001 From: Vipin Yadav Date: Fri, 29 Aug 2025 04:45:39 +0530 Subject: [PATCH 0739/1298] Add buffered reader to useragent config (#3762) --- cmd/legacy_main.go | 8 +++++-- cmd/legacy_main_test.go | 51 ++++++++++++++++++++++++++++++----------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/cmd/legacy_main.go b/cmd/legacy_main.go index dcdc5a2557..8977783ec2 100644 --- a/cmd/legacy_main.go +++ b/cmd/legacy_main.go @@ -104,7 +104,7 @@ func getUserAgent(appName string, config string) string { } func getConfigForUserAgent(mountConfig *cfg.Config) string { - // Minimum configuration details created in a bitset fashion. Right now, its restricted only to File Cache Settings. + // Minimum configuration details created in a bitset fashion. isFileCacheEnabled := "0" if cfg.IsFileCacheEnabled(mountConfig) { isFileCacheEnabled = "1" @@ -121,7 +121,11 @@ func getConfigForUserAgent(mountConfig *cfg.Config) string { if mountConfig.Write.EnableStreamingWrites { areStreamingWritesEnabled = "1" } - return fmt.Sprintf("%s:%s:%s:%s", isFileCacheEnabled, isFileCacheForRangeReadEnabled, isParallelDownloadsEnabled, areStreamingWritesEnabled) + isBufferedReadEnabled := "0" + if mountConfig.Read.EnableBufferedRead { + isBufferedReadEnabled = "1" + } + return fmt.Sprintf("%s:%s:%s:%s:%s", isFileCacheEnabled, isFileCacheForRangeReadEnabled, isParallelDownloadsEnabled, areStreamingWritesEnabled, isBufferedReadEnabled) } func createStorageHandle(newConfig *cfg.Config, userAgent string, metricHandle metrics.MetricHandle) (storageHandle storage.StorageHandle, err error) { storageClientConfig := storageutil.StorageClientConfig{ diff --git a/cmd/legacy_main_test.go b/cmd/legacy_main_test.go index 5717c98fa8..c5bbd2af6e 100644 --- a/cmd/legacy_main_test.go +++ b/cmd/legacy_main_test.go @@ -70,7 +70,7 @@ func (t *MainTest) TestGetUserAgentWhenMetadataImageTypeEnvVarIsSet() { userAgent := getUserAgent("AppName", getConfigForUserAgent(mountConfig)) - expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s AppName (GPN:gcsfuse-DLVM) (Cfg:0:0:0:0)", common.GetVersion())) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s AppName (GPN:gcsfuse-DLVM) (Cfg:0:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } @@ -79,7 +79,7 @@ func (t *MainTest) TestGetUserAgentWhenMetadataImageTypeEnvVarIsNotSet() { userAgent := getUserAgent("AppName", getConfigForUserAgent(mountConfig)) - expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } @@ -88,7 +88,7 @@ func (t *MainTest) TestGetUserAgentConfigWithNoFileCache() { userAgent := getUserAgent("AppName", getConfigForUserAgent(mountConfig)) - expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } @@ -106,7 +106,7 @@ func (t *MainTest) TestGetUserAgentConfig() { MaxSizeMb: 0, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0:0)", common.GetVersion())), }, { name: "Config with file cache disabled where maxsize is set but cache dir is not set.", @@ -115,7 +115,7 @@ func (t *MainTest) TestGetUserAgentConfig() { MaxSizeMb: -1, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0:0)", common.GetVersion())), }, { name: "Config with file cache enabled but random read disabled.", @@ -125,7 +125,7 @@ func (t *MainTest) TestGetUserAgentConfig() { MaxSizeMb: -1, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:0:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:0:0:0)", common.GetVersion())), }, { name: "Config with file cache and random read enabled.", @@ -136,7 +136,7 @@ func (t *MainTest) TestGetUserAgentConfig() { CacheFileForRangeRead: true, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:1:0:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:1:0:0:0)", common.GetVersion())), }, { name: "Config with file cache disabled and enable parallel downloads set.", @@ -147,7 +147,7 @@ func (t *MainTest) TestGetUserAgentConfig() { EnableParallelDownloads: true, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0:0)", common.GetVersion())), }, { name: "Config with file cache and parallel downloads enabled.", @@ -158,7 +158,7 @@ func (t *MainTest) TestGetUserAgentConfig() { EnableParallelDownloads: true, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:1:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:1:0:0)", common.GetVersion())), }, { name: "Config with file cache, random reads and parallel downloads enabled.", @@ -170,7 +170,7 @@ func (t *MainTest) TestGetUserAgentConfig() { EnableParallelDownloads: true, }, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:1:1:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:1:1:0:0)", common.GetVersion())), }, { name: "streaming_writes_enabled", @@ -183,7 +183,7 @@ func (t *MainTest) TestGetUserAgentConfig() { }, Write: cfg.WriteConfig{EnableStreamingWrites: true}, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:1:1)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:1:1:0)", common.GetVersion())), }, { name: "streaming_writes_disabled", @@ -196,7 +196,32 @@ func (t *MainTest) TestGetUserAgentConfig() { }, Write: cfg.WriteConfig{EnableStreamingWrites: false}, }, - expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:1:0:0)", common.GetVersion())), + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:1:0:0:0)", common.GetVersion())), + }, + { + name: "buffered_read_enabled", + mountConfig: &cfg.Config{ + Read: cfg.ReadConfig{EnableBufferedRead: true}, + }, + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0:1)", common.GetVersion())), + }, + { + name: "buffered_read_disabled", + mountConfig: &cfg.Config{ + Read: cfg.ReadConfig{EnableBufferedRead: false}, + }, + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:0:0:0:0:0)", common.GetVersion())), + }, + { + name: "file_cache_enabled_and_buffered_read_enabled", + mountConfig: &cfg.Config{ + CacheDir: "/cache/path", + FileCache: cfg.FileCacheConfig{MaxSizeMb: -1}, + Read: cfg.ReadConfig{EnableBufferedRead: true}, + }, + // Note: getConfigForUserAgent runs before config rationalization, which + // would disable buffered-read when file-cache is enabled. + expectedUserAgent: strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-AppName) (Cfg:1:0:0:0:1)", common.GetVersion())), }, } @@ -215,7 +240,7 @@ func (t *MainTest) TestGetUserAgentWhenMetadataImageTypeEnvVarSetAndAppNameNotSe userAgent := getUserAgent("", getConfigForUserAgent(mountConfig)) - expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-DLVM) (Cfg:0:0:0:0)", common.GetVersion())) + expectedUserAgent := strings.TrimSpace(fmt.Sprintf("gcsfuse/%s (GPN:gcsfuse-DLVM) (Cfg:0:0:0:0:0)", common.GetVersion())) assert.Equal(t.T(), expectedUserAgent, userAgent) } From 049d7925792cf1dd5a3a42414f7224468d0d3254 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 29 Aug 2025 09:55:04 +0530 Subject: [PATCH 0740/1298] ci: Reenable e2e test to move unfinalized object (#3757) This test was earlier disabled because MoveObject for unfinalized object was unstable and was passing for us-west4-a and failing for us-central1-a . Now it seems to be passing both the zones. So, now re-enabling this test. --- .../unfinalized_object/unfinalized_object_operations_test.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go index 4dcf8c415c..02599d979d 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go @@ -121,9 +121,6 @@ func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCanBeRenamedIfCreated } func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCanBeRenamedIfCreatedFromDifferentMount() { - // TODO: Remove this skip when b/439785781 is resolved. - t.T().Skip("Skipping this test as rename for unfinalized objects is not yet supported.") - size := operations.MiB _ = client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), setup.GenerateRandomString(size)) From e29a573fa7b861b7f76c706f8a17a7c9abb55c13 Mon Sep 17 00:00:00 2001 From: anushka <78717608+anushka567@users.noreply.github.com> Date: Fri, 29 Aug 2025 10:00:27 +0530 Subject: [PATCH 0741/1298] chore(zonal buckets): Revert zero byte reader logic to get size of unfinalized object (#3736) * prelim fixes for reverting zero byte reader fix * stabilize tests * split to cater to diff consistency model along with preventing timeouts --- cmd/mount.go | 1 - internal/cache/file/cache_handle_test.go | 2 +- internal/cache/file/cache_handler_test.go | 2 +- .../cache/file/downloader/downloader_test.go | 2 +- .../downloader/jm_parallel_downloads_test.go | 4 +- internal/gcsx/bucket_manager.go | 3 +- internal/gcsx/bucket_manager_test.go | 2 +- internal/storage/bucket_handle.go | 55 +----------- internal/storage/bucket_handle_test.go | 33 +------ internal/storage/storage_handle.go | 13 ++- internal/storage/storage_handle_test.go | 10 +-- .../rapid_appends/appends_test.go | 8 +- .../rapid_appends/reads_after_appends_test.go | 90 ++++++++++++++++++- .../rapid_appends/suites_test.go | 7 +- .../unfinalized_object_operations_test.go | 2 + 15 files changed, 121 insertions(+), 113 deletions(-) diff --git a/cmd/mount.go b/cmd/mount.go index 65c8d10298..73b1aaabac 100644 --- a/cmd/mount.go +++ b/cmd/mount.go @@ -99,7 +99,6 @@ be interacting with the file system.`) AppendThreshold: 1 << 21, // 2 MiB, a total guess. ChunkTransferTimeoutSecs: newConfig.GcsRetries.ChunkTransferTimeoutSecs, TmpObjectPrefix: ".gcsfuse_tmp/", - EnableRapidAppends: newConfig.Write.EnableRapidAppends, } bm := gcsx.NewBucketManager(bucketCfg, storageHandle) diff --git a/internal/cache/file/cache_handle_test.go b/internal/cache/file/cache_handle_test.go index 8bb0bedd6c..767327014d 100644 --- a/internal/cache/file/cache_handle_test.go +++ b/internal/cache/file/cache_handle_test.go @@ -117,7 +117,7 @@ func (cht *cacheHandleTest) SetupTest() { storageHandle := cht.fakeStorage.CreateStorageHandle() mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(&controlpb.StorageLayout{}, nil) - cht.bucket, err = storageHandle.BucketHandle(ctx, storage.TestBucketName, "", false) + cht.bucket, err = storageHandle.BucketHandle(ctx, storage.TestBucketName, "") assert.Nil(cht.T(), err) // Create test object in the bucket. diff --git a/internal/cache/file/cache_handler_test.go b/internal/cache/file/cache_handler_test.go index 169efde0a4..74d60f389c 100644 --- a/internal/cache/file/cache_handler_test.go +++ b/internal/cache/file/cache_handler_test.go @@ -71,7 +71,7 @@ func initializeCacheHandlerTestArgs(t *testing.T, fileCacheConfig *cfg.FileCache mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(&controlpb.StorageLayout{}, nil) ctx := context.Background() - bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "", false) + bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") require.NoError(t, err) // Create test object in the bucket. diff --git a/internal/cache/file/downloader/downloader_test.go b/internal/cache/file/downloader/downloader_test.go index c90748931a..55fd73614c 100644 --- a/internal/cache/file/downloader/downloader_test.go +++ b/internal/cache/file/downloader/downloader_test.go @@ -66,7 +66,7 @@ func (dt *downloaderTest) setupHelper() { mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(&controlpb.StorageLayout{}, nil) ctx := context.Background() - dt.bucket, err = storageHandle.BucketHandle(ctx, storage.TestBucketName, "", false) + dt.bucket, err = storageHandle.BucketHandle(ctx, storage.TestBucketName, "") ExpectEq(nil, err) dt.initJobTest(DefaultObjectName, []byte("taco"), DefaultSequentialReadSizeMb, CacheMaxSize, func() {}) diff --git a/internal/cache/file/downloader/jm_parallel_downloads_test.go b/internal/cache/file/downloader/jm_parallel_downloads_test.go index a9f60d2dd9..d93964daf4 100644 --- a/internal/cache/file/downloader/jm_parallel_downloads_test.go +++ b/internal/cache/file/downloader/jm_parallel_downloads_test.go @@ -145,7 +145,7 @@ func TestParallelDownloads(t *testing.T) { cache, cacheDir := configureCache(t, 2*tc.objectSize) storageHandle := configureFakeStorage(t) ctx := context.Background() - bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "", false) + bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") assert.Nil(t, err) minObj, content := createObjectInStoreAndInitCache(t, cache, bucket, "path/in/gcs/foo.txt", tc.objectSize) fileCacheConfig := &cfg.FileCacheConfig{ @@ -187,7 +187,7 @@ func TestMultipleConcurrentDownloads(t *testing.T) { storageHandle := configureFakeStorage(t) cache, cacheDir := configureCache(t, 30*util.MiB) ctx := context.Background() - bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "", false) + bucket, err := storageHandle.BucketHandle(ctx, storage.TestBucketName, "") assert.Nil(t, err) minObj1, content1 := createObjectInStoreAndInitCache(t, cache, bucket, "path/in/gcs/foo.txt", 10*util.MiB) minObj2, content2 := createObjectInStoreAndInitCache(t, cache, bucket, "path/in/gcs/bar.txt", 5*util.MiB) diff --git a/internal/gcsx/bucket_manager.go b/internal/gcsx/bucket_manager.go index c8c4bf0a9b..eeeb708b8c 100644 --- a/internal/gcsx/bucket_manager.go +++ b/internal/gcsx/bucket_manager.go @@ -67,7 +67,6 @@ type BucketConfig struct { AppendThreshold int64 ChunkTransferTimeoutSecs int64 TmpObjectPrefix string - EnableRapidAppends bool } // BucketManager manages the lifecycle of buckets. @@ -170,7 +169,7 @@ func (bm *bucketManager) SetUpBucket( if name == canned.FakeBucketName { b = canned.MakeFakeBucket(ctx) } else { - b, err = bm.storageHandle.BucketHandle(ctx, name, bm.config.BillingProject, bm.config.EnableRapidAppends) + b, err = bm.storageHandle.BucketHandle(ctx, name, bm.config.BillingProject) if err != nil { err = fmt.Errorf("BucketHandle: %w", err) return diff --git a/internal/gcsx/bucket_manager_test.go b/internal/gcsx/bucket_manager_test.go index 4436f263a4..6ba89fbc22 100644 --- a/internal/gcsx/bucket_manager_test.go +++ b/internal/gcsx/bucket_manager_test.go @@ -61,7 +61,7 @@ func (t *BucketManagerTest) SetUp(_ *TestInfo) { LocationType: "zone", }, nil) ctx := context.Background() - t.bucket, err = t.storageHandle.BucketHandle(ctx, TestBucketName, "", false) + t.bucket, err = t.storageHandle.BucketHandle(ctx, TestBucketName, "") AssertNe(nil, t.bucket) AssertEq(nil, err) diff --git a/internal/storage/bucket_handle.go b/internal/storage/bucket_handle.go index 4a00608946..ad6fe92b0c 100644 --- a/internal/storage/bucket_handle.go +++ b/internal/storage/bucket_handle.go @@ -23,13 +23,11 @@ import ( "context" "fmt" "io" - "strings" "time" "cloud.google.com/go/storage" "cloud.google.com/go/storage/control/apiv2/controlpb" "github.com/googleapis/gax-go/v2" - "github.com/googlecloudplatform/gcsfuse/v3/internal/logger" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/gcs" "github.com/googlecloudplatform/gcsfuse/v3/internal/storage/storageutil" "google.golang.org/api/iterator" @@ -40,11 +38,10 @@ const FullBucketPathHNS = "projects/_/buckets/%s" type bucketHandle struct { gcs.Bucket - bucket *storage.BucketHandle - bucketName string - bucketType *gcs.BucketType - controlClient StorageControlClient - enableRapidAppends bool + bucket *storage.BucketHandle + bucketName string + bucketType *gcs.BucketType + controlClient StorageControlClient } func (bh *bucketHandle) Name() string { @@ -143,12 +140,6 @@ func (bh *bucketHandle) StatObject(ctx context.Context, err = fmt.Errorf("error in fetching object attributes: %w", err) return } - if attrs.Finalized.IsZero() && isGCSObject(attrs) { - if err = bh.fetchLatestSizeOfUnfinalizedObject(ctx, attrs); err != nil { - err = fmt.Errorf("failed to fetch the latest size of unfinalized object %q: %w", attrs.Name, err) - return - } - } // Converting attrs to type *Object o := storageutil.ObjectAttrsToBucketObject(attrs) @@ -160,29 +151,6 @@ func (bh *bucketHandle) StatObject(ctx context.Context, return } -// Note: This is not production ready code and will be removed once StatObject -// requests return correct attr values for appendable objects. -func (bh *bucketHandle) fetchLatestSizeOfUnfinalizedObject(ctx context.Context, attrs *storage.ObjectAttrs) error { - if bh.BucketType().Zonal && bh.enableRapidAppends { - // Get object handle - obj := bh.bucket.Object(attrs.Name) - // Create a new reader - reader, err := obj.NewRangeReader(ctx, 0, 0) - if err != nil { - return fmt.Errorf("failed to create zero-byte reader for object %q: %v", attrs.Name, err) - } - err = reader.Close() - if err != nil { - logger.Warnf("failed to close zero-byte reader for object %q: %v", attrs.Name, err) - } - - // Set the size - attrs.Size = reader.Attrs.Size - return nil - } - return nil -} - func (bh *bucketHandle) getObjectHandleWithPreconditionsSet(req *gcs.CreateObjectRequest) *storage.ObjectHandle { obj := bh.bucket.Object(req.Name) @@ -422,12 +390,6 @@ func (bh *bucketHandle) ListObjects(ctx context.Context, req *gcs.ListObjectsReq err = fmt.Errorf("error in iterating through objects: %w", err) return } - if attrs.Finalized.IsZero() && isGCSObject(attrs) { - if err = bh.fetchLatestSizeOfUnfinalizedObject(ctx, attrs); err != nil { - err = fmt.Errorf("failed to fetch the latest size of unfinalized object %q: %w", attrs.Name, err) - return - } - } // Prefix attribute will be set for the objects returned as part of Prefix[] array in list response. // https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/vendor/cloud.google.com/go/storage/storage.go#L1304 @@ -706,12 +668,3 @@ func (bh *bucketHandle) GCSName(obj *gcs.MinObject) string { func isStorageConditionsNotEmpty(conditions storage.Conditions) bool { return conditions != (storage.Conditions{}) } - -// isGCSObject determines whether the GCS resource represented by attrs is a GCS object -// and not a folder/directory resource. -func isGCSObject(attrs *storage.ObjectAttrs) bool { - if !strings.HasSuffix(attrs.Name, "/") && attrs.Prefix == "" && attrs.Name != "" { - return true - } - return false -} diff --git a/internal/storage/bucket_handle_test.go b/internal/storage/bucket_handle_test.go index cd97a79271..ea5d6d3050 100644 --- a/internal/storage/bucket_handle_test.go +++ b/internal/storage/bucket_handle_test.go @@ -66,7 +66,7 @@ func createBucketHandle(testSuite *BucketHandleTest, resp *controlpb.StorageLayo var err error testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(resp, nil) - testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "", false) + testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "") testSuite.bucketHandle.controlClient = testSuite.mockClient assert.NotNil(testSuite.T(), testSuite.bucketHandle) @@ -1474,7 +1474,7 @@ func (testSuite *BucketHandleTest) TestBucketHandleWithError() { var err error // Test when the client returns an error. testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything).Return(x, errors.New("mocked error")) - testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "", false) + testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "") assert.Nil(testSuite.T(), testSuite.bucketHandle) assert.Contains(testSuite.T(), err.Error(), "mocked error") @@ -1485,10 +1485,9 @@ func (testSuite *BucketHandleTest) TestBucketHandleWithRapidAppendsEnabled() { testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything).Return(&controlpb.StorageLayout{}, nil) testSuite.mockClient.On("getClient", mock.Anything, mock.Anything).Return(&storage.Client{}, nil) - testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "", true) + testSuite.bucketHandle, err = testSuite.storageHandle.BucketHandle(context.Background(), TestBucketName, "") assert.NotNil(testSuite.T(), testSuite.bucketHandle) - assert.True(testSuite.T(), testSuite.bucketHandle.enableRapidAppends) assert.Nil(testSuite.T(), err) } @@ -1624,29 +1623,3 @@ func (testSuite *BucketHandleTest) TestCreateFolderWithGivenName() { assert.NoError(testSuite.T(), err) assert.Equal(testSuite.T(), gcs.GCSFolder(TestBucketName, &mockFolder), folder) } - -func (testSuite *BucketHandleTest) TestIsGCSObject() { - testCases := []struct { - name string - attrs *storage.ObjectAttrs - expected bool - }{ - { - name: "gcsObject", - attrs: &storage.ObjectAttrs{Name: "a/b/c.txt", Prefix: ""}, - expected: true, - }, - { - name: "gcsFolder", - attrs: &storage.ObjectAttrs{Name: "", Prefix: "a/"}, - expected: false, - }, - } - - for _, tc := range testCases { - testSuite.Run(tc.name, func() { - actual := isGCSObject(tc.attrs) - assert.Equal(testSuite.T(), tc.expected, actual) - }) - } -} diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index db79642c39..3ec6b4253d 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -58,7 +58,7 @@ type StorageHandle interface { // to that project rather than to the bucket's owning project. // // A user-project is required for all operations on Requester Pays buckets. - BucketHandle(ctx context.Context, bucketName string, billingProject string, enableRapidAppends bool) (bh *bucketHandle, err error) + BucketHandle(ctx context.Context, bucketName string, billingProject string) (bh *bucketHandle, err error) } type storageClient struct { @@ -382,7 +382,7 @@ func (sh *storageClient) controlClientForBucketHandle(bucketType *gcs.BucketType } } -func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, billingProject string, enableRapidAppends bool) (bh *bucketHandle, err error) { +func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, billingProject string) (bh *bucketHandle, err error) { var client *storage.Client bucketType, err := sh.lookupBucketType(bucketName) if err != nil { @@ -411,11 +411,10 @@ func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, bi controlClient := sh.controlClientForBucketHandle(bucketType, billingProject) bh = &bucketHandle{ - bucket: storageBucketHandle, - bucketName: bucketName, - controlClient: controlClient, - bucketType: bucketType, - enableRapidAppends: enableRapidAppends, + bucket: storageBucketHandle, + bucketName: bucketName, + controlClient: controlClient, + bucketType: bucketType, } return diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index e7111879c2..15dd2fd046 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -80,7 +80,7 @@ func (testSuite *StorageHandleTest) mockStorageLayout(bucketType gcs.BucketType) func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketExistsWithEmptyBillingProject() { storageHandle := testSuite.fakeStorage.CreateStorageHandle() testSuite.mockStorageLayout(gcs.BucketType{}) - bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, "", false) + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, "") assert.NotNil(testSuite.T(), bucketHandle) assert.Nil(testSuite.T(), err) @@ -93,7 +93,7 @@ func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketDoesNotExistWithEm storageHandle := testSuite.fakeStorage.CreateStorageHandle() testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(nil, fmt.Errorf("bucket does not exist")) - bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, "", false) + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, "") assert.NotNil(testSuite.T(), err) assert.Nil(testSuite.T(), bucketHandle) @@ -103,7 +103,7 @@ func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketExistsWithNonEmpty storageHandle := testSuite.fakeStorage.CreateStorageHandle() testSuite.mockStorageLayout(gcs.BucketType{Hierarchical: true}) - bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, projectID, false) + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, TestBucketName, projectID) assert.NotNil(testSuite.T(), bucketHandle) assert.Nil(testSuite.T(), err) @@ -119,7 +119,7 @@ func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketDoesNotExistWithNo storageHandle := testSuite.fakeStorage.CreateStorageHandle() testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything). Return(nil, fmt.Errorf("bucket does not exist")) - bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, projectID, false) + bucketHandle, err := storageHandle.BucketHandle(testSuite.ctx, invalidBucketName, projectID) assert.Nil(testSuite.T(), bucketHandle) assert.NotNil(testSuite.T(), err) @@ -287,7 +287,7 @@ func (testSuite *StorageHandleTest) TestNewStorageHandleWithInvalidClientProtoco testSuite.mockStorageLayout(gcs.BucketType{}) sh := fakeStorage.CreateStorageHandle() assert.NotNil(testSuite.T(), sh) - bh, err := sh.BucketHandle(testSuite.ctx, TestBucketName, projectID, false) + bh, err := sh.BucketHandle(testSuite.ctx, TestBucketName, projectID) assert.Nil(testSuite.T(), bh) assert.NotNil(testSuite.T(), err) diff --git a/tools/integration_tests/rapid_appends/appends_test.go b/tools/integration_tests/rapid_appends/appends_test.go index f01755b065..ef052dd1cf 100644 --- a/tools/integration_tests/rapid_appends/appends_test.go +++ b/tools/integration_tests/rapid_appends/appends_test.go @@ -29,7 +29,7 @@ import ( ) const ( - numAppends = 3 // Number of appends to perform on test file. + numAppends = 2 // Number of appends to perform on test file. appendSize = 10 // Size in bytes for each append. unfinalizedObjectSize = 10 // Size in bytes of initial unfinalized Object. ) @@ -80,13 +80,13 @@ func (t *DualMountAppendsSuite) TestAppendSessionInvalidatedByAnotherClientUponT err = newAppendFileHandle.Sync() assert.NoError(t.T(), err) - // Read from primary mount to validate the contents which has persisted in GCS after - // takeover from the secondary mount. + // Read directly using storage client to validate the contents which has persisted in + // GCS after takeover from the secondary mount. // Close the open append handle before issuing read on the file as Sync() triggered on // ReadFile() due to BWH still being initialized, is expected to error out with stale NFS file handle. operations.CloseFileShouldThrowError(t.T(), appendFileHandle) expectedContent := t.fileContent + appendContent - content, err := operations.ReadFile(path.Join(t.primaryMount.testDirPath, t.fileName)) + content, err := client.ReadObjectFromGCS(ctx, storageClient, path.Join(testDirName, t.fileName)) require.NoError(t.T(), err) assert.Equal(t.T(), expectedContent, string(content)) }() diff --git a/tools/integration_tests/rapid_appends/reads_after_appends_test.go b/tools/integration_tests/rapid_appends/reads_after_appends_test.go index 1b1ef1b7d7..2c91ba557e 100644 --- a/tools/integration_tests/rapid_appends/reads_after_appends_test.go +++ b/tools/integration_tests/rapid_appends/reads_after_appends_test.go @@ -79,7 +79,8 @@ func readRandomlyAndVerify(t *testing.T, filePath string, expectedContent []byte // Tests //////////////////////////////////////////////////////////////////////// -func (t *CommonAppendsSuite) TestAppendsAndReads() { +// TestAppendsAndReads tests appending and reading from the same mount. +func (t *SingleMountAppendsSuite) TestAppendsAndReads() { const metadataCacheTTLSecs = 10 metadataCacheEnableFlag := fmt.Sprintf("%s%v", "--metadata-cache-ttl-secs=", metadataCacheTTLSecs) fileCacheDirFlag := func() string { @@ -126,7 +127,88 @@ func (t *CommonAppendsSuite) TestAppendsAndReads() { t.mountPrimaryMount(scenario.flags) defer t.unmountPrimaryMount() - log.Printf("Running tests with flags: %v", scenario.flags) + log.Printf("Running single-mount tests with flags: %v", scenario.flags) + + for _, tc := range testCases { + t.Run(tc.name, func() { + // Initially create an unfinalized object. + t.createUnfinalizedObject() + defer t.deleteUnfinalizedObject() + + // Open this object as a file for appending on the primary mount. + appendFileHandle := operations.OpenFileInMode(t.T(), path.Join(t.primaryMount.testDirPath, t.fileName), os.O_APPEND|os.O_WRONLY|syscall.O_DIRECT) + defer operations.CloseFileShouldNotThrowError(t.T(), appendFileHandle) + + readPath := path.Join(t.primaryMount.testDirPath, t.fileName) + for i := range numAppends { + // Wait for a minute for stat to return the correct file size, which is needed by appendToFile. + if i > 0 { + time.Sleep(time.Minute) + } + + t.appendToFile(appendFileHandle, setup.GenerateRandomString(appendSize)) + sizeAfterAppend := len(t.fileContent) + + // If metadata cache is enabled, gcsfuse reads up to the cached file size. + // For same-mount appends/reads, file size is always current. + // The initial read (i=0) bypasses cache, seeing the latest file size. + tc.readAndVerify(t.T(), readPath, []byte(t.fileContent[:sizeAfterAppend])) + } + }) + } + }() + } +} + +func (t *DualMountAppendsSuite) TestAppendsAndReads() { + // Set a metadata cache TTL that is longer than the 60s stat-consistency delay. + const metadataCacheTTLSecs = 60 + metadataCacheEnableFlag := fmt.Sprintf("%s%v", "--metadata-cache-ttl-secs=", metadataCacheTTLSecs) + fileCacheDirFlag := func() string { + return "--cache-dir=" + getNewEmptyCacheDir(t.primaryMount.rootDir) + } + + testCases := []struct { + name string + readAndVerify readAndVerifyFunc + }{ + { + name: "SequentialRead", + readAndVerify: readSequentiallyAndVerify, + }, + { + name: "RandomRead", + readAndVerify: readRandomlyAndVerify, + }, + } + + for _, scenario := range []struct { + enableMetadataCache bool + enableFileCache bool + flags []string + }{{ + // all cache disabled + enableMetadataCache: false, + enableFileCache: false, + flags: []string{"--enable-rapid-appends=true", "--write-global-max-blocks=-1", "--metadata-cache-ttl-secs=0"}, + }, { + enableMetadataCache: true, + enableFileCache: false, + flags: []string{"--enable-rapid-appends=true", "--write-global-max-blocks=-1", metadataCacheEnableFlag}, + }, { + enableMetadataCache: true, + enableFileCache: true, + flags: []string{"--enable-rapid-appends=true", "--write-global-max-blocks=-1", metadataCacheEnableFlag, "--file-cache-max-size-mb=-1", fileCacheDirFlag()}, + }, { + enableMetadataCache: false, + enableFileCache: true, + flags: []string{"--enable-rapid-appends=true", "--write-global-max-blocks=-1", "--metadata-cache-ttl-secs=0", "--file-cache-max-size-mb=-1", fileCacheDirFlag()}, + }} { + func() { + t.mountPrimaryMount(scenario.flags) + defer t.unmountPrimaryMount() + + log.Printf("Running dual-mount tests with flags: %v", scenario.flags) for _, tc := range testCases { t.Run(tc.name, func() { @@ -145,9 +227,9 @@ func (t *CommonAppendsSuite) TestAppendsAndReads() { sizeAfterAppend := len(t.fileContent) // If metadata cache is enabled, gcsfuse reads up to the cached file size. - // For same-mount appends/reads, file size is always current. // The initial read (i=0) bypasses cache, seeing the latest file size. - if !scenario.enableMetadataCache || !t.isSyncNeededAfterAppend || (i == 0) { + if !scenario.enableMetadataCache || (i == 0) { + time.Sleep(time.Minute) tc.readAndVerify(t.T(), readPath, []byte(t.fileContent[:sizeAfterAppend])) } else { // Read only up to the cached file size (before append). diff --git a/tools/integration_tests/rapid_appends/suites_test.go b/tools/integration_tests/rapid_appends/suites_test.go index c2e3ec8aca..2fcd7a6033 100644 --- a/tools/integration_tests/rapid_appends/suites_test.go +++ b/tools/integration_tests/rapid_appends/suites_test.go @@ -19,12 +19,12 @@ import ( "os" "path" "testing" + "time" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) @@ -144,6 +144,7 @@ func (t *CommonAppendsSuite) createUnfinalizedObject() { // Create unfinalized object. t.fileContent = setup.GenerateRandomString(unfinalizedObjectSize) client.CreateUnfinalizedObject(ctx, t.T(), storageClient, path.Join(testDirName, t.fileName), t.fileContent) + time.Sleep(time.Minute) // Wait for a minute so that further stat() calls return correct size. } func (t *CommonAppendsSuite) mountPrimaryMount(flags []string) { @@ -177,8 +178,8 @@ func (t *DualMountAppendsSuite) unmountSecondaryMount() { func (t *CommonAppendsSuite) appendToFile(file *os.File, appendContent string) { t.T().Helper() n, err := file.WriteString(appendContent) - assert.NoError(t.T(), err) - assert.Equal(t.T(), len(appendContent), n) + require.NoError(t.T(), err) + require.Equal(t.T(), len(appendContent), n) t.fileContent += appendContent if t.isSyncNeededAfterAppend { operations.SyncFile(file, t.T()) diff --git a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go index 02599d979d..a6a833ffa2 100644 --- a/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go +++ b/tools/integration_tests/unfinalized_object/unfinalized_object_operations_test.go @@ -19,6 +19,7 @@ import ( "log" "path" "testing" + "time" "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" @@ -58,6 +59,7 @@ func (t *unfinalizedObjectOperations) TestUnfinalizedObjectCreatedOutsideOfMount writer := client.CreateUnfinalizedObject(t.ctx, t.T(), t.storageClient, path.Join(testDirName, t.fileName), setup.GenerateRandomString(size)) defer writer.Close() + time.Sleep(60 * time.Second) statRes, err := operations.StatFile(path.Join(t.testDirPath, t.fileName)) require.NoError(t.T(), err) From d9b1208b1a53b781a6b4e772230d3de5663338b1 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Fri, 29 Aug 2025 14:54:11 +0530 Subject: [PATCH 0742/1298] ci: Preserve gcsfuse log file for failure in e2e package concurrent_operations (#3763) This is needed for debugging in case of e2e test failure. Without this, there is almost no debugging information available. --- tools/integration_tests/concurrent_operations/setup_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/integration_tests/concurrent_operations/setup_test.go b/tools/integration_tests/concurrent_operations/setup_test.go index ce11845a23..d20614e7cd 100644 --- a/tools/integration_tests/concurrent_operations/setup_test.go +++ b/tools/integration_tests/concurrent_operations/setup_test.go @@ -93,6 +93,9 @@ func TestMain(m *testing.M) { log.Println("Running static mounting tests...") successCode := m.Run() + // If test failed, save the gcsfuse log files for debugging. + setup.SaveLogFileInCaseOfFailure(successCode) + // Clean up test directory created. setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) os.Exit(successCode) From e154e6d6442414a7410cf5bd9266825a8501021f Mon Sep 17 00:00:00 2001 From: Prince Kumar Date: Mon, 1 Sep 2025 13:33:14 +0530 Subject: [PATCH 0743/1298] fix(unmount): Fixing false unmount success bug (#3768) * fix(unmount): Fixing false unmount success bug * Trigger Build --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index bde0da5277..0c86100666 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/google/uuid v1.6.0 github.com/googleapis/gax-go/v2 v2.15.0 github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec - github.com/jacobsa/fuse v0.0.0-20250726160139-b8f47b05858b + github.com/jacobsa/fuse v0.0.0-20250829162853-cbc61fab5519 github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff github.com/jacobsa/ogletest v0.0.0-20170503003838-80d50a735a11 diff --git a/go.sum b/go.sum index 7b29e1df76..f62ef6fdc4 100644 --- a/go.sum +++ b/go.sum @@ -140,8 +140,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec h1:xsRGrfdnjvJtEMD2ouh8gOGIeDF9LrgXjo+9Q69RVzI= github.com/jacobsa/daemonize v0.0.0-20240917082746-f35568b6c3ec/go.mod h1:Ip4fOwzCrnDVuluHBd7FXIMb7SHOKfkt9/UDrYSZvqI= -github.com/jacobsa/fuse v0.0.0-20250726160139-b8f47b05858b h1:Sx1Oj5dTMB43tAPzgwaJ78ODgFddNVN+AoL5onAMV5k= -github.com/jacobsa/fuse v0.0.0-20250726160139-b8f47b05858b/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= +github.com/jacobsa/fuse v0.0.0-20250829162853-cbc61fab5519 h1:BisEsMgqea3bHT9ntvsVGPwDmYaVKogcufssmIlOdmw= +github.com/jacobsa/fuse v0.0.0-20250829162853-cbc61fab5519/go.mod h1:fcpw1yk/suvFhB8rT9P+pst+NLboWsBLky9csooKjPc= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd h1:9GCSedGjMcLZCrusBZuo4tyKLpKUPenUUqi34AkuFmA= github.com/jacobsa/oglematchers v0.0.0-20150720000706-141901ea67cd/go.mod h1:TlmyIZDpGmwRoTWiakdr+HA1Tukze6C6XbRVidYq02M= github.com/jacobsa/oglemock v0.0.0-20150831005832-e94d794d06ff h1:2xRHTvkpJ5zJmglXLRqHiZQNjUoOkhUyhTAhEQvPAWw= From 399c4102113d7167aa48ecb3fce1f7affafd324f Mon Sep 17 00:00:00 2001 From: Abhishek Gupta Date: Mon, 1 Sep 2025 11:32:13 +0000 Subject: [PATCH 0744/1298] test: Additional UTs for file handle (#3659) Additional UTs for file handle changes. These test some new methods along with locking/unlocking & concurrency related changes. --- internal/fs/handle/file_test.go | 503 +++++++++++++++++++++++++++++++- 1 file changed, 500 insertions(+), 3 deletions(-) diff --git a/internal/fs/handle/file_test.go b/internal/fs/handle/file_test.go index 52d3ce444f..c6c281541e 100644 --- a/internal/fs/handle/file_test.go +++ b/internal/fs/handle/file_test.go @@ -17,9 +17,11 @@ package handle import ( "bytes" "context" + crypto_rand "crypto/rand" "errors" "fmt" "io" + "math/rand" "sync" "testing" "time" @@ -108,8 +110,9 @@ func createFileInode( content []byte, localFileCache bool) *inode.FileInode { + fullObjectName := parent.Name().GcsObjectName() + objectName obj := &gcs.MinObject{ - Name: objectName, + Name: fullObjectName, Size: uint64(len(content)), Generation: 1, MetaGeneration: 1, @@ -118,7 +121,7 @@ func createFileInode( // Create object in the fake bucket to simulate existing GCS object _, err := bucket.CreateObject(context.Background(), &gcs.CreateObjectRequest{ - Name: objectName, + Name: fullObjectName, Contents: io.NopCloser(bytes.NewReader(content)), }) if err != nil { @@ -127,7 +130,7 @@ func createFileInode( return inode.NewFileInode( fuseops.InodeID(2), - inode.NewFileName(parent.Name(), obj.Name), + inode.NewFileName(parent.Name(), objectName), obj, fuseops.InodeAttributes{}, bucket, @@ -166,6 +169,118 @@ func (t *fileTest) TestFileHandleWrite() { assert.Equal(t.T(), data, buf) } +func (t *fileTest) Test_IsValidReadManager_NilReadManager() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + const objectName = "test_obj" + const objectContent = "some data" + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, objectName, []byte(objectContent), false) + fh := NewFileHandle(in, nil, false, nil, util.Read, config, nil, nil) + fh.inode.Lock() + defer fh.inode.Unlock() + fh.readManager = nil + + result := fh.isValidReadManager() + + assert.False(t.T(), result) +} + +func (t *fileTest) Test_IsValidReadManager_GenerationValidation() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + const objectName = "test_obj" + const objectContent = "some data" + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, objectName, []byte(objectContent), false) + fh := NewFileHandle(in, nil, false, nil, util.Read, config, nil, nil) + fh.inode.Lock() + defer fh.inode.Unlock() + + testCases := []struct { + name string + readerGeneration int64 + expectedIsValid bool + }{ + { + name: "Generation mismatch", + readerGeneration: 2, // Inode has generation 1 + expectedIsValid: false, + }, + { + name: "Generation match", + readerGeneration: 1, // Inode has generation 1 + expectedIsValid: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + minObj := in.Source() + minObj.Generation = tc.readerGeneration + fh.readManager = read_manager.NewReadManager(minObj, &t.bucket, &read_manager.ReadManagerConfig{Config: config}) + + result := fh.isValidReadManager() + + assert.Equal(t.T(), tc.expectedIsValid, result) + }) + } +} + +func (t *fileTest) Test_IsValidReader_NilReader() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + const objectName = "test_obj" + const objectContent = "some data" + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, objectName, []byte(objectContent), false) + fh := NewFileHandle(in, nil, false, nil, util.Read, config, nil, nil) + fh.inode.Lock() + defer fh.inode.Unlock() + fh.reader = nil + + result := fh.isValidReader() + + assert.False(t.T(), result) +} + +func (t *fileTest) Test_IsValidReader_GenerationValidation() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + const objectName = "test_obj" + const objectContent = "some data" + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, objectName, []byte(objectContent), false) + fh := NewFileHandle(in, nil, false, nil, util.Read, config, nil, nil) + fh.inode.Lock() + defer fh.inode.Unlock() + + testCases := []struct { + name string + readerGeneration int64 + expectedIsValid bool + }{ + { + name: "Generation mismatch", + readerGeneration: 2, // Inode has generation 1 + expectedIsValid: false, + }, + { + name: "Generation match", + readerGeneration: 1, // Inode has generation 1 + expectedIsValid: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func() { + minObj := in.Source() + minObj.Generation = tc.readerGeneration + fh.reader = gcsx.NewRandomReader(minObj, &t.bucket, 200, nil, false, metrics.NewNoopMetrics(), &in.MRDWrapper, config) + + result := fh.isValidReader() + + assert.Equal(t.T(), tc.expectedIsValid, result) + }) + } +} + // Test_Read_Success validates successful read behavior using the random reader. func (t *fileTest) Test_Read_Success() { expectedData := []byte("hello from reader") @@ -198,6 +313,118 @@ func (t *fileTest) Test_ReadWithReadManager_Success() { assert.Equal(t.T(), expectedData, output) } +// Test_ReadWithReadManager_Concurrent validates concurrent read behavior using the readManager. +func (t *fileTest) Test_ReadWithReadManager_Concurrent() { + // Setup + const ( + fileSize = 1 * 1024 * 1024 // 1 MiB + numReaders = 20 + maxReadSize = 16 * 1024 // 16 KiB + ) + // Create large content for the file. + objectContent := make([]byte, fileSize) + _, err := crypto_rand.Read(objectContent) + assert.NoError(t.T(), err) + + parent := createDirInode(&t.bucket, &t.clock) + in := createFileInode(t.T(), &t.bucket, &t.clock, &cfg.Config{}, parent, "concurrent_read_obj", objectContent, false) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) + + var wg sync.WaitGroup + wg.Add(numReaders) + + // Run concurrent reads + for i := 0; i < numReaders; i++ { + go func() { + defer wg.Done() + + // Each goroutine reads a random chunk. + readSize := rand.Intn(maxReadSize-1) + 1 // Ensure readSize > 0 + offset := rand.Intn(fileSize - readSize) + dst := make([]byte, readSize) + + fh.inode.Lock() // Lock required by ReadWithReadManager + // The method is responsible for unlocking. + _, n, err := fh.ReadWithReadManager(t.ctx, dst, int64(offset), 200) + + // Assertions + assert.NoError(t.T(), err) + assert.Equal(t.T(), readSize, n) + assert.Equal(t.T(), objectContent[offset:offset+readSize], dst[:n]) + }() + } + + // Wait for all goroutines to finish, with a timeout to detect deadlocks. + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + + select { + case <-done: + // Test completed successfully. + case <-time.After(10 * time.Second): + t.T().Fatal("Test timed out, potential deadlock in ReadWithReadManager") + } +} + +// Test_Read_Concurrent validates concurrent read behavior using the random reader +func (t *fileTest) Test_Read_Concurrent() { + // Setup + const ( + fileSize = 1 * 1024 * 1024 // 1 MiB + numReaders = 20 + maxReadSize = 16 * 1024 // 16 KiB + ) + // Create large content for the file. + objectContent := make([]byte, fileSize) + _, err := crypto_rand.Read(objectContent) + assert.NoError(t.T(), err) + + parent := createDirInode(&t.bucket, &t.clock) + in := createFileInode(t.T(), &t.bucket, &t.clock, &cfg.Config{}, parent, "concurrent_read_obj", objectContent, false) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) + + var wg sync.WaitGroup + wg.Add(numReaders) + + // Run concurrent reads + for i := 0; i < numReaders; i++ { + go func() { + defer wg.Done() + + // Each goroutine reads a random chunk. + readSize := rand.Intn(maxReadSize-1) + 1 // Ensure readSize > 0 + offset := rand.Intn(fileSize - readSize) + dst := make([]byte, readSize) + + fh.inode.Lock() // Lock required by ReadWithReadManager + // The method is responsible for unlocking. + _, n, err := fh.Read(t.ctx, dst, int64(offset), 200) + + // Assertions + assert.NoError(t.T(), err) + assert.Equal(t.T(), readSize, n) + assert.Equal(t.T(), objectContent[offset:offset+readSize], dst[:n]) + }() + } + + // Wait for all goroutines to finish, with a timeout to detect deadlocks. + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + + select { + case <-done: + // Test completed successfully. + case <-time.After(10 * time.Second): + t.T().Fatal("Test timed out, potential deadlock in ReadWithReadManager") + } +} + // Test_ReadWithReadManager_ErrorScenarios verifies error handling in ReadWithReadManager. func (t *fileTest) Test_ReadWithReadManager_ErrorScenarios() { type testCase struct { @@ -316,6 +543,86 @@ func (t *fileTest) Test_Read_FallbackToInode() { mockR.AssertExpectations(t.T()) } +func (t *fileTest) Test_ReadWithReadManager_ReadManagerInvalidatedByGenerationChange() { + content1 := []byte("content1") + content2 := []byte("content2-larger") + dst := make([]byte, len(content2)) + objectName := "test_obj_rm_gen_change" + + parent := createDirInode(&t.bucket, &t.clock) + in := createFileInode(t.T(), &t.bucket, &t.clock, &cfg.Config{}, parent, objectName, content1, false) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) + + // First read, to create a readManager. + fh.inode.Lock() + _, _, err := fh.ReadWithReadManager(t.ctx, make([]byte, len(content1)), 0, 200) + assert.NoError(t.T(), err) + assert.NotNil(t.T(), fh.readManager) + oldReadManager := fh.readManager + + // Now, update the object in GCS, which changes its generation. + in.Lock() + gcsSynced, err := in.Write(t.ctx, content2, 0, util.Write) + assert.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) + gcsSynced, err = in.Sync(t.ctx) + assert.NoError(t.T(), err) + assert.True(t.T(), gcsSynced) + in.Unlock() + + // The existing readManager is now for an old generation. + // The next ReadWithReadManager call should detect this, destroy the old one, + // create a new one, and read the new content. + fh.inode.Lock() + output, n, err := fh.ReadWithReadManager(t.ctx, dst, 0, 200) + + assert.NoError(t.T(), err) + assert.NotNil(t.T(), fh.readManager) + assert.NotEqual(t.T(), oldReadManager, fh.readManager) + assert.Equal(t.T(), len(content2), n) + assert.Equal(t.T(), content2, output) +} + +func (t *fileTest) Test_Read_ReaderInvalidatedByGenerationChange() { + content1 := []byte("content1") + content2 := []byte("content2-larger") + dst := make([]byte, len(content2)) + objectName := "test_obj_rm_gen_change" + + parent := createDirInode(&t.bucket, &t.clock) + in := createFileInode(t.T(), &t.bucket, &t.clock, &cfg.Config{}, parent, objectName, content1, false) + fh := NewFileHandle(in, nil, false, metrics.NewNoopMetrics(), util.Read, &cfg.Config{}, nil, nil) + + // First read, to create a reader. + fh.inode.Lock() + _, _, err := fh.Read(t.ctx, make([]byte, len(content1)), 0, 200) + assert.NoError(t.T(), err) + assert.NotNil(t.T(), fh.reader) + oldReader := fh.reader + + // Now, update the object in GCS, which changes its generation. + in.Lock() + gcsSynced, err := in.Write(t.ctx, content2, 0, util.Write) + assert.NoError(t.T(), err) + assert.False(t.T(), gcsSynced) + gcsSynced, err = in.Sync(t.ctx) + assert.NoError(t.T(), err) + assert.True(t.T(), gcsSynced) + in.Unlock() + + // The existing reader is now for an old generation. + // The next Read call should detect this, destroy the old one, + // create a new one, and read the new content. + fh.inode.Lock() + output, n, err := fh.Read(t.ctx, dst, 0, 200) + + assert.NoError(t.T(), err) + assert.NotNil(t.T(), fh.reader) + assert.NotEqual(t.T(), oldReader, fh.reader) + assert.Equal(t.T(), len(content2), n) + assert.Equal(t.T(), content2, output) +} + func (t *fileTest) TestOpenMode() { testCases := []struct { name string @@ -418,6 +725,196 @@ func (t *fileTest) TestFileHandle_CheckInvariants_WithNilReaderAndManager() { }) } +func (t *fileTest) Test_LockHandleAndRelockInode_Lock_NoDeadlockWithContention() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj_deadlock", []byte("content"), false) + fh := NewFileHandle(in, nil, false, nil, util.Read, config, nil, nil) + var wg sync.WaitGroup + const numContenders = 10 + wg.Add(2 * numContenders) + done := make(chan struct{}) + + // Simulate the flow that uses lockHandleAndRelockInode + for i := 0; i < numContenders; i++ { + go func() { + defer wg.Done() + fh.inode.Lock() + fh.lockHandleAndRelockInode(false) // This should not deadlock + fh.inode.Unlock() + fh.mu.Unlock() + }() + } + + // Simulate conflicting lock acquisition order + for i := 0; i < numContenders; i++ { + go func() { + defer wg.Done() + fh.mu.Lock() + defer fh.mu.Unlock() + // Now try to get the inode lock + fh.inode.Lock() + defer fh.inode.Unlock() + }() + } + + go func() { + wg.Wait() + close(done) + }() + + select { + case <-done: + // Success, no deadlock + case <-time.After(2 * time.Second): + t.T().Fatal("Potential deadlock detected with Lock") + } +} + +func (t *fileTest) Test_LockHandleAndRelockInode_RLock_NoDeadlockWithContention() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj_deadlock", []byte("content"), false) + fh := NewFileHandle(in, nil, false, nil, util.Read, config, nil, nil) + var wg sync.WaitGroup + const numContenders = 10 + wg.Add(2 * numContenders) + done := make(chan struct{}) + + // Simulate the flow that uses lockHandleAndRelockInode + for i := 0; i < numContenders; i++ { + go func() { + defer wg.Done() + fh.inode.Lock() + fh.lockHandleAndRelockInode(true) // This should not deadlock + fh.inode.Unlock() + fh.mu.RUnlock() + }() + } + + // Simulate conflicting lock acquisition order + for i := 0; i < numContenders; i++ { + go func() { + defer wg.Done() + fh.mu.Lock() + defer fh.mu.Unlock() + // Now try to get the inode lock + fh.inode.Lock() + defer fh.inode.Unlock() + }() + } + + go func() { + wg.Wait() + close(done) + }() + + select { + case <-done: + // Success, no deadlock + case <-time.After(2 * time.Second): + t.T().Fatal("Potential deadlock detected with RLock") + } +} + +func (t *fileTest) Test_LockHandleAndRelockInode_Mixed_NoDeadlockWithContention() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj_deadlock", []byte("content"), false) + fh := NewFileHandle(in, nil, false, nil, util.Read, config, nil, nil) + var wg sync.WaitGroup + const numRContenders = 10 + const numWContenders = 10 + wg.Add(numRContenders + numWContenders) + done := make(chan struct{}) + + // Simulate the flow that uses lockHandleAndRelockInode(false) + for i := 0; i < numWContenders; i++ { + go func() { + defer wg.Done() + fh.inode.Lock() + fh.lockHandleAndRelockInode(false) // This should not deadlock + fh.mu.Unlock() + fh.inode.Unlock() + }() + } + + // Simulate the flow that uses lockHandleAndRelockInode(true) + for i := 0; i < numRContenders; i++ { + go func() { + defer wg.Done() + fh.inode.Lock() + fh.lockHandleAndRelockInode(true) // This should not deadlock + fh.mu.RUnlock() + fh.inode.Unlock() + }() + } + + go func() { + wg.Wait() + close(done) + }() + + select { + case <-done: + // Success, no deadlock + case <-time.After(2 * time.Second): + t.T().Fatal("Potential deadlock detected with mixed Lock/RLock") + } +} + +func (t *fileTest) Test_UnlockHandleAndInode() { + parent := createDirInode(&t.bucket, &t.clock) + config := &cfg.Config{} + in := createFileInode(t.T(), &t.bucket, &t.clock, config, parent, "test_obj_deadlock", []byte("content"), false) + fh := NewFileHandle(in, nil, false, nil, util.Read, config, nil, nil) + + var wg sync.WaitGroup + const numContenders = 10 + wg.Add(3 * numContenders) + done := make(chan struct{}) + + for i := 0; i < numContenders; i++ { + go func() { + defer wg.Done() + fh.mu.Lock() + fh.inode.Lock() + fh.unlockHandleAndInode(false) + }() + } + + for i := 0; i < numContenders; i++ { + go func() { + defer wg.Done() + fh.mu.RLock() + fh.inode.Lock() + fh.unlockHandleAndInode(true) + }() + } + + for i := 0; i < numContenders; i++ { + go func() { + defer wg.Done() + fh.mu.Lock() + defer fh.mu.Unlock() + fh.inode.Lock() + defer fh.inode.Unlock() + }() + } + + go func() { + wg.Wait() + close(done) + }() + + select { + case <-done: + // Success: locks were re-acquired without blocking. + case <-time.After(2 * time.Second): + t.T().Fatal("Potential deadlock detected: locks were not released for write lock.") + } +} + func (t *fileTest) Test_ReadWithReadManager_FullReadSuccessWithBufferedRead() { const ( fileSize = 1 * 1024 * 1024 // 1 MiB From cf36be811825751baf2c45cb50fc0abf47a54036 Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 2 Sep 2025 08:49:25 +0530 Subject: [PATCH 0745/1298] enable trace logs in e2e package mount-timeout (#3770) --- .../mount_timeout/gcsfuse_mount_timeout_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go index e2861f94cc..5e1bbcf976 100644 --- a/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go +++ b/tools/integration_tests/mount_timeout/gcsfuse_mount_timeout_test.go @@ -160,8 +160,8 @@ func (testSuite *MountTimeoutTest) mountOrTimeout(bucketName, clientProtocol str }() // Iterating 10 times to account for randomness in time taken to mount. - args := []string{"--client-protocol", clientProtocol, "--log-file=" + logFile, bucketName, testSuite.dir} - for i := 0; i < iterations; i++ { + args := []string{"--client-protocol", clientProtocol, "--log-severity=trace", "--log-file=" + logFile, bucketName, testSuite.dir} + for i := range iterations { start := time.Now() if err = mounting.MountGcsfuse(testSuite.gcsfusePath, args); err != nil { err = fmt.Errorf("mount failed for bucket %q (client protocol: %s) on attempt#%v: %w", bucketName, clientProtocol, i, err) From a47f088a06db9154d34e52cbca3b025610c4468d Mon Sep 17 00:00:00 2001 From: Pranjal Chaturvedi Date: Tue, 2 Sep 2025 13:59:59 +0530 Subject: [PATCH 0746/1298] refactor: rename dir limit test package migration [GKE-GCSFuse Test migration] (#3730) * only dir mount & rename_dir_limit test migration * lint correction * recovered config file * hns bucket case handle * changed config files --- .../rename_dir_limit/rename_dir_limit_test.go | 58 +++++++++++++------ .../rename_dir_limit/rename_dir_test.go | 4 +- tools/integration_tests/test_config.yaml | 21 +++++++ .../only_dir_mounting/only_dir_mounting.go | 37 +++++++++--- 4 files changed, 93 insertions(+), 27 deletions(-) diff --git a/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go b/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go index 933a6a3150..05fa65f474 100644 --- a/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go +++ b/tools/integration_tests/rename_dir_limit/rename_dir_limit_test.go @@ -27,6 +27,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/persistent_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/static_mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" ) const DirForRenameDirLimitTests = "dirForRenameDirLimitTests" @@ -51,9 +52,38 @@ var ( func TestMain(m *testing.M) { setup.ParseSetUpFlags() - var err error + // 1. Load and parse the common configuration. + cfg := test_suite.ReadConfigFile(setup.ConfigFile()) + if len(cfg.RenameDirLimit) == 0 { + log.Println("No configuration found for rename dir limit tests in config. Using flags instead.") + // Populate the config manually. + cfg.RenameDirLimit = make([]test_suite.TestConfig, 1) + cfg.RenameDirLimit[0].TestBucket = setup.TestBucket() + cfg.RenameDirLimit[0].MountedDirectory = setup.MountedDirectory() + cfg.RenameDirLimit[0].Configs = make([]test_suite.ConfigItem, 2) + cfg.RenameDirLimit[0].Configs[0].Flags = []string{ + "--rename-dir-limit=3 --implicit-dirs --client-protocol=grpc", + "--rename-dir-limit=3", + "--rename-dir-limit=3 --client-protocol=grpc", + } + cfg.RenameDirLimit[0].Configs[0].Compatible = map[string]bool{"flat": true, "hns": false, "zonal": false} + cfg.RenameDirLimit[0].Configs[1].Flags = []string{ + "", + } + cfg.RenameDirLimit[0].Configs[1].Compatible = map[string]bool{"flat": false, "hns": true, "zonal": true} + } + setup.SetTestBucket(cfg.RenameDirLimit[0].TestBucket) ctx = context.Background() + bucketType, err := setup.BucketType(ctx, cfg.RenameDirLimit[0].TestBucket) + if err != nil { + log.Fatalf("BucketType failed: %v", err) + } + if bucketType == setup.ZonalBucket { + setup.SetIsZonalBucketRun(true) + } + + // 2. Create storage client before running tests. storageClient, err = client.CreateStorageClient(ctx) if err != nil { log.Printf("Error creating storage client: %v\n", err) @@ -61,32 +91,24 @@ func TestMain(m *testing.M) { } defer storageClient.Close() - flags := [][]string{{"--rename-dir-limit=3", "--implicit-dirs"}, {"--rename-dir-limit=3"}} - setup.AppendFlagsToAllFlagsInTheFlagsSet(&flags, "", "--client-protocol=grpc") - if hnsFlagSet, err := setup.AddHNSFlagForHierarchicalBucket(ctx, storageClient); err == nil { - flags = [][]string{hnsFlagSet} + // 4. To run mountedDirectory tests, we need both testBucket and mountedDirectory + if cfg.RenameDirLimit[0].MountedDirectory != "" && cfg.RenameDirLimit[0].TestBucket != "" { + os.Exit(setup.RunTestsForMountedDirectory(cfg.RenameDirLimit[0].MountedDirectory, m)) } - setup.ExitWithFailureIfBothTestBucketAndMountedDirectoryFlagsAreNotSet() - - if setup.TestBucket() == "" && setup.MountedDirectory() != "" { - log.Print("Please pass the name of bucket mounted at mountedDirectory to --testBucket flag.") - os.Exit(1) - } - - // Run tests for mountedDirectory only if --mountedDirectory flag is set. - setup.RunTestsForMountedDirectoryFlag(m) // Run tests for testBucket - setup.SetUpTestDirForTestBucketFlag() + // 5. Build the flag sets dynamically from the config. + flags := setup.BuildFlagSets(cfg.RenameDirLimit[0], bucketType) + setup.SetUpTestDirForTestBucket(cfg.RenameDirLimit[0].TestBucket) - successCode := static_mounting.RunTests(flags, m) + successCode := static_mounting.RunTestsWithConfigFile(&cfg.RenameDirLimit[0], flags, m) if successCode == 0 { - successCode = only_dir_mounting.RunTests(flags, onlyDirMounted, m) + successCode = only_dir_mounting.RunTestsWithConfigFile(&cfg.RenameDirLimit[0], flags, onlyDirMounted, m) } if successCode == 0 { - successCode = persistent_mounting.RunTests(flags, m) + successCode = persistent_mounting.RunTestsWithConfigFile(&cfg.RenameDirLimit[0], flags, m) } os.Exit(successCode) diff --git a/tools/integration_tests/rename_dir_limit/rename_dir_test.go b/tools/integration_tests/rename_dir_limit/rename_dir_test.go index c57d2e653d..049995e0b5 100644 --- a/tools/integration_tests/rename_dir_limit/rename_dir_test.go +++ b/tools/integration_tests/rename_dir_limit/rename_dir_test.go @@ -80,7 +80,7 @@ func TestRenameDirectoryWithTwoFiles(t *testing.T) { // As --rename-directory-limit = 3, and the number of objects in the directory is two, // which is greater than the limit, the operation should get fail. func TestRenameDirectoryWithFourFiles(t *testing.T) { - if setup.IsHierarchicalBucket(ctx, storageClient) { + if setup.ResolveIsHierarchicalBucket(ctx, setup.TestBucket(), storageClient) { t.SkipNow() } testDir := setup.SetupTestDirectory(DirForRenameDirLimitTests) @@ -134,7 +134,7 @@ func TestRenameDirectoryWithTwoFilesAndOneEmptyDirectory(t *testing.T) { // As --rename-directory-limit = 3, and the number of objects in the directory is Four, // which is greater than the limit, the operation should get fail. func TestRenameDirectoryWithTwoFilesAndOneNonEmptyDirectory(t *testing.T) { - if setup.IsHierarchicalBucket(ctx, storageClient) { + if setup.ResolveIsHierarchicalBucket(ctx, setup.TestBucket(), storageClient) { t.SkipNow() } testDir := setup.SetupTestDirectory(DirForRenameDirLimitTests) diff --git a/tools/integration_tests/test_config.yaml b/tools/integration_tests/test_config.yaml index 04fc16c82e..cdb2b68aa4 100644 --- a/tools/integration_tests/test_config.yaml +++ b/tools/integration_tests/test_config.yaml @@ -110,3 +110,24 @@ readonly: flat: true hns: true zonal: true + +rename_dir_limit: + - mounted_directory: "${MOUNTED_DIR}" + test_bucket: "${BUCKET_NAME}" + log_file: # Optional + run_on_gke: false + configs: + - flags: + - "--rename-dir-limit=3 --implicit-dirs --client-protocol=grpc" + - "--rename-dir-limit=3" + - "--rename-dir-limit=3 --client-protocol=grpc" + compatible: + flat: true + hns: false + zonal: false + - flags: + - "" + compatible: + flat: false + hns: true + zonal: true diff --git a/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go b/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go index 9c8a461c06..720aba3fa6 100644 --- a/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go +++ b/tools/integration_tests/util/mounting/only_dir_mounting/only_dir_mounting.go @@ -22,15 +22,27 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_suite" "golang.org/x/net/context" ) +// Deprecated: Use MountGcsfuseWithOnlyDirMountingWithConfigFile instead. +// TODO(b/438068132): cleanup deprecated methods after migration is complete. func MountGcsfuseWithOnlyDir(flags []string) (err error) { + config := &test_suite.TestConfig{ + TestBucket: setup.TestBucket(), + MountedDirectory: setup.MountedDirectory(), + LogFile: setup.LogFile(), + } + return MountGcsfuseWithOnlyDirWithConfigFile(config, flags) +} + +func MountGcsfuseWithOnlyDirWithConfigFile(config *test_suite.TestConfig, flags []string) (err error) { defaultArg := []string{"--only-dir", setup.OnlyDirMounted(), "--log-severity=trace", "--log-file=" + setup.LogFile(), - setup.TestBucket(), + config.TestBucket, setup.MntDir()} for i := 0; i < len(defaultArg); i++ { @@ -42,9 +54,9 @@ func MountGcsfuseWithOnlyDir(flags []string) (err error) { return err } -func mountGcsFuseForFlagsAndExecuteTests(flags [][]string, m *testing.M) (successCode int) { +func mountGcsFuseForFlagsAndExecuteTests(config *test_suite.TestConfig, flags [][]string, m *testing.M) (successCode int) { for i := 0; i < len(flags); i++ { - if err := MountGcsfuseWithOnlyDir(flags[i]); err != nil { + if err := MountGcsfuseWithOnlyDirWithConfigFile(config, flags[i]); err != nil { setup.LogAndExit(fmt.Sprintf("mountGcsfuse: %v\n", err)) } log.Printf("Running only dir mounting tests with flags: %s", flags[i]) @@ -56,7 +68,7 @@ func mountGcsFuseForFlagsAndExecuteTests(flags [][]string, m *testing.M) (succes return } -func executeTestsForOnlyDirMounting(flags [][]string, dirName string, m *testing.M) (successCode int) { +func executeTestsForOnlyDirMounting(config *test_suite.TestConfig, flags [][]string, dirName string, m *testing.M) (successCode int) { ctx := context.Background() storageClient, err := client.CreateStorageClient(ctx) if err != nil { @@ -74,7 +86,7 @@ func executeTestsForOnlyDirMounting(flags [][]string, dirName string, m *testing if err != nil { log.Println("Error deleting object on GCS: %w", err) } - successCode = mountGcsFuseForFlagsAndExecuteTests(flags, m) + successCode = mountGcsFuseForFlagsAndExecuteTests(config, flags, m) if successCode != 0 { return } @@ -82,7 +94,7 @@ func executeTestsForOnlyDirMounting(flags [][]string, dirName string, m *testing // Test scenario when only-dir-mounted directory pre-exists in bucket. client.SetupTestDirectory(ctx, storageClient, dirName) - successCode = mountGcsFuseForFlagsAndExecuteTests(flags, m) + successCode = mountGcsFuseForFlagsAndExecuteTests(config, flags, m) err = client.DeleteAllObjectsWithPrefix(ctx, storageClient, dirName) if err != nil { log.Println("Error deleting object on GCS: %w", err) @@ -93,10 +105,21 @@ func executeTestsForOnlyDirMounting(flags [][]string, dirName string, m *testing return } +// Deprecated: Use RunTestsWithConfigFile instead. +// TODO(b/438068132): cleanup deprecated methods after migration is complete. func RunTests(flags [][]string, dirName string, m *testing.M) (successCode int) { + config := &test_suite.TestConfig{ + TestBucket: setup.TestBucket(), + MountedDirectory: setup.MountedDirectory(), + LogFile: setup.LogFile(), + } + return RunTestsWithConfigFile(config, flags, dirName, m) +} + +func RunTestsWithConfigFile(config *test_suite.TestConfig, flagsSet [][]string, dirName string, m *testing.M) (successCode int) { log.Println("Running only dir mounting tests...") - successCode = executeTestsForOnlyDirMounting(flags, dirName, m) + successCode = executeTestsForOnlyDirMounting(config, flagsSet, dirName, m) log.Printf("Test log: %s\n", setup.LogFile()) From 3712cba2fea67247d635f58de015c7d485894242 Mon Sep 17 00:00:00 2001 From: Kislay Kishore Date: Tue, 2 Sep 2025 15:07:41 +0530 Subject: [PATCH 0747/1298] Upgrade dependencies (#3740) --- go.mod | 10 +++++----- go.sum | 22 ++++++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index 0c86100666..33bcb4a8b4 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/stretchr/testify v1.10.0 go.opencensus.io v0.24.0 go.opentelemetry.io/contrib/detectors/gcp v1.37.0 - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 go.opentelemetry.io/otel v1.37.0 @@ -50,9 +50,9 @@ require ( golang.org/x/sys v0.35.0 golang.org/x/text v0.28.0 golang.org/x/time v0.12.0 - google.golang.org/api v0.247.0 - google.golang.org/grpc v1.74.2 - google.golang.org/protobuf v1.36.7 + google.golang.org/api v0.248.0 + google.golang.org/grpc v1.75.0 + google.golang.org/protobuf v1.36.8 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 ) @@ -74,7 +74,7 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/go-jose/go-jose/v4 v4.1.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect diff --git a/go.sum b/go.sum index f62ef6fdc4..defa4687b3 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/fsouza/fake-gcs-server v1.52.2 h1:j6ne83nqHrlX5EEor7WWVIKdBsztGtwJ1J2 github.com/fsouza/fake-gcs-server v1.52.2/go.mod h1:47HKyIkz6oLTes1R8vEaHLwXfzYsGfmDUk1ViHHAUsA= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY= -github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -238,8 +238,8 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/detectors/gcp v1.37.0 h1:B+WbN9RPsvobe6q4vP6KgM8/9plR/HNjgGBrfcOlweA= go.opentelemetry.io/contrib/detectors/gcp v1.37.0/go.mod h1:K5zQ3TT7p2ru9Qkzk0bKtCql0RGkPj9pRjpXgZJZ+rU= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0 h1:wCeciVlAfb5DC8MQl/DlmAv/FVPNpQgFvI/71+hatuc= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.62.0/go.mod h1:WfEApdZDMlLUAev/0QQpr8EJ/z0VWDKYZ5tF5RH5T1U= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= @@ -307,8 +307,10 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.247.0 h1:tSd/e0QrUlLsrwMKmkbQhYVa109qIintOls2Wh6bngc= -google.golang.org/api v0.247.0/go.mod h1:r1qZOPmxXffXg6xS5uhx16Fa/UFY8QU/K4bfKrnvovM= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.248.0 h1:hUotakSkcwGdYUqzCRc5yGYsg4wXxpkKlW5ryVqvC1Y= +google.golang.org/api v0.248.0/go.mod h1:yAFUAF56Li7IuIQbTFoLwXTCI6XCFKueOlS7S9e4F9k= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -325,8 +327,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= -google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -336,8 +338,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= -google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 872501e512f06d52aa837b02554f7a111300e9df Mon Sep 17 00:00:00 2001 From: Nitin Garg <113666283+gargnitingoogle@users.noreply.github.com> Date: Tue, 2 Sep 2025 15:23:32 +0530 Subject: [PATCH 0748/1298] fix(control client retry): Fix handling of gax-retries for non-ZB (#3769) * address a comment in a previous commit Fix wrt comment https://github.com/GoogleCloudPlatform/gcsfuse/pull/3684#discussion_r2297178290 . * Fix handling of gax-retries for non-ZB - Create and hold raw storage control clients for both with and without gax retry scenarios. - For shared controlClient held in storageHandle, wrap the raw storage control client without gax retries with billing project, and enhanced retries for storage-layout. - For ZB, wrap the raw storage control client without gax retries with billing project, and enhanced retries for all APIs. - For non-ZB, wrap the raw storage control client with gax retries for folder APIs, with billing project, and enhanced retries for storage-layout. --- internal/storage/control_client_wrapper.go | 28 +-- .../storage/control_client_wrapper_test.go | 56 ++---- internal/storage/storage_handle.go | 49 ++++-- internal/storage/storage_handle_test.go | 166 ++++++++---------- .../storage/storageutil/control_client.go | 7 +- .../storageutil/control_client_test.go | 30 +++- 6 files changed, 169 insertions(+), 167 deletions(-) diff --git a/internal/storage/control_client_wrapper.go b/internal/storage/control_client_wrapper.go index 9cf9c4a69e..1fb2013234 100644 --- a/internal/storage/control_client_wrapper.go +++ b/internal/storage/control_client_wrapper.go @@ -357,20 +357,22 @@ func storageControlClientGaxRetryOptions(clientConfig *storageutil.StorageClient } } -func withGaxRetriesForFolderAPIs(rawControlClientWithoutGaxRetries *control.StorageControlClient, - clientConfig *storageutil.StorageClientConfig) *control.StorageControlClient { - if rawControlClientWithoutGaxRetries == nil || clientConfig == nil { - return rawControlClientWithoutGaxRetries +// addGaxRetriesForFolderAPIs updates the passed raw control client +// to add gax retries according to the given config in-place. +func addGaxRetriesForFolderAPIs(rawControlClient *control.StorageControlClient, + clientConfig *storageutil.StorageClientConfig) error { + if rawControlClient == nil || clientConfig == nil { + return fmt.Errorf("invalid input: %v, %v", rawControlClient, clientConfig) + } + if rawControlClient.CallOptions == nil { + return fmt.Errorf("cannot apply gax retries for folder APIs to raw control client: CallOptions is nil") } - // Create a copy of passed rawControlClientWithoutGaxRetries. - rawControlClientWithGaxRetries := *rawControlClientWithoutGaxRetries - rawControlClientWithGaxRetries.CallOptions = &control.StorageControlCallOptions{} - + *rawControlClient.CallOptions = control.StorageControlCallOptions{} gaxRetryOptions := storageControlClientGaxRetryOptions(clientConfig) - rawControlClientWithGaxRetries.CallOptions.RenameFolder = gaxRetryOptions - rawControlClientWithGaxRetries.CallOptions.GetFolder = gaxRetryOptions - rawControlClientWithGaxRetries.CallOptions.CreateFolder = gaxRetryOptions - rawControlClientWithGaxRetries.CallOptions.DeleteFolder = gaxRetryOptions - return &rawControlClientWithGaxRetries + rawControlClient.CallOptions.RenameFolder = gaxRetryOptions + rawControlClient.CallOptions.GetFolder = gaxRetryOptions + rawControlClient.CallOptions.CreateFolder = gaxRetryOptions + rawControlClient.CallOptions.DeleteFolder = gaxRetryOptions + return nil } diff --git a/internal/storage/control_client_wrapper_test.go b/internal/storage/control_client_wrapper_test.go index 4e71352398..d79fb2bae0 100644 --- a/internal/storage/control_client_wrapper_test.go +++ b/internal/storage/control_client_wrapper_test.go @@ -775,7 +775,7 @@ func (t *ExponentialBackoffTest) TestWaitWithJitter_ContextCancelled() { } func (t *ExponentialBackoffTest) TestWaitWithJitter_NoContextCancelled() { - initial := 1000 * time.Microsecond // A short duration to ensure it waits. Making it any shorter can cause random failures + initial := time.Millisecond // A short duration to ensure it waits. Making it any shorter can cause random failures // because context cancel itself takes about a millisecond. max := 5 * initial b := newExponentialBackoff(&exponentialBackoffConfig{ @@ -815,64 +815,42 @@ func (testSuite *ControlClientGaxRetryWrapperTest) TestStorageControlClientGaxRe require.Len(testSuite.T(), gaxOpts, 2) } -func (testSuite *ControlClientGaxRetryWrapperTest) TestWithGaxRetriesForFolderAPIs_NilRawControlClient() { +func (testSuite *ControlClientGaxRetryWrapperTest) TestAddGaxRetriesForFolderAPIs_NilRawControlClient() { // Arrange clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) // Act - result := withGaxRetriesForFolderAPIs(nil, &clientConfig) + err := addGaxRetriesForFolderAPIs(nil, &clientConfig) // Assert - require.Nil(testSuite.T(), result) + require.Error(testSuite.T(), err) } -func (testSuite *ControlClientGaxRetryWrapperTest) TestWithGaxRetriesForFolderAPIs_NilClientConfig() { +func (testSuite *ControlClientGaxRetryWrapperTest) TestAddGaxRetriesForFolderAPIs_NilClientConfig() { // Arrange rawClient := &control.StorageControlClient{} // Act - result := withGaxRetriesForFolderAPIs(rawClient, nil) + err := addGaxRetriesForFolderAPIs(rawClient, nil) // Assert - require.Equal(testSuite.T(), rawClient, result) + require.Error(testSuite.T(), err) } -func (testSuite *ControlClientGaxRetryWrapperTest) TestWithGaxRetriesForFolderAPIs_ReturnsNewClient() { +func (testSuite *ControlClientGaxRetryWrapperTest) TestAddGaxRetriesForFolderAPIs_AppliesGaxOptions() { // Arrange - rawClient := &control.StorageControlClient{} - clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) - - // Act - result := withGaxRetriesForFolderAPIs(rawClient, &clientConfig) - - // Assert - require.NotNil(testSuite.T(), result) - assert.NotSame(testSuite.T(), rawClient, result) - require.NotNil(testSuite.T(), result.CallOptions) - assert.NotSame(testSuite.T(), rawClient.CallOptions, result.CallOptions) // Should be a new CallOptions object - assert.Nil(testSuite.T(), result.CallOptions.GetStorageLayout) // GetStorageLayout should not have GAX retries applied - assert.NotNil(testSuite.T(), result.CallOptions.DeleteFolder) // DeleteFolder should have GAX retries applied - assert.NotNil(testSuite.T(), result.CallOptions.GetFolder) // GetFolder should have GAX retries applied - assert.NotNil(testSuite.T(), result.CallOptions.CreateFolder) // CreateFolder should have GAX retries applied - assert.NotNil(testSuite.T(), result.CallOptions.RenameFolder) // RenameFolder should have GAX retries applied -} - -func (testSuite *ControlClientGaxRetryWrapperTest) TestWithGaxRetriesForFolderAPIs_AppliesGaxOptions() { - // Arrange - rawClient := &control.StorageControlClient{} + rawControlClient := &control.StorageControlClient{CallOptions: &control.StorageControlCallOptions{}} clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) // Act - result := withGaxRetriesForFolderAPIs(rawClient, &clientConfig) + err := addGaxRetriesForFolderAPIs(rawControlClient, &clientConfig) // Assert - require.NotNil(testSuite.T(), result.CallOptions.RenameFolder) - assert.NotNil(testSuite.T(), result.CallOptions.GetFolder) - require.NotNil(testSuite.T(), result.CallOptions.CreateFolder) - assert.NotNil(testSuite.T(), result.CallOptions.DeleteFolder) - assert.Nil(testSuite.T(), result.CallOptions.GetStorageLayout) // GetStorageLayout should not have GAX retries applied - assert.NotNil(testSuite.T(), result.CallOptions.DeleteFolder) // DeleteFolder should have GAX retries applied - assert.NotNil(testSuite.T(), result.CallOptions.GetFolder) // GetFolder should have GAX retries applied - assert.NotNil(testSuite.T(), result.CallOptions.CreateFolder) // CreateFolder should have GAX retries applied - assert.NotNil(testSuite.T(), result.CallOptions.RenameFolder) // RenameFolder should have GAX retries applied + require.NoError(testSuite.T(), err) + require.NotNil(testSuite.T(), rawControlClient.CallOptions) + assert.Empty(testSuite.T(), rawControlClient.CallOptions.GetStorageLayout) // GetStorageLayout should not have GAX retries applied + assert.Len(testSuite.T(), rawControlClient.CallOptions.DeleteFolder, 2) // DeleteFolder should have GAX retries applied + assert.Len(testSuite.T(), rawControlClient.CallOptions.GetFolder, 2) // GetFolder should have GAX retries applied + assert.Len(testSuite.T(), rawControlClient.CallOptions.CreateFolder, 2) // CreateFolder should have GAX retries applied + assert.Len(testSuite.T(), rawControlClient.CallOptions.RenameFolder, 2) // RenameFolder should have GAX retries applied } diff --git a/internal/storage/storage_handle.go b/internal/storage/storage_handle.go index 3ec6b4253d..08326f666f 100644 --- a/internal/storage/storage_handle.go +++ b/internal/storage/storage_handle.go @@ -66,8 +66,10 @@ type storageClient struct { grpcClient *storage.Client grpcClientWithBidiConfig *storage.Client clientConfig storageutil.StorageClientConfig - // rawStorageControlClient is without any retries and without any handling for billing project. - rawStorageControlClient *control.StorageControlClient + // rawStorageControlClientWithoutGaxRetries is without any retries. + rawStorageControlClientWithoutGaxRetries *control.StorageControlClient + // rawStorageControlClientWithGaxRetries is with retry for Folder APIs. + rawStorageControlClientWithGaxRetries *control.StorageControlClient // storageControlClient is with retry for GetStorageLayout and with handling for billing project. storageControlClient StorageControlClient directPathDetector *gRPCDirectPathDetector @@ -299,7 +301,8 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien // The default protocol for the Go Storage control client's folders API is gRPC. // gcsfuse will initially mirror this behavior due to the client's lack of HTTP support. var controlClient StorageControlClient - var rawStorageControlClient *control.StorageControlClient + var rawStorageControlClientWithoutGaxRetries *control.StorageControlClient + var rawStorageControlClientWithGaxRetries *control.StorageControlClient var clientOpts []option.ClientOption // Control-client is needed for folder APIs and for getting storage-layout of the bucket. @@ -309,12 +312,22 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien if err != nil { return nil, fmt.Errorf("error in getting clientOpts for gRPC client: %w", err) } - rawStorageControlClient, err = storageutil.CreateGRPCControlClient(ctx, clientOpts) + rawStorageControlClientWithoutGaxRetries, err = storageutil.CreateGRPCControlClient(ctx, clientOpts, true) if err != nil { - return nil, fmt.Errorf("could not create StorageControl Client: %w", err) + return nil, fmt.Errorf("could not create StorageControl Client without default gax retries: %w", err) + } + // rawStorageControlClientWithGaxRetries cannot be just a wrapper over rawStorageControlClientWithoutGaxRetries, + // as it has its own dedicated array of CallOptions, and we need to keep those independent. + rawStorageControlClientWithGaxRetries, err = storageutil.CreateGRPCControlClient(ctx, clientOpts, false) + if err != nil { + return nil, fmt.Errorf("could not create StorageControl Client with default gax retries: %w", err) + } + err = addGaxRetriesForFolderAPIs(rawStorageControlClientWithGaxRetries, &clientConfig) + if err != nil { + return nil, fmt.Errorf("could not add custom gax retries to StorageControl Client: %w", err) } // special handling for mounts created with custom billing projects. - controlClientWithBillingProject := withBillingProject(rawStorageControlClient, billingProject) + controlClientWithBillingProject := withBillingProject(rawStorageControlClientWithoutGaxRetries, billingProject) // Wrap the control client with retry-on-stall logic. // This will retry on only on GetStorageLayout call for all buckets. controlClient = withRetryOnStorageLayout(controlClientWithBillingProject, &clientConfig) @@ -323,10 +336,11 @@ func NewStorageHandle(ctx context.Context, clientConfig storageutil.StorageClien } sh = &storageClient{ - rawStorageControlClient: rawStorageControlClient, - storageControlClient: controlClient, - clientConfig: clientConfig, - directPathDetector: &gRPCDirectPathDetector{clientOptions: clientOpts}, + rawStorageControlClientWithoutGaxRetries: rawStorageControlClientWithoutGaxRetries, + rawStorageControlClientWithGaxRetries: rawStorageControlClientWithGaxRetries, + storageControlClient: controlClient, + clientConfig: clientConfig, + directPathDetector: &gRPCDirectPathDetector{clientOptions: clientOpts}, } return } @@ -360,26 +374,27 @@ func (sh *storageClient) getClient(ctx context.Context, isbucketZonal bool) (*st // controlClientForBucketHandle returns a storage control client for the given bucket handle, // which takes care of properly adding support for retries and for billing project. func (sh *storageClient) controlClientForBucketHandle(bucketType *gcs.BucketType, billingProject string) StorageControlClient { - if sh.storageControlClient == nil { + if sh.rawStorageControlClientWithGaxRetries == nil || sh.rawStorageControlClientWithoutGaxRetries == nil { return nil } + var controlClientWithoutBillingProject StorageControlClient if bucketType.Zonal { // sh.storageControlClient already contains handling for billing project, // and enhanced retries for GetStorageLayout API call. Extending it here for // retries for folder APIs. // For zonal buckets, wrap the control client with retry-on-all-APIs. - return withRetryOnAllAPIs(sh.storageControlClient, &sh.clientConfig) + controlClientWithoutBillingProject = withRetryOnAllAPIs(sh.rawStorageControlClientWithoutGaxRetries, &sh.clientConfig) } else { // Apply GAX retries to the raw storage control client and returns a copy of it, // as it is important to avoid overwriting it, // as it is used with enhanced retries used by zonal buckets. - rawControlClientWithGaxFolderAPIRetries := withGaxRetriesForFolderAPIs(sh.rawStorageControlClient, &sh.clientConfig) - rawControlClientWithAllAPIRetries := withRetryOnStorageLayout(rawControlClientWithGaxFolderAPIRetries, &sh.clientConfig) - // Special handling for mounts created with custom billing projects. - // Wrap it with billing-project, if there is any. - return withBillingProject(rawControlClientWithAllAPIRetries, billingProject) + controlClientWithoutBillingProject = withRetryOnStorageLayout(sh.rawStorageControlClientWithGaxRetries, &sh.clientConfig) } + + // Special handling for mounts created with custom billing projects. + // Wrap it with billing-project, if there is any. + return withBillingProject(controlClientWithoutBillingProject, billingProject) } func (sh *storageClient) BucketHandle(ctx context.Context, bucketName string, billingProject string) (bh *bucketHandle, err error) { diff --git a/internal/storage/storage_handle_test.go b/internal/storage/storage_handle_test.go index 15dd2fd046..bf33727694 100644 --- a/internal/storage/storage_handle_test.go +++ b/internal/storage/storage_handle_test.go @@ -77,6 +77,27 @@ func (testSuite *StorageHandleTest) mockStorageLayout(bucketType gcs.BucketType) testSuite.mockClient.On("GetStorageLayout", mock.Anything, mock.Anything, mock.Anything).Return(storageLayout, nil) } +// Helpers + +func (testSuite *StorageHandleTest) controlClientCallOptionsWithoutRetry() *control.StorageControlCallOptions { + testSuite.T().Helper() + return &control.StorageControlCallOptions{} +} + +func (testSuite *StorageHandleTest) controlClientCallOptionsWithRetry() *control.StorageControlCallOptions { + testSuite.T().Helper() + clientConfig := &storageutil.StorageClientConfig{MaxRetrySleep: 100 * time.Microsecond, MaxRetryAttempts: 5} + gaxRetryOptions := storageControlClientGaxRetryOptions(clientConfig) + return &control.StorageControlCallOptions{ + CreateFolder: gaxRetryOptions, + GetFolder: gaxRetryOptions, + DeleteFolder: gaxRetryOptions, + RenameFolder: gaxRetryOptions, + } +} + +// Test functions + func (testSuite *StorageHandleTest) TestBucketHandleWhenBucketExistsWithEmptyBillingProject() { storageHandle := testSuite.fakeStorage.CreateStorageHandle() testSuite.mockStorageLayout(gcs.BucketType{}) @@ -797,13 +818,13 @@ func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NilControlC func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_ZonalBucket_NoBillingProject() { // Arrange - mockRawControlClient := &control.StorageControlClient{} clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) - mockControlClient := withRetryOnStorageLayout(mockRawControlClient, &clientConfig) + mockRawControlClientWithRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithRetry()} + mockRawControlClientWithoutRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithoutRetry()} sh := &storageClient{ - storageControlClient: mockControlClient, - rawStorageControlClient: mockRawControlClient, - clientConfig: clientConfig, + rawStorageControlClientWithoutGaxRetries: mockRawControlClientWithoutRetries, + rawStorageControlClientWithGaxRetries: mockRawControlClientWithRetries, + clientConfig: clientConfig, } bucketType := &gcs.BucketType{Zonal: true} @@ -814,24 +835,21 @@ func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_ZonalBucket require.NotNil(testSuite.T(), controlClient) retryWrapper, ok := controlClient.(*storageControlClientWithRetry) require.True(testSuite.T(), ok, "Expected a retry wrapper for zonal bucket") + assert.Same(testSuite.T(), mockRawControlClientWithoutRetries, retryWrapper.raw) assert.True(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should be enabled for all APIs on zonal buckets") - assert.Same(testSuite.T(), mockRawControlClient, retryWrapper.raw, "Expected raw client to be the same in the wrapped client.") - assert.NotSame(testSuite.T(), mockControlClient, retryWrapper) - assert.True(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should be enabled for folder APIs on zonal buckets") assert.True(testSuite.T(), retryWrapper.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout APIs on zonal buckets") } func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_ZonalBucket_WithBillingProject() { // Arrange billingProject := "test-project" - mockRawControlClient := &control.StorageControlClient{} clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) - mockControlClient := withBillingProject(mockRawControlClient, billingProject) - mockControlClient = withRetryOnStorageLayout(mockControlClient, &clientConfig) + mockRawControlClientWithRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithRetry()} + mockRawControlClientWithoutRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithoutRetry()} sh := &storageClient{ - storageControlClient: mockControlClient, - rawStorageControlClient: mockRawControlClient, - clientConfig: clientConfig, + rawStorageControlClientWithoutGaxRetries: mockRawControlClientWithoutRetries, + rawStorageControlClientWithGaxRetries: mockRawControlClientWithRetries, + clientConfig: clientConfig, } bucketType := &gcs.BucketType{Zonal: true} @@ -840,29 +858,26 @@ func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_ZonalBucket // Assert require.NotNil(testSuite.T(), controlClient) - retryWrapper, ok := controlClient.(*storageControlClientWithRetry) + // Verify that the returned control-client wraps over the billing project, then storageControlClientWithRetry, in that order. + billingProjectWrapper, ok := controlClient.(*storageControlClientWithBillingProject) + require.True(testSuite.T(), ok, "Expected a billing project wrapper") + assert.Equal(testSuite.T(), billingProject, billingProjectWrapper.billingProject) + retryWrapper, ok := billingProjectWrapper.raw.(*storageControlClientWithRetry) require.True(testSuite.T(), ok, "Expected a retry wrapper for zonal bucket") assert.True(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should be enabled for folder APIs on zonal buckets") assert.True(testSuite.T(), retryWrapper.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout APIs on zonal buckets") - assert.True(testSuite.T(), retryWrapper.enableRetriesOnFolderAPIs, "Retries should be enabled for all APIs on zonal buckets") - assert.NotSame(testSuite.T(), mockRawControlClient, retryWrapper.raw) - assert.NotSame(testSuite.T(), mockControlClient, retryWrapper) - billingProjectWrapper, ok := retryWrapper.raw.(*storageControlClientWithBillingProject) - require.True(testSuite.T(), ok, "Expected a billing project wrapper") - assert.Equal(testSuite.T(), billingProject, billingProjectWrapper.billingProject) - assert.Same(testSuite.T(), mockRawControlClient, billingProjectWrapper.raw) + assert.Same(testSuite.T(), mockRawControlClientWithoutRetries, retryWrapper.raw) } func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBucket_WithoutBillingProject() { // Arrange - mockRawControlClient := &control.StorageControlClient{} clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) - mockControlClient := withBillingProject(mockRawControlClient, "") - mockControlClient = withRetryOnStorageLayout(mockControlClient, &clientConfig) + mockRawControlClientWithRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithRetry()} + mockRawControlClientWithoutRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithoutRetry()} sh := &storageClient{ - storageControlClient: mockControlClient, - rawStorageControlClient: mockRawControlClient, - clientConfig: clientConfig, + rawStorageControlClientWithoutGaxRetries: mockRawControlClientWithoutRetries, + rawStorageControlClientWithGaxRetries: mockRawControlClientWithRetries, + clientConfig: clientConfig, } bucketType := &gcs.BucketType{Zonal: false} @@ -883,25 +898,22 @@ func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBuc // Check if it's the GAX-retries-added client gaxClient, ok := controlClientWithStorageLayoutRetries.raw.(*control.StorageControlClient) require.True(testSuite.T(), ok) - assert.NotNil(testSuite.T(), gaxClient.CallOptions) - assert.NotNil(testSuite.T(), gaxClient.CallOptions.CreateFolder) - assert.NotNil(testSuite.T(), gaxClient.CallOptions.GetFolder) - assert.NotNil(testSuite.T(), gaxClient.CallOptions.DeleteFolder) - assert.NotNil(testSuite.T(), gaxClient.CallOptions.RenameFolder) - assert.Nil(testSuite.T(), gaxClient.CallOptions.GetStorageLayout) + require.Same(testSuite.T(), mockRawControlClientWithRetries, gaxClient) } func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBucket_WithBillingProject() { // Arrange billingProject := "test-project" - mockRawControlClient := &control.StorageControlClient{} + mockRawControlClientWithRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithRetry()} + mockRawControlClientWithoutRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithoutRetry()} clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) - mockControlClient := withBillingProject(mockRawControlClient, billingProject) + mockControlClient := withBillingProject(mockRawControlClientWithRetries, billingProject) mockControlClient = withRetryOnStorageLayout(mockControlClient, &clientConfig) sh := &storageClient{ - storageControlClient: mockControlClient, - rawStorageControlClient: mockRawControlClient, - clientConfig: clientConfig, + storageControlClient: mockControlClient, + rawStorageControlClientWithGaxRetries: mockRawControlClientWithRetries, + rawStorageControlClientWithoutGaxRetries: mockRawControlClientWithoutRetries, + clientConfig: clientConfig, } bucketType := &gcs.BucketType{Zonal: false} @@ -922,23 +934,18 @@ func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBuc // Check that the inner client has GAX retries gaxClient, ok := controlClientWithAllRetriesNonZB.raw.(*control.StorageControlClient) require.True(testSuite.T(), ok) - assert.NotNil(testSuite.T(), gaxClient.CallOptions) - assert.NotNil(testSuite.T(), gaxClient.CallOptions.CreateFolder) - assert.NotNil(testSuite.T(), gaxClient.CallOptions.GetFolder) - assert.NotNil(testSuite.T(), gaxClient.CallOptions.DeleteFolder) - assert.NotNil(testSuite.T(), gaxClient.CallOptions.RenameFolder) - assert.Nil(testSuite.T(), gaxClient.CallOptions.GetStorageLayout) + require.Same(testSuite.T(), mockRawControlClientWithRetries, gaxClient) } func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBucket_ThenZonalBucket_WithoutBillingProject() { // Arrange - mockRawControlClient := &control.StorageControlClient{} clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) - mockControlClient := withRetryOnStorageLayout(mockRawControlClient, &clientConfig) + mockRawControlClientWithRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithRetry()} + mockRawControlClientWithoutRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithoutRetry()} sh := &storageClient{ - storageControlClient: mockControlClient, - rawStorageControlClient: mockRawControlClient, - clientConfig: clientConfig, + rawStorageControlClientWithoutGaxRetries: mockRawControlClientWithoutRetries, + rawStorageControlClientWithGaxRetries: mockRawControlClientWithRetries, + clientConfig: clientConfig, } // Act @@ -954,17 +961,7 @@ func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBuc require.NotNil(testSuite.T(), controlClientWithAllRetriesNonZB) assert.True(testSuite.T(), controlClientWithAllRetriesNonZB.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout API on non-zonal buckets") assert.False(testSuite.T(), controlClientWithAllRetriesNonZB.enableRetriesOnFolderAPIs, "Retries should not be enabled for folder APIs on non-zonal buckets") - // Check that the underlying control client is not a storageControlClientWithRetry and uses GAX retries for all folder APIs. - rawControlClientForNonZB, ok := controlClientWithAllRetriesNonZB.raw.(*control.StorageControlClient) - require.True(testSuite.T(), ok) - require.NotNil(testSuite.T(), rawControlClientForNonZB, "Expected a control client with GAX retries") - // Check that the inner client has GAX retries. - assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions) - assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.CreateFolder) - assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.GetFolder) - assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.DeleteFolder) - assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.RenameFolder) - assert.Nil(testSuite.T(), rawControlClientForNonZB.CallOptions.GetStorageLayout) + require.Same(testSuite.T(), mockRawControlClientWithRetries, controlClientWithAllRetriesNonZB.raw) // Act // create control-client for ZB afterwards, which should create a storageControlClientWithRetry a raw control.StorageControlClient without gax retries. @@ -976,27 +973,22 @@ func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBuc // Check that the control client is a storageControlClientWithRetry with all APIs retried. controlClientWithRetry, ok := controlClientForZB.(*storageControlClientWithRetry) require.True(testSuite.T(), ok, "Expected a control client with retry") - assert.Same(testSuite.T(), mockRawControlClient, controlClientWithRetry.raw) + assert.Same(testSuite.T(), mockRawControlClientWithoutRetries, controlClientWithRetry.raw) assert.True(testSuite.T(), controlClientWithRetry.enableRetriesOnFolderAPIs, "Retries should be enabled for folder APIs on zonal buckets") assert.True(testSuite.T(), controlClientWithRetry.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout API on zonal buckets") - // Confirm that the inner client has no GAX retries. - rawControlClientWithoutGaxRetry, ok := controlClientWithRetry.raw.(*control.StorageControlClient) - require.True(testSuite.T(), ok) - require.NotNil(testSuite.T(), rawControlClientWithoutGaxRetry) - assert.Nil(testSuite.T(), rawControlClientWithoutGaxRetry.CallOptions, "Expected no GAX retries for zonal bucket") + require.Same(testSuite.T(), mockRawControlClientWithoutRetries, controlClientWithRetry.raw) } func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBucket_ThenZonalBucket_WithBillingProject() { // Arrange billingProject := "test-project" - mockRawControlClient := &control.StorageControlClient{} clientConfig := storageutil.GetDefaultStorageClientConfig(keyFile) - mockControlClient := withBillingProject(mockRawControlClient, billingProject) - mockControlClient = withRetryOnStorageLayout(mockControlClient, &clientConfig) + mockRawControlClientWithRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithRetry()} + mockRawControlClientWithoutRetries := &control.StorageControlClient{CallOptions: testSuite.controlClientCallOptionsWithoutRetry()} sh := &storageClient{ - storageControlClient: mockControlClient, - rawStorageControlClient: mockRawControlClient, - clientConfig: clientConfig, + rawStorageControlClientWithoutGaxRetries: mockRawControlClientWithoutRetries, + rawStorageControlClientWithGaxRetries: mockRawControlClientWithRetries, + clientConfig: clientConfig, } // Act @@ -1017,15 +1009,7 @@ func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBuc require.NotNil(testSuite.T(), controlClientWithAllRetriesNonZB) assert.True(testSuite.T(), controlClientWithAllRetriesNonZB.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout API on non-zonal buckets") assert.False(testSuite.T(), controlClientWithAllRetriesNonZB.enableRetriesOnFolderAPIs, "Retries should not be enabled for folder APIs on non-zonal buckets") - // Check that the inner client has GAX retries for all folder APIs. - rawControlClientForNonZB, ok := controlClientWithAllRetriesNonZB.raw.(*control.StorageControlClient) - require.True(testSuite.T(), ok, "Expected a raw control client with GAX retries") - assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions) - assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.CreateFolder) - assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.GetFolder) - assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.DeleteFolder) - assert.NotNil(testSuite.T(), rawControlClientForNonZB.CallOptions.RenameFolder) - assert.Nil(testSuite.T(), rawControlClientForNonZB.CallOptions.GetStorageLayout) + require.Same(testSuite.T(), mockRawControlClientWithRetries, controlClientWithAllRetriesNonZB.raw) // Act // create control-client for ZB afterwards, which should create a storageControlClientWithRetry a raw control.StorageControlClient without gax retries. @@ -1034,19 +1018,15 @@ func (testSuite *StorageHandleTest) TestControlClientForBucketHandle_NonZonalBuc // Assert require.NotNil(testSuite.T(), controlClientForZB) + // Check that the control client contains a storageControlClientWithBillingProject. + controlClientWithBillingProjectForZB, ok := controlClientForZB.(*storageControlClientWithBillingProject) + require.True(testSuite.T(), ok) + require.NotNil(testSuite.T(), controlClientWithBillingProjectForZB) // Check that the control client is a storageControlClientWithRetry with all APIs retried. - controlClientWithRetry, ok := controlClientForZB.(*storageControlClientWithRetry) + controlClientWithRetry, ok := controlClientWithBillingProjectForZB.raw.(*storageControlClientWithRetry) require.True(testSuite.T(), ok, "Expected a control client with retry") + require.NotNil(testSuite.T(), controlClientWithRetry) assert.True(testSuite.T(), controlClientWithRetry.enableRetriesOnFolderAPIs, "Retries should be enabled for folder APIs on zonal buckets") assert.True(testSuite.T(), controlClientWithRetry.enableRetriesOnStorageLayoutAPI, "Retries should be enabled for storage layout API on zonal buckets") - // Check that the control client contains a storageControlClientWithBillingProject. - controlClientWithBillingProjectForZB, ok := controlClientWithRetry.raw.(*storageControlClientWithBillingProject) - require.True(testSuite.T(), ok) - require.NotNil(testSuite.T(), controlClientWithBillingProjectForZB) - assert.Same(testSuite.T(), mockRawControlClient, controlClientWithBillingProjectForZB.raw) - // Check that the inner client does not have GAX retries. - rawControlClientWithoutGaxRetry, ok := controlClientWithBillingProjectForZB.raw.(*control.StorageControlClient) - require.True(testSuite.T(), ok) - require.NotNil(testSuite.T(), rawControlClientWithoutGaxRetry) - assert.Nil(testSuite.T(), rawControlClientWithoutGaxRetry.CallOptions, "Expected no GAX retries for zonal bucket") + assert.Same(testSuite.T(), mockRawControlClientWithoutRetries, controlClientWithRetry.raw) } diff --git a/internal/storage/storageutil/control_client.go b/internal/storage/storageutil/control_client.go index 93f96a0ce6..bb0caf1438 100644 --- a/internal/storage/storageutil/control_client.go +++ b/internal/storage/storageutil/control_client.go @@ -24,7 +24,7 @@ import ( "google.golang.org/api/option" ) -func CreateGRPCControlClient(ctx context.Context, clientOpts []option.ClientOption) (controlClient *control.StorageControlClient, err error) { +func CreateGRPCControlClient(ctx context.Context, clientOpts []option.ClientOption, disableDefaultGaxRetries bool) (controlClient *control.StorageControlClient, err error) { if err := os.Setenv("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS", "true"); err != nil { logger.Fatal("error setting direct path env var: %v", err) } @@ -34,6 +34,11 @@ func CreateGRPCControlClient(ctx context.Context, clientOpts []option.ClientOpti return nil, fmt.Errorf("NewStorageControlClient: %w", err) } + // Remove default gax retry options if requested. + if disableDefaultGaxRetries { + *controlClient.CallOptions = control.StorageControlCallOptions{} + } + // Unset the environment variable, since it's used only while creation of grpc client. if err := os.Unsetenv("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS"); err != nil { logger.Fatal("error while unsetting direct path env var: %v", err) diff --git a/internal/storage/storageutil/control_client_test.go b/internal/storage/storageutil/control_client_test.go index 3fad1aa6c2..47e872a470 100644 --- a/internal/storage/storageutil/control_client_test.go +++ b/internal/storage/storageutil/control_client_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "google.golang.org/api/option" ) @@ -37,12 +38,33 @@ func (testSuite *ControlClientTest) SetupTest() { func (testSuite *ControlClientTest) TearDownTest() { } -func (testSuite *ControlClientTest) TestStorageControlClient() { +func (testSuite *ControlClientTest) TestStorageControlClientWithGaxRetries() { var clientOpts []option.ClientOption clientOpts = append(clientOpts, option.WithoutAuthentication()) - controlClient, err := CreateGRPCControlClient(context.Background(), clientOpts) + controlClient, err := CreateGRPCControlClient(context.Background(), clientOpts, false) - assert.Nil(testSuite.T(), err) - assert.NotNil(testSuite.T(), controlClient) + require.Nil(testSuite.T(), err) + require.NotNil(testSuite.T(), controlClient) + require.NotNil(testSuite.T(), controlClient.CallOptions) + assert.Greater(testSuite.T(), len(controlClient.CallOptions.CreateFolder), 0) + assert.Greater(testSuite.T(), len(controlClient.CallOptions.GetFolder), 0) + assert.Greater(testSuite.T(), len(controlClient.CallOptions.DeleteFolder), 0) + assert.Greater(testSuite.T(), len(controlClient.CallOptions.RenameFolder), 0) +} + +func (testSuite *ControlClientTest) TestStorageControlClientWithoutGaxRetries() { + var clientOpts []option.ClientOption + clientOpts = append(clientOpts, option.WithoutAuthentication()) + + controlClient, err := CreateGRPCControlClient(context.Background(), clientOpts, true) + + require.Nil(testSuite.T(), err) + require.NotNil(testSuite.T(), controlClient) + if controlClient.CallOptions != nil { + assert.Empty(testSuite.T(), controlClient.CallOptions.CreateFolder) + assert.Empty(testSuite.T(), controlClient.CallOptions.GetFolder) + assert.Empty(testSuite.T(), controlClient.CallOptions.DeleteFolder) + assert.Empty(testSuite.T(), controlClient.CallOptions.RenameFolder) + } } From 2a996fe0d4b099d2ef45b8408cf2127cfd112331 Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:27:25 +0530 Subject: [PATCH 0749/1298] refactor: migrate packages to use stretchr testify and clean up test_setup package (#3772) * migrate packages to use stretchr testify and clean up test_setup package * review comments * fix errors --- .../concurrent_listing_test.go | 208 ++++----- .../concurrent_read_test.go | 91 ++-- .../dentry_cache/delete_operation_test.go | 23 +- .../dentry_cache/notifier_test.go | 51 +-- .../dentry_cache/stat_test.go | 43 +- .../write_stall/writes_stall_on_sync_test.go | 25 +- .../integration_tests/gzip/write_gzip_test.go | 2 +- .../with_timeout_test.go | 37 +- .../without_timeout_test.go | 23 +- .../interrupt/git_clone_test.go | 38 +- .../disabled_kernel_list_cache_test.go | 51 +-- .../finite_kernel_list_cache_test.go | 63 +-- ...inite_kernel_list_cache_delete_dir_test.go | 69 +-- .../infinite_kernel_list_cache_test.go | 405 +++++++++--------- .../managed_folders/admin_permissions_test.go | 71 +-- .../list_empty_managed_folders_test.go | 29 +- .../managed_folders/view_permissions_test.go | 57 +-- .../disabled_negative_stat_cache_test.go | 27 +- .../finite_int_negative_stat_cache_test.go | 31 +- .../infinite_negative_stat_cache_test.go | 35 +- .../cache_file_for_exclude_regex_test.go | 29 +- .../cache_file_for_range_read_false_test.go | 73 ++-- .../cache_file_for_range_read_true_test.go | 31 +- .../read_cache/disabled_cache_ttl_test.go | 33 +- .../read_cache/job_chunk_test.go | 68 +-- .../read_cache/local_modification_test.go | 31 +- .../read_cache/range_read_test.go | 49 +-- .../read_cache/read_only_test.go | 83 ++-- .../read_cache/remount_test.go | 82 ++-- .../read_cache/small_cache_ttl_test.go | 55 +-- .../readdirplus_with_dentry_cache_test.go | 35 +- .../readdirplus_without_dentry_cache_test.go | 35 +- .../readonly/readonly_test.go | 6 +- .../failure_during_file_sync_test.go | 33 +- .../util/test_setup/test_setup.go | 63 --- .../util/test_setup/test_setup_test.go | 55 --- 36 files changed, 1026 insertions(+), 1114 deletions(-) delete mode 100644 tools/integration_tests/util/test_setup/test_setup.go delete mode 100644 tools/integration_tests/util/test_setup/test_setup_test.go diff --git a/tools/integration_tests/concurrent_operations/concurrent_listing_test.go b/tools/integration_tests/concurrent_operations/concurrent_listing_test.go index 20a831908a..629022dc06 100644 --- a/tools/integration_tests/concurrent_operations/concurrent_listing_test.go +++ b/tools/integration_tests/concurrent_operations/concurrent_listing_test.go @@ -24,9 +24,9 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) const ( @@ -48,13 +48,15 @@ const ( // This test-suite contains parallelizable test-case. Use "-parallel n" to limit // the degree of parallelism. By default it uses GOMAXPROCS. // Ref: https://stackoverflow.com/questions/24375966/does-go-test-run-unit-tests-concurrently -type concurrentListingTest struct{} +type concurrentListingTest struct { + suite.Suite +} -func (s *concurrentListingTest) Setup(t *testing.T) { +func (s *concurrentListingTest) SetupTest() { testDirPath = setup.SetupTestDirectory(testDirName) } -func (s *concurrentListingTest) Teardown(t *testing.T) {} +func (s *concurrentListingTest) TearDownTest() {} // createDirectoryStructureForTestCase creates initial directory structure in the // given testCaseDir. @@ -79,10 +81,10 @@ func createDirectoryStructureForTestCase(t *testing.T, testCaseDir string) { // Test_OpenDirAndLookUp helps in detecting the deadlock when // OpenDir() and LookUpInode() request for same directory comes in parallel. -func (s *concurrentListingTest) Test_OpenDirAndLookUp(t *testing.T) { - t.Parallel() // Mark the test parallelizable. +func (s *concurrentListingTest) Test_OpenDirAndLookUp() { + s.T().Parallel() // Mark the test parallelizable. testCaseDir := "Test_OpenDirAndLookUp" - createDirectoryStructureForTestCase(t, testCaseDir) + createDirectoryStructureForTestCase(s.T(), testCaseDir) targetDir := path.Join(testDirPath, testCaseDir, "explicitDir") var wg sync.WaitGroup wg.Add(2) @@ -94,10 +96,10 @@ func (s *concurrentListingTest) Test_OpenDirAndLookUp(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForLightOperations; i++ { f, err := os.Open(targetDir) - require.Nil(t, err) + require.Nil(s.T(), err) err = f.Close() - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -106,7 +108,7 @@ func (s *concurrentListingTest) Test_OpenDirAndLookUp(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForLightOperations; i++ { _, err := os.Stat(targetDir) - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -120,16 +122,16 @@ func (s *concurrentListingTest) Test_OpenDirAndLookUp(t *testing.T) { case <-done: // Operation completed successfully before timeout. case <-time.After(timeout): - assert.FailNow(t, "Possible deadlock") + assert.FailNow(s.T(), "Possible deadlock") } } // Test_Parallel_ReadDirAndLookUp tests for potential deadlocks or race conditions when // ReadDir() is called concurrently with LookUp of same dir. -func (s *concurrentListingTest) Test_Parallel_ReadDirAndLookUp(t *testing.T) { - t.Parallel() // Mark the test parallelizable. +func (s *concurrentListingTest) Test_Parallel_ReadDirAndLookUp() { + s.T().Parallel() // Mark the test parallelizable. testCaseDir := "Test_Parallel_ReadDirAndLookUp" - createDirectoryStructureForTestCase(t, testCaseDir) + createDirectoryStructureForTestCase(s.T(), testCaseDir) targetDir := path.Join(testDirPath, testCaseDir, "explicitDir") var wg sync.WaitGroup wg.Add(2) @@ -140,13 +142,13 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndLookUp(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { f, err := os.Open(targetDir) - require.Nil(t, err) + require.Nil(s.T(), err) _, err = f.Readdirnames(-1) - require.Nil(t, err) + require.Nil(s.T(), err) err = f.Close() - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -155,7 +157,7 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndLookUp(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForLightOperations; i++ { _, err := os.Stat(targetDir) - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -170,16 +172,16 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndLookUp(t *testing.T) { case <-done: // Success: Both operations finished before timeout case <-time.After(timeout): - assert.FailNow(t, "Possible deadlock or race condition detected during Readdir and directory operations") + assert.FailNow(s.T(), "Possible deadlock or race condition detected during Readdir and directory operations") } } // Test_MultipleConcurrentReadDir tests for potential deadlocks or race conditions // when multiple goroutines call Readdir() concurrently on the same directory. -func (s *concurrentListingTest) Test_MultipleConcurrentReadDir(t *testing.T) { - t.Parallel() // Mark the test parallelizable. +func (s *concurrentListingTest) Test_MultipleConcurrentReadDir() { + s.T().Parallel() // Mark the test parallelizable. testCaseDir := "Test_MultipleConcurrentReadDir" - createDirectoryStructureForTestCase(t, testCaseDir) + createDirectoryStructureForTestCase(s.T(), testCaseDir) targetDir := path.Join(testDirPath, testCaseDir, "explicitDir") var wg sync.WaitGroup goroutineCount := 10 // Number of concurrent goroutines @@ -193,13 +195,13 @@ func (s *concurrentListingTest) Test_MultipleConcurrentReadDir(t *testing.T) { for j := 0; j < iterationsForMediumOperations; j++ { f, err := os.Open(targetDir) - require.Nil(t, err) + require.Nil(s.T(), err) _, err = f.Readdirnames(-1) // Read all directory entries - require.Nil(t, err) + require.Nil(s.T(), err) err = f.Close() - require.Nil(t, err) + require.Nil(s.T(), err) } }() } @@ -215,16 +217,16 @@ func (s *concurrentListingTest) Test_MultipleConcurrentReadDir(t *testing.T) { case <-done: // Success: All Readdir operations finished before timeout case <-time.After(timeout): - assert.FailNow(t, "Possible deadlock or race condition detected during concurrent Readdir calls") + assert.FailNow(s.T(), "Possible deadlock or race condition detected during concurrent Readdir calls") } } // Test_Parallel_ReadDirAndFileOperations detects race conditions and deadlocks when one goroutine // performs Readdir() while another concurrently creates and deletes files in the same directory. -func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileOperations(t *testing.T) { - t.Parallel() // Mark the test parallelizable. +func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileOperations() { + s.T().Parallel() // Mark the test parallelizable. testCaseDir := "Test_Parallel_ReadDirAndFileOperations" - createDirectoryStructureForTestCase(t, testCaseDir) + createDirectoryStructureForTestCase(s.T(), testCaseDir) targetDir := path.Join(testDirPath, testCaseDir, "explicitDir") var wg sync.WaitGroup wg.Add(2) @@ -235,13 +237,13 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileOperations(t *testin defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { // Adjust iteration count if needed f, err := os.Open(targetDir) - require.Nil(t, err) + require.Nil(s.T(), err) _, err = f.Readdirnames(-1) - require.Nil(t, err) + require.Nil(s.T(), err) err = f.Close() - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -254,18 +256,18 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileOperations(t *testin // Create f, err := os.Create(filePath) - require.Nil(t, err) + require.Nil(s.T(), err) err = f.Close() - require.Nil(t, err) + require.Nil(s.T(), err) // Rename err = os.Rename(filePath, renamedFilePath) - require.Nil(t, err) + require.Nil(s.T(), err) // Delete err = os.Remove(renamedFilePath) - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -280,16 +282,16 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileOperations(t *testin case <-done: // Success: Both operations finished before timeout case <-time.After(timeout): - assert.FailNow(t, "Possible deadlock or race condition detected") + assert.FailNow(s.T(), "Possible deadlock or race condition detected") } } // Test_Parallel_ReadDirAndDirOperations tests for potential deadlocks or race conditions when // ReadDir() is called concurrently with directory creation and deletion operations. -func (s *concurrentListingTest) Test_Parallel_ReadDirAndDirOperations(t *testing.T) { - t.Parallel() // Mark the test parallelizable. +func (s *concurrentListingTest) Test_Parallel_ReadDirAndDirOperations() { + s.T().Parallel() // Mark the test parallelizable. testCaseDir := "Test_Parallel_ReadDirAndDirOperations" - createDirectoryStructureForTestCase(t, testCaseDir) + createDirectoryStructureForTestCase(s.T(), testCaseDir) targetDir := path.Join(testDirPath, testCaseDir, "explicitDir") var wg sync.WaitGroup wg.Add(2) @@ -300,13 +302,13 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndDirOperations(t *testing defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { f, err := os.Open(targetDir) - require.Nil(t, err) + require.Nil(s.T(), err) _, err = f.Readdirnames(-1) - require.Nil(t, err) + require.Nil(s.T(), err) err = f.Close() - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -319,15 +321,15 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndDirOperations(t *testing // Create err := os.Mkdir(dirPath, 0755) - require.Nil(t, err) + require.Nil(s.T(), err) // Rename err = os.Rename(dirPath, renamedDirPath) - require.Nil(t, err) + require.Nil(s.T(), err) // Delete err = os.Remove(renamedDirPath) - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -342,16 +344,16 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndDirOperations(t *testing case <-done: // Success: Both operations finished before timeout case <-time.After(timeout): - assert.FailNow(t, "Possible deadlock or race condition detected during Readdir and directory operations") + assert.FailNow(s.T(), "Possible deadlock or race condition detected during Readdir and directory operations") } } // Test_Parallel_ReadDirAndFileEdit tests for potential deadlocks or race conditions when // ReadDir() is called concurrently with modification of underneath file. -func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileEdit(t *testing.T) { - t.Parallel() // Mark the test parallelizable. +func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileEdit() { + s.T().Parallel() // Mark the test parallelizable. testCaseDir := "Test_Parallel_ListDirAndFileEdit" - createDirectoryStructureForTestCase(t, testCaseDir) + createDirectoryStructureForTestCase(s.T(), testCaseDir) targetDir := path.Join(testDirPath, testCaseDir, "explicitDir") var wg sync.WaitGroup wg.Add(2) @@ -362,13 +364,13 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileEdit(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { f, err := os.Open(targetDir) - require.Nil(t, err) + require.Nil(s.T(), err) _, err = f.Readdirnames(-1) - require.Nil(t, err) + require.Nil(s.T(), err) err = f.Close() - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -380,15 +382,15 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileEdit(t *testing.T) { // Create file err := os.WriteFile(filePath, []byte("Hello, world!"), setup.FilePermission_0600) - require.Nil(t, err) + require.Nil(s.T(), err) // Edit file (append some data) f, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, setup.FilePermission_0600) - require.Nil(t, err) + require.Nil(s.T(), err) _, err = f.Write([]byte("This is an edit.")) - require.Nil(t, err) + require.Nil(s.T(), err) err = f.Close() - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -403,16 +405,16 @@ func (s *concurrentListingTest) Test_Parallel_ReadDirAndFileEdit(t *testing.T) { case <-done: // Success: Both operations finished before timeout case <-time.After(timeout): - assert.FailNow(t, "Possible deadlock or race condition detected during Readdir and directory operations") + assert.FailNow(s.T(), "Possible deadlock or race condition detected during Readdir and directory operations") } } // Test_MultipleConcurrentOperations tests for potential deadlocks or race conditions when // listing, file or folder operations, stat, opendir, file modifications happening concurrently. -func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) { - t.Parallel() // Mark the test parallelizable. +func (s *concurrentListingTest) Test_MultipleConcurrentOperations() { + s.T().Parallel() // Mark the test parallelizable. testCaseDir := "Test_MultipleConcurrentOperations" - createDirectoryStructureForTestCase(t, testCaseDir) + createDirectoryStructureForTestCase(s.T(), testCaseDir) targetDir := path.Join(testDirPath, testCaseDir, "explicitDir") var wg sync.WaitGroup wg.Add(5) @@ -423,13 +425,13 @@ func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { // Adjust iteration count if needed f, err := os.Open(targetDir) - require.Nil(t, err) + require.Nil(s.T(), err) _, err = f.Readdirnames(-1) - require.Nil(t, err) + require.Nil(s.T(), err) err = f.Close() - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -441,15 +443,15 @@ func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) // Create file err := os.WriteFile(filePath, []byte("Hello, world!"), setup.FilePermission_0600) - require.Nil(t, err) + require.Nil(s.T(), err) // Edit file (append some data) f, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, setup.FilePermission_0600) - require.Nil(t, err) + require.Nil(s.T(), err) _, err = f.Write([]byte("This is an edit.")) - require.Nil(t, err) + require.Nil(s.T(), err) err = f.Close() - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -462,15 +464,15 @@ func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) // Create err := os.Mkdir(dirPath, 0755) - require.Nil(t, err) + require.Nil(s.T(), err) // Rename err = os.Rename(dirPath, renamedDirPath) - require.Nil(t, err) + require.Nil(s.T(), err) // Delete err = os.Remove(renamedDirPath) - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -479,7 +481,7 @@ func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) defer wg.Done() for i := 0; i < iterationsForLightOperations; i++ { _, err := os.Stat(targetDir) - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -488,10 +490,10 @@ func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) defer wg.Done() for i := 0; i < iterationsForLightOperations; i++ { f, err := os.Open(targetDir) - require.Nil(t, err) + require.Nil(s.T(), err) err = f.Close() - require.Nil(t, err) + require.Nil(s.T(), err) } }() @@ -506,16 +508,16 @@ func (s *concurrentListingTest) Test_MultipleConcurrentOperations(t *testing.T) case <-done: // Success: Both operations finished before timeout case <-time.After(timeout): - assert.FailNow(t, "Possible deadlock or race condition detected during Readdir and directory operations") + assert.FailNow(s.T(), "Possible deadlock or race condition detected during Readdir and directory operations") } } // Test_ListWithMoveFile tests for potential deadlocks or race conditions when // listing, file or folder operations, move file happening concurrently. -func (s *concurrentListingTest) Test_ListWithMoveFile(t *testing.T) { - t.Parallel() // Mark the test parallelizable. +func (s *concurrentListingTest) Test_ListWithMoveFile() { + s.T().Parallel() // Mark the test parallelizable. testCaseDir := "Test_ListWithMoveFile" - createDirectoryStructureForTestCase(t, testCaseDir) + createDirectoryStructureForTestCase(s.T(), testCaseDir) targetDir := path.Join(testDirPath, testCaseDir, "explicitDir") var wg sync.WaitGroup wg.Add(2) @@ -526,18 +528,18 @@ func (s *concurrentListingTest) Test_ListWithMoveFile(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { // Adjust iteration count if needed f, err := os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) _, err = f.Readdirnames(-1) - require.Nil(t, err) + require.Nil(s.T(), err) - require.NoError(t, f.Close()) + require.NoError(s.T(), f.Close()) } }() // Create file err := os.WriteFile(path.Join(testDirPath, "move_file.txt"), []byte("Hello, world!"), setup.FilePermission_0600) - require.NoError(t, err) + require.NoError(s.T(), err) // Goroutine 2: Move file go func() { @@ -545,10 +547,10 @@ func (s *concurrentListingTest) Test_ListWithMoveFile(t *testing.T) { for i := 0; i < iterationsForHeavyOperations; i++ { // Adjust iteration count if needed // Move File in the target directory err = operations.Move(path.Join(testDirPath, "move_file.txt"), path.Join(targetDir, "move_file.txt")) - require.NoError(t, err) + require.NoError(s.T(), err) // Move File out of the target directory err = operations.Move(path.Join(targetDir, "move_file.txt"), path.Join(testDirPath, "move_file.txt")) - require.NoError(t, err) + require.NoError(s.T(), err) } }() @@ -563,16 +565,16 @@ func (s *concurrentListingTest) Test_ListWithMoveFile(t *testing.T) { case <-done: // Success: Both operations finished before timeout case <-time.After(timeout): - assert.FailNow(t, "Possible deadlock or race condition detected") + assert.FailNow(s.T(), "Possible deadlock or race condition detected") } } // Test_ListWithMoveDir tests for potential deadlocks or race conditions when // listing, file or folder operations, move dir happening concurrently. -func (s *concurrentListingTest) Test_ListWithMoveDir(t *testing.T) { - t.Parallel() // Mark the test parallelizable. +func (s *concurrentListingTest) Test_ListWithMoveDir() { + s.T().Parallel() // Mark the test parallelizable. testCaseDir := "Test_ListWithMoveDir" - createDirectoryStructureForTestCase(t, testCaseDir) + createDirectoryStructureForTestCase(s.T(), testCaseDir) targetDir := path.Join(testDirPath, testCaseDir, "explicitDir") var wg sync.WaitGroup wg.Add(2) @@ -583,17 +585,17 @@ func (s *concurrentListingTest) Test_ListWithMoveDir(t *testing.T) { defer wg.Done() for i := 0; i < iterationsForMediumOperations; i++ { // Adjust iteration count if needed f, err := os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) _, err = f.Readdirnames(-1) - require.Nil(t, err) + require.Nil(s.T(), err) - require.NoError(t, f.Close()) + require.NoError(s.T(), f.Close()) } }() // Create Dir err := os.Mkdir(path.Join(testDirPath, "move_dir"), setup.DirPermission_0755) - require.NoError(t, err) + require.NoError(s.T(), err) // Goroutine 2: Move Dir go func() { @@ -601,10 +603,10 @@ func (s *concurrentListingTest) Test_ListWithMoveDir(t *testing.T) { for i := 0; i < iterationsForHeavyOperations; i++ { // Adjust iteration count if needed // Move Dir in the target dir err = operations.Move(path.Join(testDirPath, "move_dir"), path.Join(targetDir, "move_dir")) - require.NoError(t, err) + require.NoError(s.T(), err) // Move Dir out of the target dir err = operations.Move(path.Join(targetDir, "move_dir"), path.Join(testDirPath, "move_dir")) - require.NoError(t, err) + require.NoError(s.T(), err) } }() @@ -619,16 +621,16 @@ func (s *concurrentListingTest) Test_ListWithMoveDir(t *testing.T) { case <-done: // Success: Both operations finished before timeout case <-time.After(timeout): - assert.FailNow(t, "Possible deadlock or race condition detected") + assert.FailNow(s.T(), "Possible deadlock or race condition detected") } } // Test_StatWithNewFileWrite tests for potential deadlocks or race conditions when // statting and creating a new file happen concurrently. -func (s *concurrentListingTest) Test_StatWithNewFileWrite(t *testing.T) { - t.Parallel() +func (s *concurrentListingTest) Test_StatWithNewFileWrite() { + s.T().Parallel() testCaseDir := "Test_StatWithNewFileWrite" - createDirectoryStructureForTestCase(t, testCaseDir) + createDirectoryStructureForTestCase(s.T(), testCaseDir) targetDir := path.Join(testDirPath, testCaseDir, "explicitDir") var wg sync.WaitGroup wg.Add(2) @@ -640,7 +642,7 @@ func (s *concurrentListingTest) Test_StatWithNewFileWrite(t *testing.T) { for i := 0; i < iterationsForMediumOperations; i++ { _, err := os.Stat(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) } }() @@ -652,7 +654,7 @@ func (s *concurrentListingTest) Test_StatWithNewFileWrite(t *testing.T) { filePath := path.Join(targetDir, fmt.Sprintf("tmp_file_%d.txt", i)) err := os.WriteFile(filePath, []byte("Hello, world!"), setup.FilePermission_0600) - require.NoError(t, err) + require.NoError(s.T(), err) } }() @@ -667,7 +669,7 @@ func (s *concurrentListingTest) Test_StatWithNewFileWrite(t *testing.T) { case <-done: // Success: Both operations finished before timeout case <-time.After(timeout): - assert.FailNow(t, "Possible deadlock or race condition detected") + assert.FailNow(s.T(), "Possible deadlock or race condition detected") } } @@ -680,7 +682,7 @@ func TestConcurrentListing(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -698,7 +700,7 @@ func TestConcurrentListing(t *testing.T) { // returned. Hence invoking RunTest inside another test, otherwise unmount will // happen before the subtest execution starts. t.Run(fmt.Sprintf("Flags_%v", flags), func(t *testing.T) { - test_setup.RunTests(t, ts) + suite.Run(t, ts) }) setup.UnmountGCSFuse(setup.MntDir()) } diff --git a/tools/integration_tests/concurrent_operations/concurrent_read_test.go b/tools/integration_tests/concurrent_operations/concurrent_read_test.go index 2935b73dbd..b2793395bb 100644 --- a/tools/integration_tests/concurrent_operations/concurrent_read_test.go +++ b/tools/integration_tests/concurrent_operations/concurrent_read_test.go @@ -28,9 +28,9 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) const ( @@ -45,13 +45,14 @@ var testDirPathForRead string type concurrentReadTest struct { flags []string + suite.Suite } -func (s *concurrentReadTest) Setup(t *testing.T) { - mountGCSFuseAndSetupTestDir(s.flags, testDirNameForRead, t) +func (s *concurrentReadTest) SetupTest() { + mountGCSFuseAndSetupTestDir(s.flags, testDirNameForRead, s.T()) } -func (s *concurrentReadTest) Teardown(t *testing.T) { +func (s *concurrentReadTest) TearDownTest() { setup.UnmountGCSFuse(setup.MntDir()) } @@ -64,7 +65,7 @@ func (s *concurrentReadTest) Teardown(t *testing.T) { // This test validates that concurrent sequential and random read patterns work // correctly without deadlocks or race conditions. It also validates data integrity // using CRC32 checksums for sequential reads and chunk validation for random reads. -func (s *concurrentReadTest) Test_ConcurrentSequentialAndRandomReads(t *testing.T) { +func (s *concurrentReadTest) Test_ConcurrentSequentialAndRandomReads() { const ( fileSize = 500 * operations.OneMiB // 500 MiB file chunkSize = 64 * operations.OneKiB // 64 KiB chunks for reads @@ -73,7 +74,7 @@ func (s *concurrentReadTest) Test_ConcurrentSequentialAndRandomReads(t *testing. ) // Create a 500MiB test file testFilePath := path.Join(testDirPathForRead, "large_test_file.bin") - operations.CreateFileOfSize(fileSize, testFilePath, t) + operations.CreateFileOfSize(fileSize, testFilePath, s.T()) var wg sync.WaitGroup timeout := 300 * time.Second // 5 minutes timeout for 500MiB operations @@ -84,14 +85,14 @@ func (s *concurrentReadTest) Test_ConcurrentSequentialAndRandomReads(t *testing. defer wg.Done() // Use operations.ReadFileSequentially to read the entire file content, err := operations.ReadFileSequentially(testFilePath, chunkSize) - require.NoError(t, err, "Sequential reader %d: read failed.", readerID) - require.Equal(t, fileSize, len(content), "Sequential reader %d: expected to read entire file", readerID) + require.NoError(s.T(), err, "Sequential reader %d: read failed.", readerID) + require.Equal(s.T(), fileSize, len(content), "Sequential reader %d: expected to read entire file", readerID) obj := storageClient.Bucket(setup.TestBucket()).Object(path.Join(path.Base(testDirPathForRead), "large_test_file.bin")) attrs, err := obj.Attrs(ctx) - require.NoError(t, err, "obj.Attrs") + require.NoError(s.T(), err, "obj.Attrs") localCRC32C, err := operations.CalculateCRC32(bytes.NewReader(content)) - require.NoError(t, err, "Sequential reader %d: failed to calculate local CRC32C", readerID) - assert.Equal(t, attrs.CRC32C, localCRC32C, "Sequential reader %d: CRC32C mismatch. GCS: %d, Local: %d", readerID, attrs.CRC32C, localCRC32C) + require.NoError(s.T(), err, "Sequential reader %d: failed to calculate local CRC32C", readerID) + assert.Equal(s.T(), attrs.CRC32C, localCRC32C, "Sequential reader %d: CRC32C mismatch. GCS: %d, Local: %d", readerID, attrs.CRC32C, localCRC32C) }(i) } // Launch 5 random readers @@ -106,8 +107,8 @@ func (s *concurrentReadTest) Test_ConcurrentSequentialAndRandomReads(t *testing. randomOffset := int64(rand.Intn(fileSize/chunkSize)) * chunkSize // Use operations.ReadChunkFromFile for reading chunks chunk, err := operations.ReadChunkFromFile(testFilePath, chunkSize, randomOffset, os.O_RDONLY) - require.NoError(t, err, "Random reader %d: ReadChunkFromFile failed at offset %d", readerID, randomOffset) - client.ValidateObjectChunkFromGCS(ctx, storageClient, path.Base(testDirPathForRead), "large_test_file.bin", randomOffset, int64(len(chunk)), string(chunk), t) + require.NoError(s.T(), err, "Random reader %d: ReadChunkFromFile failed at offset %d", readerID, randomOffset) + client.ValidateObjectChunkFromGCS(ctx, storageClient, path.Base(testDirPathForRead), "large_test_file.bin", randomOffset, int64(len(chunk)), string(chunk), s.T()) } }(i) } @@ -120,9 +121,9 @@ func (s *concurrentReadTest) Test_ConcurrentSequentialAndRandomReads(t *testing. select { case <-done: - t.Log("All concurrent read operations completed successfully") + s.T().Log("All concurrent read operations completed successfully") case <-time.After(timeout): - assert.FailNow(t, "Concurrent read operations timed out - possible deadlock or performance issue") + assert.FailNow(s.T(), "Concurrent read operations timed out - possible deadlock or performance issue") } } @@ -131,7 +132,7 @@ func (s *concurrentReadTest) Test_ConcurrentSequentialAndRandomReads(t *testing. // This test validates that multiple goroutines can safely read from different // parts of the same file using a single shared file handle without race conditions, // with each reader handling a distinct segment of the file for comprehensive coverage. -func (s *concurrentReadTest) Test_ConcurrentSegmentReadsSharedHandle(t *testing.T) { +func (s *concurrentReadTest) Test_ConcurrentSegmentReadsSharedHandle() { const ( fileSize = 500 * operations.OneMiB // 500 MiB file numReaders = 5 // Number of concurrent readers @@ -139,13 +140,13 @@ func (s *concurrentReadTest) Test_ConcurrentSegmentReadsSharedHandle(t *testing. ) // Create a 500MiB test file testFilePath := path.Join(testDirPathForRead, "segment_test_file.bin") - operations.CreateFileOfSize(fileSize, testFilePath, t) + operations.CreateFileOfSize(fileSize, testFilePath, s.T()) // Open shared file handle that will be used by all goroutines sharedFile, err := os.Open(testFilePath) - require.NoError(t, err, "Failed to open shared file handle") + require.NoError(s.T(), err, "Failed to open shared file handle") defer func() { err := sharedFile.Close() - require.NoError(t, err, "Failed to close shared file handle") + require.NoError(s.T(), err, "Failed to close shared file handle") }() var wg sync.WaitGroup segmentData := make([][]byte, numReaders) @@ -167,8 +168,8 @@ func (s *concurrentReadTest) Test_ConcurrentSegmentReadsSharedHandle(t *testing. // Read segment using shared file handle with ReadAt buffer := make([]byte, actualSegmentSize) n, err := sharedFile.ReadAt(buffer, segmentStart) - require.NoError(t, err, "Reader %d: ReadAt failed for segment %d-%d", readerID, segmentStart, segmentEnd-1) - require.Equal(t, int(actualSegmentSize), n, "Reader %d: expected to read %d bytes, got %d", readerID, actualSegmentSize, n) + require.NoError(s.T(), err, "Reader %d: ReadAt failed for segment %d-%d", readerID, segmentStart, segmentEnd-1) + require.Equal(s.T(), int(actualSegmentSize), n, "Reader %d: expected to read %d bytes, got %d", readerID, actualSegmentSize, n) // Store segment data for later validation segmentData[readerID] = buffer }(i) @@ -182,29 +183,29 @@ func (s *concurrentReadTest) Test_ConcurrentSegmentReadsSharedHandle(t *testing. select { case <-done: - t.Log("All concurrent segment read operations completed successfully") + s.T().Log("All concurrent segment read operations completed successfully") // Reconstruct the full file from segments and validate checksum var fullContent bytes.Buffer for i, segment := range segmentData { n, err := fullContent.Write(segment) - require.NoError(t, err, "Failed to write segment %d to buffer", i) - require.Equal(t, len(segment), n, "Segment %d: wrote different number of bytes than expected", i) + require.NoError(s.T(), err, "Failed to write segment %d to buffer", i) + require.Equal(s.T(), len(segment), n, "Segment %d: wrote different number of bytes than expected", i) } // Validate total size - require.Equal(t, fileSize, fullContent.Len(), "Reconstructed file size mismatch") + require.Equal(s.T(), fileSize, fullContent.Len(), "Reconstructed file size mismatch") // Validate checksum of reconstructed content reconstructedChecksum, err := operations.CalculateCRC32(bytes.NewReader(fullContent.Bytes())) - require.NoError(t, err, "Failed to calculate reconstructed checksum") + require.NoError(s.T(), err, "Failed to calculate reconstructed checksum") obj := storageClient.Bucket(setup.TestBucket()).Object(path.Join(path.Base(testDirPathForRead), "segment_test_file.bin")) attrs, err := obj.Attrs(ctx) - require.NoError(t, err, "obj.Attrs") - assert.Equal(t, attrs.CRC32C, reconstructedChecksum, "CRC32C mismatch. GCS: %d, Local: %d", attrs.CRC32C, reconstructedChecksum) + require.NoError(s.T(), err, "obj.Attrs") + assert.Equal(s.T(), attrs.CRC32C, reconstructedChecksum, "CRC32C mismatch. GCS: %d, Local: %d", attrs.CRC32C, reconstructedChecksum) case <-time.After(timeout): - assert.FailNow(t, "Concurrent segment read operations timed out - possible deadlock or performance issue") + assert.FailNow(s.T(), "Concurrent segment read operations timed out - possible deadlock or performance issue") } } -func (s *concurrentReadTest) Test_ConcurrentReadPlusWrite(t *testing.T) { +func (s *concurrentReadTest) Test_ConcurrentReadPlusWrite() { const ( fileSize = 32 * operations.OneMiB // 32 MiB file numGoRoutines = 10 // Number of concurrent readers @@ -220,24 +221,24 @@ func (s *concurrentReadTest) Test_ConcurrentReadPlusWrite(t *testing.T) { fileName := fmt.Sprintf("test_%d.bin", workerId) filePath := path.Join(testDirPathForRead, fileName) - f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC|syscall.O_DIRECT, setup.FilePermission_0600) - require.NoError(t, err) + f, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC|syscall.O_DIRECT, setup.FilePermission_0600) //nolint:staticcheck + require.NoError(s.T(), err) randomData, err := operations.GenerateRandomData(fileSize) - require.NoError(t, err) + require.NoError(s.T(), err) n, err := f.Write(randomData) - require.NoError(t, err) - require.Equal(t, int(fileSize), n) - operations.CloseFileShouldNotThrowError(t, f) + require.NoError(s.T(), err) + require.Equal(s.T(), fileSize, n) + operations.CloseFileShouldNotThrowError(s.T(), f) content, err := operations.ReadFileSequentially(filePath, chunkSize) - require.NoError(t, err, "Sequential reader %d: read failed.", workerId) - require.Equal(t, fileSize, len(content), "Sequential reader %d: expected to read entire file", workerId) + require.NoError(s.T(), err, "Sequential reader %d: read failed.", workerId) + require.Equal(s.T(), fileSize, len(content), "Sequential reader %d: expected to read entire file", workerId) obj := storageClient.Bucket(setup.TestBucket()).Object(path.Join(path.Base(testDirPathForRead), fileName)) attrs, err := obj.Attrs(ctx) - require.NoError(t, err, "obj.Attrs") + require.NoError(s.T(), err, "obj.Attrs") localCRC32C, err := operations.CalculateCRC32(bytes.NewReader(content)) - require.NoError(t, err, "Sequential reader %d: failed to calculate local CRC32C", workerId) - assert.Equal(t, attrs.CRC32C, localCRC32C, "Sequential reader %d: CRC32C mismatch. GCS: %d, Local: %d", workerId, attrs.CRC32C, localCRC32C) + require.NoError(s.T(), err, "Sequential reader %d: failed to calculate local CRC32C", workerId) + assert.Equal(s.T(), attrs.CRC32C, localCRC32C, "Sequential reader %d: CRC32C mismatch. GCS: %d, Local: %d", workerId, attrs.CRC32C, localCRC32C) }(i) } // Wait for all goroutines or timeout @@ -249,9 +250,9 @@ func (s *concurrentReadTest) Test_ConcurrentReadPlusWrite(t *testing.T) { select { case <-done: - t.Log("All concurrent goroutines completed successfully") + s.T().Log("All concurrent goroutines completed successfully") case <-time.After(timeout): - assert.FailNow(t, "Concurrent go routines timedout.") + assert.FailNow(s.T(), "Concurrent go routines timed out.") } } @@ -264,7 +265,7 @@ func TestConcurrentRead(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -277,6 +278,6 @@ func TestConcurrentRead(t *testing.T) { // Run tests with each flag set for _, flags := range flagsSet { ts.flags = flags - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/dentry_cache/delete_operation_test.go b/tools/integration_tests/dentry_cache/delete_operation_test.go index 3fed3d17e2..a59c6748fe 100644 --- a/tools/integration_tests/dentry_cache/delete_operation_test.go +++ b/tools/integration_tests/dentry_cache/delete_operation_test.go @@ -21,47 +21,48 @@ import ( "testing" "cloud.google.com/go/storage" + "github.com/stretchr/testify/suite" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type deleteOperationTest struct { flags []string + suite.Suite } -func (s *deleteOperationTest) Setup(t *testing.T) { +func (s *deleteOperationTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *deleteOperationTest) Teardown(t *testing.T) { +func (s *deleteOperationTest) TearDownTest() { if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } } -func (s *deleteOperationTest) TestDeleteFileWhenFileIsClobbered(t *testing.T) { +func (s *deleteOperationTest) TestDeleteFileWhenFileIsClobbered() { // Create a file with initial content directly in GCS. filePath := path.Join(setup.MntDir(), testDirName, testFileName) - client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, s.T()) // Stat file to cache the entry _, err := os.Stat(filePath) - require.Nil(t, err) + require.Nil(s.T(), err) // Modify the object on GCS. objectName := path.Join(testDirName, testFileName) smallContent, err := operations.GenerateRandomData(updatedContentSize) - require.Nil(t, err) - require.Nil(t, client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) + require.Nil(s.T(), err) + require.Nil(s.T(), client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) // Deleting the file should not give error err = os.Remove(filePath) - assert.Nil(t, err) + assert.Nil(s.T(), err) } func TestDeleteOperationTest(t *testing.T) { @@ -69,12 +70,12 @@ func TestDeleteOperationTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } // Setup flags and run tests. ts.flags = []string{"--implicit-dirs", "--experimental-enable-dentry-cache", "--metadata-cache-ttl-secs=1000"} log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/dentry_cache/notifier_test.go b/tools/integration_tests/dentry_cache/notifier_test.go index ce231f17f8..e72a96b5f9 100644 --- a/tools/integration_tests/dentry_cache/notifier_test.go +++ b/tools/integration_tests/dentry_cache/notifier_test.go @@ -21,99 +21,100 @@ import ( "testing" "cloud.google.com/go/storage" + "github.com/stretchr/testify/suite" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type notifierTest struct { flags []string + suite.Suite } -func (s *notifierTest) Setup(t *testing.T) { +func (s *notifierTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *notifierTest) Teardown(t *testing.T) { +func (s *notifierTest) TearDownTest() { if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } } -func (s *notifierTest) TestWriteFileWithDentryCacheEnabled(t *testing.T) { +func (s *notifierTest) TestWriteFileWithDentryCacheEnabled() { // Create a file with initial content directly in GCS. filePath := path.Join(setup.MntDir(), testDirName, testFileName) - client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, s.T()) // Stat file to cache the entry _, err := os.Stat(filePath) - require.Nil(t, err) + require.Nil(s.T(), err) // Modify the object on GCS. objectName := path.Join(testDirName, testFileName) smallContent, err := operations.GenerateRandomData(updatedContentSize) - require.Nil(t, err) - require.Nil(t, client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) + require.Nil(s.T(), err) + require.Nil(s.T(), client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) // First Write File attempt. err = operations.WriteFile(filePath, "ShouldNotWrite") // First Write File attempt should fail because file has been clobbered. - operations.ValidateESTALEError(t, err) + operations.ValidateESTALEError(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. // Therefore, the second write succeeds even before the metadata cache TTL expires. - assert.Nil(t, err) + assert.Nil(s.T(), err) } -func (s *notifierTest) TestReadFileWithDentryCacheEnabled(t *testing.T) { +func (s *notifierTest) TestReadFileWithDentryCacheEnabled() { // Create a file with initial content directly in GCS. filePath := path.Join(setup.MntDir(), testDirName, testFileName) - client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, s.T()) // Stat file to cache the entry _, err := os.Stat(filePath) - require.Nil(t, err) + require.Nil(s.T(), err) // Modify the object on GCS. objectName := path.Join(testDirName, testFileName) smallContent, err := operations.GenerateRandomData(updatedContentSize) - require.Nil(t, err) - require.Nil(t, client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) + require.Nil(s.T(), err) + require.Nil(s.T(), client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) // First Read File attempt. _, err = operations.ReadFile(filePath) // First Read File attempt should fail because file has been clobbered. - operations.ValidateESTALEError(t, err) + operations.ValidateESTALEError(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. // Therefore, the second read succeeds even before the metadata cache TTL expires. - assert.Nil(t, err) + assert.Nil(s.T(), err) } -func (s *notifierTest) TestDeleteFileWithDentryCacheEnabled(t *testing.T) { +func (s *notifierTest) TestDeleteFileWithDentryCacheEnabled() { // Create a file with initial content directly in GCS. filePath := path.Join(setup.MntDir(), testDirName, testFileName) - client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, s.T()) // Stat file to cache the entry _, err := os.Stat(filePath) - require.Nil(t, err) + require.Nil(s.T(), err) // Delete the object directly from GCS. objectName := path.Join(testDirName, testFileName) - require.Nil(t, client.DeleteObjectOnGCS(ctx, storageClient, objectName)) + require.Nil(s.T(), client.DeleteObjectOnGCS(ctx, storageClient, objectName)) // 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(t, err) + operations.ValidateESTALEError(s.T(), err) // Stat again, it should give error as entry does not exist. _, err = os.Stat(filePath) - assert.NotNil(t, err) + assert.NotNil(s.T(), err) } func TestNotifierTest(t *testing.T) { @@ -121,12 +122,12 @@ func TestNotifierTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } // Setup flags and run tests. ts.flags = []string{"--implicit-dirs", "--experimental-enable-dentry-cache", "--metadata-cache-ttl-secs=1000"} log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/dentry_cache/stat_test.go b/tools/integration_tests/dentry_cache/stat_test.go index 8a9043d6fb..4c9df294bb 100644 --- a/tools/integration_tests/dentry_cache/stat_test.go +++ b/tools/integration_tests/dentry_cache/stat_test.go @@ -22,77 +22,78 @@ import ( "time" "cloud.google.com/go/storage" + "github.com/stretchr/testify/suite" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) type statWithDentryCacheEnabledTest struct { flags []string + suite.Suite } -func (s *statWithDentryCacheEnabledTest) Setup(t *testing.T) { +func (s *statWithDentryCacheEnabledTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *statWithDentryCacheEnabledTest) Teardown(t *testing.T) { +func (s *statWithDentryCacheEnabledTest) TearDownTest() { if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } } -func (s *statWithDentryCacheEnabledTest) TestStatWithDentryCacheEnabled(t *testing.T) { +func (s *statWithDentryCacheEnabledTest) TestStatWithDentryCacheEnabled() { // Create a file with initial content directly in GCS. filePath := path.Join(setup.MntDir(), testDirName, testFileName) - client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, s.T()) // Stat file to cache the entry _, err := os.Stat(filePath) - require.Nil(t, err) + require.Nil(s.T(), err) // Modify the object on GCS. objectName := path.Join(testDirName, testFileName) smallContent, err := operations.GenerateRandomData(updatedContentSize) - require.Nil(t, err) - require.Nil(t, client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) + require.Nil(s.T(), err) + require.Nil(s.T(), client.WriteToObject(ctx, storageClient, objectName, string(smallContent), storage.Conditions{})) // Stat again, it should give old cached attributes. fileInfo, err := os.Stat(filePath) - assert.Nil(t, err) - assert.Equal(t, int64(initialContentSize), fileInfo.Size()) + assert.Nil(s.T(), err) + assert.Equal(s.T(), int64(initialContentSize), fileInfo.Size()) // Wait for a period more than the timeout (1 second), so that entry expires in cache. time.Sleep(1100 * time.Millisecond) // Stat again, it should give updated attributes. fileInfo, err = os.Stat(filePath) - assert.Nil(t, err) - assert.Equal(t, int64(updatedContentSize), fileInfo.Size()) + assert.Nil(s.T(), err) + assert.Equal(s.T(), int64(updatedContentSize), fileInfo.Size()) } -func (s *statWithDentryCacheEnabledTest) TestStatWhenFileIsDeletedDirectlyFromGCS(t *testing.T) { +func (s *statWithDentryCacheEnabledTest) TestStatWhenFileIsDeletedDirectlyFromGCS() { // Create a file with initial content directly in GCS. filePath := path.Join(setup.MntDir(), testDirName, testFileName) - client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, t) + client.SetupFileInTestDirectory(ctx, storageClient, testDirName, testFileName, initialContentSize, s.T()) // Stat file to cache the entry _, err := os.Stat(filePath) - require.Nil(t, err) + require.Nil(s.T(), err) // Delete the object directly from GCS. objectName := path.Join(testDirName, testFileName) - require.Nil(t, client.DeleteObjectOnGCS(ctx, storageClient, objectName)) + require.Nil(s.T(), client.DeleteObjectOnGCS(ctx, storageClient, objectName)) // Stat again, it should give old cached attributes rather than giving not found error. fileInfo, err := os.Stat(filePath) - assert.Nil(t, err) - assert.Equal(t, int64(initialContentSize), fileInfo.Size()) + assert.Nil(s.T(), err) + assert.Equal(s.T(), int64(initialContentSize), fileInfo.Size()) // Wait for a period more than the timeout (1 second), so that entry expires in cache. time.Sleep(1100 * time.Millisecond) // Stat again, it should give error as file does not exist. _, err = os.Stat(filePath) - assert.NotNil(t, err) + assert.NotNil(s.T(), err) } func TestStatWithDentryCacheEnabledTest(t *testing.T) { @@ -100,12 +101,12 @@ func TestStatWithDentryCacheEnabledTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } // Setup flags and run tests. ts.flags = []string{"--implicit-dirs", "--experimental-enable-dentry-cache", "--metadata-cache-ttl-secs=1"} log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go index a60484b974..5cb97dc121 100644 --- a/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go +++ b/tools/integration_tests/emulator_tests/write_stall/writes_stall_on_sync_test.go @@ -23,9 +23,9 @@ import ( emulator_tests "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/emulator_tests/util" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -42,23 +42,24 @@ type chunkTransferTimeoutInfinity struct { proxyProcessId int proxyServerLogFile string flags []string + suite.Suite } -func (s *chunkTransferTimeoutInfinity) Setup(t *testing.T) { +func (s *chunkTransferTimeoutInfinity) SetupTest() { configPath := "../configs/write_stall_40s.yaml" - s.proxyServerLogFile = setup.CreateProxyServerLogFile(t) + s.proxyServerLogFile = setup.CreateProxyServerLogFile(s.T()) var err error s.port, s.proxyProcessId, err = emulator_tests.StartProxyServer(configPath, s.proxyServerLogFile) - require.NoError(t, err) + require.NoError(s.T(), err) setup.AppendProxyEndpointToFlagSet(&s.flags, s.port) setup.MountGCSFuseWithGivenMountFunc(s.flags, mountFunc) } -func (s *chunkTransferTimeoutInfinity) Teardown(t *testing.T) { +func (s *chunkTransferTimeoutInfinity) TearDownTest() { setup.UnmountGCSFuse(rootDir) - assert.NoError(t, emulator_tests.KillProxyServerProcess(s.proxyProcessId)) - setup.SaveGCSFuseLogFileInCaseOfFailure(t) - setup.SaveProxyServerLogFileInCaseOfFailure(s.proxyServerLogFile, t) + assert.NoError(s.T(), emulator_tests.KillProxyServerProcess(s.proxyProcessId)) + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) + setup.SaveProxyServerLogFileInCaseOfFailure(s.proxyServerLogFile, s.T()) } //////////////////////////////////////////////////////////////////////// @@ -70,15 +71,15 @@ func (s *chunkTransferTimeoutInfinity) Teardown(t *testing.T) { // It creates a file, writes data to it, and then calls Sync() to ensure // the data is written to GCS. The test measures the time taken for the Sync() // operation and asserts that it is greater than or equal to the configured stall time. -func (s *chunkTransferTimeoutInfinity) TestWriteStallCausesDelay(t *testing.T) { +func (s *chunkTransferTimeoutInfinity) TestWriteStallCausesDelay() { testDir := "TestWriteStallCausesDelay" testDirPath = setup.SetupTestDirectory(testDir) filePath := path.Join(testDirPath, "file.txt") elapsedTime, err := emulator_tests.WriteFileAndSync(filePath, fileSize) - assert.NoError(t, err) - assert.GreaterOrEqual(t, elapsedTime, stallTime) + assert.NoError(s.T(), err) + assert.GreaterOrEqual(s.T(), elapsedTime, stallTime) } //////////////////////////////////////////////////////////////////////// @@ -96,7 +97,7 @@ func TestChunkTransferTimeoutInfinity(t *testing.T) { for _, flags := range flagsSet { ts.flags = flags log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/gzip/write_gzip_test.go b/tools/integration_tests/gzip/write_gzip_test.go index 94468dae33..14362d1fbc 100644 --- a/tools/integration_tests/gzip/write_gzip_test.go +++ b/tools/integration_tests/gzip/write_gzip_test.go @@ -45,7 +45,7 @@ func verifyFullFileOverwrite(t *testing.T, filename string) { t.Fatalf("Failed to get stat info of mounted file %s: %v\n", mountedFilePath, err) } - if (*fi).Size() != int64(gcsObjectSize) { + if (*fi).Size() != gcsObjectSize { t.Fatalf("Size of file mounted through gcsfuse (%s, %d) doesn't match the size of the file on GCS (%s, %d)", mountedFilePath, (*fi).Size(), gcsObjectPath, gcsObjectSize) } diff --git a/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go b/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go index 614d42756f..f0b41daa0b 100644 --- a/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go +++ b/tools/integration_tests/inactive_stream_timeout/with_timeout_test.go @@ -24,8 +24,8 @@ import ( "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) const DefaultSequentialReadSizeMb = 5 @@ -34,14 +34,15 @@ type timeoutEnabledSuite struct { flags []string storageClient *storage.Client ctx context.Context + suite.Suite } -func (s *timeoutEnabledSuite) Setup(t *testing.T) { +func (s *timeoutEnabledSuite) SetupTest() { mountGCSFuseAndSetupTestDir(s.ctx, s.flags, s.storageClient, kTestDirName) } -func (s *timeoutEnabledSuite) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *timeoutEnabledSuite) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory setup.UnmountGCSFuseAndDeleteLogFile(gRootDir) } @@ -51,20 +52,20 @@ func (s *timeoutEnabledSuite) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *timeoutEnabledSuite) TestReaderCloses(t *testing.T) { +func (s *timeoutEnabledSuite) TestReaderCloses() { timeoutDuration := kDefaultInactiveReadTimeoutInSeconds * time.Second gcsFileName := path.Join(kTestDirName, kTestFileName) - mountFilePath := setupFile(s.ctx, s.storageClient, kTestFileName, kFileSize, t) + mountFilePath := setupFile(s.ctx, s.storageClient, kTestFileName, kFileSize, s.T()) // 1. Open file. fileHandle, err := operations.OpenFileAsReadonly(mountFilePath) - require.NoError(t, err) + require.NoError(s.T(), err) defer fileHandle.Close() // 2. Read small chunk from 0 offset. buff := make([]byte, kChunkSizeToRead) _, err = fileHandle.ReadAt(buff, 0) - require.NoError(t, err) + require.NoError(s.T(), err) endTimeRead := time.Now() // 3. Wait for timeout @@ -72,26 +73,26 @@ func (s *timeoutEnabledSuite) TestReaderCloses(t *testing.T) { endTimeWait := time.Now() // 4. "Closing reader" log should be present. - validateInactiveReaderClosedLog(t, setup.LogFile(), gcsFileName, true, endTimeRead, endTimeWait) + validateInactiveReaderClosedLog(s.T(), setup.LogFile(), gcsFileName, true, endTimeRead, endTimeWait) // 5. Further reads should work as it is, yeah it will create a new reader. _, err = fileHandle.ReadAt(buff, 8) - require.NoError(t, err) + require.NoError(s.T(), err) } -func (s *timeoutEnabledSuite) TestReaderStaysOpenWithinTimeout(t *testing.T) { +func (s *timeoutEnabledSuite) TestReaderStaysOpenWithinTimeout() { timeoutDuration := kDefaultInactiveReadTimeoutInSeconds * time.Second gcsFileName := path.Join(kTestDirName, kTestFileName) - localFilePath := setupFile(s.ctx, s.storageClient, kTestFileName, kFileSize, t) + localFilePath := setupFile(s.ctx, s.storageClient, kTestFileName, kFileSize, s.T()) fileHandle, err := operations.OpenFileAsReadonly(localFilePath) - require.NoError(t, err) + require.NoError(s.T(), err) defer fileHandle.Close() // 1. First read. buff := make([]byte, kChunkSizeToRead) _, err = fileHandle.ReadAt(buff, 0) - require.NoError(t, err) + require.NoError(s.T(), err) endTimeRead1 := time.Now() // 2. Wait for a period SHORTER than the timeout. @@ -100,11 +101,11 @@ func (s *timeoutEnabledSuite) TestReaderStaysOpenWithinTimeout(t *testing.T) { // 3. Second read. _, err = fileHandle.ReadAt(buff, int64(kChunkSizeToRead)) // Read the next chunk - require.NoError(t, err, "Second read within timeout failed") + require.NoError(s.T(), err, "Second read within timeout failed") // 4. Check log: "Closing reader for object..." should NOT be present for this object // between the first read's end and the second read's start. - validateInactiveReaderClosedLog(t, setup.LogFile(), gcsFileName, false, endTimeRead1, startTimeRead2) + validateInactiveReaderClosedLog(s.T(), setup.LogFile(), gcsFileName, false, endTimeRead1, startTimeRead2) } //////////////////////////////////////////////////////////////////////// @@ -116,7 +117,7 @@ func TestTimeoutEnabledSuite(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -141,6 +142,6 @@ func TestTimeoutEnabledSuite(t *testing.T) { } log.Printf("Running inactive_read_timeout tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go b/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go index 72774fda60..0cc5ca695f 100644 --- a/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go +++ b/tools/integration_tests/inactive_stream_timeout/without_timeout_test.go @@ -24,22 +24,23 @@ import ( "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) type timeoutDisabledSuite struct { flags []string storageClient *storage.Client ctx context.Context + suite.Suite } -func (s *timeoutDisabledSuite) Setup(t *testing.T) { +func (s *timeoutDisabledSuite) SetupTest() { mountGCSFuseAndSetupTestDir(s.ctx, s.flags, s.storageClient, kTestDirName) } -func (s *timeoutDisabledSuite) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *timeoutDisabledSuite) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory setup.UnmountGCSFuseAndDeleteLogFile(gRootDir) } @@ -49,20 +50,20 @@ func (s *timeoutDisabledSuite) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *timeoutDisabledSuite) TestNoReaderCloser(t *testing.T) { +func (s *timeoutDisabledSuite) TestNoReaderCloser() { timeoutDuration := kDefaultInactiveReadTimeoutInSeconds * time.Second gcsFileName := path.Join(kTestDirName, kTestFileName) - mountFilePath := setupFile(s.ctx, s.storageClient, kTestFileName, kFileSize, t) + mountFilePath := setupFile(s.ctx, s.storageClient, kTestFileName, kFileSize, s.T()) // 1. Open file. fileHandle, err := operations.OpenFileAsReadonly(mountFilePath) - require.NoError(t, err) + require.NoError(s.T(), err) defer fileHandle.Close() // 2. Read small chunk from 0 offset. buff := make([]byte, kChunkSizeToRead) _, err = fileHandle.ReadAt(buff, 0) - require.NoError(t, err) + require.NoError(s.T(), err) endTimeRead := time.Now() // 3. Wait for timeout @@ -70,7 +71,7 @@ func (s *timeoutDisabledSuite) TestNoReaderCloser(t *testing.T) { endTimeWait := time.Now() // 4. Shouldn't be any `Close reader logs...`. - validateInactiveReaderClosedLog(t, setup.LogFile(), gcsFileName, false, endTimeRead, endTimeWait) + validateInactiveReaderClosedLog(s.T(), setup.LogFile(), gcsFileName, false, endTimeRead, endTimeWait) } //////////////////////////////////////////////////////////////////////// @@ -82,7 +83,7 @@ func TestTimeoutDisabledSuite(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -102,6 +103,6 @@ func TestTimeoutDisabledSuite(t *testing.T) { } log.Printf("Running inactive_read_timeout tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/interrupt/git_clone_test.go b/tools/integration_tests/interrupt/git_clone_test.go index 5cf30ee21c..7ed85250ba 100644 --- a/tools/integration_tests/interrupt/git_clone_test.go +++ b/tools/integration_tests/interrupt/git_clone_test.go @@ -25,7 +25,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/internal/cache/util" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/suite" ) const ( @@ -44,11 +44,13 @@ var ( // Boilerplate //////////////////////////////////////////////////////////////////////// -type ignoreInterruptsTest struct{} +type ignoreInterruptsTest struct { + suite.Suite +} -func (s *ignoreInterruptsTest) Teardown(t *testing.T) {} +func (s *ignoreInterruptsTest) TearDownTest() {} -func (s *ignoreInterruptsTest) Setup(t *testing.T) { +func (s *ignoreInterruptsTest) SetupTest() { testDirPath = setup.SetupTestDirectory(testDirName) } @@ -96,58 +98,58 @@ func setGithubUserConfig() { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *ignoreInterruptsTest) TestGitClone(t *testing.T) { +func (s *ignoreInterruptsTest) TestGitClone() { output, err := cloneRepository() if err != nil { - t.Errorf("Git clone failed: %s: %v", string(output), err) + s.T().Errorf("Git clone failed: %s: %v", string(output), err) } } -func (s *ignoreInterruptsTest) TestGitCheckout(t *testing.T) { +func (s *ignoreInterruptsTest) TestGitCheckout() { _, err := cloneRepository() if err != nil { - t.Errorf("cloneRepository() failed: %v", err) + s.T().Errorf("cloneRepository() failed: %v", err) } output, err := checkoutBranch(branchName) if err != nil { - t.Errorf("Git checkout failed: %s: %v", string(output), err) + s.T().Errorf("Git checkout failed: %s: %v", string(output), err) } } -func (s *ignoreInterruptsTest) TestGitEmptyCommit(t *testing.T) { +func (s *ignoreInterruptsTest) TestGitEmptyCommit() { _, err := cloneRepository() if err != nil { - t.Errorf("cloneRepository() failed: %v", err) + s.T().Errorf("cloneRepository() failed: %v", err) } setGithubUserConfig() output, err := emptyCommit() if err != nil { - t.Errorf("Git empty commit failed: %s: %v", string(output), err) + s.T().Errorf("Git empty commit failed: %s: %v", string(output), err) } } -func (s *ignoreInterruptsTest) TestGitCommitWithChanges(t *testing.T) { +func (s *ignoreInterruptsTest) TestGitCommitWithChanges() { _, err := cloneRepository() if err != nil { - t.Errorf("cloneRepository() failed: %v", err) + s.T().Errorf("cloneRepository() failed: %v", err) } setGithubUserConfig() filePath := path.Join(testDirPath, repoName, testFileName) - operations.CreateFileOfSize(util.MiB, filePath, t) + operations.CreateFileOfSize(util.MiB, filePath, s.T()) output, err := gitAdd(filePath) if err != nil { - t.Errorf("Git add failed: %s: %v", string(output), err) + s.T().Errorf("Git add failed: %s: %v", string(output), err) } output, err = nonEmptyCommit() if err != nil { - t.Errorf("Git commit failed: %s: %v", string(output), err) + s.T().Errorf("Git commit failed: %s: %v", string(output), err) } } @@ -157,5 +159,5 @@ func (s *ignoreInterruptsTest) TestGitCommitWithChanges(t *testing.T) { func TestIgnoreInterrupts(t *testing.T) { ts := &ignoreInterruptsTest{} - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go index 0daff66e4a..728c3e9b24 100644 --- a/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/disabled_kernel_list_cache_test.go @@ -23,9 +23,9 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -34,13 +34,14 @@ import ( type disabledKernelListCacheTest struct { flags []string + suite.Suite } -func (s *disabledKernelListCacheTest) Setup(t *testing.T) { +func (s *disabledKernelListCacheTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *disabledKernelListCacheTest) Teardown(t *testing.T) { +func (s *disabledKernelListCacheTest) TearDownTest() { setup.UnmountGCSFuse(rootDir) } @@ -48,41 +49,41 @@ func (s *disabledKernelListCacheTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *disabledKernelListCacheTest) TestKernelListCache_AlwaysCacheMiss(t *testing.T) { +func (s *disabledKernelListCacheTest) TestKernelListCache_AlwaysCacheMiss() { targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) defer func() { - assert.Nil(t, f.Close()) + assert.Nil(s.T(), f.Close()) }() names1, err := f.Readdirnames(-1) - assert.Nil(t, err) - require.Equal(t, 2, len(names1)) - require.Equal(t, "file1.txt", names1[0]) - require.Equal(t, "file2.txt", names1[1]) + assert.Nil(s.T(), err) + require.Equal(s.T(), 2, len(names1)) + require.Equal(s.T(), "file1.txt", names1[0]) + require.Equal(s.T(), "file2.txt", names1[1]) err = f.Close() - require.NoError(t, err) + require.NoError(s.T(), err) // Adding one object to make sure to change the ReadDir() response. - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", s.T()) // Zero ttl, means readdir will always be served from gcsfuse. f, err = os.Open(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) names2, err := f.Readdirnames(-1) - assert.NoError(t, err) + assert.NoError(s.T(), err) - require.Equal(t, 3, len(names2)) - assert.Equal(t, "file1.txt", names2[0]) - assert.Equal(t, "file2.txt", names2[1]) - assert.Equal(t, "file3.txt", names2[2]) + require.Equal(s.T(), 3, len(names2)) + assert.Equal(s.T(), "file1.txt", names2[0]) + assert.Equal(s.T(), "file2.txt", names2[1]) + assert.Equal(s.T(), "file3.txt", names2[2]) } //////////////////////////////////////////////////////////////////////// @@ -94,7 +95,7 @@ func TestDisabledKernelListCacheTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -107,6 +108,6 @@ func TestDisabledKernelListCacheTest(t *testing.T) { for _, flags := range flagsSet { ts.flags = flags log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go index ab17247c4a..df3b697105 100644 --- a/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/finite_kernel_list_cache_test.go @@ -24,9 +24,9 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -35,13 +35,14 @@ import ( type finiteKernelListCacheTest struct { flags []string + suite.Suite } -func (s *finiteKernelListCacheTest) Setup(t *testing.T) { +func (s *finiteKernelListCacheTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *finiteKernelListCacheTest) Teardown(t *testing.T) { +func (s *finiteKernelListCacheTest) TearDownTest() { setup.UnmountGCSFuse(rootDir) } @@ -49,56 +50,56 @@ func (s *finiteKernelListCacheTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *finiteKernelListCacheTest) TestKernelListCache_CacheHitWithinLimit_CacheMissAfterLimit(t *testing.T) { - operations.SkipKLCTestForUnsupportedKernelVersion(t) +func (s *finiteKernelListCacheTest) TestKernelListCache_CacheHitWithinLimit_CacheMissAfterLimit() { + operations.SkipKLCTestForUnsupportedKernelVersion(s.T()) targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) defer func() { - assert.Nil(t, f.Close()) + assert.Nil(s.T(), f.Close()) }() names1, err := f.Readdirnames(-1) - require.NoError(t, err) - require.Equal(t, 2, len(names1)) - require.Equal(t, "file1.txt", names1[0]) - require.Equal(t, "file2.txt", names1[1]) + require.NoError(s.T(), err) + require.Equal(s.T(), 2, len(names1)) + require.Equal(s.T(), "file1.txt", names1[0]) + require.Equal(s.T(), "file2.txt", names1[1]) err = f.Close() - require.NoError(t, err) + require.NoError(s.T(), err) // Adding one object to make sure to change the ReadDir() response. - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", s.T()) time.Sleep(2 * time.Second) // Kernel cache will not invalidate within ttl. f, err = os.Open(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) names2, err := f.Readdirnames(-1) - assert.NoError(t, err) - require.Equal(t, 2, len(names2)) - assert.Equal(t, "file1.txt", names2[0]) - assert.Equal(t, "file2.txt", names2[1]) + assert.NoError(s.T(), err) + require.Equal(s.T(), 2, len(names2)) + assert.Equal(s.T(), "file1.txt", names2[0]) + assert.Equal(s.T(), "file2.txt", names2[1]) // Waiting 3 more seconds to exceed the 5-second TTL for invalidating the kernel cache. time.Sleep(3 * time.Second) // The response will be served from GCSFuse after the TTL expires. f, err = os.Open(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) names3, err := f.Readdirnames(-1) - assert.NoError(t, err) + assert.NoError(s.T(), err) - require.Equal(t, 3, len(names3)) - assert.Equal(t, "file1.txt", names3[0]) - assert.Equal(t, "file2.txt", names3[1]) - assert.Equal(t, "file3.txt", names3[2]) + require.Equal(s.T(), 3, len(names3)) + assert.Equal(s.T(), "file1.txt", names3[0]) + assert.Equal(s.T(), "file2.txt", names3[1]) + assert.Equal(s.T(), "file3.txt", names3[2]) } //////////////////////////////////////////////////////////////////////// @@ -110,7 +111,7 @@ func TestFiniteKernelListCacheTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -123,6 +124,6 @@ func TestFiniteKernelListCacheTest(t *testing.T) { for _, flags := range flagsSet { ts.flags = flags log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go index 679b283488..9564ebcbda 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_delete_dir_test.go @@ -23,9 +23,9 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -34,73 +34,74 @@ import ( type infiniteKernelListCacheDeleteDirTest struct { flags []string + suite.Suite } -func (s *infiniteKernelListCacheDeleteDirTest) Setup(t *testing.T) { +func (s *infiniteKernelListCacheDeleteDirTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *infiniteKernelListCacheDeleteDirTest) Teardown(t *testing.T) { +func (s *infiniteKernelListCacheDeleteDirTest) TearDownTest() { setup.UnmountGCSFuse(rootDir) } -func (s *infiniteKernelListCacheDeleteDirTest) TestKernelListCache_ListAndDeleteDirectory(t *testing.T) { +func (s *infiniteKernelListCacheDeleteDirTest) TestKernelListCache_ListAndDeleteDirectory() { targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) // (a) First read served from GCS, kernel will cache the dir response. f, err := os.Open(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) names1, err := f.Readdirnames(-1) - assert.NoError(t, err) - require.Equal(t, 2, len(names1)) - assert.Equal(t, "file1.txt", names1[0]) - assert.Equal(t, "file2.txt", names1[1]) + assert.NoError(s.T(), err) + require.Equal(s.T(), 2, len(names1)) + assert.Equal(s.T(), "file1.txt", names1[0]) + assert.Equal(s.T(), "file2.txt", names1[1]) err = f.Close() - assert.NoError(t, err) + assert.NoError(s.T(), err) // Adding one object to make sure to change the ReadDir() response. // All files including file3.txt will be deleted by os.RemoveAll - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", s.T()) err = os.RemoveAll(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) } -func (s *infiniteKernelListCacheDeleteDirTest) TestKernelListCache_DeleteAndListDirectory(t *testing.T) { +func (s *infiniteKernelListCacheDeleteDirTest) TestKernelListCache_DeleteAndListDirectory() { targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) err := os.RemoveAll(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) // Adding object to GCS to make sure to change the ReadDir() response. err = client.CreateObjectOnGCS(ctx, storageClient, path.Join(testDirName, "explicit_dir")+"/", "") - require.NoError(t, err) - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + require.NoError(s.T(), err) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", s.T()) // Read will be served from GCS as removing the directory also deletes the cache. f, err := os.Open(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) names1, err := f.Readdirnames(-1) - assert.NoError(t, err) - require.Equal(t, 1, len(names1)) - assert.Equal(t, "file3.txt", names1[0]) + assert.NoError(s.T(), err) + require.Equal(s.T(), 1, len(names1)) + assert.Equal(s.T(), "file3.txt", names1[0]) err = f.Close() - assert.NoError(t, err) + assert.NoError(s.T(), err) // 2nd RemoveAll call will also succeed. err = os.RemoveAll(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) } //////////////////////////////////////////////////////////////////////// @@ -114,7 +115,7 @@ func TestInfiniteKernelListCacheDeleteDirTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -130,6 +131,6 @@ func TestInfiniteKernelListCacheDeleteDirTest(t *testing.T) { for _, flags := range flagsSet { ts.flags = flags log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go index d359ea892f..2e4d87c920 100644 --- a/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go +++ b/tools/integration_tests/kernel_list_cache/infinite_kernel_list_cache_test.go @@ -24,9 +24,9 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -35,13 +35,14 @@ import ( type infiniteKernelListCacheTest struct { flags []string + suite.Suite } -func (s *infiniteKernelListCacheTest) Setup(t *testing.T) { +func (s *infiniteKernelListCacheTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *infiniteKernelListCacheTest) Teardown(t *testing.T) { +func (s *infiniteKernelListCacheTest) TearDownTest() { setup.UnmountGCSFuse(rootDir) } @@ -49,183 +50,183 @@ func (s *infiniteKernelListCacheTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *infiniteKernelListCacheTest) TestKernelListCache_AlwaysCacheHit(t *testing.T) { +func (s *infiniteKernelListCacheTest) TestKernelListCache_AlwaysCacheHit() { targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) defer func() { - assert.Nil(t, f.Close()) + assert.Nil(s.T(), f.Close()) }() names1, err := f.Readdirnames(-1) - require.NoError(t, err) - require.Equal(t, 2, len(names1)) - require.Equal(t, "file1.txt", names1[0]) - require.Equal(t, "file2.txt", names1[1]) + require.NoError(s.T(), err) + require.Equal(s.T(), 2, len(names1)) + require.Equal(s.T(), "file1.txt", names1[0]) + require.Equal(s.T(), "file2.txt", names1[1]) err = f.Close() - require.NoError(t, err) + require.NoError(s.T(), err) // Adding one object to make sure to change the ReadDir() response. - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", s.T()) // Waiting for 5 seconds to see if the kernel cache expires. time.Sleep(5 * time.Second) // Kernel cache will not invalidate since infinite ttl. f, err = os.Open(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) names2, err := f.Readdirnames(-1) - assert.NoError(t, err) + assert.NoError(s.T(), err) - require.Equal(t, 2, len(names2)) - assert.Equal(t, "file1.txt", names2[0]) - assert.Equal(t, "file2.txt", names2[1]) + require.Equal(s.T(), 2, len(names2)) + assert.Equal(s.T(), "file1.txt", names2[0]) + assert.Equal(s.T(), "file2.txt", names2[1]) } // (a) First ReadDir() will be served from GCSFuse filesystem. // (b) Second ReadDir() will also be served from GCSFuse filesystem, because of // addition of new file. -func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnAdditionOfFile(t *testing.T) { +func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnAdditionOfFile() { targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) defer func() { - assert.NoError(t, f.Close()) + assert.NoError(s.T(), f.Close()) }() names1, err := f.Readdirnames(-1) - require.NoError(t, err) - require.Equal(t, 2, len(names1)) - require.Equal(t, "file1.txt", names1[0]) - require.Equal(t, "file2.txt", names1[1]) + require.NoError(s.T(), err) + require.Equal(s.T(), 2, len(names1)) + require.Equal(s.T(), "file1.txt", names1[0]) + require.Equal(s.T(), "file2.txt", names1[1]) err = f.Close() - require.NoError(t, err) + require.NoError(s.T(), err) // Adding one object to make sure to change the ReadDir() response. - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", s.T()) // Ideally no invalidation since infinite ttl, but creation of a new file inside // directory evicts the list cache for that directory. fNew, err := os.Create(path.Join(targetDir, "file4.txt")) - require.NoError(t, err) - assert.NotNil(t, fNew) + require.NoError(s.T(), err) + assert.NotNil(s.T(), fNew) defer func() { - assert.NoError(t, fNew.Close()) - assert.NoError(t, os.Remove(path.Join(targetDir, "file4.txt"))) + assert.NoError(s.T(), fNew.Close()) + assert.NoError(s.T(), os.Remove(path.Join(targetDir, "file4.txt"))) }() f, err = os.Open(path.Join(testDirPath, "explicit_dir")) - assert.NoError(t, err) + assert.NoError(s.T(), err) names2, err := f.Readdirnames(-1) - assert.NoError(t, err) - require.Equal(t, 4, len(names2)) - assert.Equal(t, "file1.txt", names2[0]) - assert.Equal(t, "file2.txt", names2[1]) - assert.Equal(t, "file3.txt", names2[2]) - assert.Equal(t, "file4.txt", names2[3]) + assert.NoError(s.T(), err) + require.Equal(s.T(), 4, len(names2)) + assert.Equal(s.T(), "file1.txt", names2[0]) + assert.Equal(s.T(), "file2.txt", names2[1]) + assert.Equal(s.T(), "file3.txt", names2[2]) + assert.Equal(s.T(), "file4.txt", names2[3]) } // (a) First ReadDir() will be served from GCSFuse filesystem. // (b) Second ReadDir() will also be served from GCSFuse filesystem, because of // deletion of new file. -func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnDeletionOfFile(t *testing.T) { +func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnDeletionOfFile() { targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) defer func() { - assert.NoError(t, f.Close()) + assert.NoError(s.T(), f.Close()) }() names1, err := f.Readdirnames(-1) - assert.NoError(t, err) - require.Equal(t, 2, len(names1)) - require.Equal(t, "file1.txt", names1[0]) - require.Equal(t, "file2.txt", names1[1]) + assert.NoError(s.T(), err) + require.Equal(s.T(), 2, len(names1)) + require.Equal(s.T(), "file1.txt", names1[0]) + require.Equal(s.T(), "file2.txt", names1[1]) err = f.Close() - assert.NoError(t, err) + assert.NoError(s.T(), err) // Adding one object to make sure to change the ReadDir() response. - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", s.T()) // Ideally no invalidation since infinite ttl, but deletion of file inside // directory evicts the list cache for that directory. err = os.Remove(path.Join(targetDir, "file2.txt")) - require.NoError(t, err) + require.NoError(s.T(), err) f, err = os.Open(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) names2, err := f.Readdirnames(-1) - assert.NoError(t, err) - require.Equal(t, 2, len(names2)) - assert.Equal(t, "file1.txt", names2[0]) - assert.Equal(t, "file3.txt", names2[1]) + assert.NoError(s.T(), err) + require.Equal(s.T(), 2, len(names2)) + assert.Equal(s.T(), "file1.txt", names2[0]) + assert.Equal(s.T(), "file3.txt", names2[1]) } // (a) First ReadDir() will be served from GCSFuse filesystem. // (b) Second ReadDir() will also be served from GCSFuse filesystem, because of // file rename. -func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnFileRename(t *testing.T) { +func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnFileRename() { targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) - assert.NoError(t, err) + assert.NoError(s.T(), err) defer func() { - assert.NoError(t, f.Close()) + assert.NoError(s.T(), f.Close()) }() names1, err := f.Readdirnames(-1) - assert.NoError(t, err) - require.Equal(t, 2, len(names1)) - require.Equal(t, "file1.txt", names1[0]) - require.Equal(t, "file2.txt", names1[1]) + assert.NoError(s.T(), err) + require.Equal(s.T(), 2, len(names1)) + require.Equal(s.T(), "file1.txt", names1[0]) + require.Equal(s.T(), "file2.txt", names1[1]) err = f.Close() - assert.NoError(t, err) + assert.NoError(s.T(), err) // Adding one object to make sure to change the ReadDir() response. - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", s.T()) // Ideally no invalidation since infinite ttl, but rename of a file inside // directory evicts the list cache for that directory. err = os.Rename(path.Join(targetDir, "file2.txt"), path.Join(targetDir, "renamed_file2.txt")) - require.NoError(t, err) + require.NoError(s.T(), err) f, err = os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) names2, err := f.Readdirnames(-1) - assert.NoError(t, err) - require.Equal(t, 3, len(names2)) - assert.Equal(t, "file1.txt", names2[0]) - assert.Equal(t, "file3.txt", names2[1]) - assert.Equal(t, "renamed_file2.txt", names2[2]) + assert.NoError(s.T(), err) + require.Equal(s.T(), 3, len(names2)) + assert.Equal(s.T(), "file1.txt", names2[0]) + assert.Equal(s.T(), "file3.txt", names2[1]) + assert.Equal(s.T(), "renamed_file2.txt", names2[2]) } // explicit_dir/file1.txt @@ -250,207 +251,207 @@ func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnFileRename( // // ls explicit_dir/sub_dir // file2.txt, file3.txt, file4.txt, file5.txt -func (s *infiniteKernelListCacheTest) TestKernelListCache_EvictCacheEntryOfOnlyDirectParent(t *testing.T) { +func (s *infiniteKernelListCacheTest) TestKernelListCache_EvictCacheEntryOfOnlyDirectParent() { targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) subDir := path.Join(targetDir, "sub_dir") - operations.CreateDirectory(subDir, t) + operations.CreateDirectory(subDir, s.T()) // Create test files - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(subDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) - f3 := operations.CreateFile(path.Join(subDir, "file3.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f3) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(subDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) + f3 := operations.CreateFile(path.Join(subDir, "file3.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f3) // Initial read of parent directory (caches results) f, err := os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) names1, err := f.Readdirnames(-1) // Read all filenames - require.NoError(t, err) - require.NoError(t, f.Close()) - require.Equal(t, 2, len(names1)) - require.Equal(t, "file1.txt", names1[0]) - require.Equal(t, "sub_dir", names1[1]) - // Initial read of sub-directory (caches results) + require.NoError(s.T(), err) + require.NoError(s.T(), f.Close()) + require.Equal(s.T(), 2, len(names1)) + require.Equal(s.T(), "file1.txt", names1[0]) + require.Equal(s.T(), "sub_dir", names1[1]) + // Initial read of subdirectory (caches results) f, err = os.Open(subDir) - require.NoError(t, err) + require.NoError(s.T(), err) names2, err := f.Readdirnames(-1) - require.NoError(t, err) - require.NoError(t, f.Close()) - require.Equal(t, 2, len(names2)) - assert.Equal(t, "file2.txt", names2[0]) - assert.Equal(t, "file3.txt", names2[1]) - // Add a new file to the sub-directory to trigger a cache invalidation scenario + require.NoError(s.T(), err) + require.NoError(s.T(), f.Close()) + require.Equal(s.T(), 2, len(names2)) + assert.Equal(s.T(), "file2.txt", names2[0]) + assert.Equal(s.T(), "file3.txt", names2[1]) + // Add a new file to the subdirectory to trigger a cache invalidation scenario fNew, err := os.Create(path.Join(subDir, "file4.txt")) - require.NoError(t, err) - require.NoError(t, fNew.Close()) + require.NoError(s.T(), err) + require.NoError(s.T(), fNew.Close()) client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, - path.Join("explicit_dir", "sub_dir", "file5.txt"), "", t) + path.Join("explicit_dir", "sub_dir", "file5.txt"), "", s.T()) // Add a new file to the parent directory through the client to verify that the // cache is not invalidated in the case of the parent. client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, - path.Join("explicit_dir", "file6.txt"), "", t) + path.Join("explicit_dir", "file6.txt"), "", s.T()) // Re-read parent directory (should still use the cache and NOT show the change in the sub-dir) f1, err = os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) names1, err = f1.Readdirnames(-1) - require.NoError(t, err) - require.NoError(t, f1.Close()) - // Re-read sub-directory (cache should be invalidated and show the new file) + require.NoError(s.T(), err) + require.NoError(s.T(), f1.Close()) + // Re-read subdirectory (cache should be invalidated and show the new file) f2, err = os.Open(subDir) - require.NoError(t, err) + require.NoError(s.T(), err) names2, err = f2.Readdirnames(-1) - require.NoError(t, f2.Close()) - require.NoError(t, err) + require.NoError(s.T(), f2.Close()) + require.NoError(s.T(), err) // This is expected to be 2 as it is reading from cache for parent directory - require.Equal(t, 2, len(names1)) - assert.Equal(t, "file1.txt", names1[0]) - assert.Equal(t, "sub_dir", names1[1]) + require.Equal(s.T(), 2, len(names1)) + assert.Equal(s.T(), "file1.txt", names1[0]) + assert.Equal(s.T(), "sub_dir", names1[1]) // Cache invalidated, expect 4 items now as call went to GCS - require.Equal(t, 4, len(names2)) - assert.Equal(t, "file2.txt", names2[0]) - assert.Equal(t, "file3.txt", names2[1]) - assert.Equal(t, "file4.txt", names2[2]) - assert.Equal(t, "file5.txt", names2[3]) + require.Equal(s.T(), 4, len(names2)) + assert.Equal(s.T(), "file2.txt", names2[0]) + assert.Equal(s.T(), "file3.txt", names2[1]) + assert.Equal(s.T(), "file4.txt", names2[2]) + assert.Equal(s.T(), "file5.txt", names2[3]) } // (a) First ReadDir() will be served from GCSFuse filesystem. // (b) Second ReadDir() will also be served from GCSFuse filesystem, because of // addition of new directory. -func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnAdditionOfDirectory(t *testing.T) { +func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnAdditionOfDirectory() { targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) defer func() { - assert.NoError(t, f.Close()) + assert.NoError(s.T(), f.Close()) }() names1, err := f.Readdirnames(-1) - require.NoError(t, err) - require.Equal(t, 2, len(names1)) - require.Equal(t, "file1.txt", names1[0]) - require.Equal(t, "file2.txt", names1[1]) + require.NoError(s.T(), err) + require.Equal(s.T(), 2, len(names1)) + require.Equal(s.T(), "file1.txt", names1[0]) + require.Equal(s.T(), "file2.txt", names1[1]) err = f.Close() - require.NoError(t, err) + require.NoError(s.T(), err) // Adding one object to make sure to change the ReadDir() response. - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", s.T()) // Ideally no invalidation since infinite ttl, but creation of a new directory inside // directory evicts the list cache for that directory. err = os.Mkdir(path.Join(targetDir, "sub_dir"), setup.DirPermission_0755) - require.NoError(t, err) + require.NoError(s.T(), err) f, err = os.Open(targetDir) - assert.Nil(t, err) + assert.Nil(s.T(), err) names2, err := f.Readdirnames(-1) - assert.Nil(t, err) - require.Equal(t, 4, len(names2)) - assert.Equal(t, "file1.txt", names2[0]) - assert.Equal(t, "file2.txt", names2[1]) - assert.Equal(t, "file3.txt", names2[2]) - assert.Equal(t, "sub_dir", names2[3]) + assert.Nil(s.T(), err) + require.Equal(s.T(), 4, len(names2)) + assert.Equal(s.T(), "file1.txt", names2[0]) + assert.Equal(s.T(), "file2.txt", names2[1]) + assert.Equal(s.T(), "file3.txt", names2[2]) + assert.Equal(s.T(), "sub_dir", names2[3]) } // (a) First ReadDir() will be served from GCSFuse filesystem. // (b) Second ReadDir() will also be served from GCSFuse filesystem, because of // deletion of directory. -func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnDeletionOfDirectory(t *testing.T) { +func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnDeletionOfDirectory() { targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) err := os.Mkdir(path.Join(targetDir, "sub_dir"), setup.DirPermission_0755) - require.NoError(t, err) + require.NoError(s.T(), err) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) defer func() { - assert.NoError(t, f.Close()) + assert.NoError(s.T(), f.Close()) }() names1, err := f.Readdirnames(-1) - require.NoError(t, err) - require.Equal(t, 3, len(names1)) - require.Equal(t, "file1.txt", names1[0]) - require.Equal(t, "file2.txt", names1[1]) - require.Equal(t, "sub_dir", names1[2]) + require.NoError(s.T(), err) + require.Equal(s.T(), 3, len(names1)) + require.Equal(s.T(), "file1.txt", names1[0]) + require.Equal(s.T(), "file2.txt", names1[1]) + require.Equal(s.T(), "sub_dir", names1[2]) err = f.Close() - require.NoError(t, err) + require.NoError(s.T(), err) // Adding one object to make sure to change the ReadDir() response. - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", s.T()) // Ideally no invalidation since infinite ttl, but creation of a new file inside // directory evicts the list cache for that directory. err = os.Remove(path.Join(targetDir, "sub_dir")) - require.Nil(t, err) + require.Nil(s.T(), err) f, err = os.Open(targetDir) - assert.Nil(t, err) + assert.Nil(s.T(), err) names2, err := f.Readdirnames(-1) - assert.Nil(t, err) - require.Equal(t, 3, len(names2)) - assert.Equal(t, "file1.txt", names2[0]) - assert.Equal(t, "file2.txt", names2[1]) - assert.Equal(t, "file3.txt", names2[2]) + assert.Nil(s.T(), err) + require.Equal(s.T(), 3, len(names2)) + assert.Equal(s.T(), "file1.txt", names2[0]) + assert.Equal(s.T(), "file2.txt", names2[1]) + assert.Equal(s.T(), "file3.txt", names2[2]) } // (a) First ReadDir() will be served from GCSFuse filesystem. // (b) Second ReadDir() will also be served from GCSFuse filesystem, because of // directory rename. -func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnDirectoryRename(t *testing.T) { +func (s *infiniteKernelListCacheTest) TestKernelListCache_CacheMissOnDirectoryRename() { targetDir := path.Join(testDirPath, "explicit_dir") - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create test data - f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) - f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f2) + f1 := operations.CreateFile(path.Join(targetDir, "file1.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) + f2 := operations.CreateFile(path.Join(targetDir, "file2.txt"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f2) err := os.Mkdir(path.Join(targetDir, "sub_dir"), setup.DirPermission_0755) - require.NoError(t, err) + require.NoError(s.T(), err) // First read, kernel will cache the dir response. f, err := os.Open(targetDir) - require.NoError(t, err) + require.NoError(s.T(), err) defer func() { - assert.NoError(t, f.Close()) + assert.NoError(s.T(), f.Close()) }() names1, err := f.Readdirnames(-1) - require.NoError(t, err) - require.Equal(t, 3, len(names1)) - require.Equal(t, "file1.txt", names1[0]) - require.Equal(t, "file2.txt", names1[1]) - require.Equal(t, "sub_dir", names1[2]) + require.NoError(s.T(), err) + require.Equal(s.T(), 3, len(names1)) + require.Equal(s.T(), "file1.txt", names1[0]) + require.Equal(s.T(), "file2.txt", names1[1]) + require.Equal(s.T(), "sub_dir", names1[2]) err = f.Close() - require.NoError(t, err) + require.NoError(s.T(), err) // Adding one object to make sure to change the ReadDir() response. - client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", t) + client.CreateObjectInGCSTestDir(ctx, storageClient, testDirName, path.Join("explicit_dir", "file3.txt"), "", s.T()) // Ideally no invalidation since infinite ttl, but creation of a new file inside // directory evicts the list cache for that directory. err = os.Rename(path.Join(targetDir, "sub_dir"), path.Join(targetDir, "renamed_sub_dir")) - require.Nil(t, err) + require.Nil(s.T(), err) defer func() { - assert.Nil(t, os.Remove(path.Join(targetDir, "renamed_sub_dir"))) + assert.Nil(s.T(), os.Remove(path.Join(targetDir, "renamed_sub_dir"))) }() f, err = os.Open(targetDir) - assert.Nil(t, err) + assert.Nil(s.T(), err) names2, err := f.Readdirnames(-1) - assert.Nil(t, err) - require.Equal(t, 4, len(names2)) - assert.Equal(t, "file1.txt", names2[0]) - assert.Equal(t, "file2.txt", names2[1]) - assert.Equal(t, "file3.txt", names2[2]) - assert.Equal(t, "renamed_sub_dir", names2[3]) + assert.Nil(s.T(), err) + require.Equal(s.T(), 4, len(names2)) + assert.Equal(s.T(), "file1.txt", names2[0]) + assert.Equal(s.T(), "file2.txt", names2[1]) + assert.Equal(s.T(), "file3.txt", names2[2]) + assert.Equal(s.T(), "renamed_sub_dir", names2[3]) } //////////////////////////////////////////////////////////////////////// @@ -462,7 +463,7 @@ func TestInfiniteKernelListCacheTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -475,6 +476,6 @@ func TestInfiniteKernelListCacheTest(t *testing.T) { ts.flags = flags log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/managed_folders/admin_permissions_test.go b/tools/integration_tests/managed_folders/admin_permissions_test.go index 6361e362c5..98aecbdcd9 100644 --- a/tools/integration_tests/managed_folders/admin_permissions_test.go +++ b/tools/integration_tests/managed_folders/admin_permissions_test.go @@ -30,7 +30,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/creds_tests" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/suite" ) // ////////////////////////////////////////////////////////////////////// @@ -50,144 +50,145 @@ var ( // The permission granted by roles at project, bucket, and managed folder // levels apply additively (union) throughout the resource hierarchy. -// Hence here managed folder will have admin permission throughout all the tests. +// Hence, here managed folder will have admin permission throughout all the tests. type managedFoldersAdminPermission struct { bucketPermission string managedFoldersPermission string + suite.Suite } -func (s *managedFoldersAdminPermission) Setup(t *testing.T) { - createDirectoryStructureForNonEmptyManagedFolders(ctx, storageClient, controlClient, t) +func (s *managedFoldersAdminPermission) SetupTest() { + createDirectoryStructureForNonEmptyManagedFolders(ctx, storageClient, controlClient, s.T()) if s.managedFoldersPermission != "nil" { - providePermissionToManagedFolder(bucket, path.Join(testDir, ManagedFolder1), serviceAccount, s.managedFoldersPermission, t) - providePermissionToManagedFolder(bucket, path.Join(testDir, ManagedFolder2), serviceAccount, s.managedFoldersPermission, t) + providePermissionToManagedFolder(bucket, path.Join(testDir, ManagedFolder1), serviceAccount, s.managedFoldersPermission, s.T()) + providePermissionToManagedFolder(bucket, path.Join(testDir, ManagedFolder2), serviceAccount, s.managedFoldersPermission, s.T()) // Waiting for 60 seconds for policy changes to propagate. This values we kept based on our experiments. time.Sleep(60 * time.Second) } } -func (s *managedFoldersAdminPermission) Teardown(t *testing.T) { - // Due to bucket view permissions, it prevents cleaning resources outside of managed folders. So we are cleaning managed folders resources only. +func (s *managedFoldersAdminPermission) TearDownTest() { + // Due to bucket view permissions, it prevents cleaning resources outside managed folders. So we are cleaning managed folders resources only. if s.bucketPermission == ViewPermission { - revokePermissionToManagedFolder(bucket, path.Join(testDir, ManagedFolder1), serviceAccount, s.managedFoldersPermission, t) + revokePermissionToManagedFolder(bucket, path.Join(testDir, ManagedFolder1), serviceAccount, s.managedFoldersPermission, s.T()) setup.CleanUpDir(path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1)) - revokePermissionToManagedFolder(bucket, path.Join(testDir, ManagedFolder2), serviceAccount, s.managedFoldersPermission, t) + revokePermissionToManagedFolder(bucket, path.Join(testDir, ManagedFolder2), serviceAccount, s.managedFoldersPermission, s.T()) setup.CleanUpDir(path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder2)) return } setup.CleanUpDir(path.Join(setup.MntDir(), TestDirForManagedFolderTest)) } -func (s *managedFoldersAdminPermission) TestCreateObjectInManagedFolder(t *testing.T) { +func (s *managedFoldersAdminPermission) TestCreateObjectInManagedFolder() { testDirPath := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1) file := path.Join(testDirPath, CreateTestFile) - createFileForTest(file, t) + createFileForTest(file, s.T()) } -func (s *managedFoldersAdminPermission) TestDeleteObjectInManagedFolder(t *testing.T) { +func (s *managedFoldersAdminPermission) TestDeleteObjectInManagedFolder() { filePath := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1, FileInNonEmptyManagedFoldersTest) err := os.Remove(filePath) if err != nil { - t.Errorf("Error in removing file from managed folder: %v", err) + s.T().Errorf("Error in removing file from managed folder: %v", err) } _, err = operations.StatFile(filePath) if err == nil { - t.Errorf("file is not removed.") + s.T().Errorf("file is not removed.") } } // Managed folders will not be deleted, but they will become empty. Default empty managed folders will be hidden. -func (s *managedFoldersAdminPermission) TestDeleteManagedFolder(t *testing.T) { +func (s *managedFoldersAdminPermission) TestDeleteManagedFolder() { dirPath := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1) err := os.RemoveAll(dirPath) if err != nil { - t.Errorf("Error in removing managed folder: %v", err) + s.T().Errorf("Error in removing managed folder: %v", err) } _, err = os.Stat(dirPath) if err == nil { - t.Errorf("Directory is not removed.") + s.T().Errorf("Directory is not removed.") } } -func (s *managedFoldersAdminPermission) TestCopyObjectWithInManagedFolder(t *testing.T) { +func (s *managedFoldersAdminPermission) TestCopyObjectWithInManagedFolder() { testDirPath := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1) srcCopyFile := path.Join(testDirPath, FileInNonEmptyManagedFoldersTest) destCopyFile := path.Join(testDirPath, DestFile) err := operations.CopyFile(srcCopyFile, destCopyFile) if err != nil { - t.Errorf("Error in copying file managed folder from src: %s to dest %s: %v", srcCopyFile, destCopyFile, err) + s.T().Errorf("Error in copying file managed folder from src: %s to dest %s: %v", srcCopyFile, destCopyFile, err) } _, err = operations.StatFile(destCopyFile) if err != nil { - t.Errorf("Error in stating destination file: %v", err) + s.T().Errorf("Error in stating destination file: %v", err) } } -func (s *managedFoldersAdminPermission) TestCopyManagedFolder(t *testing.T) { +func (s *managedFoldersAdminPermission) TestCopyManagedFolder() { srcDirPath := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1) destDirPath := path.Join(setup.MntDir(), TestDirForManagedFolderTest, DestFolder) err := operations.CopyDir(srcDirPath, destDirPath) if s.bucketPermission == ViewPermission { - operations.CheckErrorForReadOnlyFileSystem(t, err) + operations.CheckErrorForReadOnlyFileSystem(s.T(), err) } else { _, err = os.Stat(destDirPath) if err != nil { - t.Errorf("Error in stating destination dir: %v", err) + s.T().Errorf("Error in stating destination dir: %v", err) } } } -func (s *managedFoldersAdminPermission) TestMoveObjectWithInManagedFolder(t *testing.T) { +func (s *managedFoldersAdminPermission) TestMoveObjectWithInManagedFolder() { testDirPath := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1) srcMoveFile := path.Join(testDirPath, FileInNonEmptyManagedFoldersTest) destMoveFile := path.Join(testDirPath, DestFile) err := operations.Move(srcMoveFile, destMoveFile) if err != nil { - t.Errorf("Error in moving file managed folder from src: %s to dest %s: %v", srcMoveFile, destMoveFile, err) + s.T().Errorf("Error in moving file managed folder from src: %s to dest %s: %v", srcMoveFile, destMoveFile, err) } _, err = operations.StatFile(destMoveFile) if err != nil { - t.Errorf("Error in stating destination file: %v", err) + s.T().Errorf("Error in stating destination file: %v", err) } _, err = operations.StatFile(srcMoveFile) if err == nil { - t.Errorf("SrcFile is not removed after move.") + s.T().Errorf("SrcFile is not removed after move.") } } -func (s *managedFoldersAdminPermission) TestMoveManagedFolder(t *testing.T) { +func (s *managedFoldersAdminPermission) TestMoveManagedFolder() { srcDirPath := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1) destDirPath := path.Join(setup.MntDir(), TestDirForManagedFolderTest, DestFolder) err := operations.Move(srcDirPath, destDirPath) if s.bucketPermission == ViewPermission { - operations.CheckErrorForReadOnlyFileSystem(t, err) + operations.CheckErrorForReadOnlyFileSystem(s.T(), err) } else { _, err = os.Stat(destDirPath) if err != nil { - t.Errorf("Error in stating destination dir: %v", err) + s.T().Errorf("Error in stating destination dir: %v", err) } _, err = os.Stat(srcDirPath) if err == nil { - t.Errorf("SrcDir is not removed after move.") + s.T().Errorf("SrcDir is not removed after move.") } } } -func (s *managedFoldersAdminPermission) TestListNonEmptyManagedFoldersWithAdminPermission(t *testing.T) { - listNonEmptyManagedFolders(t) +func (s *managedFoldersAdminPermission) TestListNonEmptyManagedFoldersWithAdminPermission() { + listNonEmptyManagedFolders(s.T()) } //////////////////////////////////////////////////////////////////////// @@ -227,7 +228,7 @@ func TestManagedFolders_FolderAdminPermission(t *testing.T) { } ts.managedFoldersPermission = permissions[i][1] - test_setup.RunTests(t, ts) + suite.Run(t, ts) } t.Cleanup(func() { client.DeleteManagedFoldersInBucket(ctx, controlClient, path.Join(testDir, ManagedFolder1), setup.TestBucket()) diff --git a/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go b/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go index 8724267036..452f2ad3c0 100644 --- a/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go +++ b/tools/integration_tests/managed_folders/list_empty_managed_folders_test.go @@ -26,7 +26,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/suite" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" ) @@ -45,13 +45,14 @@ const ( //////////////////////////////////////////////////////////////////////// type enableEmptyManagedFoldersTrue struct { + suite.Suite } -func (s *enableEmptyManagedFoldersTrue) Setup(t *testing.T) { +func (s *enableEmptyManagedFoldersTrue) SetupTest() { setup.SetupTestDirectory(TestDirForEmptyManagedFoldersTest) } -func (s *enableEmptyManagedFoldersTrue) Teardown(t *testing.T) { +func (s *enableEmptyManagedFoldersTrue) TearDownTest() { // Clean up test directory. bucket, testDir := setup.GetBucketAndObjectBasedOnTypeOfMount(TestDirForEmptyManagedFoldersTest) client.DeleteManagedFoldersInBucket(ctx, controlClient, path.Join(testDir, EmptyManagedFolder1), setup.TestBucket()) @@ -80,9 +81,9 @@ func createDirectoryStructureForEmptyManagedFoldersTest(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *enableEmptyManagedFoldersTrue) TestListDirectoryForEmptyManagedFolders(t *testing.T) { +func (s *enableEmptyManagedFoldersTrue) TestListDirectoryForEmptyManagedFolders() { // Create directory structure for testing. - createDirectoryStructureForEmptyManagedFoldersTest(t) + createDirectoryStructureForEmptyManagedFoldersTest(s.T()) // Recursively walk into directory and test. err := filepath.WalkDir(path.Join(setup.MntDir(), TestDirForEmptyManagedFoldersTest), func(path string, dir fs.DirEntry, err error) error { @@ -104,27 +105,27 @@ func (s *enableEmptyManagedFoldersTrue) TestListDirectoryForEmptyManagedFolders( if dir.Name() == TestDirForEmptyManagedFoldersTest { // numberOfObjects - 4 if len(objs) != NumberOfObjectsInDirForListTest { - t.Errorf("Incorrect number of objects in the directory %s expected %d: got %d: ", dir.Name(), NumberOfObjectsInDirForListTest, len(objs)) + s.T().Errorf("Incorrect number of objects in the directory %s expected %d: got %d: ", dir.Name(), NumberOfObjectsInDirForListTest, len(objs)) } // testBucket/managedFolderTest/emptyManagedFolder1 -- ManagedFolder1 if objs[0].Name() != EmptyManagedFolder1 || objs[0].IsDir() != true { - t.Errorf("Listed incorrect object expected %s: got %s: ", EmptyManagedFolder1, objs[0].Name()) + s.T().Errorf("Listed incorrect object expected %s: got %s: ", EmptyManagedFolder1, objs[0].Name()) } // testBucket/managedFolderTest/emptyManagedFolder2 -- ManagedFolder2 if objs[1].Name() != EmptyManagedFolder2 || objs[1].IsDir() != true { - t.Errorf("Listed incorrect object expected %s: got %s: ", EmptyManagedFolder2, objs[1].Name()) + s.T().Errorf("Listed incorrect object expected %s: got %s: ", EmptyManagedFolder2, objs[1].Name()) } // testBucket/managedFolderTest/simulatedFolder -- SimulatedFolder if objs[2].Name() != SimulatedFolder || objs[2].IsDir() != true { - t.Errorf("Listed incorrect object expected %s: got %s: ", SimulatedFolder, objs[2].Name()) + s.T().Errorf("Listed incorrect object expected %s: got %s: ", SimulatedFolder, objs[2].Name()) } // testBucket/managedFolderTest/testFile -- File if objs[3].Name() != File || objs[3].IsDir() != false { - t.Errorf("Listed incorrect object expected %s: got %s: ", File, objs[3].Name()) + s.T().Errorf("Listed incorrect object expected %s: got %s: ", File, objs[3].Name()) } return nil } @@ -132,13 +133,13 @@ func (s *enableEmptyManagedFoldersTrue) TestListDirectoryForEmptyManagedFolders( if dir.Name() == EmptyManagedFolder1 || dir.Name() == EmptyManagedFolder2 || dir.Name() == SimulatedFolder { // numberOfObjects - 0 if len(objs) != 0 { - t.Errorf("Incorrect number of objects in the directory %s expected %d: got %d: ", dir.Name(), 0, len(objs)) + s.T().Errorf("Incorrect number of objects in the directory %s expected %d: got %d: ", dir.Name(), 0, len(objs)) } } return nil }) if err != nil { - t.Errorf("error walking the path : %v\n", err) + s.T().Errorf("error walking the path : %v\n", err) return } } @@ -160,7 +161,7 @@ func TestEnableEmptyManagedFoldersTrue(t *testing.T) { // Run tests for mountedDirectory only if --mountedDirectory and --testBucket flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -176,5 +177,5 @@ func TestEnableEmptyManagedFoldersTrue(t *testing.T) { // Run tests. log.Printf("Running tests with flags: %s", flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/managed_folders/view_permissions_test.go b/tools/integration_tests/managed_folders/view_permissions_test.go index 7d4a9feb52..0ab5e3eed7 100644 --- a/tools/integration_tests/managed_folders/view_permissions_test.go +++ b/tools/integration_tests/managed_folders/view_permissions_test.go @@ -28,7 +28,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/creds_tests" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/suite" ) const ( @@ -42,21 +42,22 @@ const ( // The permission granted by roles at project, bucket, and managed folder // levels apply additively (union) throughout the resource hierarchy. -// Hence here managed folder will have view permission throughout all the tests. +// Hence, here managed folder will have view permission throughout all the tests. type managedFoldersViewPermission struct { + suite.Suite } -func (s *managedFoldersViewPermission) Setup(t *testing.T) { +func (s *managedFoldersViewPermission) SetupTest() { } -func (s *managedFoldersViewPermission) Teardown(t *testing.T) { +func (s *managedFoldersViewPermission) TearDownTest() { } -func (s *managedFoldersViewPermission) TestListNonEmptyManagedFolders(t *testing.T) { - listNonEmptyManagedFolders(t) +func (s *managedFoldersViewPermission) TestListNonEmptyManagedFolders() { + listNonEmptyManagedFolders(s.T()) } -func (s *managedFoldersViewPermission) TestCreateObjectInManagedFolder(t *testing.T) { +func (s *managedFoldersViewPermission) TestCreateObjectInManagedFolder() { filePath := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder2, DestFile) // The error must happen either at file creation or file handle close. @@ -65,69 +66,69 @@ func (s *managedFoldersViewPermission) TestCreateObjectInManagedFolder(t *testin err = file.Close() } - operations.CheckErrorForReadOnlyFileSystem(t, err) + operations.CheckErrorForReadOnlyFileSystem(s.T(), err) } -func (s *managedFoldersViewPermission) TestDeleteObjectFromManagedFolder(t *testing.T) { +func (s *managedFoldersViewPermission) TestDeleteObjectFromManagedFolder() { err := os.Remove(path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1, FileInNonEmptyManagedFoldersTest)) if err == nil { - t.Errorf("File from managed folder gets deleted with view only permission.") + s.T().Errorf("File from managed folder gets deleted with view only permission.") } - operations.CheckErrorForReadOnlyFileSystem(t, err) + operations.CheckErrorForReadOnlyFileSystem(s.T(), err) } -func (s *managedFoldersViewPermission) TestDeleteNonEmptyManagedFolder(t *testing.T) { +func (s *managedFoldersViewPermission) TestDeleteNonEmptyManagedFolder() { err := os.RemoveAll(path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1)) if err == nil { - t.Errorf("Managed folder deleted with view only permission.") + s.T().Errorf("Managed folder deleted with view only permission.") } - operations.CheckErrorForReadOnlyFileSystem(t, err) + operations.CheckErrorForReadOnlyFileSystem(s.T(), err) } -func (s *managedFoldersViewPermission) TestMoveManagedFolder(t *testing.T) { +func (s *managedFoldersViewPermission) TestMoveManagedFolder() { srcDir := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1) destDir := path.Join(setup.MntDir(), TestDirForManagedFolderTest, DestFolder) - moveAndCheckErrForViewPermission(srcDir, destDir, t) + moveAndCheckErrForViewPermission(srcDir, destDir, s.T()) } -func (s *managedFoldersViewPermission) TestMoveObjectWithInManagedFolder(t *testing.T) { +func (s *managedFoldersViewPermission) TestMoveObjectWithInManagedFolder() { srcFile := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1, FileInNonEmptyManagedFoldersTest) destFile := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1, DestFile) - moveAndCheckErrForViewPermission(srcFile, destFile, t) + moveAndCheckErrForViewPermission(srcFile, destFile, s.T()) } -func (s *managedFoldersViewPermission) TestMoveObjectOutOfManagedFolder(t *testing.T) { +func (s *managedFoldersViewPermission) TestMoveObjectOutOfManagedFolder() { srcFile := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1, FileInNonEmptyManagedFoldersTest) destFile := path.Join(setup.MntDir(), TestDirForManagedFolderTest, DestFile) - moveAndCheckErrForViewPermission(srcFile, destFile, t) + moveAndCheckErrForViewPermission(srcFile, destFile, s.T()) } -func (s *managedFoldersViewPermission) TestCopyNonEmptyManagedFolder(t *testing.T) { +func (s *managedFoldersViewPermission) TestCopyNonEmptyManagedFolder() { srcDir := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1) destDir := path.Join(setup.MntDir(), TestDirForManagedFolderTest, DestFolder) - copyDirAndCheckErrForViewPermission(srcDir, destDir, t) + copyDirAndCheckErrForViewPermission(srcDir, destDir, s.T()) } -func (s *managedFoldersViewPermission) TestCopyObjectWithInManagedFolder(t *testing.T) { +func (s *managedFoldersViewPermission) TestCopyObjectWithInManagedFolder() { srcFile := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1, FileInNonEmptyManagedFoldersTest) destFile := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1, DestFile) - copyObjectAndCheckErrForViewPermission(srcFile, destFile, t) + copyObjectAndCheckErrForViewPermission(srcFile, destFile, s.T()) } -func (s *managedFoldersViewPermission) TestCopyObjectOutOfManagedFolder(t *testing.T) { +func (s *managedFoldersViewPermission) TestCopyObjectOutOfManagedFolder() { srcFile := path.Join(setup.MntDir(), TestDirForManagedFolderTest, ManagedFolder1, FileInNonEmptyManagedFoldersTest) destFile := path.Join(setup.MntDir(), TestDirForManagedFolderTest, DestFile) - copyObjectAndCheckErrForViewPermission(srcFile, destFile, t) + copyObjectAndCheckErrForViewPermission(srcFile, destFile, s.T()) } // ////////////////////////////////////////////////////////////////////// @@ -157,7 +158,7 @@ func TestManagedFolders_FolderViewPermission(t *testing.T) { // Run tests. log.Printf("Running tests with flags and managed folder have nil permissions: %s", flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) // Provide storage.objectViewer role to managed folders. providePermissionToManagedFolder(bucket, path.Join(testDir, ManagedFolder1), serviceAccount, IAMRoleForViewPermission, t) @@ -166,5 +167,5 @@ func TestManagedFolders_FolderViewPermission(t *testing.T) { time.Sleep(60 * time.Second) log.Printf("Running tests with flags and managed folder have view permissions: %s", flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go index 7d8e3956ba..6c54bb4d1c 100644 --- a/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go +++ b/tools/integration_tests/negative_stat_cache/disabled_negative_stat_cache_test.go @@ -23,19 +23,20 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) type disabledNegativeStatCacheTest struct { flags []string + suite.Suite } -func (s *disabledNegativeStatCacheTest) Setup(t *testing.T) { +func (s *disabledNegativeStatCacheTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *disabledNegativeStatCacheTest) Teardown(t *testing.T) { +func (s *disabledNegativeStatCacheTest) TearDownTest() { setup.UnmountGCSFuse(testEnv.rootDir) } @@ -43,29 +44,29 @@ func (s *disabledNegativeStatCacheTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *disabledNegativeStatCacheTest) TestNegativeStatCacheDisabled(t *testing.T) { +func (s *disabledNegativeStatCacheTest) TestNegativeStatCacheDisabled() { targetDir := path.Join(testEnv.testDirPath, "explicit_dir") // Create test directory - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) targetFile := path.Join(targetDir, "file1.txt") // Error should be returned as file does not exist _, err := os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) - assert.NotNil(t, err) + assert.NotNil(s.T(), err) // Assert the underlying error is File Not Exist - assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") + assert.ErrorContains(s.T(), err, "explicit_dir/file1.txt: no such file or directory") // Adding the object with same name - client.CreateObjectInGCSTestDir(testEnv.ctx, testEnv.storageClient, testDirName, "explicit_dir/file1.txt", "some-content", t) + client.CreateObjectInGCSTestDir(testEnv.ctx, testEnv.storageClient, testDirName, "explicit_dir/file1.txt", "some-content", s.T()) // File should be returned, as call will be served from GCS and gcsfuse should not return from cache f, err := os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) //Assert File is found - assert.NoError(t, err) - assert.Contains(t, f.Name(), "explicit_dir/file1.txt") - assert.Nil(t, f.Close()) + assert.NoError(s.T(), err) + assert.Contains(s.T(), f.Name(), "explicit_dir/file1.txt") + assert.Nil(s.T(), f.Close()) } //////////////////////////////////////////////////////////////////////// @@ -77,7 +78,7 @@ func TestDisabledNegativeStatCacheTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -87,5 +88,5 @@ func TestDisabledNegativeStatCacheTest(t *testing.T) { // Run tests. ts.flags = flagsSet log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go index 056b2056bc..5f113b019e 100644 --- a/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go +++ b/tools/integration_tests/negative_stat_cache/finite_int_negative_stat_cache_test.go @@ -24,19 +24,20 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" ) type finiteNegativeStatCacheTest struct { flags []string + suite.Suite } -func (s *finiteNegativeStatCacheTest) Setup(t *testing.T) { +func (s *finiteNegativeStatCacheTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *finiteNegativeStatCacheTest) Teardown(t *testing.T) { +func (s *finiteNegativeStatCacheTest) TearDownTest() { setup.UnmountGCSFuse(testEnv.rootDir) } @@ -44,28 +45,28 @@ func (s *finiteNegativeStatCacheTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *finiteNegativeStatCacheTest) TestFiniteNegativeStatCache(t *testing.T) { +func (s *finiteNegativeStatCacheTest) TestFiniteNegativeStatCache() { targetDir := path.Join(testEnv.testDirPath, "explicit_dir") // Create test directory - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) targetFile := path.Join(targetDir, "file1.txt") // Error should be returned as file does not exist _, err := os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) - assert.NotNil(t, err) + assert.NotNil(s.T(), err) // Assert the underlying error is File Not Exist - assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") + assert.ErrorContains(s.T(), err, "explicit_dir/file1.txt: no such file or directory") // Adding the object with same name - client.CreateObjectInGCSTestDir(testEnv.ctx, testEnv.storageClient, testDirName, path.Join("explicit_dir", "file1.txt"), "some-content", t) + client.CreateObjectInGCSTestDir(testEnv.ctx, testEnv.storageClient, testDirName, path.Join("explicit_dir", "file1.txt"), "some-content", s.T()) // Error should be returned again, as call will not be served from GCS due to finite gcsfuse stat cache _, err = os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) - assert.NotNil(t, err) + assert.NotNil(s.T(), err) // Assert the underlying error is File Not Exist - assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") + assert.ErrorContains(s.T(), err, "explicit_dir/file1.txt: no such file or directory") //Wait for Cache to expire time.Sleep(3 * time.Second) @@ -74,9 +75,9 @@ func (s *finiteNegativeStatCacheTest) TestFiniteNegativeStatCache(t *testing.T) f, err := os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) //Assert File is found - assert.NoError(t, err) - assert.Contains(t, f.Name(), "explicit_dir/file1.txt") - assert.Nil(t, f.Close()) + assert.NoError(s.T(), err) + assert.Contains(s.T(), f.Name(), "explicit_dir/file1.txt") + assert.Nil(s.T(), f.Close()) } //////////////////////////////////////////////////////////////////////// @@ -88,7 +89,7 @@ func TestFiniteNegativeStatCacheTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -98,5 +99,5 @@ func TestFiniteNegativeStatCacheTest(t *testing.T) { // Run tests. ts.flags = flagsSet log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go index 40a7e4718d..6df3e1ad6d 100644 --- a/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go +++ b/tools/integration_tests/negative_stat_cache/infinite_negative_stat_cache_test.go @@ -24,20 +24,21 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) type infiniteNegativeStatCacheTest struct { flags []string + suite.Suite } -func (s *infiniteNegativeStatCacheTest) Setup(t *testing.T) { +func (s *infiniteNegativeStatCacheTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *infiniteNegativeStatCacheTest) Teardown(t *testing.T) { +func (s *infiniteNegativeStatCacheTest) TearDownTest() { setup.UnmountGCSFuse(testEnv.rootDir) } @@ -45,28 +46,28 @@ func (s *infiniteNegativeStatCacheTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *infiniteNegativeStatCacheTest) TestInfiniteNegativeStatCache(t *testing.T) { +func (s *infiniteNegativeStatCacheTest) TestInfiniteNegativeStatCache() { targetDir := path.Join(testEnv.testDirPath, "explicit_dir") // Create test directory - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) targetFile := path.Join(targetDir, "file1.txt") // Error should be returned as file does not exist _, err := os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) - assert.NotNil(t, err) + assert.NotNil(s.T(), err) // Assert the underlying error is File Not Exist - assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") + assert.ErrorContains(s.T(), err, "explicit_dir/file1.txt: no such file or directory") // Adding the object with same name - client.CreateObjectInGCSTestDir(testEnv.ctx, testEnv.storageClient, testDirName, "explicit_dir/file1.txt", "some-content", t) + client.CreateObjectInGCSTestDir(testEnv.ctx, testEnv.storageClient, testDirName, "explicit_dir/file1.txt", "some-content", s.T()) // Error should be returned again, as call will not be served from GCS due to infinite gcsfuse stat cache _, err = os.OpenFile(targetFile, os.O_RDONLY, os.FileMode(0600)) - assert.NotNil(t, err) + assert.NotNil(s.T(), err) // Assert the underlying error is File Not Exist - assert.ErrorContains(t, err, "explicit_dir/file1.txt: no such file or directory") + assert.ErrorContains(s.T(), err, "explicit_dir/file1.txt: no such file or directory") } // TestAlreadyExistFolder tests the scenario where a folder creation attempt fails @@ -75,29 +76,29 @@ func (s *infiniteNegativeStatCacheTest) TestInfiniteNegativeStatCache(t *testing // when a folder is created externally after gcsfuse has cached a negative stat entry for that path. // The negative cache prevents gcsfuse from seeing the externally created folder, // leading to an EEXIST error when attempting to create the same folder again. -func (s *infiniteNegativeStatCacheTest) TestAlreadyExistFolder(t *testing.T) { +func (s *infiniteNegativeStatCacheTest) TestAlreadyExistFolder() { dirName := "testAlreadyExistFolder" dirPath := path.Join(testEnv.testDirPath, dirName) dirPathOnBucket := path.Join(testDirName, dirName) // Stat should return an error because the directory doesn't exist yet, // populating the negative metadata cache. _, err := os.Stat(dirPath) - require.Error(t, err) - require.True(t, os.IsNotExist(err)) + require.Error(s.T(), err) + require.True(s.T(), os.IsNotExist(err)) // Create the directory in the bucket using a different client outside of gcsfuse. if setup.IsHierarchicalBucket(testEnv.ctx, testEnv.storageClient) { _, err = client.CreateFolderInBucket(testEnv.ctx, testEnv.storageControlClient, dirPathOnBucket) } else { err = client.CreateObjectOnGCS(testEnv.ctx, testEnv.storageClient, dirPathOnBucket+"/", "") } - require.NoError(t, err) + require.NoError(s.T(), err) // Attempting to create the directory again should fail with EEXIST because the // negative stat cache entry persists, causing LookUpInode to return a "not found" error // and triggering a directory creation attempt despite the directory already existing in GCS. err = os.Mkdir(dirPath, setup.DirPermission_0755) - assert.ErrorIs(t, err, syscall.EEXIST) + assert.ErrorIs(s.T(), err, syscall.EEXIST) } //////////////////////////////////////////////////////////////////////// @@ -109,7 +110,7 @@ func TestInfiniteNegativeStatCacheTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -119,5 +120,5 @@ func TestInfiniteNegativeStatCacheTest(t *testing.T) { // Run tests. ts.flags = flagsSet log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go b/tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go index f1057279aa..81b7325c58 100644 --- a/tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_exclude_regex_test.go @@ -26,7 +26,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -37,17 +37,18 @@ type cacheFileForExcludeRegexTest struct { flags []string storageClient *storage.Client ctx context.Context + suite.Suite } -func (s *cacheFileForExcludeRegexTest) Setup(t *testing.T) { +func (s *cacheFileForExcludeRegexTest) SetupTest() { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } -func (s *cacheFileForExcludeRegexTest) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *cacheFileForExcludeRegexTest) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } @@ -55,16 +56,16 @@ func (s *cacheFileForExcludeRegexTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *cacheFileForExcludeRegexTest) TestReadsForExcludedFile(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSizeForRangeRead, t) +func (s *cacheFileForExcludeRegexTest) TestReadsForExcludedFile() { + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSizeForRangeRead, s.T()) - expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, zeroOffset, t) - expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset1000, t) + expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, zeroOffset, s.T()) + expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset1000, s.T()) - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], true, false, 1, t) - validate(expectedOutcome2, structuredReadLogs[1], false, false, 1, t) - validateFileIsNotCached(testFileName, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], true, false, 1, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], false, false, 1, s.T()) + validateFileIsNotCached(testFileName, s.T()) } //////////////////////////////////////////////////////////////////////// @@ -84,7 +85,7 @@ func TestCacheFileForExcludeRegexTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -165,6 +166,6 @@ func TestCacheFileForExcludeRegexTest(t *testing.T) { ts.flags = append(ts.flags, test.flags.cliFlags...) } log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go index dde192a1ac..89bccc79bc 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_false_test.go @@ -27,9 +27,9 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -41,17 +41,18 @@ type cacheFileForRangeReadFalseTest struct { storageClient *storage.Client ctx context.Context isParallelDownloadsEnabled bool + suite.Suite } -func (s *cacheFileForRangeReadFalseTest) Setup(t *testing.T) { +func (s *cacheFileForRangeReadFalseTest) SetupTest() { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } -func (s *cacheFileForRangeReadFalseTest) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *cacheFileForRangeReadFalseTest) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } @@ -79,43 +80,43 @@ func readFileBetweenOffset(t *testing.T, file *os.File, startOffset, endOffSet i // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *cacheFileForRangeReadFalseTest) TestRangeReadsWithCacheMiss(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSizeForRangeRead, t) +func (s *cacheFileForRangeReadFalseTest) TestRangeReadsWithCacheMiss() { + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSizeForRangeRead, s.T()) // Do a random read on file and validate from gcs. - expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset5000, t) + expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset5000, s.T()) // Read file again from offset 1000 and validate from gcs. - expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset1000, t) + expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset1000, s.T()) - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], false, false, 1, t) - validate(expectedOutcome2, structuredReadLogs[1], false, false, 1, t) - validateFileIsNotCached(testFileName, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], false, false, 1, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], false, false, 1, s.T()) + validateFileIsNotCached(testFileName, s.T()) } -func (s *cacheFileForRangeReadFalseTest) TestReadIsTreatedNonSequentialAfterFileIsRemovedFromCache(t *testing.T) { +func (s *cacheFileForRangeReadFalseTest) TestReadIsTreatedNonSequentialAfterFileIsRemovedFromCache() { var testFileNames [2]string var expectedOutcome [4]*Expected - testFileNames[0] = setupFileInTestDir(s.ctx, s.storageClient, fileSizeSameAsCacheCapacity, t) - testFileNames[1] = setupFileInTestDir(s.ctx, s.storageClient, fileSizeSameAsCacheCapacity, t) + testFileNames[0] = setupFileInTestDir(s.ctx, s.storageClient, fileSizeSameAsCacheCapacity, s.T()) + testFileNames[1] = setupFileInTestDir(s.ctx, s.storageClient, fileSizeSameAsCacheCapacity, s.T()) randomReadChunkCount := fileSizeSameAsCacheCapacity / chunkSizeToRead readTillChunk := randomReadChunkCount / 2 - fh1 := operations.OpenFile(path.Join(testDirPath, testFileNames[0]), t) - defer operations.CloseFileShouldNotThrowError(t, fh1) - fh2 := operations.OpenFile(path.Join(testDirPath, testFileNames[1]), t) - defer operations.CloseFileShouldNotThrowError(t, fh2) + fh1 := operations.OpenFile(path.Join(testDirPath, testFileNames[0]), s.T()) + defer operations.CloseFileShouldNotThrowError(s.T(), fh1) + fh2 := operations.OpenFile(path.Join(testDirPath, testFileNames[1]), s.T()) + defer operations.CloseFileShouldNotThrowError(s.T(), fh2) // Use file handle 1 to read file 1 partially. - expectedOutcome[0] = readFileBetweenOffset(t, fh1, 0, int64(readTillChunk*chunkSizeToRead)) + expectedOutcome[0] = readFileBetweenOffset(s.T(), fh1, 0, int64(readTillChunk*chunkSizeToRead)) // Use file handle 2 to read file 2 partially. This will evict file 1 from // cache due to cache capacity constraints. - expectedOutcome[1] = readFileBetweenOffset(t, fh2, 0, int64(readTillChunk*chunkSizeToRead)) + expectedOutcome[1] = readFileBetweenOffset(s.T(), fh2, 0, int64(readTillChunk*chunkSizeToRead)) // Read remaining file 1. File 2 remains cached. Cache eviction happens on // cache handler creation, which is tied to the file handle. Since the handle // isn't recreated, eviction doesn't occur. - expectedOutcome[2] = readFileBetweenOffset(t, fh1, int64(readTillChunk*chunkSizeToRead)+1, fileSizeSameAsCacheCapacity) + expectedOutcome[2] = readFileBetweenOffset(s.T(), fh1, int64(readTillChunk*chunkSizeToRead)+1, fileSizeSameAsCacheCapacity) // Read remaining file 2. - expectedOutcome[3] = readFileBetweenOffset(t, fh2, int64(readTillChunk*chunkSizeToRead)+1, fileSizeSameAsCacheCapacity) + expectedOutcome[3] = readFileBetweenOffset(s.T(), fh2, int64(readTillChunk*chunkSizeToRead)+1, fileSizeSameAsCacheCapacity) // Merge the expected outcomes. expectedOutcome[0].EndTimeStampSeconds = expectedOutcome[2].EndTimeStampSeconds @@ -123,25 +124,25 @@ func (s *cacheFileForRangeReadFalseTest) TestReadIsTreatedNonSequentialAfterFile expectedOutcome[1].EndTimeStampSeconds = expectedOutcome[3].EndTimeStampSeconds expectedOutcome[1].content = expectedOutcome[1].content + expectedOutcome[3].content // Parse the logs and validate with expected outcome. - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - require.Equal(t, 2, len(structuredReadLogs)) - validate(expectedOutcome[0], structuredReadLogs[0], true, false, randomReadChunkCount, t) - validate(expectedOutcome[1], structuredReadLogs[1], true, false, randomReadChunkCount, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + require.Equal(s.T(), 2, len(structuredReadLogs)) + validate(expectedOutcome[0], structuredReadLogs[0], true, false, randomReadChunkCount, s.T()) + validate(expectedOutcome[1], structuredReadLogs[1], true, false, randomReadChunkCount, s.T()) // Validate after cache eviction, read was considered non-sequential and cache // hit false for first file. // Checking for the last chunk, not readTillChunk+1, due to potential kernel // over-reads on some architectures. - assert.False(t, structuredReadLogs[0].Chunks[randomReadChunkCount-1].IsSequential) - assert.False(t, structuredReadLogs[0].Chunks[randomReadChunkCount-1].CacheHit) + assert.False(s.T(), structuredReadLogs[0].Chunks[randomReadChunkCount-1].IsSequential) + assert.False(s.T(), structuredReadLogs[0].Chunks[randomReadChunkCount-1].CacheHit) // Validate for 2nd file read was considered sequential because of no cache eviction. - assert.True(t, structuredReadLogs[1].Chunks[randomReadChunkCount-1].IsSequential) + assert.True(s.T(), structuredReadLogs[1].Chunks[randomReadChunkCount-1].IsSequential) if !s.isParallelDownloadsEnabled { // When parallel downloads are enabled, we can't concretely say that the read will be cache Hit. - assert.True(t, structuredReadLogs[1].Chunks[randomReadChunkCount-1].CacheHit) + assert.True(s.T(), structuredReadLogs[1].Chunks[randomReadChunkCount-1].CacheHit) } - validateFileIsNotCached(testFileNames[0], t) - validateFileInCacheDirectory(testFileNames[1], fileSizeSameAsCacheCapacity, s.ctx, s.storageClient, t) + validateFileIsNotCached(testFileNames[0], s.T()) + validateFileInCacheDirectory(testFileNames[1], fileSizeSameAsCacheCapacity, s.ctx, s.storageClient, s.T()) } //////////////////////////////////////////////////////////////////////// @@ -161,7 +162,7 @@ func TestCacheFileForRangeReadFalseTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -197,7 +198,7 @@ func TestCacheFileForRangeReadFalseTest(t *testing.T) { ts.flags = append(ts.flags, flags.cliFlags...) } log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } // Run tests with parallel downloads enabled. @@ -248,6 +249,6 @@ func TestCacheFileForRangeReadFalseTest(t *testing.T) { } ts.isParallelDownloadsEnabled = true log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go index 0e7766f7c3..53f8da4005 100644 --- a/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go +++ b/tools/integration_tests/read_cache/cache_file_for_range_read_true_test.go @@ -26,7 +26,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -37,17 +37,18 @@ type cacheFileForRangeReadTrueTest struct { flags []string storageClient *storage.Client ctx context.Context + suite.Suite } -func (s *cacheFileForRangeReadTrueTest) Setup(t *testing.T) { +func (s *cacheFileForRangeReadTrueTest) SetupTest() { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } -func (s *cacheFileForRangeReadTrueTest) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *cacheFileForRangeReadTrueTest) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } @@ -55,23 +56,23 @@ func (s *cacheFileForRangeReadTrueTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *cacheFileForRangeReadTrueTest) TestRangeReadsWithCacheHit(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSizeForRangeRead, t) +func (s *cacheFileForRangeReadTrueTest) TestRangeReadsWithCacheHit() { + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSizeForRangeRead, s.T()) // Do a random read on file and validate from gcs. - expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset5000, t) + expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset5000, s.T()) // Wait for the cache to propagate the updates before proceeding to get cache hit. time.Sleep(4 * time.Second) // Read file again from zeroOffset 1000 and validate from gcs. - expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset1000, t) + expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset1000, s.T()) - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], false, false, 1, t) - validate(expectedOutcome2, structuredReadLogs[1], false, true, 1, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], false, false, 1, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], false, true, 1, s.T()) // Validate cached content with gcs. - validateFileInCacheDirectory(testFileName, fileSizeForRangeRead, s.ctx, s.storageClient, t) + validateFileInCacheDirectory(testFileName, fileSizeForRangeRead, s.ctx, s.storageClient, s.T()) // Validate cache size within limit. - validateCacheSizeWithinLimit(cacheCapacityForRangeReadTestInMiB, t) + validateCacheSizeWithinLimit(cacheCapacityForRangeReadTestInMiB, s.T()) } //////////////////////////////////////////////////////////////////////// @@ -91,7 +92,7 @@ func TestCacheFileForRangeReadTrueTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -164,6 +165,6 @@ func TestCacheFileForRangeReadTrueTest(t *testing.T) { ts.flags = append(ts.flags, flags.cliFlags...) } log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/read_cache/disabled_cache_ttl_test.go b/tools/integration_tests/read_cache/disabled_cache_ttl_test.go index 88006d79db..8b744f101e 100644 --- a/tools/integration_tests/read_cache/disabled_cache_ttl_test.go +++ b/tools/integration_tests/read_cache/disabled_cache_ttl_test.go @@ -21,11 +21,11 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/stretchr/testify/suite" "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) //////////////////////////////////////////////////////////////////////// @@ -36,17 +36,18 @@ type disabledCacheTTLTest struct { flags []string storageClient *storage.Client ctx context.Context + suite.Suite } -func (s *disabledCacheTTLTest) Setup(t *testing.T) { +func (s *disabledCacheTTLTest) SetupTest() { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } -func (s *disabledCacheTTLTest) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *disabledCacheTTLTest) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } @@ -54,23 +55,23 @@ func (s *disabledCacheTTLTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *disabledCacheTTLTest) TestReadAfterObjectUpdateIsCacheMiss(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) +func (s *disabledCacheTTLTest) TestReadAfterObjectUpdateIsCacheMiss() { + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, s.T()) // Read file 1st time. - expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) + expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, s.T()) // Modify the file. - modifyFile(s.ctx, s.storageClient, testFileName, t) + modifyFile(s.ctx, s.storageClient, testFileName, s.T()) // Read same file again immediately. New content should be served as cache ttl is 0. - expectedOutcome2 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, smallContentSize, true, t) + expectedOutcome2 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, smallContentSize, true, s.T()) // Read the same file again. The data should be served from cache. - expectedOutcome3 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, smallContentSize, true, t) + expectedOutcome3 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, smallContentSize, true, s.T()) // Parse the log file and validate cache hit or miss from the structured logs. - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], true, false, chunksRead, t) - validate(expectedOutcome2, structuredReadLogs[1], true, false, chunksReadAfterUpdate, t) - validate(expectedOutcome3, structuredReadLogs[2], true, true, chunksReadAfterUpdate, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], true, false, chunksRead, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], true, false, chunksReadAfterUpdate, s.T()) + validate(expectedOutcome3, structuredReadLogs[2], true, true, chunksReadAfterUpdate, s.T()) } //////////////////////////////////////////////////////////////////////// @@ -90,7 +91,7 @@ func TestDisabledCacheTTLTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -124,6 +125,6 @@ func TestDisabledCacheTTLTest(t *testing.T) { ts.flags = append(ts.flags, flags.cliFlags...) } log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/read_cache/job_chunk_test.go b/tools/integration_tests/read_cache/job_chunk_test.go index f16fb3c738..7082030d97 100644 --- a/tools/integration_tests/read_cache/job_chunk_test.go +++ b/tools/integration_tests/read_cache/job_chunk_test.go @@ -27,9 +27,9 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -43,17 +43,18 @@ type jobChunkTest struct { storageClient *storage.Client ctx context.Context chunkSize int64 + suite.Suite } -func (s *jobChunkTest) Setup(t *testing.T) { +func (s *jobChunkTest) SetupTest() { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } -func (s *jobChunkTest) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *jobChunkTest) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } @@ -84,37 +85,37 @@ func createConfigFileForJobChunkTest(cacheFileForRangeRead bool, fileName string // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *jobChunkTest) TestJobChunkSizeForSingleFileReads(t *testing.T) { +func (s *jobChunkTest) TestJobChunkSizeForSingleFileReads() { var fileSize int64 = 16 * util.MiB - testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, s.T()) - expectedOutcome := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, false, t) + expectedOutcome := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, false, s.T()) // Parse the log file and validate cache hit or miss from the structured logs. - structuredJobLogs := read_logs.GetJobLogsSortedByTimestamp(setup.LogFile(), t) - assert.Equal(t, expectedOutcome.BucketName, structuredJobLogs[0].BucketName) - assert.Equal(t, expectedOutcome.ObjectName, structuredJobLogs[0].ObjectName) + structuredJobLogs := read_logs.GetJobLogsSortedByTimestamp(setup.LogFile(), s.T()) + assert.Equal(s.T(), expectedOutcome.BucketName, structuredJobLogs[0].BucketName) + assert.Equal(s.T(), expectedOutcome.ObjectName, structuredJobLogs[0].ObjectName) // We need to check that downloadedOffset is always greater than the previous downloadedOffset // and is in multiples of chunkSize. for i := 1; i < len(structuredJobLogs[0].JobEntries); i++ { offsetDiff := structuredJobLogs[0].JobEntries[i].Offset - structuredJobLogs[0].JobEntries[i-1].Offset - assert.Greater(t, offsetDiff, int64(0)) + assert.Greater(s.T(), offsetDiff, int64(0)) // This is true for all entries except last one. // Will be true for last entry only if the fileSize is multiple of chunkSize. - assert.Equal(t, int64(0), offsetDiff%s.chunkSize) + assert.Equal(s.T(), int64(0), offsetDiff%s.chunkSize) } // Validate that last downloadedOffset is same as fileSize. - assert.Equal(t, fileSize, structuredJobLogs[0].JobEntries[len(structuredJobLogs[0].JobEntries)-1].Offset) + assert.Equal(s.T(), fileSize, structuredJobLogs[0].JobEntries[len(structuredJobLogs[0].JobEntries)-1].Offset) } -func (s *jobChunkTest) TestJobChunkSizeForMultipleFileReads(t *testing.T) { +func (s *jobChunkTest) TestJobChunkSizeForMultipleFileReads() { var fileSize int64 = 16 * util.MiB var testFileNames [2]string var expectedOutcome [2]*Expected - testFileNames[0] = setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) - testFileNames[1] = setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) + testFileNames[0] = setupFileInTestDir(s.ctx, s.storageClient, fileSize, s.T()) + testFileNames[1] = setupFileInTestDir(s.ctx, s.storageClient, fileSize, s.T()) // Read 2 files in parallel. var wg sync.WaitGroup @@ -123,14 +124,14 @@ func (s *jobChunkTest) TestJobChunkSizeForMultipleFileReads(t *testing.T) { i := i go func() { defer wg.Done() - expectedOutcome[i] = readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileNames[i], fileSize, false, t) + expectedOutcome[i] = readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileNames[i], fileSize, false, s.T()) }() } wg.Wait() // Parse the log file and validate cache hit or miss from the structured logs. - structuredJobLogs := read_logs.GetJobLogsSortedByTimestamp(setup.LogFile(), t) - require.Equal(t, 2, len(structuredJobLogs)) + structuredJobLogs := read_logs.GetJobLogsSortedByTimestamp(setup.LogFile(), s.T()) + require.Equal(s.T(), 2, len(structuredJobLogs)) // Goroutine execution order isn't guaranteed. // If the object name in expected outcome doesn't align with the logs, swap // the expected outcome objects and file names at positions 0 and 1. @@ -140,22 +141,22 @@ func (s *jobChunkTest) TestJobChunkSizeForMultipleFileReads(t *testing.T) { } for fileIndex := 0; fileIndex < 2; fileIndex++ { - assert.Equal(t, expectedOutcome[fileIndex].BucketName, structuredJobLogs[fileIndex].BucketName) - assert.Equal(t, expectedOutcome[fileIndex].ObjectName, structuredJobLogs[fileIndex].ObjectName) + assert.Equal(s.T(), expectedOutcome[fileIndex].BucketName, structuredJobLogs[fileIndex].BucketName) + assert.Equal(s.T(), expectedOutcome[fileIndex].ObjectName, structuredJobLogs[fileIndex].ObjectName) // We need to check that downloadedOffset is always greater than the previous downloadedOffset // and is in multiples of chunkSize. entriesLen := len(structuredJobLogs[fileIndex].JobEntries) for entryIndex := 1; entryIndex < entriesLen; entryIndex++ { offsetDiff := structuredJobLogs[fileIndex].JobEntries[entryIndex].Offset - structuredJobLogs[fileIndex].JobEntries[entryIndex-1].Offset - assert.Greater(t, offsetDiff, int64(0)) + assert.Greater(s.T(), offsetDiff, int64(0)) // This is true for all entries except last one. // Will be true for last entry only if the fileSize is multiple of chunkSize. - assert.Equal(t, int64(0), offsetDiff%s.chunkSize) + assert.Equal(s.T(), int64(0), offsetDiff%s.chunkSize) } // Validate that last downloadedOffset is same as fileSize. - assert.Equal(t, fileSize, structuredJobLogs[fileIndex].JobEntries[entriesLen-1].Offset) + assert.Equal(s.T(), fileSize, structuredJobLogs[fileIndex].JobEntries[entriesLen-1].Offset) } } @@ -176,7 +177,7 @@ func TestJobChunkTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -185,13 +186,13 @@ func TestJobChunkTest(t *testing.T) { ts.flags = []string{"--config-file=" + createConfigFile(&gcsfuseTestFlags{cacheSize: cacheSizeMB, cacheFileForRangeRead: true, fileName: configFileName, enableParallelDownloads: false, enableODirect: false, cacheDirPath: getDefaultCacheDirPathForTests(), clientProtocol: http1ClientProtocol})} ts.chunkSize = chunkSizeForReadCache * util.MiB log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) // Tests to validate chunk size when read cache parallel downloads are disabled with grpc client protocol. ts.flags = []string{"--config-file=" + createConfigFile(&gcsfuseTestFlags{cacheSize: cacheSizeMB, cacheFileForRangeRead: true, fileName: configFileName, enableParallelDownloads: false, enableODirect: false, cacheDirPath: getDefaultCacheDirPathForTests(), clientProtocol: grpcClientProtocol})} ts.chunkSize = chunkSizeForReadCache * util.MiB log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) // Tests to validate chunk size when read cache parallel downloads are enabled // with unlimited max parallel downloads. @@ -199,7 +200,7 @@ func TestJobChunkTest(t *testing.T) { createConfigFileForJobChunkTest(false, "unlimitedMaxParallelDownloads", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, http1ClientProtocol)} ts.chunkSize = downloadChunkSizeMB * util.MiB log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) // Tests to validate chunk size when read cache parallel downloads are enabled // with unlimited max parallel downloads with grpc enabled. @@ -207,7 +208,7 @@ func TestJobChunkTest(t *testing.T) { createConfigFileForJobChunkTest(false, "unlimitedMaxParallelDownloads", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, grpcClientProtocol)} ts.chunkSize = downloadChunkSizeMB * util.MiB log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) // Tests to validate chunk size when read cache parallel downloads are enabled // with go-routines not limited by max parallel downloads. @@ -218,7 +219,7 @@ func TestJobChunkTest(t *testing.T) { createConfigFileForJobChunkTest(false, "limitedMaxParallelDownloadsNotEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, http1ClientProtocol)} ts.chunkSize = int64(downloadChunkSizeMB) * util.MiB log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) // Tests to validate chunk size when read cache parallel downloads are enabled // with go-routines not limited by max parallel downloads with grpc enabled. @@ -226,7 +227,7 @@ func TestJobChunkTest(t *testing.T) { createConfigFileForJobChunkTest(false, "limitedMaxParallelDownloadsNotEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, grpcClientProtocol)} ts.chunkSize = int64(downloadChunkSizeMB) * util.MiB log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) // Tests to validate chunk size when read cache parallel downloads are enabled // with go-routines limited by max parallel downloads. @@ -237,7 +238,7 @@ func TestJobChunkTest(t *testing.T) { createConfigFileForJobChunkTest(false, "limitedMaxParallelDownloadsEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, http1ClientProtocol)} ts.chunkSize = int64(downloadChunkSizeMB) * util.MiB log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) // Tests to validate chunk size when read cache parallel downloads are enabled // with go-routines limited by max parallel downloads with grpc enabled. @@ -245,6 +246,5 @@ func TestJobChunkTest(t *testing.T) { createConfigFileForJobChunkTest(false, "limitedMaxParallelDownloadsEffectingChunkSize", parallelDownloadsPerFile, maxParallelDownloads, downloadChunkSizeMB, grpcClientProtocol)} ts.chunkSize = int64(downloadChunkSizeMB) * util.MiB log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) - + suite.Run(t, ts) } diff --git a/tools/integration_tests/read_cache/local_modification_test.go b/tools/integration_tests/read_cache/local_modification_test.go index 27f63dc607..aa303b9c64 100644 --- a/tools/integration_tests/read_cache/local_modification_test.go +++ b/tools/integration_tests/read_cache/local_modification_test.go @@ -25,7 +25,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/suite" ) // ////////////////////////////////////////////////////////////////////// @@ -35,17 +35,18 @@ type localModificationTest struct { flags []string storageClient *storage.Client ctx context.Context + suite.Suite } -func (s *localModificationTest) Setup(t *testing.T) { +func (s *localModificationTest) SetupTest() { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } -func (s *localModificationTest) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *localModificationTest) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } @@ -53,28 +54,28 @@ func (s *localModificationTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *localModificationTest) TestReadAfterLocalGCSFuseWriteIsCacheMiss(t *testing.T) { +func (s *localModificationTest) TestReadAfterLocalGCSFuseWriteIsCacheMiss() { testFileName := testDirName + setup.GenerateRandomString(testFileNameSuffixLength) - operations.CreateFileOfSize(fileSize, path.Join(testDirPath, testFileName), t) + operations.CreateFileOfSize(fileSize, path.Join(testDirPath, testFileName), s.T()) // Read file 1st time. - expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) + expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, s.T()) // Append data in the same file to change object generation. smallContent, err := operations.GenerateRandomData(smallContentSize) if err != nil { - t.Errorf("TestReadAfterLocalGCSFuseWriteIsCacheMiss: could not generate randomm data: %v", err) + s.T().Errorf("TestReadAfterLocalGCSFuseWriteIsCacheMiss: could not generate randomm data: %v", err) } err = operations.WriteFileInAppendMode(path.Join(testDirPath, testFileName), string(smallContent)) if err != nil { - t.Errorf("Error in appending data in file: %v", err) + s.T().Errorf("Error in appending data in file: %v", err) } // Read file 2nd time. - expectedOutcome2 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize+smallContentSize, true, t) + expectedOutcome2 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize+smallContentSize, true, s.T()) // Parse the log file and validate cache hit or miss from the structured logs. - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], true, false, chunksRead, t) - validate(expectedOutcome2, structuredReadLogs[1], true, false, chunksRead+1, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], true, false, chunksRead, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], true, false, chunksRead+1, s.T()) } //////////////////////////////////////////////////////////////////////// @@ -94,7 +95,7 @@ func TestLocalModificationTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -128,6 +129,6 @@ func TestLocalModificationTest(t *testing.T) { ts.flags = append(ts.flags, flags.cliFlags...) } log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/read_cache/range_read_test.go b/tools/integration_tests/read_cache/range_read_test.go index 9e3d751d6e..71849123db 100644 --- a/tools/integration_tests/read_cache/range_read_test.go +++ b/tools/integration_tests/read_cache/range_read_test.go @@ -21,11 +21,11 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" + "github.com/stretchr/testify/suite" "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" ) //////////////////////////////////////////////////////////////////////// @@ -37,17 +37,18 @@ type rangeReadTest struct { storageClient *storage.Client ctx context.Context isParallelDownloadsEnabled bool + suite.Suite } -func (s *rangeReadTest) Setup(t *testing.T) { +func (s *rangeReadTest) SetupTest() { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } -func (s *rangeReadTest) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *rangeReadTest) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } @@ -55,34 +56,34 @@ func (s *rangeReadTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *rangeReadTest) TestRangeReadsWithinReadChunkSize(t *testing.T) { +func (s *rangeReadTest) TestRangeReadsWithinReadChunkSize() { if s.isParallelDownloadsEnabled { // This test verifies that the reads are all cache hit within a downloaded chunk. // However, with parallel downloads, we cannot guarantee this behavior, so // we skip this test when parallel downloads are enabled. - t.SkipNow() + s.T().SkipNow() } - testFileName := setupFileInTestDir(s.ctx, s.storageClient, largeFileSize, t) + testFileName := setupFileInTestDir(s.ctx, s.storageClient, largeFileSize, s.T()) - expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, zeroOffset, t) - expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offsetForRangeReadWithin8MB, t) + expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, zeroOffset, s.T()) + expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offsetForRangeReadWithin8MB, s.T()) - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], true, false, 1, t) - validate(expectedOutcome2, structuredReadLogs[1], false, true, 1, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], true, false, 1, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], false, true, 1, s.T()) } -func (s *rangeReadTest) TestRangeReadsBeyondReadChunkSizeWithFileCached(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, largeFileSize, t) +func (s *rangeReadTest) TestRangeReadsBeyondReadChunkSizeWithFileCached() { + testFileName := setupFileInTestDir(s.ctx, s.storageClient, largeFileSize, s.T()) - expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, zeroOffset, t) - validateFileInCacheDirectory(testFileName, largeFileSize, ctx, s.storageClient, t) - expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset10MiB, t) + expectedOutcome1 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, zeroOffset, s.T()) + validateFileInCacheDirectory(testFileName, largeFileSize, ctx, s.storageClient, s.T()) + expectedOutcome2 := readChunkAndValidateObjectContentsFromGCS(s.ctx, s.storageClient, testFileName, offset10MiB, s.T()) - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], true, false, 1, t) - validate(expectedOutcome2, structuredReadLogs[1], false, true, 1, t) - validateCacheSizeWithinLimit(largeFileCacheCapacity, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], true, false, 1, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], false, true, 1, s.T()) + validateCacheSizeWithinLimit(largeFileCacheCapacity, s.T()) } //////////////////////////////////////////////////////////////////////// @@ -102,7 +103,7 @@ func TestRangeReadTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -126,7 +127,7 @@ func TestRangeReadTest(t *testing.T) { ts.flags = append(ts.flags, flags.cliFlags...) } log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } // Run tests with parallel downloads enabled. @@ -150,6 +151,6 @@ func TestRangeReadTest(t *testing.T) { } ts.isParallelDownloadsEnabled = true log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/read_cache/read_only_test.go b/tools/integration_tests/read_cache/read_only_test.go index f1a8d7a09a..1030529692 100644 --- a/tools/integration_tests/read_cache/read_only_test.go +++ b/tools/integration_tests/read_cache/read_only_test.go @@ -24,7 +24,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -35,17 +35,18 @@ type readOnlyTest struct { flags []string storageClient *storage.Client ctx context.Context + suite.Suite } -func (s *readOnlyTest) Setup(t *testing.T) { +func (s *readOnlyTest) SetupTest() { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } -func (s *readOnlyTest) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *readOnlyTest) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } @@ -72,74 +73,74 @@ func validateCacheOfMultipleObjectsUsingStructuredLogs(startIndex int, numFiles // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *readOnlyTest) TestSecondSequentialReadIsCacheHit(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) +func (s *readOnlyTest) TestSecondSequentialReadIsCacheHit() { + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, s.T()) // Read file 1st time. - expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) + expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, s.T()) // Read file 2nd time. - expectedOutcome2 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) + expectedOutcome2 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, s.T()) // Parse the log file and validate cache hit or miss from the structured logs. - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], true, false, chunksRead, t) - validate(expectedOutcome2, structuredReadLogs[1], true, true, chunksRead, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], true, false, chunksRead, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], true, true, chunksRead, s.T()) } -func (s *readOnlyTest) TestReadFileSequentiallyLargerThanCacheCapacity(t *testing.T) { +func (s *readOnlyTest) TestReadFileSequentiallyLargerThanCacheCapacity() { // Set up a file in test directory of size more than cache capacity. client.SetupFileInTestDirectory(s.ctx, s.storageClient, testDirName, - largeFileName, largeFileSize, t) + largeFileName, largeFileSize, s.T()) // Read file 1st time. - expectedOutcome1 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, true, zeroOffset, t) + expectedOutcome1 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, true, zeroOffset, s.T()) // Read file 2nd time. - expectedOutcome2 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, true, zeroOffset, t) + expectedOutcome2 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, true, zeroOffset, s.T()) // Parse the log file and validate cache hit or miss from the structured logs. - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], true, false, largeFileChunksRead, t) - validate(expectedOutcome2, structuredReadLogs[1], true, false, largeFileChunksRead, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], true, false, largeFileChunksRead, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], true, false, largeFileChunksRead, s.T()) } -func (s *readOnlyTest) TestReadFileRandomlyLargerThanCacheCapacity(t *testing.T) { +func (s *readOnlyTest) TestReadFileRandomlyLargerThanCacheCapacity() { // Set up a file in test directory of size more than cache capacity. client.SetupFileInTestDirectory(s.ctx, s.storageClient, testDirName, - largeFileName, largeFileSize, t) + largeFileName, largeFileSize, s.T()) // Do a random read on file. - expectedOutcome1 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, false, randomReadOffset, t) + expectedOutcome1 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, false, randomReadOffset, s.T()) // Read file sequentially again. - expectedOutcome2 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, true, zeroOffset, t) + expectedOutcome2 := readFileAndValidateFileIsNotCached(s.ctx, s.storageClient, true, zeroOffset, s.T()) // Parse the log file and validate cache hit or miss from the structured logs. - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], false, false, 1, t) - validate(expectedOutcome2, structuredReadLogs[1], true, false, largeFileChunksRead, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], false, false, 1, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], true, false, largeFileChunksRead, s.T()) } -func (s *readOnlyTest) TestReadMultipleFilesMoreThanCacheLimit(t *testing.T) { - fileNames := client.CreateNFilesInDir(s.ctx, s.storageClient, NumberOfFilesMoreThanCacheLimit, testFileName, fileSize, testDirName, t) +func (s *readOnlyTest) TestReadMultipleFilesMoreThanCacheLimit() { + fileNames := client.CreateNFilesInDir(s.ctx, s.storageClient, NumberOfFilesMoreThanCacheLimit, testFileName, fileSize, testDirName, s.T()) - expectedOutcome := readMultipleFiles(NumberOfFilesMoreThanCacheLimit, s.ctx, s.storageClient, fileNames, t) - expectedOutcome = append(expectedOutcome, readMultipleFiles(NumberOfFilesMoreThanCacheLimit, s.ctx, s.storageClient, fileNames, t)...) + expectedOutcome := readMultipleFiles(NumberOfFilesMoreThanCacheLimit, s.ctx, s.storageClient, fileNames, s.T()) + expectedOutcome = append(expectedOutcome, readMultipleFiles(NumberOfFilesMoreThanCacheLimit, s.ctx, s.storageClient, fileNames, s.T())...) // Parse the log file and validate cache hit or miss from the structured logs. - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validateCacheOfMultipleObjectsUsingStructuredLogs(0, NumberOfFilesMoreThanCacheLimit, expectedOutcome, structuredReadLogs, false, t) - validateCacheOfMultipleObjectsUsingStructuredLogs(NumberOfFilesMoreThanCacheLimit, NumberOfFilesMoreThanCacheLimit, expectedOutcome, structuredReadLogs, false, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validateCacheOfMultipleObjectsUsingStructuredLogs(0, NumberOfFilesMoreThanCacheLimit, expectedOutcome, structuredReadLogs, false, s.T()) + validateCacheOfMultipleObjectsUsingStructuredLogs(NumberOfFilesMoreThanCacheLimit, NumberOfFilesMoreThanCacheLimit, expectedOutcome, structuredReadLogs, false, s.T()) } -func (s *readOnlyTest) TestReadMultipleFilesWithinCacheLimit(t *testing.T) { - fileNames := client.CreateNFilesInDir(s.ctx, s.storageClient, NumberOfFilesWithinCacheLimit, testFileName, fileSize, testDirName, t) +func (s *readOnlyTest) TestReadMultipleFilesWithinCacheLimit() { + fileNames := client.CreateNFilesInDir(s.ctx, s.storageClient, NumberOfFilesWithinCacheLimit, testFileName, fileSize, testDirName, s.T()) - expectedOutcome := readMultipleFiles(NumberOfFilesWithinCacheLimit, s.ctx, s.storageClient, fileNames, t) - expectedOutcome = append(expectedOutcome, readMultipleFiles(NumberOfFilesWithinCacheLimit, s.ctx, s.storageClient, fileNames, t)...) + expectedOutcome := readMultipleFiles(NumberOfFilesWithinCacheLimit, s.ctx, s.storageClient, fileNames, s.T()) + expectedOutcome = append(expectedOutcome, readMultipleFiles(NumberOfFilesWithinCacheLimit, s.ctx, s.storageClient, fileNames, s.T())...) // Parse the log file and validate cache hit or miss from the structured logs. - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validateCacheOfMultipleObjectsUsingStructuredLogs(0, NumberOfFilesWithinCacheLimit, expectedOutcome, structuredReadLogs, false, t) - validateCacheOfMultipleObjectsUsingStructuredLogs(NumberOfFilesWithinCacheLimit, NumberOfFilesWithinCacheLimit, expectedOutcome, structuredReadLogs, true, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validateCacheOfMultipleObjectsUsingStructuredLogs(0, NumberOfFilesWithinCacheLimit, expectedOutcome, structuredReadLogs, false, s.T()) + validateCacheOfMultipleObjectsUsingStructuredLogs(NumberOfFilesWithinCacheLimit, NumberOfFilesWithinCacheLimit, expectedOutcome, structuredReadLogs, true, s.T()) } //////////////////////////////////////////////////////////////////////// @@ -159,7 +160,7 @@ func TestReadOnlyTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -207,6 +208,6 @@ func TestReadOnlyTest(t *testing.T) { ts.flags = append(ts.flags, flags.cliFlags...) } log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/read_cache/remount_test.go b/tools/integration_tests/read_cache/remount_test.go index 53040b4cb6..a9b7eef1f5 100644 --- a/tools/integration_tests/read_cache/remount_test.go +++ b/tools/integration_tests/read_cache/remount_test.go @@ -21,14 +21,13 @@ import ( "testing" "time" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" - "cloud.google.com/go/storage" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/client" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/mounting/dynamic_mounting" + "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -39,15 +38,16 @@ type remountTest struct { flags []string storageClient *storage.Client ctx context.Context + suite.Suite } -func (s *remountTest) Setup(t *testing.T) { +func (s *remountTest) SetupTest() { operations.RemoveDir(cacheDirPath) mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } -func (s *remountTest) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *remountTest) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } @@ -68,37 +68,37 @@ func readFileAndValidateCacheWithGCSForDynamicMount(bucketName string, ctx conte // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *remountTest) TestCacheIsNotReusedOnRemount(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) +func (s *remountTest) TestCacheIsNotReusedOnRemount() { + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, s.T()) // Run read operations on GCSFuse mount. - expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) - expectedOutcome2 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) - structuredReadLogsMount1 := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) + expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, s.T()) + expectedOutcome2 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, s.T()) + structuredReadLogsMount1 := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) // Re-mount GCSFuse. remountGCSFuse(s.flags) // Run read operations again on GCSFuse mount. - expectedOutcome3 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, false, t) - expectedOutcome4 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, false, t) - structuredReadLogsMount2 := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - - validate(expectedOutcome1, structuredReadLogsMount1[0], true, false, chunksRead, t) - validate(expectedOutcome2, structuredReadLogsMount1[1], true, true, chunksRead, t) - validate(expectedOutcome3, structuredReadLogsMount2[0], true, false, chunksRead, t) - validate(expectedOutcome4, structuredReadLogsMount2[1], true, true, chunksRead, t) + expectedOutcome3 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, false, s.T()) + expectedOutcome4 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, false, s.T()) + structuredReadLogsMount2 := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + + validate(expectedOutcome1, structuredReadLogsMount1[0], true, false, chunksRead, s.T()) + validate(expectedOutcome2, structuredReadLogsMount1[1], true, true, chunksRead, s.T()) + validate(expectedOutcome3, structuredReadLogsMount2[0], true, false, chunksRead, s.T()) + validate(expectedOutcome4, structuredReadLogsMount2[1], true, true, chunksRead, s.T()) } -func (s *remountTest) TestCacheIsNotReusedOnDynamicRemount(t *testing.T) { - runTestsOnlyForDynamicMount(t) +func (s *remountTest) TestCacheIsNotReusedOnDynamicRemount() { + runTestsOnlyForDynamicMount(s.T()) testBucket1 := setup.TestBucket() - testFileName1 := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) + testFileName1 := setupFileInTestDir(s.ctx, s.storageClient, fileSize, s.T()) testBucket2, err := dynamic_mounting.CreateTestBucketForDynamicMounting(ctx, storageClient) if err != nil { - t.Fatalf("Failed to create bucket for dynamic mounting test: %v", err) + s.T().Fatalf("Failed to create bucket for dynamic mounting test: %v", err) } defer func() { if err := client.DeleteBucket(ctx, storageClient, testBucket2); err != nil { - t.Logf("Failed to delete test bucket %s.Error : %v", testBucket1, err) + s.T().Logf("Failed to delete test bucket %s.Error : %v", testBucket1, err) } }() setup.SetDynamicBucketMounted(testBucket2) @@ -106,27 +106,27 @@ func (s *remountTest) TestCacheIsNotReusedOnDynamicRemount(t *testing.T) { // Introducing a sleep of 10 seconds after bucket creation to address propagation delays. time.Sleep(10 * time.Second) client.SetupTestDirectory(s.ctx, s.storageClient, testDirName) - testFileName2 := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) + testFileName2 := setupFileInTestDir(s.ctx, s.storageClient, fileSize, s.T()) // Reading files in different buckets. - expectedOutcome1 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket1, s.ctx, s.storageClient, testFileName1, true, t) - expectedOutcome2 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket2, s.ctx, s.storageClient, testFileName2, true, t) - structuredReadLogs1 := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) + expectedOutcome1 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket1, s.ctx, s.storageClient, testFileName1, true, s.T()) + expectedOutcome2 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket2, s.ctx, s.storageClient, testFileName2, true, s.T()) + structuredReadLogs1 := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) remountGCSFuse(s.flags) // Reading files in different buckets again. - expectedOutcome3 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket1, s.ctx, s.storageClient, testFileName1, false, t) - expectedOutcome4 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket2, s.ctx, s.storageClient, testFileName2, false, t) + expectedOutcome3 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket1, s.ctx, s.storageClient, testFileName1, false, s.T()) + expectedOutcome4 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket2, s.ctx, s.storageClient, testFileName2, false, s.T()) // Reading same files in different buckets again without remount. - expectedOutcome5 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket1, s.ctx, s.storageClient, testFileName1, false, t) - expectedOutcome6 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket2, s.ctx, s.storageClient, testFileName2, false, t) - structuredReadLogs2 := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - - validate(expectedOutcome1, structuredReadLogs1[0], true, false, chunksRead, t) - validate(expectedOutcome2, structuredReadLogs1[1], true, false, chunksRead, t) - validate(expectedOutcome3, structuredReadLogs2[0], true, false, chunksRead, t) - validate(expectedOutcome4, structuredReadLogs2[1], true, false, chunksRead, t) - validate(expectedOutcome5, structuredReadLogs2[2], true, true, chunksRead, t) - validate(expectedOutcome6, structuredReadLogs2[3], true, true, chunksRead, t) + expectedOutcome5 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket1, s.ctx, s.storageClient, testFileName1, false, s.T()) + expectedOutcome6 := readFileAndValidateCacheWithGCSForDynamicMount(testBucket2, s.ctx, s.storageClient, testFileName2, false, s.T()) + structuredReadLogs2 := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + + validate(expectedOutcome1, structuredReadLogs1[0], true, false, chunksRead, s.T()) + validate(expectedOutcome2, structuredReadLogs1[1], true, false, chunksRead, s.T()) + validate(expectedOutcome3, structuredReadLogs2[0], true, false, chunksRead, s.T()) + validate(expectedOutcome4, structuredReadLogs2[1], true, false, chunksRead, s.T()) + validate(expectedOutcome5, structuredReadLogs2[2], true, true, chunksRead, s.T()) + validate(expectedOutcome6, structuredReadLogs2[3], true, true, chunksRead, s.T()) } //////////////////////////////////////////////////////////////////////// @@ -178,6 +178,6 @@ func TestRemountTest(t *testing.T) { ts.flags = append(ts.flags, flags.cliFlags...) } log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/read_cache/small_cache_ttl_test.go b/tools/integration_tests/read_cache/small_cache_ttl_test.go index 57be414eb0..de8f418e8d 100644 --- a/tools/integration_tests/read_cache/small_cache_ttl_test.go +++ b/tools/integration_tests/read_cache/small_cache_ttl_test.go @@ -27,7 +27,7 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/log_parser/json_parser/read_logs" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -38,17 +38,18 @@ type smallCacheTTLTest struct { flags []string storageClient *storage.Client ctx context.Context + suite.Suite } -func (s *smallCacheTTLTest) Setup(t *testing.T) { +func (s *smallCacheTTLTest) SetupTest() { setupForMountedDirectoryTests() // Clean up the cache directory path as gcsfuse don't clean up on mounting. operations.RemoveDir(cacheDirPath) mountGCSFuseAndSetupTestDir(s.flags, s.ctx, s.storageClient) } -func (s *smallCacheTTLTest) Teardown(t *testing.T) { - setup.SaveGCSFuseLogFileInCaseOfFailure(t) +func (s *smallCacheTTLTest) TearDownTest() { + setup.SaveGCSFuseLogFileInCaseOfFailure(s.T()) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } @@ -56,47 +57,47 @@ func (s *smallCacheTTLTest) Teardown(t *testing.T) { // Test scenarios //////////////////////////////////////////////////////////////////////// -func (s *smallCacheTTLTest) TestReadAfterUpdateAndCacheExpiryIsCacheMiss(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) +func (s *smallCacheTTLTest) TestReadAfterUpdateAndCacheExpiryIsCacheMiss() { + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, s.T()) // Read file 1st time. - expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) + expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, s.T()) // Modify the file. - modifyFile(s.ctx, s.storageClient, testFileName, t) + modifyFile(s.ctx, s.storageClient, testFileName, s.T()) // Read same file again immediately. - expectedOutcome2 := readFileAndGetExpectedOutcome(testDirPath, testFileName, true, zeroOffset, t) - validateFileSizeInCacheDirectory(testFileName, fileSize, t) + expectedOutcome2 := readFileAndGetExpectedOutcome(testDirPath, testFileName, true, zeroOffset, s.T()) + validateFileSizeInCacheDirectory(testFileName, fileSize, s.T()) // Validate that stale data is served from cache in this case. if strings.Compare(expectedOutcome1.content, expectedOutcome2.content) != 0 { - t.Errorf("content mismatch. Expected old data to be served again.") + s.T().Errorf("content mismatch. Expected old data to be served again.") } // Wait for metadata cache expiry and read the file again. time.Sleep(metadataCacheTTlInSec * time.Second) - expectedOutcome3 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, smallContentSize, true, t) + expectedOutcome3 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, smallContentSize, true, s.T()) // Parse the log file and validate cache hit or miss from the structured logs. - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], true, false, chunksRead, t) - validate(expectedOutcome2, structuredReadLogs[1], true, true, chunksRead, t) - validate(expectedOutcome3, structuredReadLogs[2], true, false, chunksReadAfterUpdate, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], true, false, chunksRead, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], true, true, chunksRead, s.T()) + validate(expectedOutcome3, structuredReadLogs[2], true, false, chunksReadAfterUpdate, s.T()) } -func (s *smallCacheTTLTest) TestReadForLowMetaDataCacheTTLIsCacheHit(t *testing.T) { - testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, t) +func (s *smallCacheTTLTest) TestReadForLowMetaDataCacheTTLIsCacheHit() { + testFileName := setupFileInTestDir(s.ctx, s.storageClient, fileSize, s.T()) // Read file 1st time. - expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) + expectedOutcome1 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, s.T()) // Wait for metadata cache expiry and read the file again. time.Sleep(metadataCacheTTlInSec * time.Second) - expectedOutcome2 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) + expectedOutcome2 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, s.T()) // Read same file again immediately. - expectedOutcome3 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, t) + expectedOutcome3 := readFileAndValidateCacheWithGCS(s.ctx, s.storageClient, testFileName, fileSize, true, s.T()) // Parse the log file and validate cache hit or miss from the structured logs. - structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), t) - validate(expectedOutcome1, structuredReadLogs[0], true, false, chunksRead, t) - validate(expectedOutcome2, structuredReadLogs[1], true, true, chunksRead, t) - validate(expectedOutcome3, structuredReadLogs[2], true, true, chunksRead, t) + structuredReadLogs := read_logs.GetStructuredLogsSortedByTimestamp(setup.LogFile(), s.T()) + validate(expectedOutcome1, structuredReadLogs[0], true, false, chunksRead, s.T()) + validate(expectedOutcome2, structuredReadLogs[1], true, true, chunksRead, s.T()) + validate(expectedOutcome3, structuredReadLogs[2], true, true, chunksRead, s.T()) } //////////////////////////////////////////////////////////////////////// @@ -116,7 +117,7 @@ func TestSmallCacheTTLTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } @@ -151,6 +152,6 @@ func TestSmallCacheTTLTest(t *testing.T) { ts.flags = append(ts.flags, flags.cliFlags...) } log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } } diff --git a/tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go b/tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go index 194a578668..3d453a0f17 100644 --- a/tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go +++ b/tools/integration_tests/readdirplus/readdirplus_with_dentry_cache_test.go @@ -23,28 +23,29 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/jacobsa/fuse/fusetesting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) type readdirplusWithDentryCacheTest struct { flags []string + suite.Suite } -func (s *readdirplusWithDentryCacheTest) Setup(t *testing.T) { +func (s *readdirplusWithDentryCacheTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *readdirplusWithDentryCacheTest) Teardown(t *testing.T) { +func (s *readdirplusWithDentryCacheTest) TearDownTest() { if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } } -func (s *readdirplusWithDentryCacheTest) TestReaddirplusWithDentryCache(t *testing.T) { +func (s *readdirplusWithDentryCacheTest) TestReaddirplusWithDentryCache() { // Create directory structure // testBucket/target_dir/ -- Dir // testBucket/target_dir/file -- File @@ -52,21 +53,21 @@ func (s *readdirplusWithDentryCacheTest) TestReaddirplusWithDentryCache(t *testi // testBucket/target_dir/subDirectory -- Dir // testBucket/target_dir/subDirectory/file1 -- File targetDir := path.Join(testDirPath, targetDirName) - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create a file in the target directory. - f1 := operations.CreateFile(path.Join(targetDir, "file"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) + f1 := operations.CreateFile(path.Join(targetDir, "file"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) // Create an empty subdirectory - operations.CreateDirectory(path.Join(targetDir, "emptySubDirectory"), t) + operations.CreateDirectory(path.Join(targetDir, "emptySubDirectory"), s.T()) // Create a subdirectory with file - operations.CreateDirectoryWithNFiles(1, path.Join(targetDir, "subDirectory"), "file", t) + operations.CreateDirectoryWithNFiles(1, path.Join(targetDir, "subDirectory"), "file", s.T()) // Call Readdirplus to list the directory. startTime := time.Now() entries, err := fusetesting.ReadDirPlusPicky(targetDir) endTime := time.Now() - require.NoError(t, err, "ReadDirPlusPicky failed") + require.NoError(s.T(), err, "ReadDirPlusPicky failed") expectedEntries := []struct { name string isDir bool @@ -77,17 +78,17 @@ func (s *readdirplusWithDentryCacheTest) TestReaddirplusWithDentryCache(t *testi {name: "subDirectory", isDir: true, mode: os.ModeDir | 0755}, } // Verify the entries. - assert.Equal(t, len(expectedEntries), len(entries), "Number of entries mismatch") + assert.Equal(s.T(), len(expectedEntries), len(entries), "Number of entries mismatch") for i, expected := range expectedEntries { entry := entries[i] - assert.Equal(t, expected.name, entry.Name(), "Name mismatch for entry %d", i) - assert.Equal(t, expected.isDir, entry.IsDir(), "IsDir mismatch for entry %s", entry.Name()) - assert.Equal(t, expected.mode, entry.Mode(), "Mode mismatch for entry %s", entry.Name()) + assert.Equal(s.T(), expected.name, entry.Name(), "Name mismatch for entry %d", i) + assert.Equal(s.T(), expected.isDir, entry.IsDir(), "IsDir mismatch for entry %s", entry.Name()) + assert.Equal(s.T(), expected.mode, entry.Mode(), "Mode mismatch for entry %s", entry.Name()) } // Dentry cache is enabled, so LookUpInode should also not be called. // This applies even to the parent directory, as its inode is cached during // the test setup phase when the directory structure is created. - validateLogsForReaddirplus(t, setup.LogFile(), true, startTime, endTime) + validateLogsForReaddirplus(s.T(), setup.LogFile(), true, startTime, endTime) } func TestReaddirplusWithDentryCacheTest(t *testing.T) { @@ -95,12 +96,12 @@ func TestReaddirplusWithDentryCacheTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } // Setup flags and run tests. ts.flags = []string{"--implicit-dirs", "--experimental-enable-readdirplus", "--experimental-enable-dentry-cache"} log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go b/tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go index b206e86e83..b61383c45a 100644 --- a/tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go +++ b/tools/integration_tests/readdirplus/readdirplus_without_dentry_cache_test.go @@ -23,28 +23,29 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/jacobsa/fuse/fusetesting" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) type readdirplusWithoutDentryCacheTest struct { flags []string + suite.Suite } -func (s *readdirplusWithoutDentryCacheTest) Setup(t *testing.T) { +func (s *readdirplusWithoutDentryCacheTest) SetupTest() { mountGCSFuseAndSetupTestDir(s.flags, testDirName) } -func (s *readdirplusWithoutDentryCacheTest) Teardown(t *testing.T) { +func (s *readdirplusWithoutDentryCacheTest) TearDownTest() { if setup.MountedDirectory() == "" { // Only unmount if not using a pre-mounted directory setup.CleanupDirectoryOnGCS(ctx, storageClient, path.Join(setup.TestBucket(), testDirName)) setup.UnmountGCSFuseAndDeleteLogFile(rootDir) } } -func (s *readdirplusWithoutDentryCacheTest) TestReaddirplusWithoutDentryCache(t *testing.T) { +func (s *readdirplusWithoutDentryCacheTest) TestReaddirplusWithoutDentryCache() { // Create directory structure // testBucket/target_dir/ -- Dir // testBucket/target_dir/file -- File @@ -52,21 +53,21 @@ func (s *readdirplusWithoutDentryCacheTest) TestReaddirplusWithoutDentryCache(t // testBucket/target_dir/subDirectory -- Dir // testBucket/target_dir/subDirectory/file1 -- File targetDir := path.Join(testDirPath, targetDirName) - operations.CreateDirectory(targetDir, t) + operations.CreateDirectory(targetDir, s.T()) // Create a file in the target directory. - f1 := operations.CreateFile(path.Join(targetDir, "file"), setup.FilePermission_0600, t) - operations.CloseFileShouldNotThrowError(t, f1) + f1 := operations.CreateFile(path.Join(targetDir, "file"), setup.FilePermission_0600, s.T()) + operations.CloseFileShouldNotThrowError(s.T(), f1) // Create an empty subdirectory - operations.CreateDirectory(path.Join(targetDir, "emptySubDirectory"), t) + operations.CreateDirectory(path.Join(targetDir, "emptySubDirectory"), s.T()) // Create a subdirectory with file - operations.CreateDirectoryWithNFiles(1, path.Join(targetDir, "subDirectory"), "file", t) + operations.CreateDirectoryWithNFiles(1, path.Join(targetDir, "subDirectory"), "file", s.T()) // Call Readdirplus to list the directory. startTime := time.Now() entries, err := fusetesting.ReadDirPlusPicky(targetDir) endTime := time.Now() - require.NoError(t, err, "ReadDirPlusPicky failed") + require.NoError(s.T(), err, "ReadDirPlusPicky failed") expectedEntries := []struct { name string isDir bool @@ -77,17 +78,17 @@ func (s *readdirplusWithoutDentryCacheTest) TestReaddirplusWithoutDentryCache(t {name: "subDirectory", isDir: true, mode: os.ModeDir | 0755}, } // Verify the entries. - assert.Equal(t, len(expectedEntries), len(entries), "Number of entries mismatch") + assert.Equal(s.T(), len(expectedEntries), len(entries), "Number of entries mismatch") for i, expected := range expectedEntries { entry := entries[i] - assert.Equal(t, expected.name, entry.Name(), "Name mismatch for entry %d", i) - assert.Equal(t, expected.isDir, entry.IsDir(), "IsDir mismatch for entry %s", entry.Name()) - assert.Equal(t, expected.mode, entry.Mode(), "Mode mismatch for entry %s", entry.Name()) + assert.Equal(s.T(), expected.name, entry.Name(), "Name mismatch for entry %d", i) + assert.Equal(s.T(), expected.isDir, entry.IsDir(), "IsDir mismatch for entry %s", entry.Name()) + assert.Equal(s.T(), expected.mode, entry.Mode(), "Mode mismatch for entry %s", entry.Name()) } // Validate logs to check that ReadDirPlus was called and ReadDir was not. // Dentry cache is not enabled, so LookUpInode should be called for // parent directory as well as for all the entries. - validateLogsForReaddirplus(t, setup.LogFile(), false, startTime, endTime) + validateLogsForReaddirplus(s.T(), setup.LogFile(), false, startTime, endTime) } func TestReaddirplusWithoutDentryCacheTest(t *testing.T) { @@ -95,12 +96,12 @@ func TestReaddirplusWithoutDentryCacheTest(t *testing.T) { // Run tests for mounted directory if the flag is set. if setup.AreBothMountedDirectoryAndTestBucketFlagsSet() { - test_setup.RunTests(t, ts) + suite.Run(t, ts) return } // Run tests. ts.flags = []string{"--implicit-dirs", "--experimental-enable-readdirplus"} log.Printf("Running tests with flags: %s", ts.flags) - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/readonly/readonly_test.go b/tools/integration_tests/readonly/readonly_test.go index cbd304f2b9..027c40788b 100644 --- a/tools/integration_tests/readonly/readonly_test.go +++ b/tools/integration_tests/readonly/readonly_test.go @@ -143,18 +143,16 @@ func TestMain(m *testing.M) { os.Exit(setup.RunTestsForMountedDirectory(cfg.ReadOnly[0].MountedDirectory, m)) } - // Run tests for testBucket// Run tests for testBucket + // Run tests for testBucket // 4. Build the flag sets dynamically from the config. flags := setup.BuildFlagSets(cfg.ReadOnly[0], bucketType) - setup.SetUpTestDirForTestBucket(cfg.ReadOnly[0].TestBucket) + // 5. Run tests. successCode := static_mounting.RunTestsWithConfigFile(&cfg.ReadOnly[0], flags, m) - if successCode == 0 { successCode = persistent_mounting.RunTestsWithConfigFile(&cfg.ReadOnly[0], flags, m) } - if successCode == 0 { // These tests don't apply to GCSFuse sidecar. // Validate that tests work with viewer permission on test bucket. diff --git a/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go b/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go index a1b3afd222..a50d93acdd 100644 --- a/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go +++ b/tools/integration_tests/readonly_creds/failure_during_file_sync_test.go @@ -22,9 +22,9 @@ import ( "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/operations" "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) //////////////////////////////////////////////////////////////////////// @@ -33,13 +33,14 @@ import ( type readOnlyCredsTest struct { testDirPath string + suite.Suite } -func (r *readOnlyCredsTest) Setup(t *testing.T) { +func (r *readOnlyCredsTest) SetupTest() { r.testDirPath = path.Join(setup.MntDir(), testDirName) } -func (r *readOnlyCredsTest) Teardown(t *testing.T) { +func (r *readOnlyCredsTest) TearDownTest() { } //////////////////////////////////////////////////////////////////////// @@ -67,34 +68,34 @@ func (r *readOnlyCredsTest) assertFileSyncFailsWithPermissionError(fh *os.File, // Test scenarios //////////////////////////////////////////////////////////////////////// -func (r *readOnlyCredsTest) TestEmptyCreateFileFails_FailedFileNotInListing(t *testing.T) { +func (r *readOnlyCredsTest) TestEmptyCreateFileFails_FailedFileNotInListing() { filePath := path.Join(r.testDirPath, testFileName) fh, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, operations.FilePermission_0777) if setup.IsZonalBucketRun() { - require.Error(t, err) - assert.True(t, strings.Contains(err.Error(), permissionDeniedError)) + require.Error(r.T(), err) + assert.True(r.T(), strings.Contains(err.Error(), permissionDeniedError)) } else { - r.assertFileSyncFailsWithPermissionError(fh, t) + r.assertFileSyncFailsWithPermissionError(fh, r.T()) } - r.assertFailedFileNotInListing(t) + r.assertFailedFileNotInListing(r.T()) } -func (r *readOnlyCredsTest) TestNonEmptyCreateFileFails_FailedFileNotInListing(t *testing.T) { +func (r *readOnlyCredsTest) TestNonEmptyCreateFileFails_FailedFileNotInListing() { filePath := path.Join(r.testDirPath, testFileName) fh, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, operations.FilePermission_0777) if setup.IsZonalBucketRun() { - require.Error(t, err) - assert.True(t, strings.Contains(err.Error(), permissionDeniedError)) + require.Error(r.T(), err) + assert.True(r.T(), strings.Contains(err.Error(), permissionDeniedError)) } else { - operations.WriteWithoutClose(fh, content, t) - operations.WriteWithoutClose(fh, content, t) - r.assertFileSyncFailsWithPermissionError(fh, t) + operations.WriteWithoutClose(fh, content, r.T()) + operations.WriteWithoutClose(fh, content, r.T()) + r.assertFileSyncFailsWithPermissionError(fh, r.T()) } - r.assertFailedFileNotInListing(t) + r.assertFailedFileNotInListing(r.T()) } //////////////////////////////////////////////////////////////////////// @@ -105,5 +106,5 @@ func TestReadOnlyTest(t *testing.T) { ts := &readOnlyCredsTest{} // Run tests. - test_setup.RunTests(t, ts) + suite.Run(t, ts) } diff --git a/tools/integration_tests/util/test_setup/test_setup.go b/tools/integration_tests/util/test_setup/test_setup.go deleted file mode 100644 index 075fc42354..0000000000 --- a/tools/integration_tests/util/test_setup/test_setup.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package test_setup implements Setup and Teardown methods to be used in tests. -package test_setup - -import ( - "reflect" - "strings" - "testing" -) - -// Testable defines Tester's methods for use in this package. -type Testable interface { - Setup(*testing.T) - Teardown(*testing.T) -} - -func getTestFunc(t *testing.T, xv reflect.Value, name string) func(*testing.T) { - if m := xv.MethodByName(name); m.IsValid() { - if f, ok := m.Interface().(func(*testing.T)); ok { - return f - } - // Method exists but has the wrong type signature. - t.Fatalf("test function %v has unexpected signature (%T)", name, m.Interface()) - } - return func(*testing.T) {} -} - -// RunTests runs all "Test*" functions that are member of x as subtests -// of the current test. Setup is run before the test function and Teardown is -// run after each test. -// x must extend Testable interface by implementing Setup and TearDown methods. -func RunTests(t *testing.T, x Testable) { - xt := reflect.TypeOf(x) - xv := reflect.ValueOf(x) - - for i := 0; i < xt.NumMethod(); i++ { - methodName := xt.Method(i).Name - if !strings.HasPrefix(methodName, "Test") { - continue - } - testFunc := getTestFunc(t, xv, methodName) - t.Run(methodName, func(t *testing.T) { - // Execute Teardown in t.Cleanup() to guarantee it is run even if test - // function or setup uses t.Fatal(). - t.Cleanup(func() { x.Teardown(t) }) - x.Setup(t) - testFunc(t) - }) - } -} diff --git a/tools/integration_tests/util/test_setup/test_setup_test.go b/tools/integration_tests/util/test_setup/test_setup_test.go deleted file mode 100644 index aeafbf7dae..0000000000 --- a/tools/integration_tests/util/test_setup/test_setup_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2024 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package test_setup_test - -import ( - "testing" - - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/setup" - "github.com/googlecloudplatform/gcsfuse/v3/tools/integration_tests/util/test_setup" - . "github.com/jacobsa/ogletest" -) - -type testStructure struct { - setupCtr, teardownCtr, test1, test2 int -} - -func (t *testStructure) Setup(*testing.T) { - t.setupCtr++ -} -func (t *testStructure) TestExample1(*testing.T) { - t.test1++ -} - -func (t *testStructure) TestExample2(*testing.T) { - t.test2++ -} - -func (t *testStructure) Teardown(*testing.T) { - t.teardownCtr++ -} - -func TestRunTests(t *testing.T) { - setup.IgnoreTestIfIntegrationTestFlagIsSet(t) - - testStruct := &testStructure{} - - test_setup.RunTests(t, testStruct) - - AssertEq(testStruct.setupCtr, 2) - AssertEq(testStruct.test1, 1) - AssertEq(testStruct.test2, 1) - AssertEq(testStruct.teardownCtr, 2) -} From 9dd4d492b91c6f24ba0fd78600b92b6b84039bf3 Mon Sep 17 00:00:00 2001 From: Mohit Kumar Yadav Date: Wed, 3 Sep 2025 13:53:49 +0530 Subject: [PATCH 0750/1298] fix(config): Use BlockSizeMb value in form of MB instead of converting it to bytes during rationalization. (#3773) * change streaming writes max block size mb for cfg. * remove log line * fix tests * use 1mb block size in composite tests --- cfg/rationalize.go | 2 -- cfg/rationalize_test.go | 6 ++--- cmd/config_validation_test.go | 5 ++-- cmd/root_test.go | 23 +++++++++---------- internal/fs/inode/file.go | 2 +- internal/fs/inode/file_test.go | 4 ++-- ...ile_handle_streaming_writes_common_test.go | 2 +- .../streaming_writes_empty_gcs_object_test.go | 2 +- .../fs/streaming_writes_local_file_test.go | 2 +- 9 files changed, 22 insertions(+), 26 deletions(-) diff --git a/cfg/rationalize.go b/cfg/rationalize.go index 9854554a9f..4b0b0a8783 100644 --- a/cfg/rationalize.go +++ b/cfg/rationalize.go @@ -97,8 +97,6 @@ func resolveStreamingWriteConfig(w *WriteConfig) { w.CreateEmptyFile = false } - w.BlockSizeMb *= util.MiB - if w.GlobalMaxBlocks == -1 { w.GlobalMaxBlocks = math.MaxInt64 } diff --git a/cfg/rationalize_test.go b/cfg/rationalize_test.go index 3ec3715214..de7b770425 100644 --- a/cfg/rationalize_test.go +++ b/cfg/rationalize_test.go @@ -429,7 +429,7 @@ func TestRationalize_WriteConfig(t *testing.T) { }, expectedCreateEmptyFile: false, expectedMaxBlocksPerFile: math.MaxInt16, - expectedBlockSizeMB: 10 * 1024 * 1024, + expectedBlockSizeMB: 10, }, { name: "valid_config_global_max_blocks_less_than_blocks_per_file", @@ -444,7 +444,7 @@ func TestRationalize_WriteConfig(t *testing.T) { }, expectedCreateEmptyFile: false, expectedMaxBlocksPerFile: 20, - expectedBlockSizeMB: 5 * 1024 * 1024, + expectedBlockSizeMB: 5, }, { name: "valid_config_global_max_blocks_more_than_blocks_per_file", @@ -459,7 +459,7 @@ func TestRationalize_WriteConfig(t *testing.T) { }, expectedCreateEmptyFile: false, expectedMaxBlocksPerFile: 10, - expectedBlockSizeMB: 64 * 1024 * 1024, + expectedBlockSizeMB: 64, }, } diff --git a/cmd/config_validation_test.go b/cmd/config_validation_test.go index 9a2539620a..924fabc09c 100644 --- a/cmd/config_validation_test.go +++ b/cmd/config_validation_test.go @@ -23,7 +23,6 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -191,7 +190,7 @@ func TestValidateConfigFile_WriteConfig(t *testing.T) { expectedConfig: &cfg.Config{ Write: cfg.WriteConfig{ CreateEmptyFile: false, - BlockSizeMb: 32 * util.MiB, + BlockSizeMb: 32, EnableStreamingWrites: true, GlobalMaxBlocks: 4, MaxBlocksPerFile: 1, @@ -205,7 +204,7 @@ func TestValidateConfigFile_WriteConfig(t *testing.T) { expectedConfig: &cfg.Config{ Write: cfg.WriteConfig{ CreateEmptyFile: false, // changed due to enabled streaming writes. - BlockSizeMb: 10 * util.MiB, + BlockSizeMb: 10, EnableStreamingWrites: true, GlobalMaxBlocks: 20, MaxBlocksPerFile: 2, diff --git a/cmd/root_test.go b/cmd/root_test.go index ff99ebe371..891f54e4c8 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -23,7 +23,6 @@ import ( "time" "github.com/googlecloudplatform/gcsfuse/v3/cfg" - "github.com/googlecloudplatform/gcsfuse/v3/internal/util" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -266,7 +265,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedCreateEmptyFile: true, expectedEnableStreamingWrites: false, expectedEnableRapidAppends: true, - expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteBlockSizeMB: 32, expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, @@ -276,7 +275,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedEnableRapidAppends: true, - expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteBlockSizeMB: 32, expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, @@ -286,7 +285,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedEnableRapidAppends: true, - expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteBlockSizeMB: 32, expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, @@ -296,7 +295,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedEnableRapidAppends: true, - expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteBlockSizeMB: 32, expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, @@ -306,7 +305,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedCreateEmptyFile: false, expectedEnableStreamingWrites: false, expectedEnableRapidAppends: true, - expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteBlockSizeMB: 32, expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, @@ -316,7 +315,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedEnableRapidAppends: false, - expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteBlockSizeMB: 32, expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, @@ -326,7 +325,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedEnableRapidAppends: true, - expectedWriteBlockSizeMB: 10 * util.MiB, + expectedWriteBlockSizeMB: 10, expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 1, }, @@ -336,7 +335,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedEnableRapidAppends: true, - expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteBlockSizeMB: 32, expectedWriteGlobalMaxBlocks: 10, expectedWriteMaxBlocksPerFile: 1, }, @@ -346,7 +345,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedEnableRapidAppends: true, - expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteBlockSizeMB: 32, expectedWriteGlobalMaxBlocks: 4, expectedWriteMaxBlocksPerFile: 10, }, @@ -355,7 +354,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { args: []string{"gcsfuse", "--machine-type=a3-highgpu-8g", "--disable-autoconfig=false", "abc", "pqr"}, expectedEnableStreamingWrites: true, expectedEnableRapidAppends: true, - expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteBlockSizeMB: 32, expectedWriteGlobalMaxBlocks: 1600, }, { @@ -364,7 +363,7 @@ func TestArgsParsing_WriteConfigFlags(t *testing.T) { expectedCreateEmptyFile: false, expectedEnableStreamingWrites: true, expectedEnableRapidAppends: true, - expectedWriteBlockSizeMB: 32 * util.MiB, + expectedWriteBlockSizeMB: 32, expectedWriteGlobalMaxBlocks: 2000, expectedWriteMaxBlocksPerFile: 1, }, diff --git a/internal/fs/inode/file.go b/internal/fs/inode/file.go index c4b3e71fa9..1af0848fc0 100644 --- a/internal/fs/inode/file.go +++ b/internal/fs/inode/file.go @@ -1047,7 +1047,7 @@ func (f *FileInode) InitBufferedWriteHandlerIfEligible(ctx context.Context, open Object: latestGcsObj, ObjectName: f.name.GcsObjectName(), Bucket: f.bucket, - BlockSize: f.config.Write.BlockSizeMb, + BlockSize: f.config.Write.BlockSizeMb * util.MiB, MaxBlocksPerFile: f.config.Write.MaxBlocksPerFile, GlobalMaxBlocksSem: f.globalMaxWriteBlocksSem, ChunkTransferTimeoutSecs: f.config.GcsRetries.ChunkTransferTimeoutSecs, diff --git a/internal/fs/inode/file_test.go b/internal/fs/inode/file_test.go index beeb019830..65c3f4fb99 100644 --- a/internal/fs/inode/file_test.go +++ b/internal/fs/inode/file_test.go @@ -1891,7 +1891,7 @@ func (t *FileTest) TestRegisterFileHandle() { func getWriteConfig() *cfg.WriteConfig { return &cfg.WriteConfig{ MaxBlocksPerFile: 10, - BlockSizeMb: 10, + BlockSizeMb: 1, EnableStreamingWrites: true, } } @@ -1899,7 +1899,7 @@ func getWriteConfig() *cfg.WriteConfig { func getWriteConfigWithEnabledRapidAppends() *cfg.WriteConfig { return &cfg.WriteConfig{ MaxBlocksPerFile: 10, - BlockSizeMb: 10, + BlockSizeMb: 1, EnableStreamingWrites: true, EnableRapidAppends: true, } diff --git a/internal/fs/stale_file_handle_streaming_writes_common_test.go b/internal/fs/stale_file_handle_streaming_writes_common_test.go index fda7adbba3..8af0cf2d7b 100644 --- a/internal/fs/stale_file_handle_streaming_writes_common_test.go +++ b/internal/fs/stale_file_handle_streaming_writes_common_test.go @@ -40,7 +40,7 @@ type staleFileHandleStreamingWritesCommon struct { func (t *staleFileHandleStreamingWritesCommon) SetupSuite() { serverCfg := commonServerConfig() serverCfg.Write.EnableStreamingWrites = true - serverCfg.Write.BlockSizeMb = operations.MiB + serverCfg.Write.BlockSizeMb = 1 serverCfg.Write.MaxBlocksPerFile = 1 serverCfg.Write.GlobalMaxBlocks = math.MaxInt diff --git a/internal/fs/streaming_writes_empty_gcs_object_test.go b/internal/fs/streaming_writes_empty_gcs_object_test.go index 6568ffddda..1e6eb16471 100644 --- a/internal/fs/streaming_writes_empty_gcs_object_test.go +++ b/internal/fs/streaming_writes_empty_gcs_object_test.go @@ -36,7 +36,7 @@ type StreamingWritesEmptyGCSObjectTest struct { func (t *StreamingWritesEmptyGCSObjectTest) SetupSuite() { t.serverCfg.NewConfig = &cfg.Config{ Write: cfg.WriteConfig{ - BlockSizeMb: 10, + BlockSizeMb: 1, CreateEmptyFile: false, EnableStreamingWrites: true, GlobalMaxBlocks: 20, diff --git a/internal/fs/streaming_writes_local_file_test.go b/internal/fs/streaming_writes_local_file_test.go index 1c3e45def4..5e7059dd78 100644 --- a/internal/fs/streaming_writes_local_file_test.go +++ b/internal/fs/streaming_writes_local_file_test.go @@ -39,7 +39,7 @@ type StreamingWritesLocalFileTest struct { func (t *StreamingWritesLocalFileTest) SetupSuite() { t.serverCfg.NewConfig = &cfg.Config{ Write: cfg.WriteConfig{ - BlockSizeMb: 10, + BlockSizeMb: 1, CreateEmptyFile: false, EnableStreamingWrites: true, GlobalMaxBlocks: 20, From e9289e23d57e398767573289dc843429b47e23ab Mon Sep 17 00:00:00 2001 From: Ashmeen Kaur <57195160+ashmeenkaur@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:22:20 +0530 Subject: [PATCH 0751/1298] docs: update troubleshooting guide with not implemented functions (#3771) * update troubleshooting guide with not implemented functions * review comments * remove syncfs --- docs/troubleshooting.md | 46 +++++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index e8ee56d151..502df7d122 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -83,11 +83,11 @@ Please refer [here](https://cloud.google.com/storage/docs/gcsfuse-mount#authenti **Solution** - depending upon the use-case, you can choose one of the following options. * If you are explicitly authenticating for a specific service account by providing say a key-file, then make sure that the service account has appropriate IAM role for the operation e.g. roles/storage.objectAdmin, roles/storage.objectUser * If you are using the default service account i.e. not specifying a key-file, then ensure that - * The VM's service account has got the required IAM roles for the operation e.g. roles/storage.objectUser to allow read-write access. - * The VM's scope has been appropriately set. You can set the scope to storage-full to give the VM full-access to the cloud-storage buckets. For this: - * Turn-off the instance - * Change the VM's scope either by using the GCP console or by executing `gcloud beta compute instances set-scopes INSTANCE_NAME --scopes=storage-full` - * Start the instance + * The VM's service account has got the required IAM roles for the operation e.g. roles/storage.objectUser to allow read-write access. + * The VM's scope has been appropriately set. You can set the scope to storage-full to give the VM full-access to the cloud-storage buckets. For this: + * Turn-off the instance + * Change the VM's scope either by using the GCP console or by executing `gcloud beta compute instances set-scopes INSTANCE_NAME --scopes=storage-full` + * Start the instance ### Bad gateway error while installing/upgrading GCSFuse: `Err: http://packages.cloud.google.com/apt gcsfuse-focal/main amd64 gcsfuse amd64 1.2.0`
`502 Bad Gateway [IP: xxx.xxx.xx.xxx 80]` @@ -129,7 +129,7 @@ By default `ls` does listing but sometimes additionally does `stat` for each lis Both these errors are expected and part of GCSFuse standard operating procedure. More details [here](https://github.com/GoogleCloudPlatform/gcsfuse/discussions/2300). -### GCSFuse logs showing errors for StatObject NotFoundError +### GCSFuse logs showing errors for StatObject NotFoundError `StatObject(\"") (